schedule.bend
bendvy-ecs-core@0.1.0.1 · 173 lines · 0 definitions
1import Base
2import bendvy-ecs-core@0.1.0.1/world.bend as W
3import bendvy-ecs-core@0.1.0.1/system.bend as Sys
4
5# Static plans contain no runtime closures. H holds heterogeneous affine
6# registered runner owners; closed templates thread that owner through each step.
7# Detached static sequence: System holds a registered id and condition slot;
8# Phase is an observation label and Barrier is an explicit pending-work boundary.
9# A phase label alone does not flush commands or execute a gameplay callback.
10type Step is Data:
11 System{id: U32,condition: U32}
12 Phase{name: String}
13 Barrier{}
14# Ordered progress records for entered phases, dispatched/skipped systems and
15# applied barriers. Ran includes a system that failed; inspect RunResult as well.
16type Observation is Data:
17 Entered{name: String}
18 Ran{id: U32}
19 Skipped{id: U32}
20 Applied{}
21# Affine registered-owner bundle plus detached static steps and World namespace.
22# Raw construction is trusted setup; build validates ids against its supplied catalog.
23type Schedule<S: Data,C: Type,R: Type,E: Data,H: Type> is Type:
24 Schedule{namespace: U32,name: String,steps: List<&2,Step>,owners: H}
25# Build result preserving the actual heterogeneous owners on invalid plans.
26# Built retains those owners inside Schedule, not copied reusable runtime closures.
27type Build<S: Data,C: Type,R: Type,E: Data,H: Type> is Type:
28 Built{schedule: Schedule<S,C,R,E,H>}
29 Invalid{owners: H}
30# Every result returns Schedule with actual runner owners and World. Failed stops
31# at the first failed dispatch and keeps observations through that system; earlier
32# successful systems are not rolled back as one schedule-wide transaction. Rejected
33# is namespace admission refusal and runs no steps.
34type RunResult<S: Data,C: Type,R: Type,E: Data,H: Type,Error: Data> is Type:
35 Finished{schedule: Schedule<S,C,R,E,H>,world: W.World<S,C,R,E>,observations: List<&2,Observation>}
36 Failed{schedule: Schedule<S,C,R,E,H>,world: W.World<S,C,R,E>,error: Error,observations: List<&2,Observation>}
37 Rejected{schedule: Schedule<S,C,R,E,H>,world: W.World<S,C,R,E>}
38
39def contains(ids: List<&2,U32>,+id: U32) -> Bool:
40 match ids:
41 case []: False{}
42 case +head <> rest: Bool.or(U32.is_eq(head,id),contains(rest,id))
43
44def valid(steps: List<&2,Step>,+catalog: List<&2,U32>,+seen: List<&2,U32>) -> Bool:
45 match steps:
46 case []: True{}
47 case Phase{_} <> rest: valid(rest,catalog,seen)
48 case Barrier{} <> rest: valid(rest,catalog,seen)
49 case System{+id,_} <> rest: Bool.and(contains(catalog,id),Bool.and(Bool.not(contains(seen,id)),valid(rest,catalog,id <> seen)))
50
51def build_checked(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,allowed: Bool,+namespace: U32,+name: String,+steps: List<&2,Step>,owners: H) -> Build<S,C,R,E,H>:
52 match allowed:
53 case False{}: Invalid{owners}
54 case True{}: Built{Schedule{namespace,name,steps,owners}}
55
56# Validate that each System id occurs in the supplied catalog and appears at most
57# once in steps. Return Invalid with owners otherwise. Condition slots and catalog
58# provenance are trusted setup here; this is not a scheduler conflict analysis.
59def build(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,+namespace: U32,+name: String,+steps: List<&2,Step>,catalog: List<&2,U32>,owners: H) -> Build<S,C,R,E,H>:
60 build_checked(~S,~C,~R,~E,~H,valid(steps,catalog,[]),namespace,name,steps,owners)
61
62# Continuations keep recursion structurally decreasing on the plan, rather
63# than moving affine owners into runtime closures stored in the schedule.
64def after_outcome(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,next: H -> W.World<S,C,R,E> -> List<&2,Observation> -> RunResult<S,C,R,E,H,Error>,outcome: Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,owners: H,+namespace: U32,+name: String,+all: List<&2,Step>,+observations: List<&2,Observation>,+id: U32) -> RunResult<S,C,R,E,H,Error>:
65 match outcome:
66 case Sys.Failed{world,error}: Failed{Schedule{namespace,name,all,owners},world,error,List.append(&2,Observation,observations,[Ran{id}])}
67 case Sys.Succeeded{world,_}: next(owners,world,List.append(&2,Observation,observations,[Ran{id}]))
68
69def after_dispatch(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,next: H -> W.World<S,C,R,E> -> List<&2,Observation> -> RunResult<S,C,R,E,H,Error>,result: H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,+namespace: U32,+name: String,+all: List<&2,Step>,+observations: List<&2,Observation>,+id: U32) -> RunResult<S,C,R,E,H,Error>:
70 (owners,outcome) = result
71 after_outcome(~S,~C,~R,~E,~H,~Error,next,outcome,owners,namespace,name,all,observations,id)
72
73def conditioned(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,~dispatch: H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,next: H -> W.World<S,C,R,E> -> List<&2,Observation> -> RunResult<S,C,R,E,H,Error>,allowed: Bool,owners: H,world: W.World<S,C,R,E>,+namespace: U32,+name: String,+all: List<&2,Step>,+observations: List<&2,Observation>,+id: U32) -> RunResult<S,C,R,E,H,Error>:
74 match allowed:
75 case False{}: next(owners,world,List.append(&2,Observation,observations,[Skipped{id}]))
76 case True{}: after_dispatch(~S,~C,~R,~E,~H,~Error,next,dispatch(owners,world,id),namespace,name,all,observations,id)
77
78def after_condition(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,~dispatch: H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,next: H -> W.World<S,C,R,E> -> List<&2,Observation> -> RunResult<S,C,R,E,H,Error>,checked: W.World<S,C,R,E> & Bool,owners: H,+namespace: U32,+name: String,+all: List<&2,Step>,+observations: List<&2,Observation>,+id: U32) -> RunResult<S,C,R,E,H,Error>:
79 (world,allowed) = checked
80 conditioned(~S,~C,~R,~E,~H,~Error,~dispatch,next,allowed,owners,world,namespace,name,all,observations,id)
81
82def after_skip(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,next: H -> W.World<S,C,R,E> -> List<&2,Observation> -> RunResult<S,C,R,E,H,Error>,returned: H & W.World<S,C,R,E>,observations: List<&2,Observation>) -> RunResult<S,C,R,E,H,Error>:
83 (owners,world) = returned
84 next(owners,world,observations)
85
86def default_barrier(~S:Data,~C:Type,~R:Type,~E:Data,~H:Type,owners:H,world:W.World<S,C,R,E>) -> H & W.World<S,C,R,E>:
87 (owners,W.barrier(~S,~C,~R,~E,world))
88def execute_with_barrier(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,~dispatch: H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,~condition: W.World<S,C,R,E> -> U32 -> W.World<S,C,R,E> & Bool,~barrier:H -> W.World<S,C,R,E> -> H & W.World<S,C,R,E>,steps: List<&2,Step>,owners: H,world: W.World<S,C,R,E>,+namespace: U32,+name: String,+all: List<&2,Step>,+observations: List<&2,Observation>) -> RunResult<S,C,R,E,H,Error>:
89 match steps:
90 case []: Finished{Schedule{namespace,name,all,owners},world,observations}
91 case Phase{phase} <> rest: execute_with_barrier(~S,~C,~R,~E,~H,~Error,~dispatch,~condition,~barrier,rest,owners,world,namespace,name,all,List.append(&2,Observation,observations,[Entered{phase}]))
92 case Barrier{} <> rest: after_skip(~S,~C,~R,~E,~H,~Error,h => w => obs => execute_with_barrier(~S,~C,~R,~E,~H,~Error,~dispatch,~condition,~barrier,rest,h,w,namespace,name,all,obs),barrier(owners,world),List.append(&2,Observation,observations,[Applied{}]))
93 case System{+id,slot} <> rest: after_condition(~S,~C,~R,~E,~H,~Error,~dispatch,h => w => obs => execute_with_barrier(~S,~C,~R,~E,~H,~Error,~dispatch,~condition,~barrier,rest,h,w,namespace,name,all,obs),condition(world,slot),owners,namespace,name,all,observations,id)
94
95def execute(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,~dispatch: H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,~condition: W.World<S,C,R,E> -> U32 -> W.World<S,C,R,E> & Bool,steps: List<&2,Step>,owners: H,world: W.World<S,C,R,E>,+namespace: U32,+name: String,+all: List<&2,Step>,+observations: List<&2,Observation>) -> RunResult<S,C,R,E,H,Error>:
96 execute_with_barrier(~S,~C,~R,~E,~H,~Error,~dispatch,~condition,~default_barrier(~S,~C,~R,~E,~H),steps,owners,world,namespace,name,all,observations)
97
98def run_checked(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,~dispatch: H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,~condition: W.World<S,C,R,E> -> U32 -> W.World<S,C,R,E> & Bool,allowed: Bool,schedule: Schedule<S,C,R,E,H>,world: W.World<S,C,R,E>) -> RunResult<S,C,R,E,H,Error>:
99 match allowed schedule:
100 case False{} schedule: Rejected{schedule,world}
101 case True{} Schedule{+namespace,+name,+steps,owners}: execute(~S,~C,~R,~E,~H,~Error,~dispatch,~condition,steps,owners,world,namespace,name,steps,[])
102
103
104def run_actual(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,~dispatch: H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,~condition: W.World<S,C,R,E> -> U32 -> W.World<S,C,R,E> & Bool,schedule: Schedule<S,C,R,E,H>,world: W.World<S,C,R,E>,+actual: U32) -> RunResult<S,C,R,E,H,Error>:
105 match schedule:
106 case Schedule{+namespace,+name,+steps,owners}: run_checked(~S,~C,~R,~E,~H,~Error,~dispatch,~condition,U32.is_eq(namespace,actual),Schedule{namespace,name,steps,owners},world)
107
108def run_namespace(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,~dispatch: H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,~condition: W.World<S,C,R,E> -> U32 -> W.World<S,C,R,E> & Bool,schedule: Schedule<S,C,R,E,H>,named: W.World<S,C,R,E> & U32) -> RunResult<S,C,R,E,H,Error>:
109 (world,+actual) = named
110 run_actual(~S,~C,~R,~E,~H,~Error,~dispatch,~condition,schedule,world,actual)
111
112# Run static steps in order after matching Schedule and World namespaces.
113# Evaluate condition by its slot; false records Skipped without dispatch. True
114# threads the actual H through dispatch, stopping on Sys.Failed. Barriers call
115# W.barrier; no implicit final barrier is added. Callbacks are trusted closed
116# providers, not arbitrary capabilities inferred from ids. See ordinary App
117# schedule setup in examples/scheduled-health/application.bend.
118def run(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,~dispatch: H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,~condition: W.World<S,C,R,E> -> U32 -> W.World<S,C,R,E> & Bool,schedule: Schedule<S,C,R,E,H>,world: W.World<S,C,R,E>) -> RunResult<S,C,R,E,H,Error>:
119 run_namespace(~S,~C,~R,~E,~H,~Error,~dispatch,~condition,schedule,W.namespace(~S,~C,~R,~E,world))
120
121# Additive runner with owner-preserving condition-false reader lifecycle hook.
122def conditioned_skip(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,~skip: H -> W.World<S,C,R,E> -> U32 -> H & W.World<S,C,R,E>,~dispatch: H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,next: H -> W.World<S,C,R,E> -> List<&2,Observation> -> RunResult<S,C,R,E,H,Error>,allowed: Bool,owners: H,world: W.World<S,C,R,E>,+namespace: U32,+name: String,+all: List<&2,Step>,+observations: List<&2,Observation>,+id: U32) -> RunResult<S,C,R,E,H,Error>:
123 match allowed:
124 case False{}: after_skip(~S,~C,~R,~E,~H,~Error,next,skip(owners,world,id),List.append(&2,Observation,observations,[Skipped{id}]))
125 case True{}: after_dispatch(~S,~C,~R,~E,~H,~Error,next,dispatch(owners,world,id),namespace,name,all,observations,id)
126
127def after_condition_skip(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,~skip: H -> W.World<S,C,R,E> -> U32 -> H & W.World<S,C,R,E>,~dispatch: H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,next: H -> W.World<S,C,R,E> -> List<&2,Observation> -> RunResult<S,C,R,E,H,Error>,checked: W.World<S,C,R,E> & Bool,owners: H,+namespace: U32,+name: String,+all: List<&2,Step>,+observations: List<&2,Observation>,+id: U32) -> RunResult<S,C,R,E,H,Error>:
128 (world,allowed) = checked
129 conditioned_skip(~S,~C,~R,~E,~H,~Error,~skip,~dispatch,next,allowed,owners,world,namespace,name,all,observations,id)
130
131def execute_skip(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,~skip: H -> W.World<S,C,R,E> -> U32 -> H & W.World<S,C,R,E>,~dispatch: H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,~condition: W.World<S,C,R,E> -> U32 -> W.World<S,C,R,E> & Bool,steps: List<&2,Step>,owners: H,world: W.World<S,C,R,E>,+namespace: U32,+name: String,+all: List<&2,Step>,+observations: List<&2,Observation>) -> RunResult<S,C,R,E,H,Error>:
132 match steps:
133 case []: Finished{Schedule{namespace,name,all,owners},world,observations}
134 case Phase{phase} <> rest: execute_skip(~S,~C,~R,~E,~H,~Error,~skip,~dispatch,~condition,rest,owners,world,namespace,name,all,List.append(&2,Observation,observations,[Entered{phase}]))
135 case Barrier{} <> rest: execute_skip(~S,~C,~R,~E,~H,~Error,~skip,~dispatch,~condition,rest,owners,W.barrier(~S,~C,~R,~E,world),namespace,name,all,List.append(&2,Observation,observations,[Applied{}]))
136 case System{+id,slot} <> rest: after_condition_skip(~S,~C,~R,~E,~H,~Error,~skip,~dispatch,h => w => obs => execute_skip(~S,~C,~R,~E,~H,~Error,~skip,~dispatch,~condition,rest,h,w,namespace,name,all,obs),condition(world,slot),owners,namespace,name,all,observations,id)
137
138def run_checked_skip(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,~skip: H -> W.World<S,C,R,E> -> U32 -> H & W.World<S,C,R,E>,~dispatch: H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,~condition: W.World<S,C,R,E> -> U32 -> W.World<S,C,R,E> & Bool,allowed: Bool,schedule: Schedule<S,C,R,E,H>,world: W.World<S,C,R,E>) -> RunResult<S,C,R,E,H,Error>:
139 match allowed schedule:
140 case False{} schedule: Rejected{schedule,world}
141 case True{} Schedule{+namespace,+name,+steps,owners}: execute_skip(~S,~C,~R,~E,~H,~Error,~skip,~dispatch,~condition,steps,owners,world,namespace,name,steps,[])
142
143
144def run_actual_skip(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,~skip: H -> W.World<S,C,R,E> -> U32 -> H & W.World<S,C,R,E>,~dispatch: H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,~condition: W.World<S,C,R,E> -> U32 -> W.World<S,C,R,E> & Bool,schedule: Schedule<S,C,R,E,H>,world: W.World<S,C,R,E>,+actual: U32) -> RunResult<S,C,R,E,H,Error>:
145 match schedule:
146 case Schedule{+namespace,+name,+steps,owners}: run_checked_skip(~S,~C,~R,~E,~H,~Error,~skip,~dispatch,~condition,U32.is_eq(namespace,actual),Schedule{namespace,name,steps,owners},world)
147
148def run_namespace_skip(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,~skip: H -> W.World<S,C,R,E> -> U32 -> H & W.World<S,C,R,E>,~dispatch: H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,~condition: W.World<S,C,R,E> -> U32 -> W.World<S,C,R,E> & Bool,schedule: Schedule<S,C,R,E,H>,named: W.World<S,C,R,E> & U32) -> RunResult<S,C,R,E,H,Error>:
149 (world,+actual) = named
150 run_actual_skip(~S,~C,~R,~E,~H,~Error,~skip,~dispatch,~condition,schedule,world,actual)
151
152# As run, but a false condition invokes skip(owners,world,id) before continuing.
153# Use the provider-defined hook for skipped-reader lifecycle; the hook itself
154# returns both actual owners and World and is not assumed to be a pure no-op.
155def run_with_skip(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,~Error: Data,~skip: H -> W.World<S,C,R,E> -> U32 -> H & W.World<S,C,R,E>,~dispatch: H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,~condition: W.World<S,C,R,E> -> U32 -> W.World<S,C,R,E> & Bool,schedule: Schedule<S,C,R,E,H>,world: W.World<S,C,R,E>) -> RunResult<S,C,R,E,H,Error>:
156 run_namespace_skip(~S,~C,~R,~E,~H,~Error,~skip,~dispatch,~condition,schedule,W.namespace(~S,~C,~R,~E,world))
157
158def run_barrier_checked(~S:Data,~C:Type,~R:Type,~E:Data,~H:Type,~Error:Data,~dispatch:H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,~condition:W.World<S,C,R,E> -> U32 -> W.World<S,C,R,E> & Bool,~barrier:H -> W.World<S,C,R,E> -> H & W.World<S,C,R,E>,allowed:Bool,schedule:Schedule<S,C,R,E,H>,world:W.World<S,C,R,E>) -> RunResult<S,C,R,E,H,Error>:
159 match allowed schedule:
160 case False{} schedule:Rejected{schedule,world}
161 case True{} Schedule{+namespace,+name,+steps,owners}:execute_with_barrier(~S,~C,~R,~E,~H,~Error,~dispatch,~condition,~barrier,steps,owners,world,namespace,name,steps,[])
162def run_barrier_actual(~S:Data,~C:Type,~R:Type,~E:Data,~H:Type,~Error:Data,~dispatch:H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,~condition:W.World<S,C,R,E> -> U32 -> W.World<S,C,R,E> & Bool,~barrier:H -> W.World<S,C,R,E> -> H & W.World<S,C,R,E>,schedule:Schedule<S,C,R,E,H>,world:W.World<S,C,R,E>,+actual:U32) -> RunResult<S,C,R,E,H,Error>:
163 match schedule:
164 case Schedule{+namespace,+name,+steps,owners}:run_barrier_checked(~S,~C,~R,~E,~H,~Error,~dispatch,~condition,~barrier,U32.is_eq(namespace,actual),Schedule{namespace,name,steps,owners},world)
165def run_barrier_named(~S:Data,~C:Type,~R:Type,~E:Data,~H:Type,~Error:Data,~dispatch:H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,~condition:W.World<S,C,R,E> -> U32 -> W.World<S,C,R,E> & Bool,~barrier:H -> W.World<S,C,R,E> -> H & W.World<S,C,R,E>,schedule:Schedule<S,C,R,E,H>,named:W.World<S,C,R,E> & U32) -> RunResult<S,C,R,E,H,Error>:
166 match named:
167 case (world,actual):run_barrier_actual(~S,~C,~R,~E,~H,~Error,~dispatch,~condition,~barrier,schedule,world,actual)
168# As namespace-checked run, with a caller-supplied barrier threading H and World
169# at each explicit Barrier step. This supports owned runtime synchronization;
170# its effects are trusted and no automatic final barrier or schedule rollback
171# is added. Condition-false handling is the ordinary skip-without-dispatch path.
172def run_with_barrier(~S:Data,~C:Type,~R:Type,~E:Data,~H:Type,~Error:Data,~dispatch:H -> W.World<S,C,R,E> -> U32 -> H & Sys.Outcome<W.World<S,C,R,E>,Error,Unit>,~condition:W.World<S,C,R,E> -> U32 -> W.World<S,C,R,E> & Bool,~barrier:H -> W.World<S,C,R,E> -> H & W.World<S,C,R,E>,schedule:Schedule<S,C,R,E,H>,world:W.World<S,C,R,E>) -> RunResult<S,C,R,E,H,Error>:
173 run_barrier_named(~S,~C,~R,~E,~H,~Error,~dispatch,~condition,~barrier,schedule,W.namespace(~S,~C,~R,~E,world))