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))