restore_raw.bend

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

1import Base
2import bendvy-ecs-core@0.1.0.1/decode_data.bend as D
3import bendvy-ecs-core@0.1.0.1/entity_key.bend as Key
4import bendvy-ecs-core@0.1.0.1/snapshot_data.bend as Snapshot
5import bendvy-ecs-core@0.1.0.1/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}}]}