machine.bend
bendvy-ecs-core@0.1.0.1 · 230 lines · 0 definitions
1import Base
2import bendvy-ecs-core@0.1.0.1/deferred_command.bend as Deferred
3import bendvy-ecs-core@0.1.0.1/trace_facts.bend as TraceFacts
4import transaction.bend as Tx
5
6# Experimental machine metadata. World and component owners remain arbitrary Type.
7# This module follows the pinned reference marker snapshot semantics; that behavior
8# is not approval of the disputed blanket next-marker retention requirement.
9type Pending<V: Data> is Data:
10 NoPending{}
11 Queued{value: V, skipSame: Bool}
12
13type Transition<V: Data> is Data:
14 Transition{from: V, to: V}
15 Initial{to: V}
16
17type Slot<S: Data, M: Data, V: Data> is Data:
18 Missing{}
19 Present{current: V, pending: Pending<V>, previous: Maybe<&2,V>, changed: Bool}
20
21# Public capability projections intentionally expose less than debug metadata.
22type CurrentView<S: Data,M: Data,V: Data> is Data:
23 Unavailable{}
24 Current{value: V}
25type NextView<S: Data,M: Data,V: Data> is Data:
26 NoNext{}
27 Next{value: V}
28
29def current_view(~S: Data,~M: Data,~V: Data,slot: Slot<S,M,V>) -> CurrentView<S,M,V>:
30 match slot:
31 case Missing{}: Unavailable{}
32 case Present{current,_,_,_}: Current{current}
33def pending_view(~S: Data,~M: Data,~V: Data,pending: Pending<V>) -> NextView<S,M,V>:
34 match pending:
35 case NoPending{}: NoNext{}
36 case Queued{value,_}: Next{value}
37def next_view(~S: Data,~M: Data,~V: Data,slot: Slot<S,M,V>) -> NextView<S,M,V>:
38 match slot:
39 case Missing{}: NoNext{}
40 case Present{_,pending,_,_}: pending_view(~S,~M,~V,pending)
41
42def is_current(~S: Data,~M: Data,~V: Data,~same: V -> V -> Bool,view: CurrentView<S,M,V>,value: V) -> Bool:
43 match view:
44 case Unavailable{}: False{}
45 case Current{current}: same(current,value)
46
47type Family<S: Data,M: Data,V: Data,W: Type,
48 -take: W -> W & Slot<S,M,V>,-put: W -> Slot<S,M,V> -> W> is Data:
49 Family{name: String}
50
51type QueueInput<V: Data> is Data:
52 Set{value: V,skipSame: Bool}
53 Reset{}
54
55type Snapshot<V: Data> is Data:
56 Unscheduled{}
57 Scheduled{value: V, skipSame: Bool}
58
59type Applied<S: Data, M: Data, V: Data> is Data:
60 Applied{slot: Slot<S,M,V>, event: Maybe<&2,Transition<V>>}
61
62def queue(~S: Data,~M: Data,~V: Data,slot: Slot<S,M,V>,value: V,skipSame: Bool) -> Slot<S,M,V>:
63 match slot:
64 case Missing{}: Missing{}
65 case Present{current,_,previous,changed}: Present{current,Queued{value,skipSame},previous,changed}
66
67def reset(~S: Data,~M: Data,~V: Data,slot: Slot<S,M,V>) -> Slot<S,M,V>:
68 match slot:
69 case Missing{}: Missing{}
70 case Present{current,_,previous,changed}: Present{current,NoPending{},previous,changed}
71
72def clear_changed(~S: Data,~M: Data,~V: Data,slot: Slot<S,M,V>) -> Slot<S,M,V>:
73 match slot:
74 case Missing{}: Missing{}
75 case Present{current,pending,previous,_}: Present{current,pending,previous,False{}}
76
77def snapshot_pending(~V: Data,pending: Pending<V>) -> Snapshot<V>:
78 match pending:
79 case NoPending{}: Unscheduled{}
80 case Queued{value,skipSame}: Scheduled{value,skipSame}
81
82def snapshot(~S: Data,~M: Data,~V: Data,slot: Slot<S,M,V>) -> Snapshot<V>:
83 match slot:
84 case Missing{}: Unscheduled{}
85 case Present{_,pending,_,_}: snapshot_pending(~V,pending)
86
87def apply_decision(~S: Data,~M: Data,~V: Data,+current: V,+value: V,previous: Maybe<&2,V>,changed: Bool,skip: Bool) -> Applied<S,M,V>:
88 match skip:
89 case True{}: Applied{Present{current,NoPending{},previous,changed},None{}}
90 case False{}: Applied{Present{value,NoPending{},Some{current},True{}},Some{Transition{current,value}}}
91
92def apply_present(~S: Data,~M: Data,~V: Data,~same: V -> V -> Bool,+current: V,+value: V,previous: Maybe<&2,V>,changed: Bool,skipSame: Bool) -> Applied<S,M,V>:
93 apply_decision(~S,~M,~V,current,value,previous,changed,Bool.and(skipSame,same(current,value)))
94
95# Captured initial value is authoritative; current pending is deleted at the
96# machine's turn, matching Runtime.ts:1534 even when an earlier hook overwrote it.
97def apply_snapshot(~S: Data,~M: Data,~V: Data,~same: V -> V -> Bool,slot: Slot<S,M,V>,captured: Snapshot<V>) -> Applied<S,M,V>:
98 match slot captured:
99 case slot Unscheduled{}: Applied{slot,None{}}
100 case Missing{} Scheduled{_,_}: Applied{Missing{},None{}}
101 case Present{current,_,previous,changed} Scheduled{value,skipSame}:
102 apply_present(~S,~M,~V,~same,current,value,previous,changed,skipSame)
103
104# Journal the complete old slot before replacing it. Aliases see the current
105# world through the same affine transaction; no independent machine owner copy.
106def write_taken(~S: Data,~M: Data,~V: Data,~W: Type,~E: Data,
107 ~put: W -> Slot<S,M,V> -> W,
108 pair: W & Slot<S,M,V>,undo: List<&1,(W -> W)>,commands:List<&1,Deferred.Command<W>>,events: List<&2,E>,trace:TraceFacts.State,value: V,skipSame: Bool) -> Tx.Tx<W,E>:
109 match pair:
110 case (world,+old):
111 Tx.Tx{put(world,queue(~S,~M,~V,old,value,skipSame)),(world => put(world,old)) <> undo,commands,events,trace}
112
113def write(~S: Data,~M: Data,~V: Data,~W: Type,~E: Data,
114 ~take: W -> W & Slot<S,M,V>,~put: W -> Slot<S,M,V> -> W,
115 tx: Tx.Tx<W,E>,value: V,skipSame: Bool) -> Tx.Tx<W,E>:
116 match tx:
117 case Tx.Tx{world,undo,commands,events,trace}:
118 write_taken(~S,~M,~V,~W,~E,~put,take(world),undo,commands,events,trace,value,skipSame)
119
120def reset_taken(~S: Data,~M: Data,~V: Data,~W: Type,~E: Data,
121 ~put: W -> Slot<S,M,V> -> W,
122 pair: W & Slot<S,M,V>,undo: List<&1,(W -> W)>,commands:List<&1,Deferred.Command<W>>,events: List<&2,E>,trace:TraceFacts.State) -> Tx.Tx<W,E>:
123 match pair:
124 case (world,+old):
125 Tx.Tx{put(world,reset(~S,~M,~V,old)),(world => put(world,old)) <> undo,commands,events,trace}
126
127def reset_tx(~S: Data,~M: Data,~V: Data,~W: Type,~E: Data,
128 ~take: W -> W & Slot<S,M,V>,~put: W -> Slot<S,M,V> -> W,
129 tx: Tx.Tx<W,E>) -> Tx.Tx<W,E>:
130 match tx:
131 case Tx.Tx{world,undo,commands,events,trace}:
132 reset_taken(~S,~M,~V,~W,~E,~put,take(world),undo,commands,events,trace)
133
134def next(~S: Data,~M: Data,~V: Data,~W: Type,~E: Data,
135 ~take: W -> W & Slot<S,M,V>,~put: W -> Slot<S,M,V> -> W,
136 tx: Tx.Tx<W,E>,request: QueueInput<V>) -> Tx.Tx<W,E>:
137 match request:
138 case Set{value,skipSame}: write(~S,~M,~V,~W,~E,~take,~put,tx,value,skipSame)
139 case Reset{}: reset_tx(~S,~M,~V,~W,~E,~take,~put,tx)
140
141def read_taken(~S: Data,~M: Data,~V: Data,~W: Type,~E: Data,
142 pair: W & Slot<S,M,V>,undo: List<&1,(W -> W)>,commands:List<&1,Deferred.Command<W>>,events: List<&2,E>,trace:TraceFacts.State) -> Tx.Tx<W,E> & Slot<S,M,V>:
143 match pair:
144 case (world,slot): (Tx.Tx{world,undo,commands,events,trace},slot)
145
146def read(~S: Data,~M: Data,~V: Data,~W: Type,~E: Data,
147 ~take: W -> W & Slot<S,M,V>,tx: Tx.Tx<W,E>) -> Tx.Tx<W,E> & Slot<S,M,V>:
148 match tx:
149 case Tx.Tx{world,undo,commands,events,trace}: read_taken(~S,~M,~V,~W,~E,take(world),undo,commands,events,trace)
150
151def read_declared(~S: Data,~M: Data,~V: Data,~W: Type,~E: Data,
152 ~take: W -> W & Slot<S,M,V>,~put: W -> Slot<S,M,V> -> W,
153 _: Family<S,M,V,W,take,put>,tx: Tx.Tx<W,E>) -> Tx.Tx<W,E> & Slot<S,M,V>:
154 read(~S,~M,~V,~W,~E,~take,tx)
155
156def next_declared(~S: Data,~M: Data,~V: Data,~W: Type,~E: Data,
157 ~take: W -> W & Slot<S,M,V>,~put: W -> Slot<S,M,V> -> W,
158 _: Family<S,M,V,W,take,put>,tx: Tx.Tx<W,E>,request: QueueInput<V>) -> Tx.Tx<W,E>:
159 next(~S,~M,~V,~W,~E,~take,~put,tx,request)
160
161type Condition<V: Data> is Data:
162 InState{value: V}
163 StateChanged{}
164 Not{inner: Condition<V>}
165 Constant{value: Bool}
166 And{left: Condition<V>,right: Condition<V>}
167 Or{left: Condition<V>,right: Condition<V>}
168
169def current_matches(~S: Data,~M: Data,~V: Data,~same: V -> V -> Bool,slot: Slot<S,M,V>,value: V) -> Bool:
170 match slot:
171 case Missing{}: False{}
172 case Present{current,_,_,_}: same(current,value)
173
174def changed(~S: Data,~M: Data,~V: Data,slot: Slot<S,M,V>) -> Bool:
175 match slot:
176 case Missing{}: False{}
177 case Present{_,_,_,flag}: flag
178
179# Arbitrary public variadic combinations become a structural Boolean tree.
180# Empty and/or preserve the primary API identities.
181def all(~V: Data,items: List<&2,Condition<V>>) -> Condition<V>:
182 match items:
183 case Nil{}: Constant{True{}}
184 case Con{head,tail}: And{head,all(~V,tail)}
185
186def any(~V: Data,items: List<&2,Condition<V>>) -> Condition<V>:
187 match items:
188 case Nil{}: Constant{False{}}
189 case Con{head,tail}: Or{head,any(~V,tail)}
190
191def evaluate(~S: Data,~M: Data,~V: Data,~same: V -> V -> Bool,condition: Condition<V>,+slot: Slot<S,M,V>) -> Bool:
192 match condition:
193 case InState{value}: current_matches(~S,~M,~V,~same,slot,value)
194 case StateChanged{}: changed(~S,~M,~V,slot)
195 case Not{inner}: Bool.not(evaluate(~S,~M,~V,~same,inner,slot))
196 case Constant{value}: value
197 case And{left,right}: Bool.and(evaluate(~S,~M,~V,~same,left,slot),evaluate(~S,~M,~V,~same,right,slot))
198 case Or{left,right}: Bool.or(evaluate(~S,~M,~V,~same,left,slot),evaluate(~S,~M,~V,~same,right,slot))
199
200# A heterogeneous marker snapshot owns each callback exactly once. Each callback
201# captures a Data machine value and closed lenses, never a world/component owner.
202type Marker<S: Data,W: Type> is Type:
203 Marker{steps: List<&1,(W -> W)>}
204
205def marker_steps(~W: Type,steps: List<&1,(W -> W)>,world: W) -> W:
206 match steps:
207 case Nil{}: world
208 case Con{step,rest}: marker_steps(~W,rest,step(world))
209
210def run_marker(~S: Data,~W: Type,marker: Marker<S,W>,world: W) -> W:
211 match marker:
212 case Marker{steps}: marker_steps(~W,steps,world)
213
214# Registered schedule/transition owners live alongside the world and are also
215# threaded, rather than copied into one closure per machine.
216type OwnedMarker<S: Data,W: Type,H: Type> is Type:
217 OwnedMarker{steps: List<&1,(H -> W -> H & W)>}
218
219def marker_next(~W: Type,~H: Type,next: H -> W -> H & W,pair: H & W) -> H & W:
220 (owners,world)=pair
221 next(owners,world)
222
223def owned_marker_steps(~W: Type,~H: Type,steps: List<&1,(H -> W -> H & W)>,owners: H,world: W) -> H & W:
224 match steps:
225 case Nil{}: (owners,world)
226 case Con{step,rest}: marker_next(~W,~H,h => w => owned_marker_steps(~W,~H,rest,h,w),step(owners,world))
227
228def run_owned_marker(~S: Data,~W: Type,~H: Type,marker: OwnedMarker<S,W,H>,owners: H,world: W) -> H & W:
229 match marker:
230 case OwnedMarker{steps}: owned_marker_steps(~W,~H,steps,owners,world)