ordinary_lifetime_scope.bend

bendvy-ecs-core@0.1.0.1 · 164 lines · 0 definitions

1import Base
2import bendvy-ecs-core@0.1.0.1/lifetime_scope.bend as Sc
3import bendvy-ecs-core@0.1.0.1/lifetime_scope_world_spawn.bend as Spawn
4import bendvy-ecs-core@0.1.0.1/world.bend as W
5import bendvy-ecs-core@0.1.0.1/ordinary_schema.bend as Schema
6import bendvy-ecs-core@0.1.0.1/ordinary_schema_provision.bend as Provision
7import bendvy-ecs-core@0.1.0.1/relation_commands.bend as A
8import bendvy-ecs-core@0.1.0.1/relation_types.bend as G
9import bendvy-ecs-core@0.1.0.1/relation_failure_reader.bend as Rel
10import bendvy-ecs-core@0.1.0.1/ordinary_app_linked_cleanup.bend as LC
11import bendvy-ecs-core@0.1.0.1/ordinary_app_linked_cleanup_system.bend as LCS
12import bendvy-ecs-core@0.1.0.1/ordinary_component_clear_declaration.bend as Clear
13import bendvy-ecs-core@0.1.0.1/column.bend as Col
14import bendvy-ecs-core@0.1.0.1/ordinary_component_binding.bend as Binding
15import bendvy-ecs-core@0.1.0.1/capabilities.bend as Cap
16import bendvy-ecs-core@0.1.0.1/transaction.bend as T
17import bendvy-ecs-core@0.1.0.1/commands.bend as Cmd
18import bendvy-ecs-core@0.1.0.1/system.bend as Sys
19
20# Scope metadata does not add a component/schema entry. Original owners survive.
21# Trusted schema adapter: build an empty component store with scope membership
22# and relation-graph wrappers. Component payload C may contain affine owners;
23# empty constructs C once. Scope membership adds no component schema entry.
24def empty_store(~S:Data,~C:Type,~empty:Unit -> C,unit:Unit) -> A.Store<S,Sc.Store<S,C>>:
25  A.empty(~S,~Sc.Store<S,C>,Sc.empty(~S,~C,empty(unit)))
26def counted(~S:Data,~C:Type,members:List<&2,Sc.Member>,graph:G.Graph<S>,seen:C & List<&2,Maybe<&2,U32>>) -> A.Store<S,Sc.Store<S,C>> & List<&2,Maybe<&2,U32>>:
27  match seen:
28    case (components,counts):(A.Store{Sc.Store{components,members},graph},counts)
29# Trusted provisioning adapter: run the original component counter through the
30# scope/relation wrappers. Returns the wrapped store with its membership and
31# graph owners threaded back; counting does not assign scopes or delete entities.
32def count_store(~S:Data,~C:Type,~count:C -> List<&2,W.Handle<S>> -> C & List<&2,Maybe<&2,U32>>,store:A.Store<S,Sc.Store<S,C>>,handles:List<&2,W.Handle<S>>) -> A.Store<S,Sc.Store<S,C>> & List<&2,Maybe<&2,U32>>:
33  match store:
34    case A.Store{Sc.Store{components,members},graph}:counted(~S,~C,members,graph,count(components,handles))
35# Lift an ordinary schema to the scope/relation-wrapped Store type.
36# Preserves the existing entries and resource schema; no synthetic scope component
37# is registered. Use this during setup, not to change a live World's schema.
38def schema(~S:Data,~C:Type,~R:Type,value:Schema.Schema<S,C,R>) -> Schema.Schema<S,A.Store<S,Sc.Store<S,C>>,R>:
39  match value:
40    case Schema.Schema{entries}:Schema.Schema{entries}
41# Lift the matching schema provision with its empty/count adapters.
42# The component/resource entries remain unchanged while the Store gains scope
43# membership and relation state. Pass the result to ordinary-lifetime-scope-io.create.
44# Concrete setup: test-fixtures/public-scopes/feature-timing-v1/fixture.bend:
45# provision, create and scoped_binding; main.bend exercises deferred cleanup.
46def provision(~S:Data,~C:Type,~R:Type,~empty:Unit -> C,~count:C -> List<&2,W.Handle<S>> -> C & List<&2,Maybe<&2,U32>>,value:Provision.Provision<S,C,R,empty,count>) -> Provision.Provision<S,A.Store<S,Sc.Store<S,C>>,R,empty_store(~S,~C,~empty),count_store(~S,~C,~count)>:
47  match value:
48    case Provision.Provision{value}:Provision.Provision{schema(~S,~C,~R,value)}
49# Reuse the canonical Type-result request; rejected payloads are not forced to Data.
50# Capabilities a lifetime-scope system body receives for payload type P.
51# spawn consumes a scope/payload request and returns an owning SpawnResult;
52# cleanup queues scope cleanup and returns QueueStatus. The opaque owner H must
53# be threaded through both. P may be affine Type; a scope is metadata, not an
54# entity owner or evidence that any member is currently live.
55type Ops<-H:Type,-S:Data,-P:Type> is Type:
56  Ops{spawn:Cap.OwnedRequest<H,Sc.Scope<S> & P,Sc.SpawnResult<S,P>>,cleanup:Cap.Request<H,Sc.Scope<S>,A.QueueStatus>}
57# Advanced capability projection: consume Ops and obtain its owned spawn
58# request operation. Normal system bodies use spawn to thread their opaque owner.
59def spawn_cap(~H:Type,~S:Data,~P:Type,ops:Ops<H,S,P>) -> Cap.OwnedRequest<H,Sc.Scope<S> & P,Sc.SpawnResult<S,P>>:
60  match ops:
61    case Ops{spawn,_}:spawn
62# Advanced capability projection: consume Ops and obtain its cleanup request.
63# Normal system bodies use cleanup; projection alone queues no work.
64def cleanup_cap(~H:Type,~S:Data,~P:Type,ops:Ops<H,S,P>) -> Cap.Request<H,Sc.Scope<S>,A.QueueStatus>:
65  match ops:
66    case Ops{_,cleanup}:cleanup
67# Request a deferred spawn in a scope from this World's namespace.
68# Returns the updated opaque owner and either Spawned{handle}, a reserved handle
69# not yet live, or SpawnRejected{payload,error}, returning the incoming affine P.
70# A foreign-world scope is refused; reservation exhaustion also returns payload.
71# Accepted requests retain P in deferred work. At an explicit command barrier,
72# activation/populate precede membership assignment; the body does not see a live
73# entity merely because this request succeeded. Only immediate refusal promises
74# that payload result: a later failed system discards its Out and staged work,
75# so placing rejected P inside Out does not make it a failed-system recovery API.
76def spawn(~H:Type,~S:Data,~P:Type,~ops:Ops<H,S,P>,owner:H,scope:Sc.Scope<S>,payload:P) -> H & Sc.SpawnResult<S,P>:
77  Cap.owned_request(~H,~(Sc.Scope<S> & P),~Sc.SpawnResult<S,P>,~spawn_cap(~H,~S,~P,ops),owner,(scope,payload))
78# Queue cleanup of the given scope, threading the opaque owner back.
79# Queued means accepted deferred work, not completed deletion; a foreign scope
80# returns MissingEntity without queuing cleanup. Membership is observed when the
81# command is applied at an explicit barrier, so same-world/name scopes refer to
82# the same group rather than distinct owners.
83# Cleanup uses the supplied linked-relation descriptors and component clear route.
84# An unscoped persistent entity survives only outside the linked deletion closure;
85# an unscoped linked descendant can still be deleted, while ordinary links from
86# survivors to removed targets are cleared. Queuing cleanup does not flush commands.
87# See docs/parity/scopes.md for the qualified survivor and relation boundary.
88def cleanup(~H:Type,~S:Data,~P:Type,~ops:Ops<H,S,P>,owner:H,scope:Sc.Scope<S>) -> H & A.QueueStatus:
89  Cap.request(~H,~Sc.Scope<S>,~A.QueueStatus,~cleanup_cap(~H,~S,~P,ops),owner,scope)
90def spawn_request(~S:Data,~C:Type,~R:Type,~E:Data,~P:Type,~populate:W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>> -> W.Handle<S> -> P -> W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,owner:T.Tx<W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,A.Notice<S,E>>,input:Sc.Scope<S> & P) -> T.Tx<W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,A.Notice<S,E>> & Sc.SpawnResult<S,P>:
91  match input:
92    case (scope,payload):Spawn.tx_spawn_in(~S,~C,~R,~E,~P,~populate,owner,scope,payload)
93def cleanup_request(~S:Data,~C:Type,~R:Type,~E:Data,~clear:C -> W.Handle<S> -> C & List<&2,A.Notice<S,E>>,~record:W.Handle<S> -> A.Notice<S,E>,~declarations:List<&2,Rel.Declaration<S>>,owner:T.Tx<W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,A.Notice<S,E>>,scope:Sc.Scope<S>) -> T.Tx<W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,A.Notice<S,E>> & A.QueueStatus:
94  Sc.tx_cleanup(~S,~C,~R,~E,~clear,~record,~LC.descriptors(~S,declarations),owner,scope)
95# Trusted system-construction adapter for the canonical transaction owner.
96# Supplies spawn/cleanup operations using the closed populate, clear, record and
97# relation declarations. populate receives the full World at deferred application;
98# clear must match its Clear declaration, and relation declarations must cover the
99# links cleanup is expected to maintain. These callbacks are schema/engine setup,
100# not extra runtime authority exposed to the polymorphic gameplay body.
101def provide(~S:Data,~C:Type,~R:Type,~E:Data,~P:Type,~populate:W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>> -> W.Handle<S> -> P -> W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,~clear:C -> W.Handle<S> -> C & List<&2,A.Notice<S,E>>,~record:W.Handle<S> -> A.Notice<S,E>,~declarations:List<&2,Rel.Declaration<S>>) -> Ops<T.Tx<W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,A.Notice<S,E>>,S,P>:
102  Ops{Cap.OwnedRequest{spawn_request(~S,~C,~R,~E,~P,~populate)},Cap.Request{cleanup_request(~S,~C,~R,~E,~clear,~record,~declarations)}}
103# Sys failure retains World/error only; body output is discarded by that existing
104# contract. Request-level rejected payload recovery does not imply failed-Out recovery.
105def returned(~S:Data,~C:Type,~R:Type,~E:Data,~Error:Data,~Out:Type,output:Out,result:T.Returned<W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,Error>) -> Sys.Outcome<W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,Error,Out>:
106  match result:
107    case T.Returned{world,T.Success{}}:Sys.Succeeded{world,output}
108    case T.Returned{world,T.Failure{error}}:Sys.Failed{world,error}
109def invoked(~S:Data,~C:Type,~R:Type,~E:Data,~Error:Data,~Out:Type,seen:T.Tx<W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,A.Notice<S,E>> & (T.Outcome<Error> & Out)) -> Sys.Outcome<W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,Error,Out>:
110  match seen:
111    case (tx,(outcome,output)):returned(~S,~C,~R,~E,~Error,~Out,output,Cmd.tx_finish(~S,~A.Store<S,Sc.Store<S,C>>,~R,~A.Notice<S,E>,~Error,tx,outcome))
112# Execute one body with lifetime capabilities inside an ordinary transaction.
113# Threads World through transaction finish: success returns Out; failure returns
114# World/error and discards Out, including any affine payload stored only there.
115# Failed staged spawns/cleanup are not published. Successful completion still
116# leaves structural work pending until the caller applies an explicit barrier.
117# Host IO and arbitrary trusted callback effects are not made reversible here.
118def runner(~S:Data,~C:Type,~R:Type,~E:Data,~P:Type,~populate:W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>> -> W.Handle<S> -> P -> W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,~clear:C -> W.Handle<S> -> C & List<&2,A.Notice<S,E>>,~record:W.Handle<S> -> A.Notice<S,E>,~declarations:List<&2,Rel.Declaration<S>>,~Args:Type,~Error:Data,~Out:Type,~body:@-H:Type -> @-ops:Ops<H,S,P> -> Args -> H -> H & (T.Outcome<Error> & Out),world:W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,args:Args) -> Sys.Outcome<W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,Error,Out>:
119  invoked(~S,~C,~R,~E,~Error,~Out,body(T.Tx<W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,A.Notice<S,E>>,provide(~S,~C,~R,~E,~P,~populate,~clear,~record,~declarations),args,T.begin(~W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,~A.Notice<S,E>,world)))
120# Register a scope system with access derived from the component-clear
121# certificate, relation declarations and LifetimeScope:write. Returns the ordinary
122# Registry registration result with the returned World on success or refusal.
123# Registration runs no body and applies no spawn/cleanup. Supply the matching
124# trusted populate/clear routes, then retain the actual returned Registry for run.
125def register(~S:Data,~C:Type,~R:Type,~E:Data,~P:Type,~populate:W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>> -> W.Handle<S> -> P -> W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,~clear:C -> W.Handle<S> -> C & List<&2,A.Notice<S,E>>,~record:W.Handle<S> -> A.Notice<S,E>,~declarations:List<&2,Rel.Declaration<S>>,~Args:Type,~Error:Data,~Out:Type,~body:@-H:Type -> @-ops:Ops<H,S,P> -> Args -> H -> H & (T.Outcome<Error> & Out),~clearDeclaration:Clear.Declaration<S,C,A.Notice<S,E>,clear>,world:W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,name:String) -> Sys.RegistrationResult<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>,Args,Error,Out,runner(~S,~C,~R,~E,~P,~populate,~clear,~record,~declarations,~Args,~Error,~Out,~body)>:
126  Sys.register(~S,~A.Store<S,Sc.Store<S,C>>,~R,~A.Notice<S,E>,~Args,~Error,~Out,~runner(~S,~C,~R,~E,~P,~populate,~clear,~record,~declarations,~Args,~Error,~Out,~body),world,name,List.append(&2,String,LCS.writes(Clear.keys(~S,~C,~A.Notice<S,E>,~clear,clearDeclaration)),List.append(&2,String,LCS.relation_accesses(~S,declarations),["LifetimeScope:write"])))
127# Run the registered scope system, consuming its Registry, World and Args.
128# Returns the updated Registry/World and success output or typed system failure;
129# registration refusal returns the ordinary rejected-run Args. Failure discards
130# body Out rather than returning affine contents separately. Earlier committed
131# systems remain, and successful queued work requires a later explicit barrier.
132# Current consumer: test-fixtures/public-scopes/feature-timing-v1/main.bend.
133def run(~S:Data,~C:Type,~R:Type,~E:Data,~P:Type,~populate:W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>> -> W.Handle<S> -> P -> W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,~clear:C -> W.Handle<S> -> C & List<&2,A.Notice<S,E>>,~record:W.Handle<S> -> A.Notice<S,E>,~declarations:List<&2,Rel.Declaration<S>>,~Args:Type,~Error:Data,~Out:Type,~body:@-H:Type -> @-ops:Ops<H,S,P> -> Args -> H -> H & (T.Outcome<Error> & Out),owner:Sys.Registry<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>,Args,Error,Out,runner(~S,~C,~R,~E,~P,~populate,~clear,~record,~declarations,~Args,~Error,~Out,~body)>,world:W.World<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>>,args:Args) -> Sys.RunResult<S,A.Store<S,Sc.Store<S,C>>,R,A.Notice<S,E>,Args,Error,Out,runner(~S,~C,~R,~E,~P,~populate,~clear,~record,~declarations,~Args,~Error,~Out,~body)>:
134  Sys.run(~S,~A.Store<S,Sc.Store<S,C>>,~R,~A.Notice<S,E>,~Args,~Error,~Out,~runner(~S,~C,~R,~E,~P,~populate,~clear,~record,~declarations,~Args,~Error,~Out,~body),owner,world,args)
135
136# Project ordinary families through the metadata wrapper without changing owners.
137# Trusted storage-adapter remainder for a projected component column.
138# Retains the original component rest, scope membership and relation graph while
139# the selected column is out. Gameplay should use the derived binding instead
140# of constructing or unpacking this storage wrapper.
141type ComponentRest<S:Data,Rest:Type> is Type:
142  ComponentRest{rest:Rest,members:List<&2,Sc.Member>,graph:G.Graph<S>}
143def taken_column(~S:Data,~C:Type,~Rest:Type,~P:Type,members:List<&2,Sc.Member>,graph:G.Graph<S>,seen:Rest & Col.Column<S,P>) -> ComponentRest<S,Rest> & Col.Column<S,P>:
144  match seen:
145    case (rest,column):(ComponentRest{rest,members,graph},column)
146# Trusted schema projection: take one component column through scope/relation
147# wrappers, returning ComponentRest with every sibling and metadata owner.
148# Pair it with put_column from the same original store projection.
149def take_column(~S:Data,~C:Type,~Rest:Type,~P:Type,~take:C -> Rest & Col.Column<S,P>,store:A.Store<S,Sc.Store<S,C>>) -> ComponentRest<S,Rest> & Col.Column<S,P>:
150  match store:
151    case A.Store{Sc.Store{components,members},graph}:taken_column(~S,~C,~Rest,~P,members,graph,take(components))
152# Trusted schema reconstruction: consume the matching ComponentRest and column
153# to rebuild the wrapped Store, preserving membership and relation graph.
154# It does not itself change scope membership or apply structural commands.
155def put_column(~S:Data,~C:Type,~Rest:Type,~P:Type,~put:Rest -> Col.Column<S,P> -> C,rest:ComponentRest<S,Rest>,column:Col.Column<S,P>) -> A.Store<S,Sc.Store<S,C>>:
156  match rest:
157    case ComponentRest{rest,members,graph}:A.Store{Sc.Store{put(rest,column),members},graph}
158# Derive a component binding for the wrapped Store from its original binding.
159# Retains the schema identity, view and typed column path while adding scope and
160# relation owners to the remainder. This adapts schema setup; it grants no new
161# access merely from a component name. See fixture.bend:scoped_binding in the
162# public-scopes/feature-timing-v1 consumer.
163def component_binding(~S:Data,~C:Type,~Rest:Type,~P:Type,~V:Data,~Token:Data,~take:C -> Rest & Col.Column<S,P>,~put:Rest -> Col.Column<S,P> -> C,~view:P -> P & V,binding:Binding.Binding<S,C,Rest,P,V,Token,take,put,view>) -> Binding.Binding<S,A.Store<S,Sc.Store<S,C>>,ComponentRest<S,Rest>,P,V,Token,take_column(~S,~C,~Rest,~P,~take),put_column(~S,~C,~Rest,~P,~put),view>:
164  Binding.bind(~S,~A.Store<S,Sc.Store<S,C>>,~ComponentRest<S,Rest>,~P,~V,~Token,~take_column(~S,~C,~Rest,~P,~take),~put_column(~S,~C,~Rest,~P,~put),~view,Binding.identity(~S,~C,~Rest,~P,~V,~Token,~take,~put,~view,binding))