restore_raw.bend
bendvy-ecs-core@0.1.0.0 · 117 lines · 0 definitions
1import Base
2import bendvy-ecs-core@0.1.0.0/decode_data.bend as D
3import bendvy-ecs-core@0.1.0.0/entity_key.bend as Key
4import bendvy-ecs-core@0.1.0.0/snapshot_data.bend as Snapshot
5import bendvy-ecs-core@0.1.0.0/restore_raw_number.bend as Number
6
7# Detached structural parsing failure or unsupported raw version. Schema-specific
8# name/value/construction errors are a later typed restore stage, not these errors.
9type Error is Data:
10 Invalid{error:D.Error}
11 UnsupportedVersion{version:D.Raw}
12# Accepted contains a structurally parsed typed Snapshot; it is not yet schema
13# validated or applied. Rejected retains complete original Raw and parsing error.
14type Parsed is Data:
15 Accepted{snapshot:Snapshot.Snapshot}
16 Rejected{original:D.Raw,error:Error}
17# Internal parser step result, not a second public restore/admission outcome.
18type Decoded<T:Data> is Data:
19 Value{value:T}
20 Failed{error:Error}
21def invalid(~T:Data,path:String,expected:String,raw:D.Raw) -> Decoded<T>:
22 Failed{Invalid{D.Invalid{path,expected,raw}}}
23def numeric_positive(path:String,raw:D.Raw,positive:Bool,value:U32,nonzero:Bool) -> Decoded<U32>:
24 match positive nonzero:
25 case True{} False{}:invalid(~U32,path,"positive unsigned 32-bit integer",raw)
26 case _ _:Value{value}
27def numeric_checked(+path:String,+raw:D.Raw,positive:Bool,result:Number.Checked) -> Decoded<U32>:
28 match result:
29 case Number.Invalid{}:invalid(~U32,path,"unsigned 32-bit integer",raw)
30 case Number.Value{+value}:numeric_positive(path,raw,positive,value,U32.is_gt(value,0))
31def number(+path:String,+raw:D.Raw,positive:Bool) -> Decoded<U32>:
32 numeric_checked(path,raw,positive,Number.checked(raw))
33def key_generation(index:U32,result:Decoded<U32>) -> Decoded<Key.Key>:
34 match result:
35 case Value{generation}:Value{Key.EntityKey{index,generation}}
36 case Failed{error}:Failed{error}
37def key_index(+path:String,+fields:List<&2,D.Field>,result:Decoded<U32>) -> Decoded<Key.Key>:
38 match result:
39 case Value{index}:key_generation(index,number(path ++ ".generation",D.field("generation",fields),False{}))
40 case Failed{error}:Failed{error}
41def key(+path:String,raw:D.Raw) -> Decoded<Key.Key>:
42 match raw:
43 case D.Object{+fields}:key_index(path,fields,number(path ++ ".index",D.field("index",fields),False{}))
44 case other:invalid(~Key.Key,path,"object",other)
45def entity_fields(saved:Key.Key,path:String,raw:D.Raw) -> Decoded<Snapshot.Entity>:
46 match raw:
47 case D.Object{fields}:Value{Snapshot.Entity{saved,fields}}
48 case other:invalid(~Snapshot.Entity,path,"object",other)
49def entity_key(+path:String,+fields:List<&2,D.Field>,result:Decoded<Key.Key>) -> Decoded<Snapshot.Entity>:
50 match result:
51 case Value{saved}:entity_fields(saved,path ++ ".components",D.field("components",fields))
52 case Failed{error}:Failed{error}
53def entity(+path:String,raw:D.Raw) -> Decoded<Snapshot.Entity>:
54 match raw:
55 case D.Object{+fields}:entity_key(path,fields,key(path ++ ".id",D.field("id",fields)))
56 case other:invalid(~Snapshot.Entity,path,"object",other)
57def entities_tail(head:Snapshot.Entity,result:Decoded<List<&2,Snapshot.Entity>>) -> Decoded<List<&2,Snapshot.Entity>>:
58 match result:
59 case Value{tail}:Value{head <> tail}
60 case Failed{error}:Failed{error}
61def entities_head(next:Unit -> Decoded<List<&2,Snapshot.Entity>>,result:Decoded<Snapshot.Entity>) -> Decoded<List<&2,Snapshot.Entity>>:
62 match result:
63 case Value{head}:entities_tail(head,next(Unit{}))
64 case Failed{error}:Failed{error}
65def entities(values:List<&2,D.Raw>,+index:U32) -> Decoded<List<&2,Snapshot.Entity>>:
66 match values:
67 case []:Value{[]}
68 case raw <> rest:entities_head((_ => entities(rest,U32.add(index,1))),entity("$.entities[" ++ U32.show(index) ++ "]",raw))
69def resources(next:U32,values:List<&2,Snapshot.Entity>,raw:D.Raw) -> Decoded<Snapshot.Snapshot>:
70 match raw:
71 case D.Object{fields}:Value{Snapshot.Snapshot{1,next,values,fields}}
72 case other:invalid(~Snapshot.Snapshot,"$.resources","object",other)
73def entities_checked(next:U32,fields:List<&2,D.Field>,result:Decoded<List<&2,Snapshot.Entity>>) -> Decoded<Snapshot.Snapshot>:
74 match result:
75 case Value{values}:resources(next,values,D.field("resources",fields))
76 case Failed{error}:Failed{error}
77def entity_array(next:U32,fields:List<&2,D.Field>,raw:D.Raw) -> Decoded<Snapshot.Snapshot>:
78 match raw:
79 case D.Array{values}:entities_checked(next,fields,entities(values,0))
80 case other:invalid(~Snapshot.Snapshot,"$.entities","array",other)
81def progress(+fields:List<&2,D.Field>,result:Decoded<U32>) -> Decoded<Snapshot.Snapshot>:
82 match result:
83 case Value{next}:entity_array(next,fields,D.field("entities",fields))
84 case Failed{error}:Failed{error}
85def version_checked(+fields:List<&2,D.Field>,+raw:D.Raw,result:Number.Checked) -> Decoded<Snapshot.Snapshot>:
86 match result:
87 case Number.Value{1}:progress(fields,number("$.nextEntity",D.field("nextEntity",fields),True{}))
88 case _:Failed{UnsupportedVersion{raw}}
89def object(+raw:D.Raw) -> Decoded<Snapshot.Snapshot>:
90 match raw:
91 case D.Object{+fields}:version_checked(fields,D.field("version",fields),Number.checked(D.field("version",fields)))
92 case other:invalid(~Snapshot.Snapshot,"$","object",other)
93def finished(original:D.Raw,result:Decoded<Snapshot.Snapshot>) -> Parsed:
94 match result:
95 case Value{snapshot}:Accepted{snapshot}
96 case Failed{error}:Rejected{original,error}
97# Parse the four-field snapshot object into typed detached Data without touching
98# World. Version must be 1; nextEntity is positive U32; saved index/generation
99# accept full U32 including zero as opaque tokens, not target allocator slots.
100# Exact numeric conversion rejects fractions/nonfinite/out-of-range input before
101# narrowing. Entities/components/resources shapes are checked in authored order;
102# D.field keeps existing first-match semantics, not a new duplicate-property rule.
103# Complete-key duplicates, schema dispatch and capacity are checked by typed restore.
104def parse(+raw:D.Raw) -> Parsed:finished(raw,object(raw))
105def saved_key(value:Key.Key) -> D.Raw:
106 match value:
107 case Key.EntityKey{index,generation}:D.Object{[D.Field{"index",D.Number{index}},D.Field{"generation",D.Number{generation}}]}
108def saved_entities(values:List<&2,Snapshot.Entity>) -> List<&2,D.Raw>:
109 match values:
110 case []:[]
111 case Snapshot.Entity{key,components} <> rest:D.Object{[D.Field{"id",saved_key(key)},D.Field{"components",D.Object{components}}]} <> saved_entities(rest)
112# Encode typed Snapshot to the four-field Raw representation preserving entity and
113# field order. This is not schema validation, JSON serialization or a Runtime
114# checkpoint; an arbitrary typed Snapshot may still be refused by restore.
115def encode(value:Snapshot.Snapshot) -> D.Raw:
116 match value:
117 case Snapshot.Snapshot{version,next,entities,resources}:D.Object{[D.Field{"version",D.Number{version}},D.Field{"nextEntity",D.Number{next}},D.Field{"entities",D.Array{saved_entities(entities)}},D.Field{"resources",D.Object{resources}}]}