local.bend
bendvy-ecs-core@0.1.0.0 · 136 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/world.bend as W
4import bendvy-ecs-core@0.1.0.0/registration.bend as Reg
5
6# Runner identity is a closed erased index; each registration owns its Local.
7# Runner completion retaining World and the returned Local owner on both
8# success and failure. Only success returns Out; failure does not automatically
9# restore Local or recover body outputs. Runner defines World rollback behavior.
10type Outcome<World: Type,L: Type,Error: Data,Out: Type> is Type:
11 Succeeded{world: World,local: L,output: Out}
12 Failed{world: World,local: L,error: Error}
13# Affine per-registration Local owner with runtime namespace and actual
14# registration metadata. L may be arbitrary Type; no other registration shares
15# it implicitly. Raw construction is trusted administration.
16type Instance<S: Data,C: Type,R: Type,E: Data,L: Type,Args: Type,Error: Data,Out: Type,-runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>> is Type:
17 Instance{namespace: U32,id: U32,name: String,access: List<&2,String>,local: L}
18# Registered returns World and the Local instance; RegistrationRejected
19# returns World and the original Local owner without invoking the runner.
20type Registered<S: Data,C: Type,R: Type,E: Data,L: Type,Args: Type,Error: Data,Out: Type,-runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>> is Type:
21 Registered{world: W.World<S,C,R,E>,instance: Instance<S,C,R,E,L,Args,Error,Out,runner>}
22 RegistrationRejected{world: W.World<S,C,R,E>,local: L}
23# Completed returns World, retained Local instance and success/error result.
24# Refused returns those owners and original Args before runner execution.
25# Runner failure retains its returned Local modifications, not an old copy.
26type Run<S: Data,C: Type,R: Type,E: Data,L: Type,Args: Type,Error: Data,Out: Type,-runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>> is Type:
27 Completed{world: W.World<S,C,R,E>,instance: Instance<S,C,R,E,L,Args,Error,Out,runner>,result: Result<&2,&1,Error,Out>,tick:Tick.Tick}
28 Refused{world: W.World<S,C,R,E>,instance: Instance<S,C,R,E,L,Args,Error,Out,runner>,args: Args}
29# Disposed returns World and output of the explicit disposeLocal callback.
30# DisposalRejected returns World and original instance without invoking that
31# callback; no implicit finalizer is invented by dropping a result variant.
32type Disposal<S: Data,C: Type,R: Type,E: Data,L: Type,Args: Type,Error: Data,Out: Type,-runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>,Disposed: Type> is Type:
33 Disposed{world: W.World<S,C,R,E>,output: Disposed}
34 DisposalRejected{world: W.World<S,C,R,E>,instance: Instance<S,C,R,E,L,Args,Error,Out,runner>}
35
36# Rank-2 provider: callbacks get declared operations, not the concrete cell.
37# Trusted implementation cell for opaque Local capability callbacks.
38# Ordinary bodies receive abstract H and Ops, not this concrete owner.
39type Cell<L: Type> is Type:
40 Cell{local: L}
41# Opaque Local read/replacement operations. read returns detached Data V;
42# replace consumes L and returns H without a displaced-owner receipt.
43# Use explicit recovery APIs if replacement must return the displaced owner.
44type Ops<H: Type,L: Type,V: Data> is Type:
45 Ops{read: H -> H & V,replace: H -> L -> H}
46def projected(~L: Type,~V: Data,result: L & V) -> Cell<L> & V:
47 (local,value) = result
48 (Cell{local},value)
49def read_cell(~L: Type,~V: Data,~project: L -> L & V,cell: Cell<L>) -> Cell<L> & V:
50 match cell:
51 case Cell{local}: projected(~L,~V,project(local))
52
53def replace_cell(~L: Type,cell: Cell<L>,local: L) -> Cell<L>:
54 match cell:
55 case Cell{old}: Cell{local}
56def modify_cell(~L: Type,cell: Cell<L>,modify: L -> L) -> Cell<L>:
57 match cell:
58 case Cell{local}: Cell{modify(local)}
59def unwrapped(~L: Type,~Out: Type,result: Cell<L> & Out) -> L & Out:
60 match result:
61 case (Cell{local},out): (local,out)
62# Run a universally quantified Local body through read/replace capabilities.
63# Returns the retained Local owner and Out. project must preserve its L owner;
64# replacement does not invoke a finalizer or journal arbitrary mutations.
65# Contract and accepted scenarios: docs/design/public-local-policy.md.
66def provide(~L: Type,~V: Data,~Out: Type,~project: L -> L & V,~consume: @-H: Type -> Ops<H,L,V> -> H -> H & Out,local: L) -> L & Out:
67 unwrapped(~L,~Out,consume(Cell<L>,Ops{read_cell(~L,~V,~project),replace_cell(~L)},Cell{local}))
68
69# Local capability invocation with affine Args and Out, returning retained
70# L. As with provide, the opaque body gets no concrete cell and the caller
71# must handle any operation-specific ownership/recovery it requires.
72def provide_with(~L: Type,~V: Data,~Args: Type,~Out: Type,~project: L -> L & V,~consume: @-H: Type -> Ops<H,L,V> -> H -> Args -> H & Out,local: L,args: Args) -> L & Out:
73 unwrapped(~L,~Out,consume(Cell<L>,Ops{read_cell(~L,~V,~project),replace_cell(~L)},Cell{local},args))
74
75def register_result(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>,registered: W.World<S,C,R,E> & W.Registration,+namespace: U32,+name: String,+access: List<&2,String>,local: L) -> Registered<S,C,R,E,L,Args,Error,Out,runner>:
76 match registered:
77 case (world,W.Registered{id}): Registered{world,Instance{namespace,id,name,access,local}}
78 case (world,W.RegistrationRejected{_}): RegistrationRejected{world,local}
79def register_named(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>,named: W.World<S,C,R,E> & U32,+name: String,+access: List<&2,String>,local: L) -> Registered<S,C,R,E,L,Args,Error,Out,runner>:
80 (world,+namespace) = named
81 register_result(~S,~C,~R,~E,~L,~Args,~Error,~Out,~runner,W.register(~S,~C,~R,~E,world,name,access),namespace,name,access,local)
82# Register a closed runner and consume its initial Local owner. Returns
83# instance on success, original Local on refusal, and World on both paths.
84# Raw access strings are trusted metadata; ordinary wrappers derive them.
85def register(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>,world: W.World<S,C,R,E>,name: String,access: List<&2,String>,local: L) -> Registered<S,C,R,E,L,Args,Error,Out,runner>:
86 register_named(~S,~C,~R,~E,~L,~Args,~Error,~Out,~runner,W.namespace(~S,~C,~R,~E,world),name,access,local)
87
88def completed(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>,namespace: U32,id: U32,name: String,access: List<&2,String>,tick:Tick.Tick,outcome: Outcome<W.World<S,C,R,E>,L,Error,Out>) -> Run<S,C,R,E,L,Args,Error,Out,runner>:
89 match outcome:
90 case Succeeded{world,local,out}: Completed{W.leave_run(~S,~C,~R,~E,world),Instance{namespace,id,name,access,local},Done{out},tick}
91 case Failed{world,local,error}: Completed{W.leave_run(~S,~C,~R,~E,world),Instance{namespace,id,name,access,local},Fail{error},tick}
92def entered(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>,namespace: U32,id: U32,name: String,access: List<&2,String>,local:L,args:Args,observed:W.World<S,C,R,E> & Tick.Tick) -> Run<S,C,R,E,L,Args,Error,Out,runner>:
93 match observed:
94 case (world,tick):completed(~S,~C,~R,~E,~L,~Args,~Error,~Out,~runner,namespace,id,name,access,tick,runner(world,local,args))
95def checked(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>,allowed: Bool,instance: Instance<S,C,R,E,L,Args,Error,Out,runner>,world: W.World<S,C,R,E>,args: Args) -> Run<S,C,R,E,L,Args,Error,Out,runner>:
96 match allowed instance:
97 case False{} instance: Refused{world,instance,args}
98 case True{} Instance{namespace,id,name,access,local}: entered(~S,~C,~R,~E,~L,~Args,~Error,~Out,~runner,namespace,id,name,access,local,args,W.enter_run(~S,~C,~R,~E,world))
99# Validate namespace and live registration metadata before invoking runner.
100# Refused returns original Args/instance/World. Completed retains the Local
101# returned by the runner even on error; World rollback is the runner's job.
102# This primitive has no query lifecycle cursor of its own.
103def run(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>,instance: Instance<S,C,R,E,L,Args,Error,Out,runner>,world: W.World<S,C,R,E>,args: Args) -> Run<S,C,R,E,L,Args,Error,Out,runner>:
104 match instance world:
105 case Instance{+namespace,+id,+name,+access,local} W.World{+actual,allocator,store,resource,events,pending,+registrations,nextSystemId,clock}:
106 checked(~S,~C,~R,~E,~L,~Args,~Error,~Out,~runner,Bool.and(U32.is_eq(namespace,actual),W.registration_list_matches(registrations,id,name,access)),Instance{namespace,id,name,access,local},W.World{actual,allocator,store,resource,events,pending,registrations,nextSystemId,clock},args)
107# Return the same Local instance without invoking a body or modifying L.
108def skip(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>,instance: Instance<S,C,R,E,L,Args,Error,Out,runner>) -> Instance<S,C,R,E,L,Args,Error,Out,runner>:
109 instance
110
111# Return the retained instance and registration ID without running a body.
112# An observed ID does not bypass namespace/live-registration validation.
113def identity(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>,instance: Instance<S,C,R,E,L,Args,Error,Out,runner>) -> Instance<S,C,R,E,L,Args,Error,Out,runner> & U32:
114 match instance:
115 case Instance{namespace,+id,name,access,local}: (Instance{namespace,id,name,access,local},id)
116def observed(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>,~V: Data,namespace: U32,id: U32,name: String,access: List<&2,String>,value: L & V) -> Instance<S,C,R,E,L,Args,Error,Out,runner> & V:
117 (local,value) = value
118 (Instance{namespace,id,name,access,local},value)
119# Return the instance with a detached Data projection of Local. project
120# must return its L owner; this trusted callback is not a World transaction
121# and grants no authority to execute a registration in another World.
122def observe(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>,~V: Data,~project: L -> L & V,instance: Instance<S,C,R,E,L,Args,Error,Out,runner>) -> Instance<S,C,R,E,L,Args,Error,Out,runner> & V:
123 match instance:
124 case Instance{namespace,id,name,access,local}: observed(~S,~C,~R,~E,~L,~Args,~Error,~Out,~runner,~V,namespace,id,name,access,project(local))
125
126def disposal(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>,~Disposed: Type,~disposeLocal: L -> Disposed,instance: Instance<S,C,R,E,L,Args,Error,Out,runner>,result: W.World<S,C,R,E> & Bool) -> Disposal<S,C,R,E,L,Args,Error,Out,runner,Disposed>:
127 match instance result:
128 case instance, (world,False{}): DisposalRejected{world,instance}
129 case Instance{_,_,_,_,local}, (world,True{}): Disposed{world,disposeLocal(local)}
130# Remove the matching live registration, then consume L once with the
131# explicit disposeLocal callback and return its output. Refusal retains
132# instance/World without calling disposeLocal. Arbitrary callback effects
133# are outside ECS rollback; inspect the result instead of assuming disposal.
134def dispose(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World<S,C,R,E> -> L -> Args -> Outcome<W.World<S,C,R,E>,L,Error,Out>,~Disposed: Type,~disposeLocal: L -> Disposed,instance: Instance<S,C,R,E,L,Args,Error,Out,runner>,world: W.World<S,C,R,E>) -> Disposal<S,C,R,E,L,Args,Error,Out,runner,Disposed>:
135 match instance:
136 case Instance{+namespace,+id,+name,+access,local}: disposal(~S,~C,~R,~E,~L,~Args,~Error,~Out,~runner,~Disposed,~disposeLocal,Instance{namespace,id,name,access,local},Reg.remove(~S,~C,~R,~E,world,namespace,id,name,access))