Multi-Resource Handoffs (Hybrid Pattern)
While the @app.task decorator abstracts resource locking, it enforces a strict single-resource lifecycle (request -> wait -> release).
If your simulation requires a task to lock multiple resources concurrently or orchestrate a handover (e.g., requesting a crane, moving a part, locking a machine, and then releasing the crane while keeping the machine locked), you must use the Hybrid Pattern by spawning a custom generator.
Overlapping Lock Limit
In many production facilities, physical objects cannot transition between stations without a transfer mechanism (like a crane or robotic arm):
1. Part requests Crane ──────> Crane is acquired and locked.
2. Part requests Machine ────> Blocks until Machine is free (Crane remains locked).
3. Machine is acquired ─────-> Part releases Crane (Handoff complete).
4. Part processes on Machine.
5. Part releases Machine.
Code Example: Spawning a Custom Handover Generator
Instead of using @app.task, you can write a raw generator function, retrieve resource handles via context.get_resource(), and schedule the processes dynamically with context.spawn().
import logging
from dynamic_des import SimulationContext, ConsoleEgress
logging.basicConfig(level=logging.INFO)
app = (
SimulationContext(sim_id="Line_A", factor=1.0)
.add_resource("crane", current_cap=1)
.add_resource("mill", current_cap=1)
.add_arrival("standard", dist="exponential", rate=1.0)
.add_egress(ConsoleEgress())
)
# 1. Custom process logic handling concurrent locks
def handoff_worker(context: SimulationContext, part_id: int):
crane = context.get_resource("crane")
mill = context.get_resource("mill")
task_key = f"task-{part_id}"
context.env.publish_event(task_key, {"status": "queued"})
# Lock the Crane for transport
with crane.request() as crane_req:
yield crane_req
context.env.publish_event(task_key, {"status": "crane_loaded"})
yield context.env.timeout(1.0) # Transport delay
# Request the Mill while still holding the Crane!
with mill.request() as mill_req:
yield mill_req
context.env.publish_event(task_key, {"status": "mill_entered"})
# The Crane is automatically released here as we exit the outer block,
# but the Mill remains locked!
# Perform milling operation
yield context.env.timeout(4.0)
context.env.publish_event(task_key, {"status": "finished"})
# 2. Wire the custom generator to the arrival loop
@app.arrival_loop("standard")
def arrival_loop(context: SimulationContext):
part_id = 0
while True:
yield context.wait_for_arrival("standard")
# Spawn the custom handover process
context.spawn(handoff_worker(context, part_id))
part_id += 1
if __name__ == "__main__":
app.run(until=30.0)