recoverable_component.bend

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

1import Base
2import bendvy-ecs-core@0.1.0.0/clock_state.bend as Clock
3import bendvy-ecs-core@0.1.0.0/deferred_command.bend as Deferred
4import entity_key.bend as Key
5import entity_allocator.bend as Alloc
6import bendvy-ecs-core@0.1.0.0/world.bend as W
7import bendvy-ecs-core@0.1.0.0/column.bend as Col
8import bendvy-ecs-core@0.1.0.0/component.bend as C
9import bendvy-ecs-core@0.1.0.0/lifecycle.bend as Life
10# Canonical single-column provider. No caller-authored take/put/project.
11type Tag is Data:Tag{}
12def take(~S:Data,~P:Type,column:Col.Column<S,P>) -> Unit & Col.Column<S,P>:(Unit{},column)
13def put(~S:Data,~P:Type,unit:Unit,column:Col.Column<S,P>) -> Col.Column<S,P>:column
14def project(~P:Type,payload:P) -> P & Unit:(payload,Unit{})
15type Record<S:Data,P:Type> is Type:
16  Record{handle:W.Handle<S>,stamp:Life.Stamp,payload:Maybe<P>}
17type Replacement<World:Type,S:Data,P:Type> is Type:
18  Applied{world:World,previous:Record<S,P>}
19  Refused{world:World,incoming:Maybe<P>,error:W.WorldError}
20def family(~S:Data,~P:Type,~R:Type,~E:Data,name:String) -> C.Family<S,Col.Column<S,P>,Unit,P,Unit,Tag,take(~S,~P),put(~S,~P),project(~P)>:
21  C.Family{name}
22def replaced(~S:Data,~P:Type,~R:Type,~E:Data,handle:W.Handle<S>,stamp:Life.Stamp,result:C.Replacement<W.World<S,Col.Column<S,P>,R,E>,P>) -> Replacement<W.World<S,Col.Column<S,P>,R,E>,S,P>:
23  match result:
24    case C.Replaced{world,previous}:Applied{world,Record{handle,stamp,previous}}
25    case C.ReplaceRejected{world,incoming,error}:Refused{world,incoming,error}
26def stamped(~S:Data,~P:Type,~R:Type,~E:Data,name:String,+handle:W.Handle<S>,incoming:Maybe<P>,observed:W.World<S,Col.Column<S,P>,R,E> & Life.Stamp) -> Replacement<W.World<S,Col.Column<S,P>,R,E>,S,P>:
27  match observed:
28    case (world,stamp):replaced(~S,~P,~R,~E,handle,stamp,C.replace(~S,~Col.Column<S,P>,~R,~E,~Unit,~P,~Unit,~Tag,~take(~S,~P),~put(~S,~P),~project(~P),family(~S,~P,~R,~E,name),world,handle,incoming))
29def replace(~S:Data,~P:Type,~R:Type,~E:Data,+name:String,world:W.World<S,Col.Column<S,P>,R,E>,+handle:W.Handle<S>,incoming:Maybe<P>) -> Replacement<W.World<S,Col.Column<S,P>,R,E>,S,P>:
30  stamped(~S,~P,~R,~E,name,handle,incoming,C.lifecycle(~S,~Col.Column<S,P>,~R,~E,~Unit,~P,~Unit,~Tag,~take(~S,~P),~put(~S,~P),~project(~P),family(~S,~P,~R,~E,name),world,handle))
31def restored(~S:Data,~P:Type,+handle:W.Handle<S>,stamp:Life.Stamp,released:Life.Stamp,result:Col.SwapOutcome<S,P>) -> Col.Column<S,P> & Record<S,P>:
32  match handle result:
33    case W.Handle{_,Key.EntityKey{id,_}},Col.Accepted{column,payload}:(Col.restore_stamp(~S,~P,column,id,stamp),Record{handle,released,payload})
34    # For a saved ordinary record, ensure covers its positive live id, hence
35    # swap accepts. Raw rejection returns the untouched incoming owner/record;
36    # it does not claim that a different payload was displaced.
37    case W.Handle{_,Key.EntityKey{id,_}},Col.Rejected{column,payload}:(column,Record{handle,stamp,payload})
38def undo_stamped(~S:Data,~P:Type,+handle:W.Handle<S>,stamp:Life.Stamp,payload:Maybe<P>,observed:Col.Column<S,P> & Life.Stamp) -> Col.Column<S,P> & Record<S,P>:
39  match handle observed:
40    case W.Handle{_,Key.EntityKey{+id,_}}, (column,released):restored(~S,~P,handle,stamp,released,Col.swap(~S,~P,Col.ensure(~S,~P,column,id),id,payload))
41def undo_column(~S:Data,~P:Type,+handle:W.Handle<S>,stamp:Life.Stamp,payload:Maybe<P>,column:Col.Column<S,P>) -> Col.Column<S,P> & Record<S,P>:
42  match handle:
43    case W.Handle{_,Key.EntityKey{id,_}}:undo_stamped(~S,~P,handle,stamp,payload,Col.lifecycle(~S,~P,column,id))
44def undone(~S:Data,~P:Type,~R:Type,~E:Data,ns:U32,allocator:Alloc.Allocator,resource:R,events:List<&2,E>,pending:List<Deferred.Command<W.World<S,Col.Column<S,P>,R,E>>>,regs:List<&2,W.RegistrationMeta>,sid:U32,clock:Clock.State,result:Col.Column<S,P> & Record<S,P>) -> W.World<S,Col.Column<S,P>,R,E> & Record<S,P>:
45  match result:
46    case (column,record):(W.World{ns,allocator,column,resource,events,pending,regs,sid,clock},record)
47# Trusted inverse: ordinary grants never alter allocation/live state or inject
48# records. Canonical ensure preserves every cell and covers the saved live id;
49# undo restores its exact stamp and returns the displaced owner with its own
50# pre-undo stamp. World clock and every unrelated owner remain unchanged.
51def undo_valid(~S:Data,~P:Type,~R:Type,~E:Data,record:Record<S,P>,world:W.World<S,Col.Column<S,P>,R,E>) -> W.World<S,Col.Column<S,P>,R,E> & Record<S,P>:
52  match record world:
53    case Record{handle,stamp,payload},W.World{ns,allocator,column,resource,events,pending,regs,sid,clock}:
54      undone(~S,~P,~R,~E,ns,allocator,resource,events,pending,regs,sid,clock,undo_column(~S,~P,handle,stamp,payload,column))
55
56# Invalid raw records remain intact; ordinary journal records use the accepted
57# path because component operations cannot change namespace or entity liveness.
58def undo_checked(~S:Data,~P:Type,~R:Type,~E:Data,record:Record<S,P>,checked:W.World<S,Col.Column<S,P>,R,E> & Bool) -> W.World<S,Col.Column<S,P>,R,E> & Record<S,P>:
59  match checked:
60    case (world,False{}):(world,record)
61    case (world,True{}):undo_valid(~S,~P,~R,~E,record,world)
62def undo(~S:Data,~P:Type,~R:Type,~E:Data,record:Record<S,P>,world:W.World<S,Col.Column<S,P>,R,E>) -> W.World<S,Col.Column<S,P>,R,E> & Record<S,P>:
63  match record:
64    case Record{+handle,stamp,payload}:undo_checked(~S,~P,~R,~E,Record{handle,stamp,payload},W.valid(~S,~Col.Column<S,P>,~R,~E,world,handle))