Hot Reload for Development¶
Hot reload dramatically speeds up the development cycle by allowing you to see changes instantly without restarting the server. This guide covers development workflows and best practices.
Quick Start¶
Start the server with file watching enabled:
The --watch flag enables automatic hot reload when source files change. The --debug flag provides detailed logging.
What Gets Watched¶
By default, the file watcher monitors:
- Python files (
.py) in loaded content pack source directories - Changes are debounced (default 0.5 seconds) to handle rapid saves
Customizing the Watch¶
# Custom debounce delay
maid server start --watch --watch-debounce 1.0
# Load specific packs to watch
maid server start --watch --content my-pack --content other-pack
Development Workflow¶
1. Initial Setup¶
Structure your content pack for easy development:
my-content-pack/
├── src/
│ └── my_content_pack/
│ ├── __init__.py
│ ├── pack.py # ContentPack implementation
│ ├── systems/
│ │ ├── __init__.py
│ │ └── combat.py
│ ├── commands/
│ │ ├── __init__.py
│ │ └── attack.py
│ └── components/
│ ├── __init__.py
│ └── health.py
├── tests/
│ └── test_pack.py
└── pyproject.toml
2. Start Development Server¶
3. Edit and Save¶
Make changes to your pack's code. When you save a file:
- The file watcher detects the change
- Hot reload is triggered for the pack
- Old systems, commands, and handlers are unregistered
- New versions are registered
- Migrations run if needed
You'll see output like:
[INFO] File changed: combat.py
[INFO] Triggering reload for pack: my-content-pack
[INFO] Successfully reloaded pack 'my-content-pack' in 0.125s
4. Test in Real-Time¶
Connect to your development server and test changes immediately:
Or use a MUD client pointed at localhost:4000.
Manual Hot Reload¶
You can trigger hot reload manually via CLI or API:
CLI¶
# Reload a specific module
maid dev reload my_content_pack.systems.combat --type module
# Reload an entire pack
maid dev reload my-content-pack --type pack
# Watch mode with manual control
maid dev reload my-content-pack --watch --watch-path src/
Programmatic¶
# In a dev shell or script
import asyncio
from maid_engine.core.engine import GameEngine
from maid_engine.config import get_settings
from my_content_pack import MyContentPack
async def reload_pack():
settings = get_settings()
engine = GameEngine(settings)
# Load initial pack
engine.load_content_pack(MyContentPack())
await engine.start()
# Later, reload with updated code
# (after reimporting the module)
import importlib
import my_content_pack
importlib.reload(my_content_pack)
result = await engine.hot_reload.reload_pack(
"my-content-pack",
my_content_pack.MyContentPack()
)
print(f"Reload: {result}")
asyncio.run(reload_pack())
Interactive Development Shell¶
Use the development shell for interactive testing:
This gives you an IPython shell with MAID loaded:
# In the shell
from maid_engine.core.engine import GameEngine
from maid_engine.config import get_settings
from my_content_pack import MyContentPack
settings = get_settings()
engine = GameEngine(settings)
engine.load_content_pack(MyContentPack())
# Start the engine
import asyncio
asyncio.get_event_loop().run_until_complete(engine.start())
# Test commands, systems, etc.
world = engine.world
entity = world.create_entity()
# Reload after making changes
result = asyncio.get_event_loop().run_until_complete(
engine.hot_reload.reload_pack("my-content-pack", MyContentPack())
)
Debugging Hot Reload Issues¶
Enable Verbose Logging¶
# Via environment variable
MAID_LOG_LEVEL=DEBUG maid server start --watch
# Or in your pack's settings
settings.log_level = "DEBUG"
Common Issues¶
Pack Not Reloading¶
Check that:
- The file watcher is running (
Watching: my-pack at /path/to/src) - You're saving files in the watched directory
- The file has a
.pyextension
# Verify what's being watched
watcher = engine.hot_reload._file_watcher # If accessible
print(watcher.watched_packs)
Import Errors After Reload¶
If you see import errors, the module cache may be stale:
import importlib
import sys
# Clear cached modules before reload
modules_to_clear = [
key for key in sys.modules
if key.startswith("my_content_pack")
]
for mod in modules_to_clear:
del sys.modules[mod]
# Now reload
result = await engine.hot_reload.reload_pack("my-pack", MyContentPack())
State Lost After Reload¶
If your system loses state, implement StatefulSystem:
from maid_engine.plugins.migration import StatefulSystem
class CombatSystem(System):
priority = 50
def __init__(self, world: World) -> None:
super().__init__(world)
self._active_combats: dict[UUID, Combat] = {}
self._cooldowns: dict[UUID, float] = {}
def capture_state(self) -> dict[str, Any]:
"""Capture state before hot reload."""
return {
"active_combats": {
str(k): v.to_dict()
for k, v in self._active_combats.items()
},
"cooldowns": {
str(k): v
for k, v in self._cooldowns.items()
},
}
def restore_state(self, state: dict[str, Any]) -> None:
"""Restore state after hot reload."""
self._active_combats = {
UUID(k): Combat.from_dict(v)
for k, v in state.get("active_combats", {}).items()
}
self._cooldowns = {
UUID(k): v
for k, v in state.get("cooldowns", {}).items()
}
async def update(self, delta: float) -> None:
# Normal system logic
pass
Hot Reload Hooks for Development¶
Use hooks to add development-specific behavior:
def dev_pre_reload(ctx: HotReloadContext) -> None:
"""Called before reload - save dev state."""
print(f"\n{'='*50}")
print(f"Reloading: {ctx.pack_name}")
print(f"{'='*50}")
def dev_post_reload(ctx: HotReloadContext) -> None:
"""Called after reload - report status."""
print(f"Reload completed in {ctx.elapsed_time:.3f}s")
print(f"{'='*50}\n")
# Register development hooks
if settings.debug:
engine.hot_reload.register_hook("pre_load", dev_pre_reload)
engine.hot_reload.register_hook("post_load", dev_post_reload)
Testing During Development¶
Run Tests Without Restarting¶
# In another terminal while server is running
uv run pytest packages/my-content-pack/tests/ -v
# Or run specific tests
uv run pytest packages/my-content-pack/tests/test_combat.py -v
Test Pack Protocol Compliance¶
Test Pack Lifecycle¶
Performance Considerations¶
Development vs Production¶
In development:
- Enable verbose logging
- Use shorter debounce delays (0.3-0.5s)
- Keep rollback enabled
In production:
- Minimize logging
- Use longer debounce delays (1-2s)
- Plan reload windows during low activity
Measuring Reload Time¶
# Get reload history
history = engine.hot_reload.get_reload_history()
for result in history:
print(f"{result.pack_name}: {result.elapsed_time:.3f}s")
if result.migrations_run > 0:
print(f" Migrations: {result.migrations_run}")
Optimizing Reload Speed¶
- Keep packs focused - Smaller packs reload faster
- Minimize
on_loadwork - Defer expensive initialization - Use lazy imports - Import only when needed
- Profile migrations - Ensure migrations are efficient
Next Steps¶
- Data Migrations - Learn about migrating component data
- Hot Reload Overview - Review the hot reload architecture