commands.bend
bendvy-ecs-core@0.1.0.0 · 180 lines · 0 definitions
1import Base
2import bendvy-ecs-core@0.1.0.0/trace_world.bend as TraceWorld
3import bendvy-ecs-core@0.1.0.0/deferred_command.bend as Deferred
4import bendvy-ecs-core@0.1.0.0/trace_facts.bend as TraceFacts
5import bendvy-ecs-core@0.1.0.0/world.bend as W
6import bendvy-ecs-core@0.1.0.0/transaction.bend as T
7
8# World pending commands are a reverse accumulator; input here is FIFO.
9def enqueue_list(~S: Data,~C: Type,~R: Type,~E: Data,commands:List<Deferred.Command<W.World<S,C,R,E>>>,world: W.World<S,C,R,E>) -> W.World<S,C,R,E>:
10 match commands:
11 case []: world
12 case command <> rest: enqueue_list(~S,~C,~R,~E,rest,W.enqueue_command(~S,~C,~R,~E,world,command))
13
14# Append the supplied FIFO command list to World pending work, returning World.
15# This transfers affine command closures but does not execute them; W.barrier is
16# the explicit visibility boundary. Raw command callbacks are trusted adapters.
17def enqueue(~S: Data,~C: Type,~R: Type,~E: Data,world: W.World<S,C,R,E>,commands:List<Deferred.Command<W.World<S,C,R,E>>>) -> W.World<S,C,R,E>:
18 enqueue_list(~S,~C,~R,~E,commands,world)
19
20# Publish these detached Data events to World in list order. This is immediate
21# publication, not transactional staging or synchronization of an event Runtime.
22def publish(~S: Data,~C: Type,~R: Type,~E: Data,world: W.World<S,C,R,E>,events: List<&2,E>) -> W.World<S,C,R,E>:
23 W.event_publish(~S,~C,~R,~E,world,events)
24
25# Finish with canonical World adapters: success enqueues staged commands and
26# publishes staged Data events in authored order; failure replays inverse actions
27# and drops staged commands/events. Returns World plus outcome. It does not apply
28# the structural barrier or undo arbitrary external effects.
29def tx_finish(~S: Data,~C: Type,~R: Type,~E: Data,~Error: Data,tx: T.Tx<W.World<S,C,R,E>,E>,outcome: T.Outcome<Error>) -> T.Returned<W.World<S,C,R,E>,Error>:
30 T.finish(~W.World<S,C,R,E>,~E,~Error,~enqueue(~S,~C,~R,~E),~publish(~S,~C,~R,~E),tx,outcome)
31
32def spawned(~S: Data,~C: Type,~R: Type,~E: Data,~P: Type,~populate: W.World<S,C,R,E> -> W.Handle<S> -> P -> W.World<S,C,R,E>,activated: W.World<S,C,R,E> & W.Status,handle: W.Handle<S>,payload: P) -> W.World<S,C,R,E>:
33 match activated:
34 case (world,W.Accepted{}): populate(world,handle,payload)
35 case (world,W.Rejected{_}): world
36
37# Trusted deferred-command application: activate the reserved handle, then call
38# populate only on acceptance. Payload is consumed, including when activation
39# refuses; this low-level callback does not return an incoming-payload receipt.
40def spawn_apply(~S: Data,~C: Type,~R: Type,~E: Data,~P: Type,~populate: W.World<S,C,R,E> -> W.Handle<S> -> P -> W.World<S,C,R,E>,world: W.World<S,C,R,E>,+handle: W.Handle<S>,payload: P) -> W.World<S,C,R,E>:
41 spawned(~S,~C,~R,~E,~P,~populate,W.activate(~S,~C,~R,~E,world,handle),handle,payload)
42
43def spawn_applied_traced(~S:Data,~C:Type,~R:Type,~E:Data,~P:Type,~populate:W.World<S,C,R,E> -> W.Handle<S> -> P -> W.World<S,C,R,E>,trace:TraceFacts.State,+handle:W.Handle<S>,payload:P,result:W.World<S,C,R,E> & W.Status) -> W.World<S,C,R,E> & TraceFacts.State:
44 match result:
45 case (world,+status):(spawned(~S,~C,~R,~E,~P,~populate,(world,status),handle,payload),TraceWorld.applied(~S,trace,"spawn",handle,status))
46# Build a deferred spawn closure owning its actual payload. enabled selects raw
47# or operation-fact recording; neither variant activates until command execution.
48def spawn_command(~S:Data,~C:Type,~R:Type,~E:Data,~P:Type,~populate:W.World<S,C,R,E> -> W.Handle<S> -> P -> W.World<S,C,R,E>,enabled:Bool,+handle:W.Handle<S>,payload:P) -> Deferred.Command<W.World<S,C,R,E>>:
49 match enabled:
50 case False{}:Deferred.Raw{world=>spawn_apply(~S,~C,~R,~E,~P,~populate,world,handle,payload)}
51 case True{}:Deferred.Observed{trace=>world=>spawn_applied_traced(~S,~C,~R,~E,~P,~populate,trace,handle,payload,W.activate(~S,~C,~R,~E,world,handle))}
52# Only successful actual deferred spawns establish pending authority. The inverse
53# clears at the World's current capacity after any intervening growth.
54def track_pending(~S:Data,~C:Type,~R:Type,~E:Data,tx:T.Tx<W.World<S,C,R,E>,E>,+handle:W.Handle<S>) -> T.Tx<W.World<S,C,R,E>,E>:
55 match tx:
56 case T.Tx{world,undo,commands,events,trace}:T.Tx{W.mark_pending(~S,~C,~R,~E,world,handle),(w => W.clear_pending(~S,~C,~R,~E,w,handle)) <> undo,commands,events,trace}
57
58def spawn_reserved(~S: Data,~C: Type,~R: Type,~E: Data,~P: Type,~populate: W.World<S,C,R,E> -> W.Handle<S> -> P -> W.World<S,C,R,E>,reservation: W.Reservation<S>,world: W.World<S,C,R,E>,undo: List<(W.World<S,C,R,E> -> W.World<S,C,R,E>)>,commands:List<Deferred.Command<W.World<S,C,R,E>>>,events: List<&2,E>,+trace:TraceFacts.State,payload: P) -> T.Tx<W.World<S,C,R,E>,E> & W.Reservation<S>:
59 match reservation:
60 case W.ReservationRejected{error}: (T.Tx{world,undo,commands,events,trace},W.ReservationRejected{error})
61 case W.Reserved{+handle}: (track_pending(~S,~C,~R,~E,T.Tx{world,undo,spawn_command(~S,~C,~R,~E,~P,~populate,TraceFacts.enabled(trace),handle,payload) <> commands,events,TraceWorld.deferred(~S,trace,"spawn",TraceFacts.StageOperation{},handle,TraceFacts.Attempted{})},handle),W.Reserved{handle})
62
63def spawn_join(~S: Data,~C: Type,~R: Type,~E: Data,~P: Type,~populate: W.World<S,C,R,E> -> W.Handle<S> -> P -> W.World<S,C,R,E>,reserved: W.World<S,C,R,E> & W.Reservation<S>,undo: List<(W.World<S,C,R,E> -> W.World<S,C,R,E>)>,commands:List<Deferred.Command<W.World<S,C,R,E>>>,events: List<&2,E>,trace:TraceFacts.State,payload: P) -> T.Tx<W.World<S,C,R,E>,E> & W.Reservation<S>:
64 (world,reservation) = reserved
65 spawn_reserved(~S,~C,~R,~E,~P,~populate,reservation,world,undo,commands,events,trace,payload)
66
67# IDs stay reserved on failure, matching the historical allocator policy.
68# Reserve an entity handle now and stage activation/population for the barrier.
69# Reserved is not yet live: ordinary component reads cannot observe it. The Type
70# payload moves into the command; reservation refusal does not return that payload.
71# Failure drops staged work and clears pending authority but does not rewind the
72# allocator's reserved-ID progress. See examples/captured-query/schema.bend.
73def tx_spawn(~S: Data,~C: Type,~R: Type,~E: Data,~P: Type,~populate: W.World<S,C,R,E> -> W.Handle<S> -> P -> W.World<S,C,R,E>,tx: T.Tx<W.World<S,C,R,E>,E>,payload: P) -> T.Tx<W.World<S,C,R,E>,E> & W.Reservation<S>:
74 match tx:
75 case T.Tx{world,undo,commands,events,trace}: spawn_join(~S,~C,~R,~E,~P,~populate,W.reserve_id(~S,~C,~R,~E,world),undo,commands,events,trace,payload)
76
77def deactivate_join(~S: Data,~C: Type,~R: Type,~E: Data,deactivated: W.World<S,C,R,E> & W.Status) -> W.World<S,C,R,E>:
78 (world,status) = deactivated
79 world
80
81# Low-level liveness-only command application, discarding its Status. Component
82# storage is retained; use the schema-cleanup variant for ordinary entity disposal.
83def despawn_apply(~S: Data,~C: Type,~R: Type,~E: Data,world: W.World<S,C,R,E>,handle: W.Handle<S>) -> W.World<S,C,R,E>:
84 deactivate_join(~S,~C,~R,~E,W.deactivate(~S,~C,~R,~E,world,handle))
85
86def despawn_applied_traced(~S:Data,~C:Type,~R:Type,~E:Data,trace:TraceFacts.State,handle:W.Handle<S>,result:W.World<S,C,R,E> & W.Status) -> W.World<S,C,R,E> & TraceFacts.State:
87 match result:
88 case (world,status):(world,TraceWorld.applied(~S,trace,"despawn",handle,status))
89# Construct the liveness-only deferred command, optionally recording application
90# status as an execution fact. Construction alone does not change liveness.
91def despawn_command(~S:Data,~C:Type,~R:Type,~E:Data,enabled:Bool,+handle:W.Handle<S>) -> Deferred.Command<W.World<S,C,R,E>>:
92 match enabled:
93 case False{}:Deferred.Raw{world=>despawn_apply(~S,~C,~R,~E,world,handle)}
94 case True{}:Deferred.Observed{trace=>world=>despawn_applied_traced(~S,~C,~R,~E,trace,handle,W.deactivate(~S,~C,~R,~E,world,handle))}
95def despawn_checked(~S: Data,~C: Type,~R: Type,~E: Data,allowed: Bool,world: W.World<S,C,R,E>,undo: List<(W.World<S,C,R,E> -> W.World<S,C,R,E>)>,commands:List<Deferred.Command<W.World<S,C,R,E>>>,events: List<&2,E>,+trace:TraceFacts.State,+handle: W.Handle<S>) -> T.Tx<W.World<S,C,R,E>,E> & W.Status:
96 match allowed:
97 case False{}: (T.Tx{world,undo,commands,events,trace},W.Rejected{W.MissingEntity{}})
98 case True{}: (T.Tx{world,undo,despawn_command(~S,~C,~R,~E,TraceFacts.enabled(trace),handle) <> commands,events,TraceWorld.deferred(~S,trace,"despawn",TraceFacts.StageOperation{},handle,TraceFacts.Attempted{})},W.Accepted{})
99
100def despawn_join(~S: Data,~C: Type,~R: Type,~E: Data,checked: W.World<S,C,R,E> & Bool,undo: List<(W.World<S,C,R,E> -> W.World<S,C,R,E>)>,commands:List<Deferred.Command<W.World<S,C,R,E>>>,events: List<&2,E>,trace:TraceFacts.State,handle: W.Handle<S>) -> T.Tx<W.World<S,C,R,E>,E> & W.Status:
101 (world,allowed) = checked
102 despawn_checked(~S,~C,~R,~E,allowed,world,undo,commands,events,trace,handle)
103
104# Low-level liveness-only operation retains component storage. Gameplay uses tx_despawn_with.
105# Stage liveness-only deactivation for a live or legitimately pending handle.
106# Foreign/stale/otherwise invalid handles return Rejected(MissingEntity) with Tx.
107# Accepted is queue admission, not applied success; storage cleanup is not included.
108def tx_despawn(~S: Data,~C: Type,~R: Type,~E: Data,tx: T.Tx<W.World<S,C,R,E>,E>,+handle: W.Handle<S>) -> T.Tx<W.World<S,C,R,E>,E> & W.Status:
109 match tx:
110 case T.Tx{world,undo,commands,events,trace}: despawn_join(~S,~C,~R,~E,W.command_valid(~S,~C,~R,~E,world,handle),undo,commands,events,trace,handle)
111
112# Schema-owned clear releases every component family after accepted deactivation.
113def clear_join(~S: Data,~C: Type,~R: Type,~E: Data,+handle:W.Handle<S>,cleared: W.World<S,C,R,E> & Unit) -> W.World<S,C,R,E>:
114 (world,unit) = cleared
115 W.release_cleared(~S,~C,~R,~E,world,handle)
116
117def clear_accepted(~S: Data,~C: Type,~R: Type,~E: Data,~clear: C -> W.Handle<S> -> C,activated: W.World<S,C,R,E> & W.Status,+handle: W.Handle<S>) -> W.World<S,C,R,E>:
118 match activated:
119 case (world,W.Rejected{_}): world
120 case (world,W.Accepted{}): clear_join(~S,~C,~R,~E,handle,W.with_store(~S,~C,~R,~E,~Unit,(store => (clear(store,handle),Unit{})),world))
121
122# Trusted schema-cleanup command: after accepted deactivation, clear the component
123# store and release the cleared slot. Rejected deactivation skips clear. The clear
124# adapter consumes actual payloads; this is not a recovery receipt.
125def despawn_apply_with(~S: Data,~C: Type,~R: Type,~E: Data,~clear: C -> W.Handle<S> -> C,world: W.World<S,C,R,E>,+handle: W.Handle<S>) -> W.World<S,C,R,E>:
126 clear_accepted(~S,~C,~R,~E,~clear,W.deactivate(~S,~C,~R,~E,world,handle),handle)
127
128def despawn_checked_with(~S: Data,~C: Type,~R: Type,~E: Data,~clear: C -> W.Handle<S> -> C,allowed: Bool,world: W.World<S,C,R,E>,undo: List<(W.World<S,C,R,E> -> W.World<S,C,R,E>)>,commands:List<Deferred.Command<W.World<S,C,R,E>>>,events: List<&2,E>,trace:TraceFacts.State,handle: W.Handle<S>) -> T.Tx<W.World<S,C,R,E>,E> & W.Status:
129 match allowed:
130 case False{}: (T.Tx{world,undo,commands,events,trace},W.Rejected{W.MissingEntity{}})
131 case True{}: (T.Tx{world,undo,Deferred.Raw{(w => despawn_apply_with(~S,~C,~R,~E,~clear,w,handle))} <> commands,events,trace},W.Accepted{})
132
133def despawn_join_with(~S: Data,~C: Type,~R: Type,~E: Data,~clear: C -> W.Handle<S> -> C,checked: W.World<S,C,R,E> & Bool,undo: List<(W.World<S,C,R,E> -> W.World<S,C,R,E>)>,commands:List<Deferred.Command<W.World<S,C,R,E>>>,events: List<&2,E>,trace:TraceFacts.State,handle: W.Handle<S>) -> T.Tx<W.World<S,C,R,E>,E> & W.Status:
134 (world,allowed) = checked
135 despawn_checked_with(~S,~C,~R,~E,~clear,allowed,world,undo,commands,events,trace,handle)
136
137# Stage schema-owned cleanup after command-valid handle admission. Return Tx and
138# Status now; liveness/components change at the barrier after successful finish.
139# Failure drops the staged closure. clear is trusted complete-schema provisioning.
140def tx_despawn_with(~S: Data,~C: Type,~R: Type,~E: Data,~clear: C -> W.Handle<S> -> C,tx: T.Tx<W.World<S,C,R,E>,E>,+handle: W.Handle<S>) -> T.Tx<W.World<S,C,R,E>,E> & W.Status:
141 match tx:
142 case T.Tx{world,undo,commands,events,trace}: despawn_join_with(~S,~C,~R,~E,~clear,W.command_valid(~S,~C,~R,~E,world,handle),undo,commands,events,trace,handle)
143
144# Cleanup owns actual removed payloads; its typed metadata is published only on success.
145def clear_observed_join(~S: Data,~C: Type,~R: Type,~E: Data,~record: W.Handle<S> -> E,+handle: W.Handle<S>,cleared: W.World<S,C,R,E> & List<&2,E>) -> W.World<S,C,R,E>:
146 (world,records) = cleared
147 W.release_cleared(~S,~C,~R,~E,W.event_publish(~S,~C,~R,~E,world,List.append(&2,E,records,[record(handle)])),handle)
148
149def clear_observed_accepted(~S: Data,~C: Type,~R: Type,~E: Data,~clear: C -> W.Handle<S> -> C & List<&2,E>,~record: W.Handle<S> -> E,activated: W.World<S,C,R,E> & W.Status,+handle: W.Handle<S>) -> W.World<S,C,R,E>:
150 match activated:
151 case (world,W.Rejected{_}): world
152 case (world,W.Accepted{}): clear_observed_join(~S,~C,~R,~E,~record,handle,W.with_store(~S,~C,~R,~E,~List<&2,E>,(store => clear(store,handle)),world))
153
154def despawn_valid_observed(~S: Data,~C: Type,~R: Type,~E: Data,~clear: C -> W.Handle<S> -> C & List<&2,E>,~record: W.Handle<S> -> E,checked: W.World<S,C,R,E> & Bool,+handle: W.Handle<S>) -> W.World<S,C,R,E>:
155 match checked:
156 case (world,False{}): world
157 case (world,True{}): clear_observed_accepted(~S,~C,~R,~E,~clear,~record,W.deactivate(~S,~C,~R,~E,world,handle),handle)
158
159# Apply checked live-entity cleanup, publishing its component removal records
160# followed by the entity record only after accepted deactivation. Invalid handles
161# leave World unchanged and skip cleanup/publication.
162def despawn_apply_observed(~S: Data,~C: Type,~R: Type,~E: Data,~clear: C -> W.Handle<S> -> C & List<&2,E>,~record: W.Handle<S> -> E,world: W.World<S,C,R,E>,+handle: W.Handle<S>) -> W.World<S,C,R,E>:
163 despawn_valid_observed(~S,~C,~R,~E,~clear,~record,W.valid(~S,~C,~R,~E,world,handle),handle)
164
165def despawn_checked_observed(~S: Data,~C: Type,~R: Type,~E: Data,~clear: C -> W.Handle<S> -> C & List<&2,E>,~record: W.Handle<S> -> E,allowed: Bool,world: W.World<S,C,R,E>,undo: List<(W.World<S,C,R,E> -> W.World<S,C,R,E>)>,commands:List<Deferred.Command<W.World<S,C,R,E>>>,events: List<&2,E>,trace:TraceFacts.State,+handle: W.Handle<S>) -> T.Tx<W.World<S,C,R,E>,E> & W.Status:
166 match allowed:
167 case False{}: (T.Tx{world,undo,commands,events,trace},W.Rejected{W.MissingEntity{}})
168 case True{}: (T.Tx{world,undo,Deferred.Raw{(w => despawn_apply_observed(~S,~C,~R,~E,~clear,~record,w,handle))} <> commands,events,trace},W.Accepted{})
169
170def despawn_join_observed(~S: Data,~C: Type,~R: Type,~E: Data,~clear: C -> W.Handle<S> -> C & List<&2,E>,~record: W.Handle<S> -> E,checked: W.World<S,C,R,E> & Bool,undo: List<(W.World<S,C,R,E> -> W.World<S,C,R,E>)>,commands:List<Deferred.Command<W.World<S,C,R,E>>>,events: List<&2,E>,trace:TraceFacts.State,+handle: W.Handle<S>) -> T.Tx<W.World<S,C,R,E>,E> & W.Status:
171 (world,allowed) = checked
172 despawn_checked_observed(~S,~C,~R,~E,~clear,~record,allowed,world,undo,commands,events,trace,handle)
173
174# Stage cleanup plus typed lifecycle publication for a command-valid handle.
175# Queue admission returns Tx and Status; barrier application rechecks live validity
176# before consuming payloads and publishing records. These Data records do not own
177# the removed Type payloads or imply automatic recovery.
178def tx_despawn_observed(~S: Data,~C: Type,~R: Type,~E: Data,~clear: C -> W.Handle<S> -> C & List<&2,E>,~record: W.Handle<S> -> E,tx: T.Tx<W.World<S,C,R,E>,E>,+handle: W.Handle<S>) -> T.Tx<W.World<S,C,R,E>,E> & W.Status:
179 match tx:
180 case T.Tx{world,undo,commands,events,trace}: despawn_join_observed(~S,~C,~R,~E,~clear,~record,W.command_valid(~S,~C,~R,~E,world,handle),undo,commands,events,trace,handle)