@carbonenginejs/runtime-utils 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +23 -0
- package/README.md +56 -0
- package/THIRD-PARTY-NOTICES.md +38 -0
- package/docs/README.md +81 -0
- package/docs/architecture.md +101 -0
- package/docs/concepts/foundation-consolidation.md +86 -0
- package/docs/const-kb.md +92 -0
- package/docs/core-types/DECORATOR-TODOS.md +25 -0
- package/docs/core-types/README.md +203 -0
- package/docs/reference/api.md +70 -0
- package/docs/reference/classes/README.md +115 -0
- package/package.json +132 -0
- package/src/arrays.js +5 -0
- package/src/audio/audioFormats.js +34 -0
- package/src/audio/index.js +1 -0
- package/src/box3.js +1385 -0
- package/src/bytes.js +56 -0
- package/src/compression.js +56 -0
- package/src/constants/index.js +6 -0
- package/src/constants.js +15 -0
- package/src/curve.js +419 -0
- package/src/d3d/dxgiFormats.js +46 -0
- package/src/d3d/index.js +2 -0
- package/src/d3d/primitiveTopology.js +11 -0
- package/src/document/CjsCarbonDocument.js +212 -0
- package/src/document/CjsClassRegistry.js +373 -0
- package/src/document/CjsDocumentDehydrator.js +142 -0
- package/src/document/CjsDocumentHydrator.js +156 -0
- package/src/document/CjsStructRegistry.js +348 -0
- package/src/document/hydrationAdapter.js +129 -0
- package/src/document/index.js +6 -0
- package/src/geometry/box.js +137 -0
- package/src/geometry/cylinder.js +244 -0
- package/src/geometry/helpers/LICENSE +15 -0
- package/src/geometry/helpers/earcut.js +766 -0
- package/src/geometry/helpers/misc.js +103 -0
- package/src/geometry/index.js +8 -0
- package/src/geometry/json.js +165 -0
- package/src/geometry/lathe.js +172 -0
- package/src/geometry/octahedron.js +0 -0
- package/src/geometry/plane.js +81 -0
- package/src/geometry/shape.js +95 -0
- package/src/geometry/sphere.js +123 -0
- package/src/geometry/torus.js +96 -0
- package/src/graphics/colorSpaces.js +22 -0
- package/src/graphics/index.js +4 -0
- package/src/graphics/pixelFormats.js +158 -0
- package/src/graphics/textureDimensions.js +22 -0
- package/src/graphics/trinityEnums.js +87 -0
- package/src/index.js +58 -0
- package/src/is.js +524 -0
- package/src/json.js +23 -0
- package/src/lifecycle/CjsLifecycleState.js +77 -0
- package/src/lifecycle/index.js +1 -0
- package/src/lne3.js +497 -0
- package/src/lookup.js +48 -0
- package/src/mat3.js +131 -0
- package/src/mat4.js +699 -0
- package/src/math/index.js +25 -0
- package/src/math/scalar.js +63 -0
- package/src/media/index.js +1 -0
- package/src/media/mediaTypes.js +50 -0
- package/src/mesh.js +394 -0
- package/src/model/CjsEventEmitter.js +333 -0
- package/src/model/CjsModel.js +1544 -0
- package/src/model/CjsModelState.js +72 -0
- package/src/model/index.js +4 -0
- package/src/model/sourceRecordUtils.js +54 -0
- package/src/noise.js +310 -0
- package/src/num.js +827 -0
- package/src/path.js +21 -0
- package/src/pln.js +762 -0
- package/src/pool.js +160 -0
- package/src/quat.js +144 -0
- package/src/ray3.js +1085 -0
- package/src/renderContext/formats.js +145 -0
- package/src/renderContext/index.js +5 -0
- package/src/renderContext/presentation.js +125 -0
- package/src/renderContext/resources.js +27 -0
- package/src/renderContext/upscaling.js +22 -0
- package/src/renderContext/window.js +20 -0
- package/src/runtime/CjsRuntimeState.js +50 -0
- package/src/schema/CjsSchema.js +1009 -0
- package/src/schema/index.js +17 -0
- package/src/shader/index.js +1 -0
- package/src/shader/shaderStages.js +37 -0
- package/src/sph3.js +754 -0
- package/src/tangent.js +288 -0
- package/src/text.js +40 -0
- package/src/tri3.js +650 -0
- package/src/types/carbonTypes.js +635 -0
- package/src/types/index.js +2 -0
- package/src/utils.js +58 -0
- package/src/validation.js +46 -0
- package/src/vec2.js +229 -0
- package/src/vec3.js +1172 -0
- package/src/vec4.js +347 -0
- package/src/vertex.js +108 -0
- package/src/webgpu/index.js +1 -0
- package/src/webgpu/textureFormats.js +121 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { exportCarbonValue } from "../types/index.js";
|
|
2
|
+
import { CjsSchema } from "../schema/index.js";
|
|
3
|
+
import { CjsCarbonDocument } from "./CjsCarbonDocument.js";
|
|
4
|
+
|
|
5
|
+
/** Converts runtime object graphs into neutral Carbon documents. */
|
|
6
|
+
export class CjsDocumentDehydrator
|
|
7
|
+
{
|
|
8
|
+
Dehydrate(value, options = {})
|
|
9
|
+
{
|
|
10
|
+
return CjsDocumentDehydrator.dehydrate(value, options);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static dehydrate(value, options = {})
|
|
14
|
+
{
|
|
15
|
+
const state = {
|
|
16
|
+
nextId: options.firstId || 1,
|
|
17
|
+
objectIds: new Map(),
|
|
18
|
+
nodes: [],
|
|
19
|
+
refs: {},
|
|
20
|
+
includeClassMetadata: Boolean(options.includeClassMetadata || options.trace || options.debug),
|
|
21
|
+
includeRefIndex: Boolean(options.includeRefIndex || options.trace || options.debug)
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const rootRef = CjsDocumentDehydrator.dehydrateValue(value, state);
|
|
25
|
+
if (!CjsCarbonDocument.isRef(rootRef))
|
|
26
|
+
{
|
|
27
|
+
throw new TypeError("CarbonDocument roots must be source-shaped objects");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (state.includeRefIndex)
|
|
31
|
+
{
|
|
32
|
+
for (const node of state.nodes)
|
|
33
|
+
{
|
|
34
|
+
state.refs[String(node.id)] = {
|
|
35
|
+
kind: node.kind
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return CjsCarbonDocument.create({
|
|
41
|
+
format: options.format || { id: "runtime", version: 1 },
|
|
42
|
+
roots: [{ name: options.rootName || "default", ref: rootRef }],
|
|
43
|
+
nodes: state.nodes.sort((a, b) => a.id - b.id),
|
|
44
|
+
refs: state.includeRefIndex ? state.refs : null,
|
|
45
|
+
metadata: options.metadata || {},
|
|
46
|
+
reports: options.reports || []
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static dehydrateValue(value, state)
|
|
51
|
+
{
|
|
52
|
+
if (CjsDocumentDehydrator.isSourceShapedObject(value)) return CjsDocumentDehydrator.dehydrateSourceObject(value, state);
|
|
53
|
+
if (Array.isArray(value)) return value.map(item => CjsDocumentDehydrator.dehydrateValue(item, state));
|
|
54
|
+
if (value instanceof Map) return Object.fromEntries(Array.from(value.entries()).map(([key, item]) => [key, CjsDocumentDehydrator.dehydrateValue(item, state)]));
|
|
55
|
+
if (value instanceof Set) return Array.from(value.values()).map(item => CjsDocumentDehydrator.dehydrateValue(item, state));
|
|
56
|
+
if (value && typeof value === "object" && !ArrayBuffer.isView(value))
|
|
57
|
+
{
|
|
58
|
+
return Object.fromEntries(
|
|
59
|
+
Object.entries(value)
|
|
60
|
+
.filter(([key]) => !key.startsWith("_"))
|
|
61
|
+
.map(([key, item]) => [key, CjsDocumentDehydrator.dehydrateValue(item, state)])
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return exportCarbonValue(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static dehydrateSourceObject(value, state)
|
|
68
|
+
{
|
|
69
|
+
if (state.objectIds.has(value)) return CjsCarbonDocument.createRef(state.objectIds.get(value));
|
|
70
|
+
|
|
71
|
+
const id = state.nextId++;
|
|
72
|
+
state.objectIds.set(value, id);
|
|
73
|
+
|
|
74
|
+
const shape = value._sourceShape || null;
|
|
75
|
+
const schemaName = typeof value.GetValues === "function"
|
|
76
|
+
? CjsSchema.getClassName(value.constructor)
|
|
77
|
+
: null;
|
|
78
|
+
const kind = schemaName || value._sourceClassName;
|
|
79
|
+
if (!kind)
|
|
80
|
+
{
|
|
81
|
+
throw new TypeError("Hydratable objects require an explicit schema className.");
|
|
82
|
+
}
|
|
83
|
+
const fields = {};
|
|
84
|
+
const fieldNames = new Set();
|
|
85
|
+
|
|
86
|
+
if (shape?.fields)
|
|
87
|
+
{
|
|
88
|
+
for (const field of shape.fields)
|
|
89
|
+
{
|
|
90
|
+
if (schemaName && CjsSchema.isFieldHidden(value.constructor, field.name)) continue;
|
|
91
|
+
fieldNames.add(field.name);
|
|
92
|
+
fields[field.name] = CjsDocumentDehydrator.dehydrateValue(value[field.name], state);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (typeof value.GetValues === "function")
|
|
96
|
+
{
|
|
97
|
+
for (const [key, item] of Object.entries(value.GetValues()))
|
|
98
|
+
{
|
|
99
|
+
fieldNames.add(key);
|
|
100
|
+
fields[key] = CjsDocumentDehydrator.dehydrateValue(item, state);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const raw = {};
|
|
105
|
+
for (const [key, item] of Object.entries(value))
|
|
106
|
+
{
|
|
107
|
+
if (key.startsWith("_") || fieldNames.has(key)) continue;
|
|
108
|
+
if (schemaName && CjsSchema.isFieldHidden(value.constructor, key)) continue;
|
|
109
|
+
raw[key] = CjsDocumentDehydrator.dehydrateValue(item, state);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const node = {
|
|
113
|
+
id,
|
|
114
|
+
kind,
|
|
115
|
+
fields
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
if (state.includeClassMetadata)
|
|
119
|
+
{
|
|
120
|
+
node.source = shape?.source || {};
|
|
121
|
+
node.meta = {
|
|
122
|
+
family: shape?.family || null,
|
|
123
|
+
hashes: shape?.hashes || null,
|
|
124
|
+
blue: shape?.blue || null
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (Object.keys(raw).length) node.raw = raw;
|
|
129
|
+
|
|
130
|
+
state.nodes.push(node);
|
|
131
|
+
|
|
132
|
+
return CjsCarbonDocument.createRef(id);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
static isSourceShapedObject(value)
|
|
136
|
+
{
|
|
137
|
+
return Boolean(value && typeof value === "object" && (
|
|
138
|
+
value._sourceClassName
|
|
139
|
+
|| (typeof value.GetValues === "function" && CjsSchema.getClassName(value.constructor))
|
|
140
|
+
));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { normalizeCarbonValue } from "../types/index.js";
|
|
2
|
+
import { CjsSchema } from "../schema/index.js";
|
|
3
|
+
import { CjsCarbonDocument } from "./CjsCarbonDocument.js";
|
|
4
|
+
import { resolveHydrationAdapter } from "./hydrationAdapter.js";
|
|
5
|
+
|
|
6
|
+
/** Constructs runtime object graphs from neutral Carbon documents. */
|
|
7
|
+
export class CjsDocumentHydrator
|
|
8
|
+
{
|
|
9
|
+
Hydrate(document, options = {})
|
|
10
|
+
{
|
|
11
|
+
return CjsDocumentHydrator.hydrate(document, options);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
HydrateRoot(document, options = {})
|
|
15
|
+
{
|
|
16
|
+
return CjsDocumentHydrator.hydrateRoot(document, options);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static hydrate(document, options = {})
|
|
20
|
+
{
|
|
21
|
+
const normalized = CjsCarbonDocument.normalize(document);
|
|
22
|
+
const nodeById = new Map(normalized.nodes.map(node => [node.id, node]));
|
|
23
|
+
const instanceById = new Map();
|
|
24
|
+
const reports = [];
|
|
25
|
+
const adapter = resolveHydrationAdapter(options);
|
|
26
|
+
|
|
27
|
+
for (const node of normalized.nodes)
|
|
28
|
+
{
|
|
29
|
+
instanceById.set(node.id, CjsDocumentHydrator.createNodeTarget(node, options, reports, adapter));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const node of normalized.nodes)
|
|
33
|
+
{
|
|
34
|
+
CjsDocumentHydrator.applyNodeValues(instanceById.get(node.id), node, instanceById, options, adapter);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Phase 3: every instance is constructed and valued, so references are
|
|
38
|
+
// resolved - now let callers run their own post-graph init.
|
|
39
|
+
for (const node of normalized.nodes)
|
|
40
|
+
{
|
|
41
|
+
adapter.finalize(instanceById.get(node.id), { kind: node.kind, node, options });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const roots = normalized.roots.map(root => ({
|
|
45
|
+
name: root.name,
|
|
46
|
+
value: CjsDocumentHydrator.resolveDocumentValue(root.ref, instanceById, options)
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
root: roots[0]?.value ?? null,
|
|
51
|
+
roots,
|
|
52
|
+
document: normalized,
|
|
53
|
+
reports,
|
|
54
|
+
get(id)
|
|
55
|
+
{
|
|
56
|
+
return instanceById.get(Number(id)) || null;
|
|
57
|
+
},
|
|
58
|
+
getNode(id)
|
|
59
|
+
{
|
|
60
|
+
return nodeById.get(Number(id)) || null;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static hydrateRoot(document, options = {})
|
|
66
|
+
{
|
|
67
|
+
return CjsDocumentHydrator.hydrate(document, options).root;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static createNodeTarget(node, options, reports, adapter)
|
|
71
|
+
{
|
|
72
|
+
if (adapter)
|
|
73
|
+
{
|
|
74
|
+
const built = adapter.construct(node.kind, { kind: node.kind, node, options });
|
|
75
|
+
if (built !== undefined) return built;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const ClassConstructor = CjsDocumentHydrator.resolveClass(node.kind, options);
|
|
79
|
+
if (ClassConstructor)
|
|
80
|
+
{
|
|
81
|
+
return new ClassConstructor();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
throw new TypeError(`No class is registered for hydratable type ${node.kind}.`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static applyNodeValues(target, node, instanceById, options, adapter)
|
|
88
|
+
{
|
|
89
|
+
const registeredSchema = target?.constructor
|
|
90
|
+
? CjsSchema.getSchema(target.constructor)
|
|
91
|
+
: null;
|
|
92
|
+
const shape = target?._sourceShape || (registeredSchema?.className ? registeredSchema : null);
|
|
93
|
+
const fieldByName = new Map((shape?.fields || []).map(field => [field.name, field]));
|
|
94
|
+
const values = {};
|
|
95
|
+
|
|
96
|
+
for (const [key, item] of Object.entries(node.fields || {}))
|
|
97
|
+
{
|
|
98
|
+
if (CjsSchema.isFieldHidden(target?.constructor, key)) continue;
|
|
99
|
+
const field = fieldByName.get(key) || null;
|
|
100
|
+
values[key] = CjsDocumentHydrator.hydrateFieldValue(item, field, instanceById, options);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const [key, item] of Object.entries(node.raw || {}))
|
|
104
|
+
{
|
|
105
|
+
if (CjsSchema.isFieldHidden(target?.constructor, key)) continue;
|
|
106
|
+
values[key] = CjsDocumentHydrator.resolveDocumentValue(item, instanceById, options);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const apply = adapter || resolveHydrationAdapter(options);
|
|
110
|
+
apply.applyValues(target, values, { kind: node.kind, shape, node, options });
|
|
111
|
+
return target;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static hydrateFieldValue(value, field, instanceById, options)
|
|
115
|
+
{
|
|
116
|
+
const resolved = CjsDocumentHydrator.resolveDocumentValue(value, instanceById, options);
|
|
117
|
+
const descriptor = field?.type || field?.jsType || null;
|
|
118
|
+
const kind = descriptor?.kind;
|
|
119
|
+
|
|
120
|
+
if ((kind === "array" || kind === "list") && Array.isArray(resolved)) return resolved;
|
|
121
|
+
if ((kind === "model" || kind === "objectRef" || kind === "struct" || kind === "rawStruct" || kind === "unknown") && resolved && typeof resolved === "object") return resolved;
|
|
122
|
+
if (CjsDocumentHydrator.isShapeIncompatibleMathArray(resolved, descriptor)) return resolved;
|
|
123
|
+
|
|
124
|
+
return descriptor ? normalizeCarbonValue(resolved, descriptor) : resolved;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static resolveDocumentValue(value, instanceById, options)
|
|
128
|
+
{
|
|
129
|
+
if (CjsCarbonDocument.isRef(value))
|
|
130
|
+
{
|
|
131
|
+
const id = Number(value.$ref);
|
|
132
|
+
if (instanceById.has(id)) return instanceById.get(id);
|
|
133
|
+
if (options.allowMissingRefs) return null;
|
|
134
|
+
throw new TypeError(`CarbonDocument ref ${id} does not exist`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (Array.isArray(value)) return value.map(item => CjsDocumentHydrator.resolveDocumentValue(item, instanceById, options));
|
|
138
|
+
if (value && typeof value === "object")
|
|
139
|
+
{
|
|
140
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, CjsDocumentHydrator.resolveDocumentValue(item, instanceById, options)]));
|
|
141
|
+
}
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
static resolveClass(kind, options)
|
|
146
|
+
{
|
|
147
|
+
const Schema = options.registry || CjsSchema;
|
|
148
|
+
return Schema.GetConstructor(kind);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
static isShapeIncompatibleMathArray(value, descriptor)
|
|
152
|
+
{
|
|
153
|
+
const expectedLength = descriptor?.length;
|
|
154
|
+
return Array.isArray(value) && Number.isInteger(expectedLength) && value.length !== expectedLength;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/** Maps serialized Carbon struct names to explicit constructors and layouts. */
|
|
2
|
+
export class CjsStructRegistry
|
|
3
|
+
{
|
|
4
|
+
constructor(options = {})
|
|
5
|
+
{
|
|
6
|
+
const {
|
|
7
|
+
name = "structs",
|
|
8
|
+
family = "",
|
|
9
|
+
constructors = null,
|
|
10
|
+
layouts = null,
|
|
11
|
+
entries = [],
|
|
12
|
+
aliases = null,
|
|
13
|
+
metadata = {}
|
|
14
|
+
} = options;
|
|
15
|
+
|
|
16
|
+
this.name = String(name || "structs");
|
|
17
|
+
this.family = String(family || "");
|
|
18
|
+
this.metadata = { ...metadata };
|
|
19
|
+
this.entries = new Map();
|
|
20
|
+
this.aliases = new Map();
|
|
21
|
+
this.reports = [];
|
|
22
|
+
|
|
23
|
+
if (constructors || layouts)
|
|
24
|
+
{
|
|
25
|
+
this.RegisterMaps({ constructors, layouts, aliases });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.RegisterMany(entries);
|
|
29
|
+
this.RegisterAliases(aliases);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get size()
|
|
33
|
+
{
|
|
34
|
+
return this.entries.size;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
RegisterMaps(options = {})
|
|
38
|
+
{
|
|
39
|
+
const {
|
|
40
|
+
constructors = {},
|
|
41
|
+
layouts = {},
|
|
42
|
+
aliases = {}
|
|
43
|
+
} = options;
|
|
44
|
+
|
|
45
|
+
const names = new Set([
|
|
46
|
+
...Object.keys(constructors || {}).filter(name => typeof constructors[name] === "function"),
|
|
47
|
+
...Object.keys(layouts || {})
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
for (const structName of names)
|
|
51
|
+
{
|
|
52
|
+
this.Register({
|
|
53
|
+
structName,
|
|
54
|
+
constructor: typeof constructors?.[structName] === "function" ? constructors[structName] : null,
|
|
55
|
+
layout: layouts?.[structName] || null,
|
|
56
|
+
aliases: aliasesForStructName(structName, aliases),
|
|
57
|
+
family: layouts?.[structName]?.family || this.family
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
RegisterMany(entries = [])
|
|
65
|
+
{
|
|
66
|
+
const list = Array.isArray(entries) ? entries : Object.values(entries || {});
|
|
67
|
+
for (const entry of list)
|
|
68
|
+
{
|
|
69
|
+
this.Register(entry);
|
|
70
|
+
}
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Register(entry)
|
|
75
|
+
{
|
|
76
|
+
const normalized = normalizeStructEntry(entry, this.family);
|
|
77
|
+
const existing = this.entries.get(normalized.structName);
|
|
78
|
+
|
|
79
|
+
if (existing)
|
|
80
|
+
{
|
|
81
|
+
this.entries.set(normalized.structName, mergeStructEntries(existing, normalized));
|
|
82
|
+
this.ReportEntryClashes(existing, normalized);
|
|
83
|
+
}
|
|
84
|
+
else
|
|
85
|
+
{
|
|
86
|
+
this.entries.set(normalized.structName, normalized);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const alias of normalized.aliases)
|
|
90
|
+
{
|
|
91
|
+
this.RegisterAlias(alias, normalized.structName);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
RegisterAliases(aliases = null)
|
|
98
|
+
{
|
|
99
|
+
if (!aliases) return this;
|
|
100
|
+
|
|
101
|
+
if (Array.isArray(aliases))
|
|
102
|
+
{
|
|
103
|
+
for (const item of aliases)
|
|
104
|
+
{
|
|
105
|
+
if (Array.isArray(item)) this.RegisterAlias(item[0], item[1]);
|
|
106
|
+
else this.RegisterAlias(item.alias, item.structName || item.name || item.target);
|
|
107
|
+
}
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const [key, value] of Object.entries(aliases))
|
|
112
|
+
{
|
|
113
|
+
if (Array.isArray(value))
|
|
114
|
+
{
|
|
115
|
+
for (const alias of value)
|
|
116
|
+
{
|
|
117
|
+
this.RegisterAlias(alias, key);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else
|
|
121
|
+
{
|
|
122
|
+
this.RegisterAlias(key, value);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
RegisterAlias(alias, structName)
|
|
130
|
+
{
|
|
131
|
+
if (!alias || !structName) return this;
|
|
132
|
+
|
|
133
|
+
const normalizedAlias = String(alias);
|
|
134
|
+
const normalizedStructName = String(structName);
|
|
135
|
+
const existing = this.aliases.get(normalizedAlias);
|
|
136
|
+
|
|
137
|
+
if (existing && existing !== normalizedStructName)
|
|
138
|
+
{
|
|
139
|
+
this.reports.push({
|
|
140
|
+
level: "warning",
|
|
141
|
+
code: "alias-clash",
|
|
142
|
+
alias: normalizedAlias,
|
|
143
|
+
existingStructName: existing,
|
|
144
|
+
structName: normalizedStructName,
|
|
145
|
+
message: `Struct alias ${normalizedAlias} already points to ${existing}`
|
|
146
|
+
});
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (this.entries.has(normalizedAlias) && normalizedAlias !== normalizedStructName)
|
|
151
|
+
{
|
|
152
|
+
this.reports.push({
|
|
153
|
+
level: "warning",
|
|
154
|
+
code: "alias-struct-clash",
|
|
155
|
+
alias: normalizedAlias,
|
|
156
|
+
structName: normalizedStructName,
|
|
157
|
+
message: `Struct alias ${normalizedAlias} also names a registered struct`
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
this.aliases.set(normalizedAlias, normalizedStructName);
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
ResolveName(name)
|
|
166
|
+
{
|
|
167
|
+
let current = String(name || "");
|
|
168
|
+
const seen = new Set();
|
|
169
|
+
|
|
170
|
+
while (this.aliases.has(current) && !seen.has(current))
|
|
171
|
+
{
|
|
172
|
+
seen.add(current);
|
|
173
|
+
current = this.aliases.get(current);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return current;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
GetEntry(name)
|
|
180
|
+
{
|
|
181
|
+
return this.entries.get(this.ResolveName(name)) || null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
Has(name)
|
|
185
|
+
{
|
|
186
|
+
return Boolean(this.GetEntry(name));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
GetConstructor(name)
|
|
190
|
+
{
|
|
191
|
+
return this.GetEntry(name)?.constructor || null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
GetLayout(name)
|
|
195
|
+
{
|
|
196
|
+
return this.GetEntry(name)?.layout || null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
Create(name, values = {})
|
|
200
|
+
{
|
|
201
|
+
const StructConstructor = this.GetConstructor(name);
|
|
202
|
+
return StructConstructor ? new StructConstructor(values) : { ...values };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
GetReports()
|
|
206
|
+
{
|
|
207
|
+
return this.reports.map(report => ({ ...report }));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
ToJSON()
|
|
211
|
+
{
|
|
212
|
+
return {
|
|
213
|
+
name: this.name,
|
|
214
|
+
family: this.family,
|
|
215
|
+
metadata: { ...this.metadata },
|
|
216
|
+
entries: Array.from(this.entries.values()).map(entry => ({
|
|
217
|
+
structName: entry.structName,
|
|
218
|
+
family: entry.family,
|
|
219
|
+
aliases: [...entry.aliases],
|
|
220
|
+
hasConstructor: Boolean(entry.constructor),
|
|
221
|
+
hasLayout: Boolean(entry.layout),
|
|
222
|
+
layout: summarizeLayout(entry.layout)
|
|
223
|
+
})),
|
|
224
|
+
aliases: Object.fromEntries(this.aliases),
|
|
225
|
+
reports: this.GetReports()
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
[Symbol.iterator]()
|
|
230
|
+
{
|
|
231
|
+
return this.entries.values();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
ReportEntryClashes(existing, incoming)
|
|
235
|
+
{
|
|
236
|
+
if (existing.constructor && incoming.constructor && existing.constructor !== incoming.constructor)
|
|
237
|
+
{
|
|
238
|
+
this.reports.push(createStructClash("constructor-clash", existing, incoming));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (existing.layout && incoming.layout && existing.layout !== incoming.layout)
|
|
242
|
+
{
|
|
243
|
+
this.reports.push(createStructClash("layout-clash", existing, incoming));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
static from(options = {})
|
|
248
|
+
{
|
|
249
|
+
return options instanceof CjsStructRegistry ? options : new CjsStructRegistry(options);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
static fromMaps(options = {})
|
|
253
|
+
{
|
|
254
|
+
return new CjsStructRegistry(options);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function normalizeStructEntry(entry, defaultFamily)
|
|
259
|
+
{
|
|
260
|
+
if (!entry || typeof entry !== "object")
|
|
261
|
+
{
|
|
262
|
+
throw new TypeError("Struct registry entry must be an object");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const entryConstructor = Object.hasOwn(entry, "constructor") ? entry.constructor : null;
|
|
266
|
+
const constructor = typeof entryConstructor === "function" ? entryConstructor : null;
|
|
267
|
+
const layout = entry.layout || null;
|
|
268
|
+
const structName = String(entry.structName || entry.name || layout?.structName || constructor?.sourceStruct || "");
|
|
269
|
+
|
|
270
|
+
if (!structName)
|
|
271
|
+
{
|
|
272
|
+
throw new TypeError("Struct registry entry is missing a structName");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
structName,
|
|
277
|
+
constructor,
|
|
278
|
+
layout,
|
|
279
|
+
aliases: normalizeAliases(entry.aliases),
|
|
280
|
+
family: String(entry.family || layout?.family || defaultFamily || ""),
|
|
281
|
+
metadata: { ...(entry.metadata || {}) }
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function mergeStructEntries(existing, incoming)
|
|
286
|
+
{
|
|
287
|
+
return {
|
|
288
|
+
structName: existing.structName,
|
|
289
|
+
constructor: existing.constructor || incoming.constructor,
|
|
290
|
+
layout: existing.layout || incoming.layout,
|
|
291
|
+
aliases: Array.from(new Set([...existing.aliases, ...incoming.aliases])),
|
|
292
|
+
family: existing.family || incoming.family,
|
|
293
|
+
metadata: { ...existing.metadata, ...incoming.metadata }
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function normalizeAliases(aliases)
|
|
298
|
+
{
|
|
299
|
+
if (!aliases) return [];
|
|
300
|
+
if (Array.isArray(aliases)) return aliases.map(String).filter(Boolean);
|
|
301
|
+
return [String(aliases)].filter(Boolean);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function aliasesForStructName(structName, aliases)
|
|
305
|
+
{
|
|
306
|
+
if (!aliases || Array.isArray(aliases)) return [];
|
|
307
|
+
|
|
308
|
+
const direct = aliases[structName];
|
|
309
|
+
if (Array.isArray(direct)) return direct;
|
|
310
|
+
|
|
311
|
+
const matched = [];
|
|
312
|
+
for (const [alias, target] of Object.entries(aliases))
|
|
313
|
+
{
|
|
314
|
+
if (target === structName) matched.push(alias);
|
|
315
|
+
}
|
|
316
|
+
return matched;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function createStructClash(code, existing, incoming)
|
|
320
|
+
{
|
|
321
|
+
return {
|
|
322
|
+
level: "warning",
|
|
323
|
+
code,
|
|
324
|
+
structName: existing.structName,
|
|
325
|
+
message: `${existing.structName} has conflicting struct registry metadata`,
|
|
326
|
+
existing: summarizeEntry(existing),
|
|
327
|
+
incoming: summarizeEntry(incoming)
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function summarizeEntry(entry)
|
|
332
|
+
{
|
|
333
|
+
return {
|
|
334
|
+
structName: entry.structName,
|
|
335
|
+
hasConstructor: Boolean(entry.constructor),
|
|
336
|
+
hasLayout: Boolean(entry.layout)
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function summarizeLayout(layout)
|
|
341
|
+
{
|
|
342
|
+
if (!layout || typeof layout !== "object") return null;
|
|
343
|
+
return {
|
|
344
|
+
structName: layout.structName || layout.name || null,
|
|
345
|
+
byteSize: layout.byteSize ?? layout.structureSize ?? null,
|
|
346
|
+
fieldCount: Array.isArray(layout.fields) ? layout.fields.length : null
|
|
347
|
+
};
|
|
348
|
+
}
|