schedule_provision.bend
bendvy-ecs-core@0.1.0.0 · 114 lines · 0 definitions
1import Base
2import bendvy-ecs-core@0.1.0.0/world.bend as W
3import bendvy-ecs-core@0.1.0.0/system.bend as Sys
4import bendvy-ecs-core@0.1.0.0/schedule.bend as Sch
5
6# Schema authoring provisions nominal requirement tokens. Gameplay cannot turn
7# these tokens into component/resource/service providers. Actual availability
8# is observed from the owned application's provisioning record before any step.
9type Requirement<S: Data> is Data:
10 Component{id: U32}
11 Resource{id: U32}
12 Service{id: U32}
13# Authored left-to-right schedule tree with per-entry provisioning requirements.
14# Empty contributes no steps; Sequence preserves both child orders.
15type Plan<S: Data> is Data:
16 Empty{}
17 Entry{step: Sch.Step,requirements: List<&2,Requirement<S>>}
18 Sequence{left: Plan<S>,right: Plan<S>}
19# Affine schedule owner paired with its deduplicated setup requirements.
20type Provisioned<S: Data,C: Type,R: Type,E: Data,H: Type> is Type:
21 Provisioned{schedule: Sch.Schedule<S,C,R,E,H>,requirements: List<&2,Requirement<S>>}
22# Built retains the provisioned schedule; Invalid returns the supplied owners.
23type Build<S: Data,C: Type,R: Type,E: Data,H: Type> is Type:
24 Built{schedule: Provisioned<S,C,R,E,H>}
25 Invalid{owners: H}
26# Missing returns schedule and observed World before any schedule step runs.
27# Executed contains the underlying schedule result and original requirement list;
28# inspect that result for success, rejection or system failure.
29type ProvisionResult<S: Data,C: Type,R: Type,E: Data,H: Type,Error: Data> is Type:
30 Executed{result: Sch.RunResult<S,C,R,E,H,Error>,requirements: List<&2,Requirement<S>>}
31 Missing{schedule: Provisioned<S,C,R,E,H>,world: W.World<S,C,R,E>,missing: List<&2,Requirement<S>>}
32
33def same(~S: Data,a: Requirement<S>,b: Requirement<S>) -> Bool:
34 match a b:
35 case Component{a} Component{b}: U32.is_eq(a,b)
36 case Resource{a} Resource{b}: U32.is_eq(a,b)
37 case Service{a} Service{b}: U32.is_eq(a,b)
38 case _ _: False{}
39
40def contains(~S: Data,items: List<&2,Requirement<S>>,+item: Requirement<S>) -> Bool:
41 match items:
42 case []: False{}
43 case head <> rest: Bool.or(same(~S,head,item),contains(~S,rest,item))
44
45def unique_add(~S: Data,present: Bool,+item: Requirement<S>,rest: List<&2,Requirement<S>>) -> List<&2,Requirement<S>>:
46 match present:
47 case True{}: rest
48 case False{}: item <> rest
49
50def unique_seen(~S: Data,items: List<&2,Requirement<S>>,+seen: List<&2,Requirement<S>>) -> List<&2,Requirement<S>>:
51 match items:
52 case []: []
53 case +head <> rest: unique_add(~S,contains(~S,seen,head),head,unique_seen(~S,rest,head <> seen))
54
55def unique(~S: Data,items: List<&2,Requirement<S>>) -> List<&2,Requirement<S>>:
56 unique_seen(~S,items,[])
57
58def flatten(~S: Data,plan: Plan<S>) -> List<&2,Sch.Step>:
59 match plan:
60 case Empty{}: []
61 case Entry{step,_}: [step]
62 case Sequence{left,right}: List.append(&2,Sch.Step,flatten(~S,left),flatten(~S,right))
63
64# Collect authored requirements left before right, retaining duplicates.
65# build deduplicates this list for its provisioned schedule.
66def requirements(~S: Data,plan: Plan<S>) -> List<&2,Requirement<S>>:
67 match plan:
68 case Empty{}: []
69 case Entry{_,needs}: needs
70 case Sequence{left,right}: List.append(&2,Requirement<S>,requirements(~S,left),requirements(~S,right))
71
72def finish_build(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,built: Sch.Build<S,C,R,E,H>,needs: List<&2,Requirement<S>>) -> Build<S,C,R,E,H>:
73 match built:
74 case Sch.Invalid{owners}: Invalid{owners}
75 case Sch.Built{schedule}: Built{Provisioned{schedule,needs}}
76
77# Flatten the authored plan and validate system ids against the trusted catalog,
78# rejecting duplicates/unknown systems with owners returned. Retain first-occurrence
79# requirement order; namespace/catalog provenance and condition slots are setup.
80def build(~S: Data,~C: Type,~R: Type,~E: Data,~H: Type,+namespace: U32,+name: String,+plan: Plan<S>,catalog: List<&2,U32>,owners: H) -> Build<S,C,R,E,H>:
81 finish_build(~S,~C,~R,~E,~H,Sch.build(~S,~C,~R,~E,~H,namespace,name,flatten(~S,plan),catalog,owners),unique(~S,requirements(~S,plan)))
82
83def missing_next(~S: Data,present: Bool,item: Requirement<S>,next: List<&2,Requirement<S>>) -> List<&2,Requirement<S>>:
84 match present:
85 case False{}: item <> next
86 case True{}: next
87
88def missing_requirements(~S: Data,needs: List<&2,Requirement<S>>,+available: List<&2,Requirement<S>>) -> List<&2,Requirement<S>>:
89 match needs:
90 case []: []
91 case +head <> rest: missing_next(~S,contains(~S,available,head),head,missing_requirements(~S,rest,available))
92
93def 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,~barrier:H -> W.World<S,C,R,E> -> H & W.World<S,C,R,E>,missing: List<&2,Requirement<S>>,schedule: Sch.Schedule<S,C,R,E,H>,world: W.World<S,C,R,E>,needs: List<&2,Requirement<S>>) -> ProvisionResult<S,C,R,E,H,Error>:
94 match missing:
95 case first <> rest: Missing{Provisioned{schedule,needs},world,first <> rest}
96 case []: Executed{Sch.run_with_barrier(~S,~C,~R,~E,~H,~Error,~dispatch,~condition,~barrier,schedule,world),needs}
97
98def run_available(~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>,observed: W.World<S,C,R,E> & List<&2,Requirement<S>>,schedule: Sch.Schedule<S,C,R,E,H>,+needs: List<&2,Requirement<S>>) -> ProvisionResult<S,C,R,E,H,Error>:
99 (world,available) = observed
100 run_checked(~S,~C,~R,~E,~H,~Error,~dispatch,~condition,~barrier,missing_requirements(~S,needs,available),schedule,world,needs)
101
102# Observe availability once through trusted provision before executing any step.
103# Missing requirements return the observed World and owned schedule without
104# dispatch; provision itself is not promised pure. Otherwise use schedule.bend
105# execution semantics and the supplied explicit barrier action. No final flush
106# or whole-schedule rollback is added; all result branches retain their owners.
107def 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>,~provision: W.World<S,C,R,E> -> W.World<S,C,R,E> & List<&2,Requirement<S>>,schedule: Provisioned<S,C,R,E,H>,world: W.World<S,C,R,E>) -> ProvisionResult<S,C,R,E,H,Error>:
108 match schedule:
109 case Provisioned{schedule,needs}: run_available(~S,~C,~R,~E,~H,~Error,~dispatch,~condition,~barrier,provision(world),schedule,needs)
110
111# Run with provisioning admission and the normal World barrier at explicit
112# Barrier steps. As with run_with_barrier, completion does not imply a final flush.
113def 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,~provision: W.World<S,C,R,E> -> W.World<S,C,R,E> & List<&2,Requirement<S>>,schedule: Provisioned<S,C,R,E,H>,world: W.World<S,C,R,E>) -> ProvisionResult<S,C,R,E,H,Error>:
114 run_with_barrier(~S,~C,~R,~E,~H,~Error,~dispatch,~condition,~Sch.default_barrier(~S,~C,~R,~E,~H),~provision,schedule,world)