row_column.bend
bendvy-ecs-core@0.1.0.0 · 67 lines · 0 definitions
1import Base
2import bendvy-ecs-core@0.1.0.0/change_tick.bend as Tick
3import column.bend as Col
4import lifecycle.bend as L
5
6# The captured ID and actual column travel with the owner in one affine token.
7# No attach API accepts a different column or an external target ID.
8type Cold<-S:Data,-C:Type> is Type:
9 Cold{column:Col.Column<S,C>}
10 Wrapped{inner:Cold<S,C>}
11type Cell<-S:Data,-C:Type> is Type:
12 Taken{id:U32,owner:Maybe<&1,C>,stamp:L.Stamp,dirty:Bool,cold:Cold<S,C>}
13 Untaken{column:Col.Column<S,C>,incoming:Maybe<&1,C>,error:Unit}
14type Projected<-S:Data,-C:Type,-V:Data> is Type:
15 Projected{cell:Cell<S,C>,value:Maybe<&2,V>}
16 Deferred{cell:Cell<S,C>}
17
18def cold_column(~S:Data,~C:Type,cold:Cold<S,C>) -> Col.Column<S,C>:
19 match cold:
20 case Cold{column}: column
21 case Wrapped{inner}: cold_column(~S,~C,inner)
22def detached(~S:Data,~C:Type,+id:U32,stamp:L.Stamp,result:Col.SwapOutcome<S,C>) -> Cell<S,C>:
23 match result:
24 case Col.Accepted{column,owner}: Taken{id,owner,stamp,False{},Cold{column}}
25 case Col.Rejected{column,incoming}: Untaken{column,incoming,Unit{}}
26def detach_stamped(~S:Data,~C:Type,+id:U32,result:Col.Column<S,C> & L.Stamp) -> Cell<S,C>:
27 match result:
28 case (column,stamp): detached(~S,~C,id,stamp,Col.swap(~S,~C,column,id,None{}))
29def detach(~S:Data,~C:Type,column:Col.Column<S,C>,+id:U32) -> Cell<S,C>:
30 detach_stamped(~S,~C,id,Col.lifecycle(~S,~C,column,id))
31
32def projected_owner(~S:Data,~C:Type,~V:Data,+id:U32,stamp:L.Stamp,dirty:Bool,cold:Cold<S,C>,result:C & V) -> Projected<S,C,V>:
33 match result:
34 case (owner,value): Projected{Taken{id,Some{owner},stamp,dirty,cold},Some{value}}
35def project(~S:Data,~C:Type,~V:Data,~project:C -> C & V,cell:Cell<S,C>) -> Projected<S,C,V>:
36 match cell:
37 case Taken{id,None{},stamp,dirty,cold}: Projected{Taken{id,None{},stamp,dirty,cold},None{}}
38 case Taken{id,Some{owner},stamp,dirty,cold}: projected_owner(~S,~C,~V,id,stamp,dirty,cold,project(owner))
39 case Untaken{column,incoming,error}: Deferred{Untaken{column,incoming,error}}
40
41# Even a malformed token's attachment refusal returns its actual incoming owner.
42# Trusted row adapters must handle this outcome before exposing a World.
43def attach_marked(~S:Data,~C:Type,+id:U32,stamp:L.Stamp,dirty:Bool,result:Col.SwapOutcome<S,C>) -> Col.SwapOutcome<S,C>:
44 match dirty result:
45 case False{} result: result
46 case True{} Col.Accepted{column,previous}: Col.Accepted{Col.restore_stamp(~S,~C,column,id,stamp),previous}
47 case True{} Col.Rejected{column,incoming}: Col.Rejected{column,incoming}
48def attach(~S:Data,~C:Type,cell:Cell<S,C>) -> Col.SwapOutcome<S,C>:
49 match cell:
50 case Untaken{column,incoming,_}: Col.Rejected{column,incoming}
51 case Taken{+id,owner,stamp,dirty,cold}: attach_marked(~S,~C,id,stamp,dirty,Col.swap(~S,~C,cold_column(~S,~C,cold),id,owner))
52
53type Replacement<-S:Data,-C:Type> is Type:
54 Replaced{cell:Cell<S,C>,previous:Maybe<&1,C>,stamp:L.Stamp}
55 Refused{cell:Cell<S,C>,incoming:C}
56def replacement_present(~S:Data,~C:Type,+id:U32,+stamp:L.Stamp,cold:Cold<S,C>,incoming:C,+tick:Tick.Tick,observed:Maybe<&1,C> & Bool) -> Replacement<S,C>:
57 match observed:
58 case (previous,present): Replaced{Taken{id,Some{incoming},L.after_write(True{},present,stamp,tick),True{},cold},previous,stamp}
59def owner_present(~C:Type,owner:Maybe<&1,C>) -> Maybe<&1,C> & Bool:
60 match owner:
61 case None{}: (None{},False{})
62 case Some{owner}: (Some{owner},True{})
63# Caller validates entity/clock first and journals returned previous+stamp now.
64def replace(~S:Data,~C:Type,cell:Cell<S,C>,incoming:C,+tick:Tick.Tick) -> Replacement<S,C>:
65 match cell:
66 case Taken{+id,owner,+stamp,_,cold}: replacement_present(~S,~C,id,stamp,cold,incoming,tick,owner_present(~C,owner))
67 case Untaken{column,held,error}: Refused{Untaken{column,held,error},incoming}