1 import Base 2 import ./trace_relation.bend as TraceRel 3 import ./deferred_command.bend as Deferred 4 import ./trace_facts.bend as TraceFacts 5 import entity_key.bend as Key 6 import relation_types.bend as G 7 import relation_graph.bend as C 8 import world.bend as W 9 import transaction.bend as X 10 11 # Experiment-only transport: never duplicates or inspects arbitrary component owners. 12 # This initial seam implements relate/unrelate, not linked cleanup or keyed retention. 13 type Store is Type: 14 Store{components: Components,graph: G.Graph} 15 type Notice is Data: 16 Original{event: Original} 17 RelationFailed{failure: G.Failure} 18 ComponentRemoved{id:Key.Key} 19 CleanupPaused{frames: List<&2,G.CleanupFrame>} 20 type QueueStatus is Data: 21 Queued{} 22 MissingEntity{} 23 def empty(~S: Data,~C: Type,components: C) -> Store: 24 Store{components,G.Graph{[],[]}} 25 def graph_read(~S: Data,~C: Type,store: Store) -> Store & G.Graph: 26 match store: 27 case Store{components,+graph}: (Store{components,graph},graph) 28 def graph_replace(~S: Data,~C: Type,store: Store,replacement: G.Graph) -> Store: 29 match store: 30 case Store{components,_}: Store{components,replacement} 31 def relate_result(~S: Data,~C: Type,result: G.CoreResult,components: C) -> Store & Maybe<&2,G.Error>: 32 match result: 33 case G.CoreResult{graph,error}: (Store{components,graph},error) 34 def store_relate(~S: Data,~C: Type,store: Store,descriptor: G.Descriptor,source:Key.Key,target:Key.Key,sourceLive: Bool,targetLive: Bool) -> Store & Maybe<&2,G.Error>: 35 match store: 36 case Store{components,graph}: relate_result(~S,~C,C.relate(~S,graph,descriptor,source,target,sourceLive,targetLive),components) 37 def publish_relate(~S: Data,~C: Type,~R: Type,~E: Data,observed: W.World,R,Notice> & Maybe<&2,G.Error>,descriptor: G.Descriptor,source:Key.Key,target:Key.Key) -> W.World,R,Notice>: 38 match observed: 39 case (world,None{}): world 40 case (world,Some{error}): W.event_publish(~S,~Store,~R,~Notice,world,[RelationFailed{G.Failure{descriptor,G.Relate{},source,target,error}}]) 41 def apply_live_result(~S: Data,~C: Type,~R: Type,~E: Data,checked: W.World,R,Notice> & Bool,+descriptor: G.Descriptor,+source:Key.Key,+target:Key.Key,sourceLive: Bool) -> W.World,R,Notice> & Maybe<&2,G.Error>: 42 (world,targetLive) = checked 43 W.with_store(~S,~Store,~R,~Notice,~Maybe<&2,G.Error>,store => store_relate(~S,~C,store,descriptor,source,target,sourceLive,targetLive),world) 44 def apply_source_result(~S: Data,~C: Type,~R: Type,~E: Data,checked: W.World,R,Notice> & Bool,descriptor: G.Descriptor,source:Key.Key,target:Key.Key,targetHandle: W.Handle) -> W.World,R,Notice> & Maybe<&2,G.Error>: 45 (world,sourceLive) = checked 46 apply_live_result(~S,~C,~R,~E,W.valid(~S,~Store,~R,~Notice,world,targetHandle),descriptor,source,target,sourceLive) 47 def apply_relate_result(~S: Data,~C: Type,~R: Type,~E: Data,world: W.World,R,Notice>,descriptor: G.Descriptor,+source: W.Handle,+target: W.Handle) -> W.World,R,Notice> & Maybe<&2,G.Error>: 48 match source target: 49 case W.Handle{+sn,+sid} W.Handle{+tn,+tid}: apply_source_result(~S,~C,~R,~E,W.valid(~S,~Store,~R,~Notice,world,W.Handle{sn,sid}),descriptor,sid,tid,W.Handle{tn,tid}) 50 def apply_relate(~S:Data,~C:Type,~R:Type,~E:Data,world:W.World,R,Notice>,+descriptor:G.Descriptor,+source:W.Handle,+target:W.Handle) -> W.World,R,Notice>: 51 publish_relate(~S,~C,~R,~E,apply_relate_result(~S,~C,~R,~E,world,descriptor,source,target),descriptor,W.handle_key(~S,source),W.handle_key(~S,target)) 52 def applied_relate_traced(~S:Data,~C:Type,~R:Type,~E:Data,trace:TraceFacts.State,+descriptor:G.Descriptor,+source:W.Handle,+target:W.Handle,result:W.World,R,Notice> & Maybe<&2,G.Error>) -> W.World,R,Notice> & TraceFacts.State: 53 match result: 54 case (world,+error):(publish_relate(~S,~C,~R,~E,(world,error),descriptor,W.handle_key(~S,source),W.handle_key(~S,target)),TraceRel.relate_applied(~S,trace,descriptor,source,target,error)) 55 def relate_command(~S:Data,~C:Type,~R:Type,~E:Data,enabled:Bool,+descriptor:G.Descriptor,+source:W.Handle,+target:W.Handle) -> Deferred.Command,R,Notice>>: 56 match enabled: 57 case False{}:Deferred.Raw{world=>apply_relate(~S,~C,~R,~E,world,descriptor,source,target)} 58 case True{}:Deferred.Observed{trace=>world=>applied_relate_traced(~S,~C,~R,~E,trace,descriptor,source,target,apply_relate_result(~S,~C,~R,~E,world,descriptor,source,target))} 59 def relate_staged(~S:Data,~C:Type,~R:Type,~E:Data,+descriptor:G.Descriptor,+source:W.Handle,+target:W.Handle,tx:X.Tx,R,Notice>,Notice>) -> X.Tx,R,Notice>,Notice>: 60 match tx: 61 case X.Tx{world,undo,commands,events,+trace}:X.Tx{world,undo,relate_command(~S,~C,~R,~E,TraceFacts.enabled(trace),descriptor,source,target) <> commands,events,TraceRel.relate(~S,trace,descriptor,TraceFacts.StageOperation{},source,target,TraceFacts.Attempted{})} 62 def relate_refused(~S:Data,~C:Type,~R:Type,~E:Data,descriptor:G.Descriptor,source:W.Handle,target:W.Handle,tx:X.Tx,R,Notice>,Notice>) -> X.Tx,R,Notice>,Notice>: 63 match tx: 64 case X.Tx{world,undo,commands,events,trace}:X.Tx{world,undo,commands,events,TraceRel.relate(~S,trace,descriptor,TraceFacts.StageOperation{},source,target,TraceFacts.Refused{"MissingEntity"})} 65 def queue_checked(~S: Data,~C: Type,~R: Type,~E: Data,same: Bool,tx: X.Tx,R,Notice>,Notice>,descriptor: G.Descriptor,source: W.Handle,target: W.Handle) -> X.Tx,R,Notice>,Notice> & QueueStatus: 66 match same: 67 case False{}: (relate_refused(~S,~C,~R,~E,descriptor,source,target,tx),MissingEntity{}) 68 case True{}: (relate_staged(~S,~C,~R,~E,descriptor,source,target,tx),Queued{}) 69 def queue_namespaced(~S: Data,~C: Type,~R: Type,~E: Data,checked: W.World,R,Notice> & U32,undo: List<(W.World,R,Notice> -> W.World,R,Notice>)>,commands:List,R,Notice>>>,events: List<&2,Notice>,trace:TraceFacts.State,descriptor: G.Descriptor,source: W.Handle,target: W.Handle) -> X.Tx,R,Notice>,Notice> & QueueStatus: 70 match checked source target: 71 case (world,+namespace) W.Handle{+sn,+sid} W.Handle{+tn,+tid}: queue_checked(~S,~C,~R,~E,Bool.and(U32.is_eq(namespace,sn),U32.is_eq(namespace,tn)),X.Tx{world,undo,commands,events,trace},descriptor,W.Handle{sn,sid},W.Handle{tn,tid}) 72 def queue_relate(~S: Data,~C: Type,~R: Type,~E: Data,tx: X.Tx,R,Notice>,Notice>,descriptor: G.Descriptor,source: W.Handle,target: W.Handle) -> X.Tx,R,Notice>,Notice> & QueueStatus: 73 match tx: 74 case X.Tx{world,undo,commands,events,trace}: queue_namespaced(~S,~C,~R,~E,W.namespace(~S,~Store,~R,~Notice,world),undo,commands,events,trace,descriptor,source,target) 75 def store_unrelate(~S: Data,~C: Type,store: Store,descriptor: G.Descriptor,source:Key.Key) -> Store & Unit: 76 match store: 77 case Store{components,graph}: (Store{components,C.unrelate(~S,graph,descriptor,source)},Unit{}) 78 def discard_unit(~S: Data,~C: Type,~R: Type,~E: Data,result: W.World,R,Notice> & Unit) -> W.World,R,Notice>: 79 (world,_) = result 80 world 81 def apply_unrelate(~S: Data,~C: Type,~R: Type,~E: Data,world: W.World,R,Notice>,descriptor: G.Descriptor,source: W.Handle) -> W.World,R,Notice>: 82 match source: 83 case W.Handle{_,id}: discard_unit(~S,~C,~R,~E,W.with_store(~S,~Store,~R,~Notice,~Unit,store => store_unrelate(~S,~C,store,descriptor,id),world)) 84 def unrelate_command(~S:Data,~C:Type,~R:Type,~E:Data,enabled:Bool,+descriptor:G.Descriptor,+source:W.Handle) -> Deferred.Command,R,Notice>>: 85 match enabled: 86 case False{}:Deferred.Raw{world=>apply_unrelate(~S,~C,~R,~E,world,descriptor,source)} 87 case True{}:Deferred.Observed{trace=>world=>(apply_unrelate(~S,~C,~R,~E,world,descriptor,source),TraceRel.unrelate(~S,trace,descriptor,TraceFacts.ApplyOperation{},source,TraceFacts.Applied{}))} 88 def unrelate_staged(~S:Data,~C:Type,~R:Type,~E:Data,+descriptor:G.Descriptor,+source:W.Handle,tx:X.Tx,R,Notice>,Notice>) -> X.Tx,R,Notice>,Notice>: 89 match tx: 90 case X.Tx{world,undo,commands,events,+trace}:X.Tx{world,undo,unrelate_command(~S,~C,~R,~E,TraceFacts.enabled(trace),descriptor,source) <> commands,events,TraceRel.unrelate(~S,trace,descriptor,TraceFacts.StageOperation{},source,TraceFacts.Attempted{})} 91 def unrelate_refused(~S:Data,~C:Type,~R:Type,~E:Data,descriptor:G.Descriptor,source:W.Handle,tx:X.Tx,R,Notice>,Notice>) -> X.Tx,R,Notice>,Notice>: 92 match tx: 93 case X.Tx{world,undo,commands,events,trace}:X.Tx{world,undo,commands,events,TraceRel.unrelate(~S,trace,descriptor,TraceFacts.StageOperation{},source,TraceFacts.Refused{"MissingEntity"})} 94 def queue_clear_checked(~S: Data,~C: Type,~R: Type,~E: Data,same: Bool,tx: X.Tx,R,Notice>,Notice>,descriptor: G.Descriptor,source: W.Handle) -> X.Tx,R,Notice>,Notice> & QueueStatus: 95 match same: 96 case False{}: (unrelate_refused(~S,~C,~R,~E,descriptor,source,tx),MissingEntity{}) 97 case True{}: (unrelate_staged(~S,~C,~R,~E,descriptor,source,tx),Queued{}) 98 def queue_clear_namespaced(~S: Data,~C: Type,~R: Type,~E: Data,checked: W.World,R,Notice> & U32,undo: List<(W.World,R,Notice> -> W.World,R,Notice>)>,commands:List,R,Notice>>>,events: List<&2,Notice>,trace:TraceFacts.State,descriptor: G.Descriptor,source: W.Handle) -> X.Tx,R,Notice>,Notice> & QueueStatus: 99 match checked source: 100 case (world,namespace) W.Handle{+sn,+sid}: queue_clear_checked(~S,~C,~R,~E,U32.is_eq(namespace,sn),X.Tx{world,undo,commands,events,trace},descriptor,W.Handle{sn,sid}) 101 def queue_unrelate(~S: Data,~C: Type,~R: Type,~E: Data,tx: X.Tx,R,Notice>,Notice>,descriptor: G.Descriptor,source: W.Handle) -> X.Tx,R,Notice>,Notice> & QueueStatus: 102 match tx: 103 case X.Tx{world,undo,commands,events,trace}: queue_clear_namespaced(~S,~C,~R,~E,W.namespace(~S,~Store,~R,~Notice,world),undo,commands,events,trace,descriptor,source) 104 def store_inverse(~S: Data,~C: Type,store: Store,descriptor: G.Descriptor,target:Key.Key) -> Store & List<&2,Key.Key>: 105 match store: 106 case Store{components,+graph}: (Store{components,graph},C.inverse(~S,graph,descriptor,target)) 107 def inverse_present(~S: Data,~C: Type,~R: Type,~E: Data,result: W.World,R,Notice> & List<&2,Key.Key>) -> W.World,R,Notice> & Maybe<&2,List<&2,Key.Key>>: 108 (world,sources) = result 109 (world,Some{sources}) 110 def inverse_checked(~S: Data,~C: Type,~R: Type,~E: Data,checked: W.World,R,Notice> & Bool,descriptor: G.Descriptor,target: W.Handle) -> W.World,R,Notice> & Maybe<&2,List<&2,Key.Key>>: 111 match checked target: 112 case (world,False{}) _: (world,None{}) 113 case (world,True{}) W.Handle{_,id}: inverse_present(~S,~C,~R,~E,W.with_store(~S,~Store,~R,~Notice,~List<&2,Key.Key>,store => store_inverse(~S,~C,store,descriptor,id),world)) 114 def inverse(~S: Data,~C: Type,~R: Type,~E: Data,world: W.World,R,Notice>,descriptor: G.Descriptor,+target: W.Handle) -> W.World,R,Notice> & Maybe<&2,List<&2,Key.Key>>: 115 inverse_checked(~S,~C,~R,~E,W.valid(~S,~Store,~R,~Notice,world,target),descriptor,target)