Zone Hold Action Ifc
Told when a hold on guide-path space begins and when it ends.
Required rather than optional, and both members abstract rather than defaulted, because both facts are the space's to know and neither can be worked out by the holder. A hold begins when the region has drained, which depends on traffic and so varies between replications; a hold taken for a stated duration ends at that same unknown instant plus the duration. A holder that is not told has to keep a second copy of the duration and add it to a grant time it was also not told, which is two owners of one fact -- the defect family this subsystem has already produced four times.
It is an action rather than a listener, and the distinction is the contract: whatever the closure was holding up is expected to proceed from holdEnded. Nothing enforces that, but a model that closes an aisle and never acts on its reopening is almost certainly missing the rest of itself.
Requiring one on a plain request closes a worse hole than the one it was introduced for. Without a grant notification a holder never learns it has the space, so it can never give the space back, and the zones stay closed to traffic for the rest of the run with nothing holding them -- silent, and unrecoverable.
The contract
holdBegan fires exactly once for every request that is granted, after the statistics are settled -- an action may give the space straight back, which is legitimate and would otherwise be recorded against a hold that had not yet been counted as having started.
holdEnded fires exactly once for every hold that began, whichever ended it: the clock, on a hold taken for a stated duration, or the holder giving it back.
A request abandoned before it was granted produces neither, because abandonment is always the caller's own act -- there is no path by which the space gives up a request on its own. That is why there are two members here and not three.
holdEnded is called after the zones have been given back and the handovers scheduled, so that an action sees a settled state rather than a zone that is neither held nor handed on.
Asking for the same space again from holdEnded takes it back at once, ahead of any vehicle that has been waiting for it. That is the drain-priority rule doing its job rather than a flaw -- a reservation has to beat a waiting vehicle or a closure on a busy aisle would never happen -- but it means a closure re-taken every time it ends holds its zone for the rest of the run. The statistics show it plainly; nothing raises.
The usual implementer is the model element that drives the closures, so that the thing which schedules them is the thing which acts on them:
class SpillDriver(parent: ModelElement) : ModelElement(parent, "SpillDriver"), ZoneHoldActionIfc {
override fun holdBegan(allocation: ZoneAllocation) { }
override fun holdEnded(allocation: ZoneAllocation) { dispatchNextSpill() }
}