component.bend

bendvy-ecs-core@0.1.0.0 · 369 lines · 0 definitions

1import Base
2import bendvy-ecs-core@0.1.0.0/change_tick.bend as Tick
3import bendvy-ecs-core@0.1.0.0/clock_state.bend as Clock
4import bendvy-ecs-core@0.1.0.0/deferred_command.bend as Deferred
5import bendvy-ecs-core@0.1.0.0/entity_key_render.bend as KeyRender
6import bendvy-ecs-core@0.1.0.0/trace_facts.bend as TraceFacts
7import lifecycle.bend as L
8import column.bend as Col
9import world.bend as W
10import transaction.bend as T
11import entity_key.bend as K
12import entity_allocator.bend as A
13
14# Caller-authored schema lenses are trusted provisioning. The gameplay callback
15# receives only universally quantified provider handles, never these owners.
16# Trusted schema provisioning for one component column and detached Data view V.
17# take/put must return the actual affine storage owners; project returns C with V.
18# The nominal Token distinguishes families; the display name is not access proof.
19type Family<-S: Data,-Store: Type,-Rest: Type,-C: Type,-V: Data,-Token: Data,-take: Store -> Rest & Col.Column<S,C>,-put: Rest -> Col.Column<S,C> -> Store,-project: C -> C & V> is Data:
20  Family{name: String}
21# Detached read result: Found holds V, ComponentAbsent means a valid entity lacks
22# this component, and MissingEntity covers invalid, stale or foreign handles.
23type Access<V: Data> is Data:
24  Found{value: V}
25  ComponentAbsent{}
26  MissingEntity{}
27# Immediate replacement result retaining World in every branch. Replaced returns
28# the displaced actual payload owner; rejection returns the incoming optional owner
29# and error. Neither Type payload is copied into detached observation data.
30type Replacement<W: Type,C: Type> is Type:
31  Replaced{world: W,previous: Maybe<&1,C>}
32  ReplaceRejected{world: W,incoming: Maybe<&1,C>,error: W.WorldError}
33# Internal storage-adapter result carrying the displaced or rejected owner.
34# It is not a handle-validation result or a gameplay capability.
35type Swap<C: Type> is Type:
36  Swapped{previous: Maybe<&1,C>}
37  SwapRejected{incoming: Maybe<&1,C>}
38# Transactional write result. Written retains the Tx with its inverse journal;
39# WriteRejected additionally returns the actual incoming C. WriteFailure has no
40# incoming owner. These results do not themselves commit or finish the transaction.
41type SetResult<Tx: Type,C: Type> is Type:
42  Written{tx: Tx}
43  WriteRejected{tx: Tx,incoming: C,error: W.WorldError}
44  WriteFailure{tx: Tx,error: W.WorldError}
45# Transactional removal result retaining Tx on success or refusal. The removed
46# payload remains in the inverse journal for rollback rather than being returned.
47type RemoveResult<Tx: Type> is Type:
48  Removed{tx: Tx}
49  RemoveRejected{tx: Tx,error: W.WorldError}
50
51# Define trusted column/projection plumbing for this family. This does not insert
52# a component, validate an entity or register a gameplay system. Ordinary declaration
53# providers expose only the appropriate opaque-owner capabilities to gameplay.
54def define(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,name: String) -> Family<S,Store,Rest,C,V,Token,take,put,project>:
55  Family{name}
56
57def access(~V: Data,value: Maybe<&2,V>) -> Access<V>:
58  match value:
59    case None{}: ComponentAbsent{}
60    case Some{value}: Found{value}
61
62def get_projected(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,rest: Rest,observed: Col.Column<S,C> & Maybe<&2,V>) -> Store & Access<V>:
63  (column,value) = observed
64  (put(rest,column),access(~V,value))
65
66def get_store(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+id: U32,split: Rest & Col.Column<S,C>) -> Store & Access<V>:
67  (rest,column) = split
68  get_projected(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,rest,Col.view(~S,~C,~V,~project,column,id))
69
70def get_world(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+id: U32,world: W.World<S,Store,R,E>) -> W.World<S,Store,R,E> & Access<V>:
71  match world:
72    case W.World{+namespace,allocator,store,resource,+events,pending,+registrations,+nextSystemId,+clock}:
73      W.with_store_finish(~S,~Store,~R,~E,~Access<V>,get_store(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,id,take(store)),namespace,allocator,resource,events,pending,registrations,nextSystemId,clock)
74
75def get_allowed(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,checked: W.World<S,Store,R,E> & Bool,+handle: W.Handle<S>) -> W.World<S,Store,R,E> & Access<V>:
76  match checked handle:
77    case (world,False{}) _: (world,MissingEntity{})
78    case (world,True{}) W.Handle{_,K.EntityKey{+id,_}}:
79      get_world(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,id,world)
80
81# Read a detached view while returning World and the actual projected payload to
82# its column. Validate namespace, generation and live state before column access;
83# invalid/stale/foreign handles yield MissingEntity, not ComponentAbsent.
84# See examples/health-decay/schema.bend for family provisioning.
85def get(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,family: Family<S,Store,Rest,C,V,Token,take,put,project>,world: W.World<S,Store,R,E>,+handle: W.Handle<S>) -> W.World<S,Store,R,E> & Access<V>:
86  get_allowed(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,W.valid(~S,~Store,~R,~E,world,handle),handle)
87
88def has_access(~V: Data,value: Access<V>) -> Access<Bool>:
89  match value:
90    case MissingEntity{}: MissingEntity{}
91    case ComponentAbsent{}: Found{False{}}
92    case Found{_}: Found{True{}}
93
94def has_join(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,observed: W.World<S,Store,R,E> & Access<V>) -> W.World<S,Store,R,E> & Access<Bool>:
95  (world,value) = observed
96  (world,has_access(~V,value))
97
98# Return presence as Found(Bool), preserving MissingEntity for invalid handles.
99# This delegates to get, so the configured projection is evaluated on a present
100# payload; it is not a projection-free raw occupancy query.
101def has(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,family: Family<S,Store,Rest,C,V,Token,take,put,project>,world: W.World<S,Store,R,E>,handle: W.Handle<S>) -> W.World<S,Store,R,E> & Access<Bool>:
102  has_join(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,get(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,family,world,handle))
103
104def owner_present(~C: Type,owner: Maybe<&1,C>) -> Maybe<&1,C> & Bool:
105  match owner:
106    case None{}: (None{},False{})
107    case Some{owner}: (Some{owner},True{})
108
109def mark_swapped(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,rest: Rest,column: Col.Column<S,C>,+id: U32,incoming: Bool,tick: Tick.Tick,observed: Maybe<&1,C> & Bool) -> Store & Swap<C>:
110  (previous,wasPresent) = observed
111  (put(rest,Col.mark(~S,~C,column,id,incoming,wasPresent,tick)),Swapped{previous})
112
113def swap_join(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,rest: Rest,+id: U32,incoming: Bool,tick: Tick.Tick,result: Col.SwapOutcome<S,C>) -> Store & Swap<C>:
114  match result:
115    case Col.Accepted{column,previous}: mark_swapped(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,rest,column,id,incoming,tick,owner_present(~C,previous))
116    case Col.Rejected{column,incoming}: (put(rest,column),SwapRejected{incoming})
117
118def swap_store(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+id: U32,incoming: Maybe<&1,C>,incomingPresent: Bool,tick: Tick.Tick,split: Rest & Col.Column<S,C>) -> Store & Swap<C>:
119  (rest,column) = split
120  swap_join(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,rest,id,incomingPresent,tick,Col.swap(~S,~C,Col.ensure(~S,~C,column,id),id,incoming))
121
122def swap_world(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+id: U32,incoming: Maybe<&1,C>,incomingPresent: Bool,tick: Tick.Tick,world: W.World<S,Store,R,E>) -> W.World<S,Store,R,E> & Swap<C>:
123  match world:
124    case W.World{+namespace,allocator,store,resource,+events,pending,+registrations,+nextSystemId,+clock}:
125      W.with_store_finish(~S,~Store,~R,~E,~Swap<C>,swap_store(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,id,incoming,incomingPresent,tick,take(store)),namespace,allocator,resource,events,pending,registrations,nextSystemId,clock)
126
127def replaced(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,result: W.World<S,Store,R,E> & Swap<C>) -> Replacement<W.World<S,Store,R,E>,C>:
128  match result:
129    case (world,Swapped{previous}): Replaced{world,previous}
130    case (world,SwapRejected{incoming}): ReplaceRejected{world,incoming,W.CapacityExceeded{}}
131
132def replace_clock_advanced(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+id: U32,incomingPresent: Bool,incoming: Maybe<&1,C>,advanced: W.World<S,Store,R,E> & Tick.Tick) -> Replacement<W.World<S,Store,R,E>,C>:
133  (world,+tick) = advanced
134  replaced(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,swap_world(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,id,incoming,incomingPresent,tick,world))
135
136def replace_present(~S:Data,~Store:Type,~R:Type,~E:Data,~Rest:Type,~C:Type,~V:Data,~Token:Data,~take:Store -> Rest & Col.Column<S,C>,~put:Rest -> Col.Column<S,C> -> Store,~project:C -> C & V,world:W.World<S,Store,R,E>,id:U32,observed:Maybe<&1,C> & Bool) -> Replacement<W.World<S,Store,R,E>,C>:
137  match observed:
138    case (incoming,present):replace_clock_advanced(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,id,present,incoming,W.write_clock(~S,~Store,~R,~E,world))
139
140def replace_allowed(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,checked: W.World<S,Store,R,E> & Bool,+handle: W.Handle<S>,incoming: Maybe<&1,C>) -> Replacement<W.World<S,Store,R,E>,C>:
141  match checked handle:
142    case (world,False{}) _: ReplaceRejected{world,incoming,W.MissingEntity{}}
143    case (world,True{}) W.Handle{_,K.EntityKey{+id,_}}:
144      replace_present(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,world,id,owner_present(~C,incoming))
145
146# Ordinary writes require a live entity. Foreign handles are rejected before
147# any column access, size computation, allocation or owned replacement move.
148# Immediately insert/replace with Some(C), or remove with None, on a live handle.
149# Return displaced C on success and incoming C on rejection; capacity errors
150# remain explicit. This operation is not deferred and has no automatic undo journal.
151def replace(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,family: Family<S,Store,Rest,C,V,Token,take,put,project>,world: W.World<S,Store,R,E>,+handle: W.Handle<S>,incoming: Maybe<&1,C>) -> Replacement<W.World<S,Store,R,E>,C>:
152  replace_allowed(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,W.valid(~S,~Store,~R,~E,world,handle),handle,incoming)
153
154# Deferred creation may populate reserved entities before activation. This is
155# provisioning/structural command machinery, not a gameplay capability.
156# Trusted structural adapter permitting metadata-valid reserved entities as well
157# as live ones. Retains replacement owners/results like replace; this is not a
158# public gameplay bypass for accessing an arbitrary inactive entity.
159def structural_replace(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,family: Family<S,Store,Rest,C,V,Token,take,put,project>,world: W.World<S,Store,R,E>,+handle: W.Handle<S>,incoming: Maybe<&1,C>) -> Replacement<W.World<S,Store,R,E>,C>:
160  replace_allowed(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,W.metadata_valid(~S,~Store,~R,~E,world,handle),handle,incoming)
161
162def restore_done(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,result: Replacement<W.World<S,Store,R,E>,C>) -> W.World<S,Store,R,E>:
163  match result:
164    case Replaced{world,_}: world
165    case ReplaceRejected{world,_,_}: world
166
167# Inverse closures own their old component, not a copied payload. Replaying
168# drops the replaced current owner exactly once and restores the journal owner.
169def restore_swap(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,rest: Rest,result: Col.SwapOutcome<S,C>) -> Store & Unit:
170  match result:
171    case Col.Accepted{column,_}: (put(rest,column),Unit{})
172    case Col.Rejected{column,_}: (put(rest,column),Unit{})
173
174def restore_store(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+id: U32,incoming: Maybe<&1,C>,split: Rest & Col.Column<S,C>) -> Store & Unit:
175  (rest,column) = split
176  restore_swap(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,rest,Col.swap(~S,~C,Col.ensure(~S,~C,column,id),id,incoming))
177
178def restore_world(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+id: U32,incoming: Maybe<&1,C>,world: W.World<S,Store,R,E>) -> W.World<S,Store,R,E> & Unit:
179  match world:
180    case W.World{+namespace,allocator,store,resource,+events,pending,+registrations,+nextSystemId,+clock}:
181      W.with_store_finish(~S,~Store,~R,~E,~Unit,restore_store(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,id,incoming,take(store)),namespace,allocator,resource,events,pending,registrations,nextSystemId,clock)
182
183def restore_observed(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,observed: W.World<S,Store,R,E> & Unit) -> W.World<S,Store,R,E>:
184  (world,_) = observed
185  world
186
187def restore_allowed(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,checked: W.World<S,Store,R,E> & Bool,handle: W.Handle<S>,incoming: Maybe<&1,C>) -> W.World<S,Store,R,E>:
188  match checked handle:
189    case (world,False{}) _: world
190    case (world,True{}) W.Handle{_,K.EntityKey{+id,_}}: restore_observed(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,restore_world(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,id,incoming,world))
191
192def restore(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,family: Family<S,Store,Rest,C,V,Token,take,put,project>,+handle: W.Handle<S>,incoming: Maybe<&1,C>,world: W.World<S,Store,R,E>) -> W.World<S,Store,R,E>:
193  restore_allowed(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,W.metadata_valid(~S,~Store,~R,~E,world,handle),handle,incoming)
194
195def lifecycle_join(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,rest: Rest,observed: Col.Column<S,C> & L.Stamp) -> Store & L.Stamp:
196  (column,stamp) = observed
197  (put(rest,column),stamp)
198
199def lifecycle_store(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+id: U32,split: Rest & Col.Column<S,C>) -> Store & L.Stamp:
200  (rest,column) = split
201  lifecycle_join(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,rest,Col.lifecycle(~S,~C,column,id))
202
203def lifecycle_world(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+id: U32,world: W.World<S,Store,R,E>) -> W.World<S,Store,R,E> & L.Stamp:
204  match world:
205    case W.World{+namespace,allocator,store,resource,+events,pending,+registrations,+nextSystemId,+clock}:
206      W.with_store_finish(~S,~Store,~R,~E,~L.Stamp,lifecycle_store(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,id,take(store)),namespace,allocator,resource,events,pending,registrations,nextSystemId,clock)
207
208def lifecycle_allowed(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,checked: W.World<S,Store,R,E> & Bool,handle: W.Handle<S>) -> W.World<S,Store,R,E> & L.Stamp:
209  match checked handle:
210    case (world,False{}) _: (world,L.Stamp{None{},None{}})
211    case (world,True{}) W.Handle{_,K.EntityKey{+id,_}}: lifecycle_world(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,id,world)
212
213# Read this component slot's lifecycle stamp while returning World. Invalid live
214# handle validation yields Stamp(None,None); no detached component payload is returned.
215def lifecycle(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,family: Family<S,Store,Rest,C,V,Token,take,put,project>,world: W.World<S,Store,R,E>,+handle: W.Handle<S>) -> W.World<S,Store,R,E> & L.Stamp:
216  lifecycle_allowed(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,W.valid(~S,~Store,~R,~E,world,handle),handle)
217
218def restore_stamp_store(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+id: U32,stamp: L.Stamp,split: Rest & Col.Column<S,C>) -> Store & Unit:
219  (rest,column) = split
220  (put(rest,Col.restore_stamp(~S,~C,column,id,stamp)),Unit{})
221
222def restore_stamp_world(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+id: U32,stamp: L.Stamp,world: W.World<S,Store,R,E>) -> W.World<S,Store,R,E> & Unit:
223  match world:
224    case W.World{+namespace,allocator,store,resource,+events,pending,+registrations,+nextSystemId,+clock}:
225      W.with_store_finish(~S,~Store,~R,~E,~Unit,restore_stamp_store(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,id,stamp,take(store)),namespace,allocator,resource,events,pending,registrations,nextSystemId,clock)
226
227def restore_stamp_done(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,observed: W.World<S,Store,R,E> & Unit) -> W.World<S,Store,R,E>:
228  (world,_) = observed
229  world
230
231def restore_stamp(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+handle: W.Handle<S>,stamp: L.Stamp,world: W.World<S,Store,R,E>) -> W.World<S,Store,R,E>:
232  match handle:
233    case W.Handle{_,K.EntityKey{+id,_}}: restore_stamp_done(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,restore_stamp_world(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,id,stamp,world))
234
235def tx_get_join(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,undo: List<(W.World<S,Store,R,E> -> W.World<S,Store,R,E>)>,commands:List<Deferred.Command<W.World<S,Store,R,E>>>,events: List<&2,E>,trace:TraceFacts.State,observed: W.World<S,Store,R,E> & Access<V>) -> T.Tx<W.World<S,Store,R,E>,E> & Access<V>:
236  (world,value) = observed
237  (T.Tx{world,undo,commands,events,trace},value)
238
239# Read through the current Tx World, preserving its journal, commands, events and
240# trace. Returns the same Access distinctions as get; reads do not finish the Tx.
241def tx_get(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,family: Family<S,Store,Rest,C,V,Token,take,put,project>,tx: T.Tx<W.World<S,Store,R,E>,E>,handle: W.Handle<S>) -> T.Tx<W.World<S,Store,R,E>,E> & Access<V>:
242  match tx:
243    case T.Tx{world,undo,commands,events,trace}:
244      tx_get_join(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,undo,commands,events,trace,get(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,family,world,handle))
245
246def trace_error(error:W.WorldError) -> String:
247  match error:
248    case W.MissingEntity{}:"MissingEntity"
249    case W.CapacityExceeded{}:"CapacityExceeded"
250    case W.NamespaceExhausted{}:"NamespaceExhausted"
251    case W.SystemIdExhausted{}:"SystemIdExhausted"
252def trace_handle(~S:Data,handle:W.Handle<S>) -> String:
253  match handle:
254    case W.Handle{namespace,key}:U32.show(namespace) ++ ":" ++ KeyRender.show(key)
255def trace_write(~S:Data,state:TraceFacts.State,name:String,handle:W.Handle<S>,result:TraceFacts.FactResult) -> TraceFacts.State:
256  match state:
257    case TraceFacts.Off{}:TraceFacts.Off{}
258    case TraceFacts.On{facts}:TraceFacts.On{TraceFacts.Fact{TraceFacts.ComponentValue{},name,TraceFacts.WriteOperation{},Some{trace_handle(~S,handle)},TraceFacts.OpaqueValue{},TraceFacts.OpaqueValue{},result} <> facts}
259def trace_refusal(~S:Data,state:TraceFacts.State,name:String,handle:W.Handle<S>,error:W.WorldError) -> TraceFacts.State:
260  match state:
261    case TraceFacts.Off{}:TraceFacts.Off{}
262    case TraceFacts.On{facts}:TraceFacts.On{TraceFacts.Fact{TraceFacts.ComponentValue{},name,TraceFacts.WriteOperation{},Some{trace_handle(~S,handle)},TraceFacts.OpaqueValue{},TraceFacts.OpaqueValue{},TraceFacts.Refused{trace_error(error)}} <> facts}
263def family_name(~S:Data,~Store:Type,~Rest:Type,~C:Type,~V:Data,~Token:Data,~take:Store -> Rest & Col.Column<S,C>,~put:Rest -> Col.Column<S,C> -> Store,~project:C -> C & V,family:Family<S,Store,Rest,C,V,Token,take,put,project>) -> String:
264  match family:
265    case Family{name}:name
266def tx_written(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+family: Family<S,Store,Rest,C,V,Token,take,put,project>,+handle: W.Handle<S>,undo: List<(W.World<S,Store,R,E> -> W.World<S,Store,R,E>)>,commands:List<Deferred.Command<W.World<S,Store,R,E>>>,events: List<&2,E>,trace:TraceFacts.State,+stamp: L.Stamp,result: Replacement<W.World<S,Store,R,E>,C>) -> SetResult<T.Tx<W.World<S,Store,R,E>,E>,C>:
267  match result:
268    case Replaced{world,previous}:
269      Written{T.Tx{world,(world => restore_stamp(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,handle,stamp,restore(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,family,handle,previous,world))) <> undo,commands,events,trace_write(~S,trace,family_name(~S,~Store,~Rest,~C,~V,~Token,~take,~put,~project,family),handle,TraceFacts.Attempted{})}}
270    case ReplaceRejected{world,Some{incoming},+error}:
271      WriteRejected{T.Tx{world,undo,commands,events,trace_refusal(~S,trace,family_name(~S,~Store,~Rest,~C,~V,~Token,~take,~put,~project,family),handle,error)},incoming,error}
272    case ReplaceRejected{world,None{},+error}:
273      WriteFailure{T.Tx{world,undo,commands,events,trace_refusal(~S,trace,family_name(~S,~Store,~Rest,~C,~V,~Token,~take,~put,~project,family),handle,error)},error}
274
275def tx_set_stamped(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+family: Family<S,Store,Rest,C,V,Token,take,put,project>,+handle: W.Handle<S>,undo: List<(W.World<S,Store,R,E> -> W.World<S,Store,R,E>)>,commands:List<Deferred.Command<W.World<S,Store,R,E>>>,events: List<&2,E>,trace:TraceFacts.State,incoming: C,observed: W.World<S,Store,R,E> & L.Stamp) -> SetResult<T.Tx<W.World<S,Store,R,E>,E>,C>:
276  (world,stamp) = observed
277  tx_written(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,family,handle,undo,commands,events,trace,stamp,replace(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,family,world,handle,Some{incoming}))
278
279def stamped_store_join(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,stamp:L.Stamp,result:Store & Swap<C>) -> Store & (Swap<C> & L.Stamp):
280  match result:
281    case (store,outcome): (store,(outcome,stamp))
282
283def stamped_column(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,rest:Rest,+id:U32,incoming:C,+tick:Tick.Tick,allowed:Bool,observed:Col.Column<S,C> & L.Stamp) -> Store & (Swap<C> & L.Stamp):
284  match allowed observed:
285    case False{}, (column,stamp): (put(rest,column),(SwapRejected{Some{incoming}},stamp))
286    case True{}, (column,stamp): stamped_store_join(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,stamp,swap_join(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,rest,id,True{},tick,Col.swap(~S,~C,Col.ensure(~S,~C,column,id),id,Some{incoming})))
287
288def stamped_store(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+id:U32,incoming:C,+tick:Tick.Tick,allowed:Bool,split:Rest & Col.Column<S,C>) -> Store & (Swap<C> & L.Stamp):
289  match split:
290    case (rest,column): stamped_column(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,rest,id,incoming,tick,allowed,Col.lifecycle(~S,~C,column,id))
291
292def stamped_replaced(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,observed:W.World<S,Store,R,E> & (Swap<C> & L.Stamp)) -> Replacement<W.World<S,Store,R,E>,C> & L.Stamp:
293  match observed:
294    case (world,(Swapped{previous},stamp)): (Replaced{world,previous},stamp)
295    case (world,(SwapRejected{incoming},stamp)): (ReplaceRejected{world,incoming,W.CapacityExceeded{}},stamp)
296
297def stamped_clocked(~S:Data,~Store:Type,~R:Type,~E:Data,~Rest:Type,~C:Type,~V:Data,~Token:Data,~take:Store -> Rest & Col.Column<S,C>,~put:Rest -> Col.Column<S,C> -> Store,~project:C -> C & V,world:W.World<S,Store,R,E>,id:U32,incoming:C,tick:Tick.Tick) -> Replacement<W.World<S,Store,R,E>,C> & L.Stamp:
298  match world:
299    case W.World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock}:
300      stamped_replaced(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,W.with_store_finish(~S,~Store,~R,~E,~(Swap<C> & L.Stamp),stamped_store(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,id,incoming,tick,True{},take(store)),namespace,allocator,resource,events,pending,registrations,nextSystemId,clock))
301
302def stamped_advanced(~S:Data,~Store:Type,~R:Type,~E:Data,~Rest:Type,~C:Type,~V:Data,~Token:Data,~take:Store -> Rest & Col.Column<S,C>,~put:Rest -> Col.Column<S,C> -> Store,~project:C -> C & V,id:U32,incoming:C,advanced:W.World<S,Store,R,E> & Tick.Tick) -> Replacement<W.World<S,Store,R,E>,C> & L.Stamp:
303  (world,tick)=advanced
304  stamped_clocked(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,world,id,incoming,tick)
305
306def stamped_world(~S:Data,~Store:Type,~R:Type,~E:Data,~Rest:Type,~C:Type,~V:Data,~Token:Data,~take:Store -> Rest & Col.Column<S,C>,~put:Rest -> Col.Column<S,C> -> Store,~project:C -> C & V,id:U32,incoming:C,world:W.World<S,Store,R,E>) -> Replacement<W.World<S,Store,R,E>,C> & L.Stamp:
307  stamped_advanced(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,id,incoming,W.write_clock(~S,~Store,~R,~E,world))
308
309def stamped_allowed(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,checked:W.World<S,Store,R,E> & Bool,handle:W.Handle<S>,incoming:C) -> Replacement<W.World<S,Store,R,E>,C> & L.Stamp:
310  match checked handle:
311    case (world,False{}) _: (ReplaceRejected{world,Some{incoming},W.MissingEntity{}},L.Stamp{None{},None{}})
312    case (world,True{}) W.Handle{_,K.EntityKey{+id,_}}: stamped_world(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,id,incoming,world)
313
314def tx_set_fused(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+family:Family<S,Store,Rest,C,V,Token,take,put,project>,+handle:W.Handle<S>,undo:List<(W.World<S,Store,R,E> -> W.World<S,Store,R,E>)>,commands:List<Deferred.Command<W.World<S,Store,R,E>>>,events:List<&2,E>,trace:TraceFacts.State,observed:Replacement<W.World<S,Store,R,E>,C> & L.Stamp) -> SetResult<T.Tx<W.World<S,Store,R,E>,E>,C>:
315  match observed:
316    case (result,stamp): tx_written(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,family,handle,undo,commands,events,trace,stamp,result)
317
318# Write an actual possibly-affine Type payload into the current Tx World and
319# journal the old payload and stamp for rollback. Successful writes are visible
320# within this Tx immediately; finish decides commit versus inverse replay. Refusal
321# returns the incoming owner through WriteRejected. Ordinary rollback is not a
322# receipt returning every newly written payload; use explicit recovery APIs for that.
323def tx_set(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+family: Family<S,Store,Rest,C,V,Token,take,put,project>,tx: T.Tx<W.World<S,Store,R,E>,E>,+handle: W.Handle<S>,incoming: C) -> SetResult<T.Tx<W.World<S,Store,R,E>,E>,C>:
324  match tx:
325    case T.Tx{world,undo,commands,events,trace}:
326      tx_set_fused(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,family,handle,undo,commands,events,trace,stamped_allowed(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,W.valid(~S,~Store,~R,~E,world,handle),handle,incoming))
327
328def tx_removed(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+family: Family<S,Store,Rest,C,V,Token,take,put,project>,+handle: W.Handle<S>,undo: List<(W.World<S,Store,R,E> -> W.World<S,Store,R,E>)>,commands:List<Deferred.Command<W.World<S,Store,R,E>>>,events: List<&2,E>,trace:TraceFacts.State,+stamp: L.Stamp,result: Replacement<W.World<S,Store,R,E>,C>) -> RemoveResult<T.Tx<W.World<S,Store,R,E>,E>>:
329  match result:
330    case Replaced{world,previous}:
331      Removed{T.Tx{world,(world => restore_stamp(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,handle,stamp,restore(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,family,handle,previous,world))) <> undo,commands,events,trace}}
332    case ReplaceRejected{world,_,error}:
333      RemoveRejected{T.Tx{world,undo,commands,events,trace},error}
334
335def tx_remove_stamped(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+family: Family<S,Store,Rest,C,V,Token,take,put,project>,+handle: W.Handle<S>,undo: List<(W.World<S,Store,R,E> -> W.World<S,Store,R,E>)>,commands:List<Deferred.Command<W.World<S,Store,R,E>>>,events: List<&2,E>,trace:TraceFacts.State,observed: W.World<S,Store,R,E> & L.Stamp) -> RemoveResult<T.Tx<W.World<S,Store,R,E>,E>>:
336  (world,stamp) = observed
337  tx_removed(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,family,handle,undo,commands,events,trace,stamp,replace(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,family,world,handle,None{}))
338
339# Remove from a live entity within Tx, journaling the previous owner/stamp for
340# rollback. Removing an already absent component can succeed; invalid handles or
341# capacity refusal return RemoveRejected. Finish is still the caller's choice.
342def tx_remove(~S: Data,~Store: Type,~R: Type,~E: Data,~Rest: Type,~C: Type,~V: Data,~Token: Data,~take: Store -> Rest & Col.Column<S,C>,~put: Rest -> Col.Column<S,C> -> Store,~project: C -> C & V,+family: Family<S,Store,Rest,C,V,Token,take,put,project>,tx: T.Tx<W.World<S,Store,R,E>,E>,+handle: W.Handle<S>) -> RemoveResult<T.Tx<W.World<S,Store,R,E>,E>>:
343  match tx:
344    case T.Tx{world,undo,commands,events,trace}:
345      tx_remove_stamped(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,family,handle,undo,commands,events,trace,lifecycle(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,family,world,handle))
346
347def displayed_access(~V:Data,~display:V -> String,value:Access<V>) -> TraceFacts.Value:
348  match value:
349    case Found{value}:TraceFacts.DisplayedValue{display(value)}
350    case ComponentAbsent{}:TraceFacts.UnavailableValue{}
351    case MissingEntity{}:TraceFacts.UnavailableValue{}
352def displayed_written(~S:Data,~Store:Type,~R:Type,~E:Data,~Rest:Type,~C:Type,~V:Data,~Token:Data,~take:Store -> Rest & Col.Column<S,C>,~put:Rest -> Col.Column<S,C> -> Store,~project:C -> C & V,before:TraceFacts.Value,after:TraceFacts.Value,result:SetResult<T.Tx<W.World<S,Store,R,E>,E>,C>) -> SetResult<T.Tx<W.World<S,Store,R,E>,E>,C>:
353  match result:
354    case Written{tx}:Written{T.annotate_newest(~W.World<S,Store,R,E>,~E,tx,before,after)}
355    case WriteRejected{tx,incoming,error}:WriteRejected{T.annotate_newest(~W.World<S,Store,R,E>,~E,tx,before,after),incoming,error}
356    case WriteFailure{tx,error}:WriteFailure{T.annotate_newest(~W.World<S,Store,R,E>,~E,tx,before,after),error}
357def displayed_viewed(~S:Data,~Store:Type,~R:Type,~E:Data,~Rest:Type,~C:Type,~V:Data,~Token:Data,~take:Store -> Rest & Col.Column<S,C>,~put:Rest -> Col.Column<S,C> -> Store,~project:C -> C & V,~display:V -> String,family:Family<S,Store,Rest,C,V,Token,take,put,project>,handle:W.Handle<S>,incoming:C & V,observed:T.Tx<W.World<S,Store,R,E>,E> & Access<V>) -> SetResult<T.Tx<W.World<S,Store,R,E>,E>,C>:
358  match incoming observed:
359    case (incoming,after), (tx,before):displayed_written(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,displayed_access(~V,~display,before),TraceFacts.DisplayedValue{display(after)},tx_set(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,family,tx,handle,incoming))
360def displayed_enabled(~S:Data,~Store:Type,~R:Type,~E:Data,~Rest:Type,~C:Type,~V:Data,~Token:Data,~take:Store -> Rest & Col.Column<S,C>,~put:Rest -> Col.Column<S,C> -> Store,~project:C -> C & V,~display:V -> String,+family:Family<S,Store,Rest,C,V,Token,take,put,project>,+handle:W.Handle<S>,incoming:C,observed:T.Tx<W.World<S,Store,R,E>,E> & Bool) -> SetResult<T.Tx<W.World<S,Store,R,E>,E>,C>:
361  match observed:
362    case (tx,False{}):tx_set(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,family,tx,handle,incoming)
363    case (tx,True{}):displayed_viewed(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,~display,family,handle,project(incoming),tx_get(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,family,tx,handle))
364# Trace-aware tx_set: when tracing is enabled, project the current and incoming
365# values and render detached V strings to annotate the attempted write/refusal.
366# Disabled tracing delegates directly without these presentation reads. Ownership
367# and write results remain those of tx_set; project/display are trusted adapters.
368def tx_set_displayed(~S:Data,~Store:Type,~R:Type,~E:Data,~Rest:Type,~C:Type,~V:Data,~Token:Data,~take:Store -> Rest & Col.Column<S,C>,~put:Rest -> Col.Column<S,C> -> Store,~project:C -> C & V,~display:V -> String,family:Family<S,Store,Rest,C,V,Token,take,put,project>,tx:T.Tx<W.World<S,Store,R,E>,E>,handle:W.Handle<S>,incoming:C) -> SetResult<T.Tx<W.World<S,Store,R,E>,E>,C>:
369  displayed_enabled(~S,~Store,~R,~E,~Rest,~C,~V,~Token,~take,~put,~project,~display,family,handle,incoming,T.trace_enabled(~W.World<S,Store,R,E>,~E,tx))