|
| 1 | +# Handler Integration Guide |
| 2 | + |
| 3 | +This guide explains how to integrate Theater handlers at different stages of the runtime lifecycle. |
| 4 | + |
| 5 | +## Handler Integration Timeline |
| 6 | + |
| 7 | +Handlers can be integrated at different points depending on their dependencies: |
| 8 | + |
| 9 | +### 1. ✅ Handler Registry Creation (10/11 handlers) |
| 10 | + |
| 11 | +These handlers can be registered when creating the `HandlerRegistry`, before the runtime starts: |
| 12 | + |
| 13 | +| Handler | Dependencies | When to Register | |
| 14 | +|---------|--------------|------------------| |
| 15 | +| **environment** | Config, Permissions | Registry creation | |
| 16 | +| **random** | Config, Permissions | Registry creation | |
| 17 | +| **timing** | Config, Permissions | Registry creation | |
| 18 | +| **runtime** | Config, `theater_tx`, Permissions | Registry creation | |
| 19 | +| **http-client** | Config, Permissions | Registry creation | |
| 20 | +| **filesystem** | Config, Permissions | Registry creation | |
| 21 | +| **store** | Config, Permissions | Registry creation | |
| 22 | +| **supervisor** | Config, Permissions | Registry creation | |
| 23 | +| **message-server** | Config, MessageRouter, Permissions | Registry creation | |
| 24 | +| **http-framework** | Permissions | Registry creation | |
| 25 | + |
| 26 | +**Key Insight:** Even though `RuntimeHandler` needs the `theater_tx` channel, we create that channel *before* creating the handler registry, so it's available at registration time! |
| 27 | + |
| 28 | +```rust |
| 29 | +// Create channels first |
| 30 | +let (theater_tx, theater_rx) = mpsc::channel::<TheaterCommand>(32); |
| 31 | + |
| 32 | +// Create registry and pass theater_tx to handlers that need it |
| 33 | +let mut registry = HandlerRegistry::new(); |
| 34 | +registry.register(RuntimeHandler::new( |
| 35 | + RuntimeHostConfig {}, |
| 36 | + theater_tx.clone(), // ✅ Available! |
| 37 | + None |
| 38 | +)); |
| 39 | + |
| 40 | +// Then create runtime |
| 41 | +let runtime = TheaterRuntime::new(theater_tx, theater_rx, None, registry).await?; |
| 42 | +``` |
| 43 | + |
| 44 | +### 2. ⚠️ Actor Creation Time (1/11 handlers) |
| 45 | + |
| 46 | +This handler requires dependencies that only exist when spawning actors: |
| 47 | + |
| 48 | +| Handler | Blocker | When to Integrate | |
| 49 | +|---------|---------|-------------------| |
| 50 | +| **process** | Requires `ActorHandle` (created during actor spawn) | Per-actor integration | |
| 51 | + |
| 52 | +**Why ProcessHandler is Different:** |
| 53 | + |
| 54 | +```rust |
| 55 | +pub fn new( |
| 56 | + config: ProcessHostConfig, |
| 57 | + actor_handle: ActorHandle, // ❌ Only exists during actor spawning |
| 58 | + permissions: Option<ProcessPermissions>, |
| 59 | +) -> Self |
| 60 | +``` |
| 61 | + |
| 62 | +The `ActorHandle` is created by the runtime when spawning an actor, not during handler registry setup. Each actor needs its own ProcessHandler instance with its specific ActorHandle. |
| 63 | + |
| 64 | +**Solution:** ProcessHandler must be integrated during the actor spawning process, typically in TheaterServer or custom actor creation logic. |
| 65 | + |
| 66 | +## Common Misconceptions |
| 67 | + |
| 68 | +### ❌ "Handlers needing runtime resources can't be registered early" |
| 69 | + |
| 70 | +**Reality:** Many handlers need runtime resources like `theater_tx`, but these are created *before* the handler registry, so they're available at registration time. |
| 71 | + |
| 72 | +### ❌ "All handlers are created per-actor" |
| 73 | + |
| 74 | +**Reality:** Handlers registered in the HandlerRegistry are shared across actors (via `create_instance()` cloning). Only handlers requiring per-actor state (like ProcessHandler needing an ActorHandle) must be created per-actor. |
| 75 | + |
| 76 | +### ✅ "Handler dependencies determine integration point" |
| 77 | + |
| 78 | +**Correct:** The key question is "When does this dependency become available?" |
| 79 | +- Available before runtime creation? → Register in HandlerRegistry |
| 80 | +- Available only during actor creation? → Integrate per-actor |
| 81 | + |
| 82 | +## Integration Examples |
| 83 | + |
| 84 | +### Example 1: Standard Runtime (10/11 handlers) |
| 85 | + |
| 86 | +```rust |
| 87 | +use theater::handler::HandlerRegistry; |
| 88 | +use theater::messages::TheaterCommand; |
| 89 | +use tokio::sync::mpsc; |
| 90 | + |
| 91 | +async fn create_runtime() -> Result<()> { |
| 92 | + // 1. Create channels |
| 93 | + let (theater_tx, theater_rx) = mpsc::channel(32); |
| 94 | + |
| 95 | + // 2. Create handler registry |
| 96 | + let mut registry = HandlerRegistry::new(); |
| 97 | + |
| 98 | + // Register all compatible handlers |
| 99 | + registry.register(EnvironmentHandler::new(env_config, None)); |
| 100 | + registry.register(RandomHandler::new(random_config, None)); |
| 101 | + registry.register(TimingHandler::new(timing_config, None)); |
| 102 | + registry.register(RuntimeHandler::new(runtime_config, theater_tx.clone(), None)); // ✅ |
| 103 | + registry.register(HttpClientHandler::new(http_config, None)); |
| 104 | + registry.register(FilesystemHandler::new(fs_config, None)); |
| 105 | + registry.register(StoreHandler::new(store_config, None)); |
| 106 | + registry.register(SupervisorHandler::new(supervisor_config, None)); |
| 107 | + |
| 108 | + let message_router = MessageRouter::new(); |
| 109 | + registry.register(MessageServerHandler::new(None, message_router)); |
| 110 | + registry.register(HttpFrameworkHandler::new(None)); |
| 111 | + |
| 112 | + // 3. Create runtime |
| 113 | + let runtime = TheaterRuntime::new( |
| 114 | + theater_tx, |
| 115 | + theater_rx, |
| 116 | + None, |
| 117 | + registry, |
| 118 | + ).await?; |
| 119 | + |
| 120 | + // 4. Run runtime |
| 121 | + runtime.run().await?; |
| 122 | + |
| 123 | + Ok(()) |
| 124 | +} |
| 125 | +``` |
| 126 | + |
| 127 | +### Example 2: Per-Actor ProcessHandler (TheaterServer approach) |
| 128 | + |
| 129 | +ProcessHandler integration happens in the actor spawning logic: |
| 130 | + |
| 131 | +```rust |
| 132 | +// This happens inside TheaterRuntime::spawn_actor() or similar |
| 133 | +async fn spawn_actor_with_process_handler( |
| 134 | + manifest: ManifestConfig, |
| 135 | + theater_tx: Sender<TheaterCommand>, |
| 136 | +) -> Result<TheaterId> { |
| 137 | + // 1. Create actor handle |
| 138 | + let actor_handle = ActorHandle::new(/* ... */); |
| 139 | + |
| 140 | + // 2. Create ProcessHandler with actor's handle |
| 141 | + let process_handler = ProcessHandler::new( |
| 142 | + ProcessHostConfig::default(), |
| 143 | + actor_handle.clone(), // ✅ Now available |
| 144 | + None, |
| 145 | + ); |
| 146 | + |
| 147 | + // 3. Add to actor's handler list |
| 148 | + actor_handlers.push(Box::new(process_handler)); |
| 149 | + |
| 150 | + // 4. Continue actor creation... |
| 151 | + Ok(actor_id) |
| 152 | +} |
| 153 | +``` |
| 154 | + |
| 155 | +## Summary |
| 156 | + |
| 157 | +- **10/11 handlers** can be registered in the HandlerRegistry at runtime creation |
| 158 | +- **1/11 handlers** (ProcessHandler) requires per-actor integration |
| 159 | +- The key is understanding *when* each dependency becomes available |
| 160 | +- RuntimeHandler was incorrectly assumed to need per-actor integration, but it only needs `theater_tx` which is created before the handler registry |
| 161 | + |
| 162 | +## See Also |
| 163 | + |
| 164 | +- `/crates/theater/examples/full-runtime.rs` - Working example with 10/11 handlers |
| 165 | +- `/crates/theater-server/` - Complete server implementation with all 11 handlers |
0 commit comments