@multitapio/multitap 0.0.13 → 0.0.14
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/dist/codegen/config.d.ts +5 -2
- package/dist/codegen/config.d.ts.map +1 -1
- package/dist/codegen/rust.d.ts +3 -6
- package/dist/codegen/rust.d.ts.map +1 -1
- package/dist/codegen/typescript.d.ts +3 -6
- package/dist/codegen/typescript.d.ts.map +1 -1
- package/dist/diagnostics.js +404 -8
- package/dist/executor.d.ts +27 -0
- package/dist/executor.d.ts.map +1 -1
- package/dist/init-data.d.ts +25 -0
- package/dist/init-data.d.ts.map +1 -0
- package/dist/lib.js +610 -9
- package/dist/schema.d.ts +68 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/session.d.ts +5 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/test-session.d.ts +4 -0
- package/dist/test-session.d.ts.map +1 -1
- package/dist/types/codegen/config.d.ts +5 -2
- package/dist/types/codegen/config.d.ts.map +1 -1
- package/dist/types/codegen/rust.d.ts +3 -6
- package/dist/types/codegen/rust.d.ts.map +1 -1
- package/dist/types/codegen/typescript.d.ts +3 -6
- package/dist/types/codegen/typescript.d.ts.map +1 -1
- package/dist/types/executor.d.ts +27 -0
- package/dist/types/executor.d.ts.map +1 -1
- package/dist/types/init-data.d.ts +25 -0
- package/dist/types/init-data.d.ts.map +1 -0
- package/dist/types/schema.d.ts +68 -0
- package/dist/types/schema.d.ts.map +1 -1
- package/dist/types/session.d.ts +5 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/test-session.d.ts +4 -0
- package/dist/types/test-session.d.ts.map +1 -1
- package/dist/types/vite/codegen-runner.d.ts +2 -1
- package/dist/types/vite/codegen-runner.d.ts.map +1 -1
- package/dist/types/vite/module-builder.d.ts +2 -1
- package/dist/types/vite/module-builder.d.ts.map +1 -1
- package/dist/types/vite/plugin.d.ts.map +1 -1
- package/dist/vite/codegen-runner.d.ts +2 -1
- package/dist/vite/codegen-runner.d.ts.map +1 -1
- package/dist/vite/index.js +1403 -692
- package/dist/vite/module-builder.d.ts +2 -1
- package/dist/vite/module-builder.d.ts.map +1 -1
- package/dist/vite/plugin.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/vite/index.js
CHANGED
|
@@ -137,8 +137,10 @@ function topologicalSort(plugins) {
|
|
|
137
137
|
function mergeConfigs(graph) {
|
|
138
138
|
const componentOwners = /* @__PURE__ */ new Map();
|
|
139
139
|
const eventOwners = /* @__PURE__ */ new Map();
|
|
140
|
+
const dataOwners = /* @__PURE__ */ new Map();
|
|
140
141
|
const mergedComponents = [];
|
|
141
142
|
const mergedEvents = [];
|
|
143
|
+
const mergedData = [];
|
|
142
144
|
for (const pluginPath of graph.order) {
|
|
143
145
|
const node = graph.plugins.get(pluginPath);
|
|
144
146
|
for (const comp of node.config.state.components ?? []) {
|
|
@@ -177,6 +179,25 @@ function mergeConfigs(graph) {
|
|
|
177
179
|
eventOwners.set(event.name, graph.root.dir);
|
|
178
180
|
mergedEvents.push(event);
|
|
179
181
|
}
|
|
182
|
+
for (const pluginPath of graph.order) {
|
|
183
|
+
const node = graph.plugins.get(pluginPath);
|
|
184
|
+
for (const data of node.config.data ?? []) {
|
|
185
|
+
if (dataOwners.has(data.name)) {
|
|
186
|
+
const owner = dataOwners.get(data.name);
|
|
187
|
+
throw new Error(`Data item "${data.name}" defined in both ${owner} and ${pluginPath}`);
|
|
188
|
+
}
|
|
189
|
+
dataOwners.set(data.name, pluginPath);
|
|
190
|
+
mergedData.push(data);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (const data of graph.root.config.data ?? []) {
|
|
194
|
+
if (dataOwners.has(data.name)) {
|
|
195
|
+
const owner = dataOwners.get(data.name);
|
|
196
|
+
throw new Error(`Data item "${data.name}" defined in both ${owner} and root config`);
|
|
197
|
+
}
|
|
198
|
+
dataOwners.set(data.name, graph.root.dir);
|
|
199
|
+
mergedData.push(data);
|
|
200
|
+
}
|
|
180
201
|
const mergedState = {
|
|
181
202
|
maxEntities: graph.root.config.state.maxEntities,
|
|
182
203
|
components: mergedComponents
|
|
@@ -200,10 +221,14 @@ function mergeConfigs(graph) {
|
|
|
200
221
|
if (graph.root.config.fixtures !== void 0) {
|
|
201
222
|
mergedConfig.fixtures = graph.root.config.fixtures;
|
|
202
223
|
}
|
|
224
|
+
if (mergedData.length > 0) {
|
|
225
|
+
mergedConfig.data = mergedData;
|
|
226
|
+
}
|
|
203
227
|
return {
|
|
204
228
|
config: mergedConfig,
|
|
205
229
|
pluginOrder: graph.order,
|
|
206
|
-
componentOwners
|
|
230
|
+
componentOwners,
|
|
231
|
+
dataOwners
|
|
207
232
|
};
|
|
208
233
|
}
|
|
209
234
|
|
|
@@ -425,118 +450,796 @@ function getElementSize(type) {
|
|
|
425
450
|
}
|
|
426
451
|
}
|
|
427
452
|
|
|
428
|
-
// src/
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
453
|
+
// src/schema.ts
|
|
454
|
+
var HEADER_SIZE = 44;
|
|
455
|
+
var TYPE_SIZES = {
|
|
456
|
+
bool: 1,
|
|
457
|
+
uint8: 1,
|
|
458
|
+
int8: 1,
|
|
459
|
+
uint16: 2,
|
|
460
|
+
int16: 2,
|
|
461
|
+
uint32: 4,
|
|
462
|
+
int32: 4,
|
|
463
|
+
entityRef: 4,
|
|
464
|
+
f32: 4,
|
|
465
|
+
vec2: 8,
|
|
466
|
+
vec3: 12,
|
|
467
|
+
quat: 16,
|
|
468
|
+
enum: 1
|
|
469
|
+
};
|
|
470
|
+
var INPUT_TYPE_SIZES = {
|
|
471
|
+
bool: 1,
|
|
472
|
+
uint8: 1,
|
|
473
|
+
int8: 1,
|
|
474
|
+
uint16: 2,
|
|
475
|
+
int16: 2,
|
|
476
|
+
uint32: 4,
|
|
477
|
+
int32: 4,
|
|
478
|
+
f32: 4,
|
|
479
|
+
flags8: 1,
|
|
480
|
+
flags16: 2,
|
|
481
|
+
flags32: 4,
|
|
482
|
+
vec2: 8,
|
|
483
|
+
vec3: 12,
|
|
484
|
+
quat: 16
|
|
485
|
+
};
|
|
486
|
+
var PRIMITIVE_TYPES = /* @__PURE__ */ new Set([
|
|
487
|
+
"bool",
|
|
488
|
+
"uint8",
|
|
489
|
+
"int8",
|
|
490
|
+
"uint16",
|
|
491
|
+
"int16",
|
|
492
|
+
"uint32",
|
|
493
|
+
"int32",
|
|
494
|
+
"entityRef",
|
|
495
|
+
"f32"
|
|
496
|
+
]);
|
|
497
|
+
var INPUT_PRIMITIVE_SCALAR_TYPES = /* @__PURE__ */ new Set([
|
|
498
|
+
"bool",
|
|
499
|
+
"uint8",
|
|
500
|
+
"int8",
|
|
501
|
+
"uint16",
|
|
502
|
+
"int16",
|
|
503
|
+
"uint32",
|
|
504
|
+
"int32",
|
|
505
|
+
"f32"
|
|
506
|
+
]);
|
|
507
|
+
var ENUM_VARIANT_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
508
|
+
function parseArrayType(type) {
|
|
509
|
+
const match = type.match(/^(\w+)\[(\d+)\]$/);
|
|
510
|
+
if (!match || !match[1] || !match[2]) return null;
|
|
511
|
+
const elementType = match[1];
|
|
512
|
+
const length = parseInt(match[2], 10);
|
|
513
|
+
if (!PRIMITIVE_TYPES.has(elementType)) return null;
|
|
514
|
+
return { elementType, length };
|
|
515
|
+
}
|
|
516
|
+
function parseInputArrayType(type) {
|
|
517
|
+
const match = type.match(/^(\w+)\[(\d+)\]$/);
|
|
518
|
+
if (!match || !match[1] || !match[2]) return null;
|
|
519
|
+
const elementType = match[1];
|
|
520
|
+
const length = parseInt(match[2], 10);
|
|
521
|
+
if (!INPUT_PRIMITIVE_SCALAR_TYPES.has(elementType)) {
|
|
522
|
+
throw new Error(
|
|
523
|
+
`Invalid array element type '${elementType}': arrays only support primitive scalar types (bool, uint8, int8, uint16, int16, uint32, int32, f32)`
|
|
524
|
+
);
|
|
432
525
|
}
|
|
433
|
-
|
|
434
|
-
|
|
526
|
+
return { elementType, length };
|
|
527
|
+
}
|
|
528
|
+
function getTypeSize(type) {
|
|
529
|
+
const arrayInfo = parseArrayType(type);
|
|
530
|
+
if (arrayInfo) {
|
|
531
|
+
const elementSize = TYPE_SIZES[arrayInfo.elementType];
|
|
532
|
+
if (elementSize === void 0) {
|
|
533
|
+
throw new Error(`Unknown element type: ${arrayInfo.elementType}`);
|
|
534
|
+
}
|
|
535
|
+
return elementSize * arrayInfo.length;
|
|
435
536
|
}
|
|
436
|
-
|
|
437
|
-
|
|
537
|
+
const size = TYPE_SIZES[type];
|
|
538
|
+
if (size === void 0) {
|
|
539
|
+
throw new Error(`Unknown type: ${type}`);
|
|
438
540
|
}
|
|
439
|
-
return
|
|
541
|
+
return size;
|
|
440
542
|
}
|
|
441
|
-
function
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
return "number";
|
|
454
|
-
default:
|
|
455
|
-
throw new Error(`Unknown type '${typ}' in TypeScript code generation`);
|
|
543
|
+
function getInputTypeSize(type) {
|
|
544
|
+
const arrayInfo = parseInputArrayType(type);
|
|
545
|
+
if (arrayInfo) {
|
|
546
|
+
const elementSize = INPUT_TYPE_SIZES[arrayInfo.elementType];
|
|
547
|
+
if (elementSize === void 0) {
|
|
548
|
+
throw new Error(`Unknown element type: ${arrayInfo.elementType}`);
|
|
549
|
+
}
|
|
550
|
+
return elementSize * arrayInfo.length;
|
|
551
|
+
}
|
|
552
|
+
const size = INPUT_TYPE_SIZES[type];
|
|
553
|
+
if (size === void 0) {
|
|
554
|
+
throw new Error(`Unknown type: ${type}`);
|
|
456
555
|
}
|
|
556
|
+
return size;
|
|
457
557
|
}
|
|
458
|
-
function
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
case "int16":
|
|
466
|
-
case "uint32":
|
|
467
|
-
case "int32":
|
|
468
|
-
case "f32":
|
|
469
|
-
case "flags8":
|
|
470
|
-
case "flags16":
|
|
471
|
-
case "flags32":
|
|
472
|
-
case "vec2":
|
|
473
|
-
case "vec3":
|
|
474
|
-
case "quat":
|
|
475
|
-
return "number";
|
|
476
|
-
default:
|
|
477
|
-
throw new Error(`Unknown input type '${typ}' in TypeScript code generation`);
|
|
558
|
+
function getValidatedTypeSize(type, componentName, fieldName) {
|
|
559
|
+
try {
|
|
560
|
+
return getTypeSize(type);
|
|
561
|
+
} catch (error) {
|
|
562
|
+
const location = fieldName ? `${componentName}.${fieldName}` : componentName;
|
|
563
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
564
|
+
throw new Error(`Invalid type '${type}' for '${location}': ${message}`);
|
|
478
565
|
}
|
|
479
566
|
}
|
|
480
|
-
function
|
|
481
|
-
|
|
482
|
-
if (
|
|
483
|
-
|
|
567
|
+
function validateEnumVariants(rawVariants, subject) {
|
|
568
|
+
const variants = rawVariants ?? [];
|
|
569
|
+
if (!Array.isArray(variants)) {
|
|
570
|
+
throw new Error(`enum ${subject} must have at least one variant`);
|
|
484
571
|
}
|
|
485
|
-
if (
|
|
486
|
-
|
|
572
|
+
if (variants.length === 0) {
|
|
573
|
+
throw new Error(`enum ${subject} must have at least one variant`);
|
|
487
574
|
}
|
|
488
|
-
if (
|
|
489
|
-
|
|
575
|
+
if (variants.length > 256) {
|
|
576
|
+
throw new Error(`enum ${subject} has ${variants.length} variants, maximum is 256`);
|
|
490
577
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
578
|
+
const seen = /* @__PURE__ */ new Set();
|
|
579
|
+
const seenNormalized = /* @__PURE__ */ new Map();
|
|
580
|
+
for (const v of variants) {
|
|
581
|
+
if (typeof v !== "string" || v.trim().length === 0) {
|
|
582
|
+
throw new Error(`enum ${subject} has empty variant name`);
|
|
583
|
+
}
|
|
584
|
+
if (!ENUM_VARIANT_NAME_RE.test(v)) {
|
|
585
|
+
throw new Error(
|
|
586
|
+
`enum ${subject} has invalid variant name ${JSON.stringify(v)} (must match ${ENUM_VARIANT_NAME_RE})`
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
if (seen.has(v)) {
|
|
590
|
+
throw new Error(`enum ${subject} has duplicate variant '${v}'`);
|
|
591
|
+
}
|
|
592
|
+
seen.add(v);
|
|
593
|
+
const normalized = toUpperSnake2(v);
|
|
594
|
+
const prev = seenNormalized.get(normalized);
|
|
595
|
+
if (prev !== void 0) {
|
|
596
|
+
throw new Error(
|
|
597
|
+
`enum ${subject} has variants ${JSON.stringify(prev)} and ${JSON.stringify(v)} which collide as ${JSON.stringify(normalized)}`
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
seenNormalized.set(normalized, v);
|
|
513
601
|
}
|
|
602
|
+
return variants;
|
|
514
603
|
}
|
|
515
|
-
function
|
|
516
|
-
let
|
|
517
|
-
|
|
518
|
-
|
|
604
|
+
function toUpperSnake2(s) {
|
|
605
|
+
let result = "";
|
|
606
|
+
for (let i = 0; i < s.length; i++) {
|
|
607
|
+
const c = s[i];
|
|
608
|
+
if (i > 0 && c >= "A" && c <= "Z") {
|
|
609
|
+
result += "_";
|
|
610
|
+
}
|
|
611
|
+
result += c.toUpperCase();
|
|
519
612
|
}
|
|
520
|
-
|
|
521
|
-
|
|
613
|
+
return result;
|
|
614
|
+
}
|
|
615
|
+
function compileStateSchema(schema) {
|
|
616
|
+
const { maxEntities, components: componentDefs, events: eventDefs } = schema;
|
|
617
|
+
if (maxEntities <= 0 || maxEntities > 65535) {
|
|
618
|
+
throw new Error(`maxEntities must be between 1 and 65535, got ${maxEntities}`);
|
|
522
619
|
}
|
|
523
|
-
if (
|
|
524
|
-
|
|
620
|
+
if (!Array.isArray(componentDefs)) {
|
|
621
|
+
throw new Error("schema.components must be an array to preserve deterministic ordering");
|
|
525
622
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
623
|
+
const numEntityComponents = componentDefs.filter((componentDef) => {
|
|
624
|
+
const singletonFlag = componentDef.singleton;
|
|
625
|
+
return singletonFlag !== true;
|
|
626
|
+
}).length;
|
|
627
|
+
const bitmaskSize = Math.ceil(numEntityComponents / 8);
|
|
628
|
+
const entityRecordSize = 2 + bitmaskSize;
|
|
629
|
+
const entityTableOffset = HEADER_SIZE;
|
|
630
|
+
const entityTableSize = maxEntities * entityRecordSize;
|
|
631
|
+
const freeStackOffset = entityTableOffset + entityTableSize;
|
|
632
|
+
const freeStackSize = 2 + maxEntities * 2;
|
|
633
|
+
const componentStorageOffset = freeStackOffset + freeStackSize;
|
|
634
|
+
const components = [];
|
|
635
|
+
const entityComponents = [];
|
|
636
|
+
const singletonComponents = [];
|
|
637
|
+
const componentByName = /* @__PURE__ */ new Map();
|
|
638
|
+
const seenComponentNames = /* @__PURE__ */ new Set();
|
|
639
|
+
let storageOffset = componentStorageOffset;
|
|
640
|
+
const pendingSingletons = [];
|
|
641
|
+
let bitIndex = 0;
|
|
642
|
+
for (let i = 0; i < componentDefs.length; i++) {
|
|
643
|
+
const componentDef = componentDefs[i];
|
|
644
|
+
if (componentDef === void 0 || componentDef === null || typeof componentDef !== "object") {
|
|
645
|
+
throw new Error(`Component at index ${i} must be an object`);
|
|
646
|
+
}
|
|
647
|
+
const rawName = componentDef.name;
|
|
648
|
+
if (typeof rawName !== "string" || rawName.trim().length === 0) {
|
|
649
|
+
throw new Error(`Component at index ${i} must have a non-empty string 'name'`);
|
|
650
|
+
}
|
|
651
|
+
const name = rawName;
|
|
652
|
+
if (seenComponentNames.has(name)) {
|
|
653
|
+
throw new Error(`Duplicate component name '${name}' in schema`);
|
|
654
|
+
}
|
|
655
|
+
seenComponentNames.add(name);
|
|
656
|
+
const rawType = componentDef.type;
|
|
657
|
+
if (typeof rawType !== "string" || rawType.trim().length === 0) {
|
|
658
|
+
throw new Error(`Component '${name}' must have a string 'type'`);
|
|
659
|
+
}
|
|
660
|
+
const componentType = rawType;
|
|
661
|
+
const fields = [];
|
|
662
|
+
let componentSize = 0;
|
|
663
|
+
let isTag = false;
|
|
664
|
+
let isBlob = false;
|
|
665
|
+
let blobMaxSize;
|
|
666
|
+
const isSingleton = componentDef.singleton === true;
|
|
667
|
+
if (componentType === "tag") {
|
|
668
|
+
isTag = true;
|
|
669
|
+
if (isSingleton) {
|
|
670
|
+
throw new Error(`Component '${name}' cannot be a singleton tag`);
|
|
671
|
+
}
|
|
672
|
+
} else if (componentType === "compound") {
|
|
673
|
+
const rawFields = componentDef.fields;
|
|
674
|
+
if (!Array.isArray(rawFields)) {
|
|
675
|
+
throw new Error(`Component '${name}' with type 'compound' must declare a 'fields' array`);
|
|
676
|
+
}
|
|
677
|
+
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
678
|
+
let fieldOffset = 0;
|
|
679
|
+
for (let fieldIndex = 0; fieldIndex < rawFields.length; fieldIndex++) {
|
|
680
|
+
const fieldDef = rawFields[fieldIndex];
|
|
681
|
+
if (fieldDef === void 0 || fieldDef === null || typeof fieldDef !== "object") {
|
|
682
|
+
throw new Error(`Component '${name}' has an invalid field at index ${fieldIndex}`);
|
|
683
|
+
}
|
|
684
|
+
const rawFieldName = fieldDef.name;
|
|
685
|
+
if (typeof rawFieldName !== "string" || rawFieldName.trim().length === 0) {
|
|
686
|
+
throw new Error(`Component '${name}' has a field with a missing or empty 'name'`);
|
|
687
|
+
}
|
|
688
|
+
if (seenFieldNames.has(rawFieldName)) {
|
|
689
|
+
throw new Error(`Component '${name}' has duplicate field '${rawFieldName}'`);
|
|
690
|
+
}
|
|
691
|
+
seenFieldNames.add(rawFieldName);
|
|
692
|
+
const rawFieldType = fieldDef.type;
|
|
693
|
+
if (typeof rawFieldType !== "string" || rawFieldType.trim().length === 0) {
|
|
694
|
+
throw new Error(`Field '${name}.${rawFieldName}' must have a string 'type'`);
|
|
695
|
+
}
|
|
696
|
+
const size = getValidatedTypeSize(rawFieldType, name, rawFieldName);
|
|
697
|
+
const arrayInfo = parseArrayType(rawFieldType);
|
|
698
|
+
const compiledField = {
|
|
699
|
+
name: rawFieldName,
|
|
700
|
+
type: rawFieldType,
|
|
701
|
+
size,
|
|
702
|
+
offset: fieldOffset
|
|
703
|
+
};
|
|
704
|
+
if (arrayInfo) {
|
|
705
|
+
compiledField.arrayLength = arrayInfo.length;
|
|
706
|
+
compiledField.arrayElementType = arrayInfo.elementType;
|
|
707
|
+
}
|
|
708
|
+
if (rawFieldType === "enum") {
|
|
709
|
+
compiledField.variants = validateEnumVariants(
|
|
710
|
+
fieldDef.variants,
|
|
711
|
+
`field '${name}.${rawFieldName}'`
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
fields.push(compiledField);
|
|
715
|
+
fieldOffset += size;
|
|
716
|
+
componentSize += size;
|
|
717
|
+
}
|
|
718
|
+
} else if (componentType === "blob") {
|
|
719
|
+
if (!isSingleton) {
|
|
720
|
+
throw new Error(`Component '${name}' with type 'blob' must be a singleton`);
|
|
721
|
+
}
|
|
722
|
+
const maxSize = componentDef.maxSize;
|
|
723
|
+
if (typeof maxSize !== "number" || maxSize <= 0) {
|
|
724
|
+
throw new Error(`Component '${name}' with type 'blob' requires positive 'maxSize'`);
|
|
725
|
+
}
|
|
726
|
+
componentSize = 4 + maxSize;
|
|
727
|
+
isBlob = true;
|
|
728
|
+
blobMaxSize = maxSize;
|
|
729
|
+
} else {
|
|
730
|
+
const fieldType = componentType;
|
|
731
|
+
const size = getValidatedTypeSize(fieldType, name);
|
|
732
|
+
const arrayInfo = parseArrayType(fieldType);
|
|
733
|
+
const compiledField = {
|
|
734
|
+
name: "",
|
|
735
|
+
type: fieldType,
|
|
736
|
+
size,
|
|
737
|
+
offset: 0
|
|
738
|
+
};
|
|
739
|
+
if (arrayInfo) {
|
|
740
|
+
compiledField.arrayLength = arrayInfo.length;
|
|
741
|
+
compiledField.arrayElementType = arrayInfo.elementType;
|
|
742
|
+
}
|
|
743
|
+
if (fieldType === "enum") {
|
|
744
|
+
compiledField.variants = validateEnumVariants(
|
|
745
|
+
componentDef.variants,
|
|
746
|
+
`component '${name}'`
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
fields.push(compiledField);
|
|
750
|
+
componentSize = size;
|
|
751
|
+
}
|
|
752
|
+
const compiled = {
|
|
753
|
+
name,
|
|
754
|
+
index: isSingleton ? -1 : bitIndex,
|
|
755
|
+
isTag,
|
|
756
|
+
isSingleton,
|
|
757
|
+
size: componentSize,
|
|
758
|
+
storageOffset: 0,
|
|
759
|
+
fields
|
|
760
|
+
};
|
|
761
|
+
if (isBlob) {
|
|
762
|
+
compiled.isBlob = true;
|
|
763
|
+
compiled.blobMaxSize = blobMaxSize;
|
|
764
|
+
}
|
|
765
|
+
components.push(compiled);
|
|
766
|
+
componentByName.set(name, compiled);
|
|
767
|
+
if (isSingleton) {
|
|
768
|
+
pendingSingletons.push(compiled);
|
|
769
|
+
singletonComponents.push(compiled);
|
|
770
|
+
} else {
|
|
771
|
+
if (!isTag && componentSize > 0) {
|
|
772
|
+
compiled.storageOffset = storageOffset;
|
|
773
|
+
storageOffset += maxEntities * componentSize;
|
|
774
|
+
} else {
|
|
775
|
+
compiled.storageOffset = 0;
|
|
776
|
+
}
|
|
777
|
+
entityComponents.push(compiled);
|
|
778
|
+
bitIndex += 1;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
const singletonStorageOffset = storageOffset;
|
|
782
|
+
let singletonCursor = singletonStorageOffset;
|
|
783
|
+
for (const singleton of pendingSingletons) {
|
|
784
|
+
singleton.storageOffset = singletonCursor;
|
|
785
|
+
singletonCursor += singleton.size;
|
|
786
|
+
}
|
|
787
|
+
const singletonStorageSize = singletonCursor - singletonStorageOffset;
|
|
788
|
+
let totalSize = singletonCursor;
|
|
789
|
+
let events;
|
|
790
|
+
let eventByName;
|
|
791
|
+
let eventStorageOffset;
|
|
792
|
+
let eventStorageSize;
|
|
793
|
+
if (eventDefs && eventDefs.length > 0) {
|
|
794
|
+
events = [];
|
|
795
|
+
eventByName = /* @__PURE__ */ new Map();
|
|
796
|
+
eventStorageOffset = totalSize;
|
|
797
|
+
let eventCursor = eventStorageOffset;
|
|
798
|
+
const seenEventNames = /* @__PURE__ */ new Set();
|
|
799
|
+
for (let i = 0; i < eventDefs.length; i++) {
|
|
800
|
+
const eventDef = eventDefs[i];
|
|
801
|
+
if (typeof eventDef.name !== "string" || eventDef.name.trim().length === 0) {
|
|
802
|
+
throw new Error(`Event at index ${i} must have a non-empty string 'name'`);
|
|
803
|
+
}
|
|
804
|
+
if (seenEventNames.has(eventDef.name)) {
|
|
805
|
+
throw new Error(`Duplicate event name '${eventDef.name}' in schema`);
|
|
806
|
+
}
|
|
807
|
+
seenEventNames.add(eventDef.name);
|
|
808
|
+
if (seenComponentNames.has(eventDef.name)) {
|
|
809
|
+
throw new Error(`Event name '${eventDef.name}' conflicts with component name`);
|
|
810
|
+
}
|
|
811
|
+
if (!Array.isArray(eventDef.fields) || eventDef.fields.length === 0) {
|
|
812
|
+
throw new Error(`Event '${eventDef.name}' must have at least one field`);
|
|
813
|
+
}
|
|
814
|
+
const maxEvents = eventDef.maxEvents && eventDef.maxEvents > 0 ? eventDef.maxEvents : 64;
|
|
815
|
+
const eventFields = [];
|
|
816
|
+
let fieldOffset = 0;
|
|
817
|
+
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
818
|
+
for (let fi = 0; fi < eventDef.fields.length; fi++) {
|
|
819
|
+
const fieldDef = eventDef.fields[fi];
|
|
820
|
+
if (typeof fieldDef.name !== "string" || fieldDef.name.trim().length === 0) {
|
|
821
|
+
throw new Error(`Event '${eventDef.name}' has a field with a missing or empty 'name' at index ${fi}`);
|
|
822
|
+
}
|
|
823
|
+
if (seenFieldNames.has(fieldDef.name)) {
|
|
824
|
+
throw new Error(`Event '${eventDef.name}' has duplicate field '${fieldDef.name}'`);
|
|
825
|
+
}
|
|
826
|
+
seenFieldNames.add(fieldDef.name);
|
|
827
|
+
if (typeof fieldDef.type !== "string" || fieldDef.type.trim().length === 0) {
|
|
828
|
+
throw new Error(`Event field '${eventDef.name}.${fieldDef.name}' must have a string 'type'`);
|
|
829
|
+
}
|
|
830
|
+
const arrayInfo = parseArrayType(fieldDef.type);
|
|
831
|
+
if (arrayInfo) {
|
|
832
|
+
throw new Error(
|
|
833
|
+
`Event field '${eventDef.name}.${fieldDef.name}' has array type '${fieldDef.type}'; events do not support array types`
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
const size = getValidatedTypeSize(fieldDef.type, eventDef.name, fieldDef.name);
|
|
837
|
+
const compiledField = {
|
|
838
|
+
name: fieldDef.name,
|
|
839
|
+
type: fieldDef.type,
|
|
840
|
+
size,
|
|
841
|
+
offset: fieldOffset
|
|
842
|
+
};
|
|
843
|
+
if (fieldDef.type === "enum" && fieldDef.variants) {
|
|
844
|
+
compiledField.variants = validateEnumVariants(fieldDef.variants, `event field '${eventDef.name}.${fieldDef.name}'`);
|
|
845
|
+
}
|
|
846
|
+
eventFields.push(compiledField);
|
|
847
|
+
fieldOffset += size;
|
|
848
|
+
}
|
|
849
|
+
const recordSize = fieldOffset;
|
|
850
|
+
const bufferSize = 2 + maxEvents * recordSize;
|
|
851
|
+
const compiledEvent = {
|
|
852
|
+
name: eventDef.name,
|
|
853
|
+
index: i,
|
|
854
|
+
maxEvents,
|
|
855
|
+
recordSize,
|
|
856
|
+
storageOffset: eventCursor,
|
|
857
|
+
bufferSize,
|
|
858
|
+
fields: eventFields
|
|
859
|
+
};
|
|
860
|
+
events.push(compiledEvent);
|
|
861
|
+
eventByName.set(eventDef.name, compiledEvent);
|
|
862
|
+
eventCursor += bufferSize;
|
|
863
|
+
}
|
|
864
|
+
eventStorageSize = eventCursor - eventStorageOffset;
|
|
865
|
+
totalSize = eventCursor;
|
|
866
|
+
}
|
|
867
|
+
return {
|
|
868
|
+
maxEntities,
|
|
869
|
+
components,
|
|
870
|
+
entityComponents,
|
|
871
|
+
singletonComponents,
|
|
872
|
+
componentByName,
|
|
873
|
+
definition: schema,
|
|
874
|
+
events,
|
|
875
|
+
eventByName,
|
|
876
|
+
headerOffset: 0,
|
|
877
|
+
headerSize: HEADER_SIZE,
|
|
878
|
+
entityTableOffset,
|
|
879
|
+
entityRecordSize,
|
|
880
|
+
freeStackOffset,
|
|
881
|
+
freeStackSize,
|
|
882
|
+
componentStorageOffset,
|
|
883
|
+
singletonStorageOffset,
|
|
884
|
+
singletonStorageSize,
|
|
885
|
+
eventStorageOffset,
|
|
886
|
+
eventStorageSize,
|
|
887
|
+
totalSize,
|
|
888
|
+
bitmaskSize
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
var INIT_DATA_MAGIC = 1297369412;
|
|
892
|
+
var INIT_DATA_HEADER_SIZE = 8;
|
|
893
|
+
var MAX_INIT_DATA_ARRAY_LENGTH = 65536;
|
|
894
|
+
function getInitDataTypeSize(type) {
|
|
895
|
+
const quatArrayMatch = type.match(/^quat\[(\d+)\]$/);
|
|
896
|
+
if (quatArrayMatch) {
|
|
897
|
+
throw new Error(`Quat arrays are not supported in init data. Use individual quat fields or a different data structure.`);
|
|
898
|
+
}
|
|
899
|
+
const vec3ArrayMatch = type.match(/^vec3\[(\d+)\]$/);
|
|
900
|
+
if (vec3ArrayMatch?.[1]) {
|
|
901
|
+
const length = parseInt(vec3ArrayMatch[1], 10);
|
|
902
|
+
if (length > MAX_INIT_DATA_ARRAY_LENGTH) {
|
|
903
|
+
throw new Error(`Array length ${length} exceeds maximum ${MAX_INIT_DATA_ARRAY_LENGTH}`);
|
|
904
|
+
}
|
|
905
|
+
return 12 * length;
|
|
906
|
+
}
|
|
907
|
+
const vec2ArrayMatch = type.match(/^vec2\[(\d+)\]$/);
|
|
908
|
+
if (vec2ArrayMatch?.[1]) {
|
|
909
|
+
const length = parseInt(vec2ArrayMatch[1], 10);
|
|
910
|
+
if (length > MAX_INIT_DATA_ARRAY_LENGTH) {
|
|
911
|
+
throw new Error(`Array length ${length} exceeds maximum ${MAX_INIT_DATA_ARRAY_LENGTH}`);
|
|
912
|
+
}
|
|
913
|
+
return 8 * length;
|
|
914
|
+
}
|
|
915
|
+
const arrayInfo = parseArrayType(type);
|
|
916
|
+
if (arrayInfo) {
|
|
917
|
+
if (arrayInfo.length > MAX_INIT_DATA_ARRAY_LENGTH) {
|
|
918
|
+
throw new Error(`Array length ${arrayInfo.length} exceeds maximum ${MAX_INIT_DATA_ARRAY_LENGTH}`);
|
|
919
|
+
}
|
|
920
|
+
const elementSize = TYPE_SIZES[arrayInfo.elementType];
|
|
921
|
+
if (elementSize === void 0) {
|
|
922
|
+
throw new Error(`Unknown element type: ${arrayInfo.elementType}`);
|
|
923
|
+
}
|
|
924
|
+
return elementSize * arrayInfo.length;
|
|
925
|
+
}
|
|
926
|
+
const size = TYPE_SIZES[type];
|
|
927
|
+
if (size === void 0) {
|
|
928
|
+
throw new Error(`Unknown type: ${type}`);
|
|
929
|
+
}
|
|
930
|
+
return size;
|
|
931
|
+
}
|
|
932
|
+
function parseVec3ArrayType(type) {
|
|
933
|
+
const match = type.match(/^vec3\[(\d+)\]$/);
|
|
934
|
+
if (!match?.[1]) return null;
|
|
935
|
+
return { elementType: "vec3", length: parseInt(match[1], 10) };
|
|
936
|
+
}
|
|
937
|
+
function parseVec2ArrayType(type) {
|
|
938
|
+
const match = type.match(/^vec2\[(\d+)\]$/);
|
|
939
|
+
if (!match?.[1]) return null;
|
|
940
|
+
return { elementType: "vec2", length: parseInt(match[1], 10) };
|
|
941
|
+
}
|
|
942
|
+
function compileInitDataSchema(items) {
|
|
943
|
+
const compiledItems = [];
|
|
944
|
+
const itemByName = /* @__PURE__ */ new Map();
|
|
945
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
946
|
+
let offset = INIT_DATA_HEADER_SIZE;
|
|
947
|
+
for (let i = 0; i < items.length; i++) {
|
|
948
|
+
const item = items[i];
|
|
949
|
+
if (!item) continue;
|
|
950
|
+
if (typeof item.name !== "string" || item.name.trim().length === 0) {
|
|
951
|
+
throw new Error(`Init data item at index ${i} must have a non-empty string 'name'`);
|
|
952
|
+
}
|
|
953
|
+
if (seenNames.has(item.name)) {
|
|
954
|
+
throw new Error(`Duplicate init data item name '${item.name}'`);
|
|
955
|
+
}
|
|
956
|
+
seenNames.add(item.name);
|
|
957
|
+
if (item.type !== "compound" && item.type !== "blob") {
|
|
958
|
+
throw new Error(`Init data item '${item.name}' must have type 'compound' or 'blob'`);
|
|
959
|
+
}
|
|
960
|
+
let itemSize = 0;
|
|
961
|
+
const fields = [];
|
|
962
|
+
if (item.type === "blob") {
|
|
963
|
+
if (typeof item.maxSize !== "number" || item.maxSize <= 0) {
|
|
964
|
+
throw new Error(`Init data item '${item.name}' with type 'blob' requires positive 'maxSize'`);
|
|
965
|
+
}
|
|
966
|
+
itemSize = 4 + item.maxSize;
|
|
967
|
+
const compiled = {
|
|
968
|
+
name: item.name,
|
|
969
|
+
type: "blob",
|
|
970
|
+
size: itemSize,
|
|
971
|
+
storageOffset: offset,
|
|
972
|
+
fields: [],
|
|
973
|
+
isBlob: true,
|
|
974
|
+
blobMaxSize: item.maxSize
|
|
975
|
+
};
|
|
976
|
+
compiledItems.push(compiled);
|
|
977
|
+
itemByName.set(item.name, compiled);
|
|
978
|
+
} else {
|
|
979
|
+
if (!Array.isArray(item.fields) || item.fields.length === 0) {
|
|
980
|
+
throw new Error(`Init data item '${item.name}' with type 'compound' must have non-empty 'fields' array`);
|
|
981
|
+
}
|
|
982
|
+
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
983
|
+
let fieldOffset = 0;
|
|
984
|
+
for (let fi = 0; fi < item.fields.length; fi++) {
|
|
985
|
+
const field = item.fields[fi];
|
|
986
|
+
if (!field) continue;
|
|
987
|
+
if (typeof field.name !== "string" || field.name.trim().length === 0) {
|
|
988
|
+
throw new Error(`Init data item '${item.name}' has field with missing name at index ${fi}`);
|
|
989
|
+
}
|
|
990
|
+
if (seenFieldNames.has(field.name)) {
|
|
991
|
+
throw new Error(`Init data item '${item.name}' has duplicate field '${field.name}'`);
|
|
992
|
+
}
|
|
993
|
+
seenFieldNames.add(field.name);
|
|
994
|
+
if (typeof field.type !== "string" || field.type.trim().length === 0) {
|
|
995
|
+
throw new Error(`Init data field '${item.name}.${field.name}' must have a string 'type'`);
|
|
996
|
+
}
|
|
997
|
+
let fieldSize;
|
|
998
|
+
try {
|
|
999
|
+
fieldSize = getInitDataTypeSize(field.type);
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1002
|
+
throw new Error(`Invalid type '${field.type}' for init data field '${item.name}.${field.name}': ${message}`);
|
|
1003
|
+
}
|
|
1004
|
+
const compiledField = {
|
|
1005
|
+
name: field.name,
|
|
1006
|
+
type: field.type,
|
|
1007
|
+
size: fieldSize,
|
|
1008
|
+
offset: fieldOffset
|
|
1009
|
+
};
|
|
1010
|
+
const vec3Array = parseVec3ArrayType(field.type);
|
|
1011
|
+
if (vec3Array) {
|
|
1012
|
+
compiledField.arrayLength = vec3Array.length;
|
|
1013
|
+
compiledField.arrayElementType = "f32";
|
|
1014
|
+
} else {
|
|
1015
|
+
const vec2Array = parseVec2ArrayType(field.type);
|
|
1016
|
+
if (vec2Array) {
|
|
1017
|
+
compiledField.arrayLength = vec2Array.length;
|
|
1018
|
+
compiledField.arrayElementType = "f32";
|
|
1019
|
+
} else {
|
|
1020
|
+
const primArray = parseArrayType(field.type);
|
|
1021
|
+
if (primArray) {
|
|
1022
|
+
compiledField.arrayLength = primArray.length;
|
|
1023
|
+
compiledField.arrayElementType = primArray.elementType;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
fields.push(compiledField);
|
|
1028
|
+
fieldOffset += fieldSize;
|
|
1029
|
+
itemSize += fieldSize;
|
|
1030
|
+
}
|
|
1031
|
+
const compiled = {
|
|
1032
|
+
name: item.name,
|
|
1033
|
+
type: "compound",
|
|
1034
|
+
size: itemSize,
|
|
1035
|
+
storageOffset: offset,
|
|
1036
|
+
fields
|
|
1037
|
+
};
|
|
1038
|
+
compiledItems.push(compiled);
|
|
1039
|
+
itemByName.set(item.name, compiled);
|
|
1040
|
+
}
|
|
1041
|
+
offset += itemSize;
|
|
1042
|
+
}
|
|
1043
|
+
return {
|
|
1044
|
+
items: compiledItems,
|
|
1045
|
+
itemByName,
|
|
1046
|
+
headerSize: INIT_DATA_HEADER_SIZE,
|
|
1047
|
+
totalSize: offset
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
function compileInputSchema(schema) {
|
|
1051
|
+
const controls = [];
|
|
1052
|
+
const commands = [];
|
|
1053
|
+
const controlByName = /* @__PURE__ */ new Map();
|
|
1054
|
+
const commandByName = /* @__PURE__ */ new Map();
|
|
1055
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
1056
|
+
let index = 0;
|
|
1057
|
+
for (let i = 0; i < schema.controls.length; i++) {
|
|
1058
|
+
const controlDef = schema.controls[i];
|
|
1059
|
+
if (typeof controlDef.name !== "string" || controlDef.name.trim().length === 0) {
|
|
1060
|
+
throw new Error(`Control at index ${i} must have a non-empty string 'name'`);
|
|
1061
|
+
}
|
|
1062
|
+
if (seenNames.has(controlDef.name)) {
|
|
1063
|
+
throw new Error(`Duplicate control/command name '${controlDef.name}' in schema`);
|
|
1064
|
+
}
|
|
1065
|
+
seenNames.add(controlDef.name);
|
|
1066
|
+
if (typeof controlDef.type !== "string" || controlDef.type.trim().length === 0) {
|
|
1067
|
+
throw new Error(`Control '${controlDef.name}' must have a string 'type'`);
|
|
1068
|
+
}
|
|
1069
|
+
const size = getInputTypeSize(controlDef.type);
|
|
1070
|
+
const compiled = {
|
|
1071
|
+
name: controlDef.name,
|
|
1072
|
+
index: index++,
|
|
1073
|
+
type: controlDef.type,
|
|
1074
|
+
size,
|
|
1075
|
+
options: controlDef.options,
|
|
1076
|
+
hint: controlDef.hint,
|
|
1077
|
+
retain: controlDef.retain === "always" ? "always" : "tick"
|
|
1078
|
+
};
|
|
1079
|
+
controls.push(compiled);
|
|
1080
|
+
controlByName.set(controlDef.name, compiled);
|
|
1081
|
+
if (index > 255) {
|
|
1082
|
+
throw new Error(`Too many controls/commands: index ${index} exceeds u8 limit (255)`);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
for (let i = 0; i < schema.commands.length; i++) {
|
|
1086
|
+
const commandDef = schema.commands[i];
|
|
1087
|
+
if (typeof commandDef.name !== "string" || commandDef.name.trim().length === 0) {
|
|
1088
|
+
throw new Error(`Command at index ${i} must have a non-empty string 'name'`);
|
|
1089
|
+
}
|
|
1090
|
+
if (seenNames.has(commandDef.name)) {
|
|
1091
|
+
throw new Error(`Duplicate control/command name '${commandDef.name}' in schema`);
|
|
1092
|
+
}
|
|
1093
|
+
seenNames.add(commandDef.name);
|
|
1094
|
+
const args = [];
|
|
1095
|
+
let offset = 0;
|
|
1096
|
+
for (let ai = 0; ai < commandDef.args.length; ai++) {
|
|
1097
|
+
const argDef = commandDef.args[ai];
|
|
1098
|
+
if (typeof argDef.name !== "string" || argDef.name.trim().length === 0) {
|
|
1099
|
+
throw new Error(`Command '${commandDef.name}' has an argument with missing 'name' at index ${ai}`);
|
|
1100
|
+
}
|
|
1101
|
+
const size = getInputTypeSize(argDef.type);
|
|
1102
|
+
const arrayInfo = parseInputArrayType(argDef.type);
|
|
1103
|
+
const arg = {
|
|
1104
|
+
name: argDef.name,
|
|
1105
|
+
type: argDef.type,
|
|
1106
|
+
size,
|
|
1107
|
+
offset
|
|
1108
|
+
};
|
|
1109
|
+
if (arrayInfo) {
|
|
1110
|
+
arg.arrayLength = arrayInfo.length;
|
|
1111
|
+
arg.arrayElementType = arrayInfo.elementType;
|
|
1112
|
+
}
|
|
1113
|
+
args.push(arg);
|
|
1114
|
+
offset += size;
|
|
1115
|
+
}
|
|
1116
|
+
const compiled = {
|
|
1117
|
+
name: commandDef.name,
|
|
1118
|
+
index: index++,
|
|
1119
|
+
args,
|
|
1120
|
+
totalSize: offset
|
|
1121
|
+
};
|
|
1122
|
+
commands.push(compiled);
|
|
1123
|
+
commandByName.set(commandDef.name, compiled);
|
|
1124
|
+
if (index > 255) {
|
|
1125
|
+
throw new Error(`Too many controls/commands: index ${index} exceeds u8 limit (255)`);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return { controls, commands, controlByName, commandByName };
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// src/codegen/typescript.ts
|
|
1132
|
+
function tsType(field) {
|
|
1133
|
+
if (field.arrayElementType) {
|
|
1134
|
+
return tsTypeMapping(field.arrayElementType);
|
|
1135
|
+
}
|
|
1136
|
+
if (field.type === "vec2" || field.type === "vec3" || field.type === "quat") {
|
|
1137
|
+
return "number";
|
|
1138
|
+
}
|
|
1139
|
+
if (field.type === "enum") {
|
|
1140
|
+
return "number";
|
|
1141
|
+
}
|
|
1142
|
+
return tsTypeMapping(field.type);
|
|
1143
|
+
}
|
|
1144
|
+
function tsTypeMapping(typ) {
|
|
1145
|
+
switch (typ) {
|
|
1146
|
+
case "bool":
|
|
1147
|
+
return "boolean";
|
|
1148
|
+
case "uint8":
|
|
1149
|
+
case "int8":
|
|
1150
|
+
case "uint16":
|
|
1151
|
+
case "int16":
|
|
1152
|
+
case "uint32":
|
|
1153
|
+
case "int32":
|
|
1154
|
+
case "f32":
|
|
1155
|
+
case "entityRef":
|
|
1156
|
+
return "number";
|
|
1157
|
+
default:
|
|
1158
|
+
throw new Error(`Unknown type '${typ}' in TypeScript code generation`);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
function tsInputType(typ) {
|
|
1162
|
+
switch (typ) {
|
|
1163
|
+
case "bool":
|
|
1164
|
+
return "boolean";
|
|
1165
|
+
case "uint8":
|
|
1166
|
+
case "int8":
|
|
1167
|
+
case "uint16":
|
|
1168
|
+
case "int16":
|
|
1169
|
+
case "uint32":
|
|
1170
|
+
case "int32":
|
|
1171
|
+
case "f32":
|
|
1172
|
+
case "flags8":
|
|
1173
|
+
case "flags16":
|
|
1174
|
+
case "flags32":
|
|
1175
|
+
case "vec2":
|
|
1176
|
+
case "vec3":
|
|
1177
|
+
case "quat":
|
|
1178
|
+
return "number";
|
|
1179
|
+
default:
|
|
1180
|
+
throw new Error(`Unknown input type '${typ}' in TypeScript code generation`);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
function tsLoadFn(field) {
|
|
1184
|
+
let typ = field.type;
|
|
1185
|
+
if (field.arrayElementType) {
|
|
1186
|
+
typ = field.arrayElementType;
|
|
1187
|
+
}
|
|
1188
|
+
if (typ === "vec2" || typ === "vec3" || typ === "quat") {
|
|
1189
|
+
typ = "f32";
|
|
1190
|
+
}
|
|
1191
|
+
if (typ === "enum") {
|
|
1192
|
+
typ = "uint8";
|
|
1193
|
+
}
|
|
1194
|
+
return tsLoadFnForType(typ);
|
|
1195
|
+
}
|
|
1196
|
+
function tsLoadFnForType(typ) {
|
|
1197
|
+
switch (typ) {
|
|
1198
|
+
case "bool":
|
|
1199
|
+
case "uint8":
|
|
1200
|
+
return "getUint8";
|
|
1201
|
+
case "int8":
|
|
1202
|
+
return "getInt8";
|
|
1203
|
+
case "uint16":
|
|
1204
|
+
return "getUint16";
|
|
1205
|
+
case "int16":
|
|
1206
|
+
return "getInt16";
|
|
1207
|
+
case "uint32":
|
|
1208
|
+
case "entityRef":
|
|
1209
|
+
return "getUint32";
|
|
1210
|
+
case "int32":
|
|
1211
|
+
return "getInt32";
|
|
1212
|
+
case "f32":
|
|
1213
|
+
return "getFloat32";
|
|
1214
|
+
default:
|
|
1215
|
+
throw new Error(`Unknown type '${typ}' in TypeScript load function generation`);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
function tsStoreFn(field) {
|
|
1219
|
+
let typ = field.type;
|
|
1220
|
+
if (field.arrayElementType) {
|
|
1221
|
+
typ = field.arrayElementType;
|
|
1222
|
+
}
|
|
1223
|
+
if (typ === "vec2" || typ === "vec3" || typ === "quat") {
|
|
1224
|
+
typ = "f32";
|
|
1225
|
+
}
|
|
1226
|
+
if (typ === "enum") {
|
|
1227
|
+
typ = "uint8";
|
|
1228
|
+
}
|
|
1229
|
+
return tsStoreFnForType(typ);
|
|
1230
|
+
}
|
|
1231
|
+
function tsStoreFnForType(typ) {
|
|
1232
|
+
switch (typ) {
|
|
1233
|
+
case "bool":
|
|
1234
|
+
case "uint8":
|
|
1235
|
+
return "setUint8";
|
|
1236
|
+
case "int8":
|
|
1237
|
+
return "setInt8";
|
|
1238
|
+
case "uint16":
|
|
1239
|
+
return "setUint16";
|
|
1240
|
+
case "int16":
|
|
1241
|
+
return "setInt16";
|
|
1242
|
+
case "uint32":
|
|
540
1243
|
case "entityRef":
|
|
541
1244
|
return "setUint32";
|
|
542
1245
|
case "int32":
|
|
@@ -1938,40 +2641,278 @@ export function get${pascal}${argPascal}YFromPayload(payload: Uint8Array): numbe
|
|
|
1938
2641
|
if (offset < 0) {
|
|
1939
2642
|
return 0;
|
|
1940
2643
|
}
|
|
1941
|
-
return readF32(payload, offset + ${arg.offset} + 4);
|
|
2644
|
+
return readF32(payload, offset + ${arg.offset} + 4);
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
/** Get ${cmd.name}.${arg.name}.z (f32), or 0 if command not present. */
|
|
2648
|
+
export function get${pascal}${argPascal}ZFromPayload(payload: Uint8Array): number {
|
|
2649
|
+
const offset = findControl(payload, ${cmd.index});
|
|
2650
|
+
if (offset < 0) {
|
|
2651
|
+
return 0;
|
|
2652
|
+
}
|
|
2653
|
+
return readF32(payload, offset + ${arg.offset} + 8);
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
/** Get ${cmd.name}.${arg.name}.w (f32), or 1 if command not present (identity quaternion). */
|
|
2657
|
+
export function get${pascal}${argPascal}WFromPayload(payload: Uint8Array): number {
|
|
2658
|
+
const offset = findControl(payload, ${cmd.index});
|
|
2659
|
+
if (offset < 0) {
|
|
2660
|
+
return 1;
|
|
2661
|
+
}
|
|
2662
|
+
return readF32(payload, offset + ${arg.offset} + 12);
|
|
2663
|
+
}
|
|
2664
|
+
`;
|
|
2665
|
+
} else {
|
|
2666
|
+
const readFn = arg.type === "f32" ? "readF32" : arg.type === "uint8" ? "readU8" : arg.type === "int8" ? "readI8" : arg.type === "uint16" ? "readU16" : arg.type === "int16" ? "readI16" : arg.type === "uint32" ? "readU32" : arg.type === "int32" ? "readI32" : "readU8";
|
|
2667
|
+
out += `
|
|
2668
|
+
/** Get ${cmd.name}.${arg.name} value, or 0 if command not present. */
|
|
2669
|
+
export function get${pascal}${argPascal}FromPayload(payload: Uint8Array): ${tsInputType(arg.type)} {
|
|
2670
|
+
const offset = findControl(payload, ${cmd.index});
|
|
2671
|
+
if (offset < 0) {
|
|
2672
|
+
return 0;
|
|
2673
|
+
}
|
|
2674
|
+
return ${readFn}(payload, offset + ${arg.offset});
|
|
2675
|
+
}
|
|
2676
|
+
`;
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
return out;
|
|
2680
|
+
}
|
|
2681
|
+
function generateInitDataHeader(schema) {
|
|
2682
|
+
return `// =============================================================================
|
|
2683
|
+
// Init Data Bindings
|
|
2684
|
+
// =============================================================================
|
|
2685
|
+
|
|
2686
|
+
// Init data is read-only constant data set once per session.
|
|
2687
|
+
// The data is shared by all clients and hashed into session identification.
|
|
2688
|
+
|
|
2689
|
+
/** Init data buffer magic number */
|
|
2690
|
+
export const INIT_DATA_MAGIC = 0x${INIT_DATA_MAGIC.toString(16)};
|
|
2691
|
+
|
|
2692
|
+
/** Init data header size in bytes */
|
|
2693
|
+
export const INIT_DATA_HEADER_SIZE = ${INIT_DATA_HEADER_SIZE};
|
|
2694
|
+
|
|
2695
|
+
/** Total init data buffer size in bytes */
|
|
2696
|
+
export const INIT_DATA_TOTAL_SIZE = ${schema.totalSize};
|
|
2697
|
+
|
|
2698
|
+
/** Internal DataView for init data - set via setInitData() */
|
|
2699
|
+
let _initDataView: DataView | null = null;
|
|
2700
|
+
|
|
2701
|
+
/** Set init data buffer. Called by executor during initialization. */
|
|
2702
|
+
export function setInitData(data: Uint8Array): void {
|
|
2703
|
+
if (data.length !== INIT_DATA_TOTAL_SIZE) {
|
|
2704
|
+
throw new Error(\`Init data size mismatch: expected \${INIT_DATA_TOTAL_SIZE}, got \${data.length}\`);
|
|
2705
|
+
}
|
|
2706
|
+
_initDataView = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
2707
|
+
|
|
2708
|
+
// Verify magic number
|
|
2709
|
+
const magic = _initDataView.getUint32(0, true);
|
|
2710
|
+
if (magic !== INIT_DATA_MAGIC) {
|
|
2711
|
+
throw new Error(\`Invalid init data magic: expected 0x\${INIT_DATA_MAGIC.toString(16)}, got 0x\${magic.toString(16)}\`);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
/** Check if init data has been set */
|
|
2716
|
+
export function hasInitData(): boolean {
|
|
2717
|
+
return _initDataView !== null;
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
/** Get the init data DataView (throws if not set) */
|
|
2721
|
+
function getInitDataView(): DataView {
|
|
2722
|
+
if (!_initDataView) {
|
|
2723
|
+
throw new Error('Init data not set. Call setInitData() first.');
|
|
2724
|
+
}
|
|
2725
|
+
return _initDataView;
|
|
2726
|
+
}
|
|
2727
|
+
`;
|
|
2728
|
+
}
|
|
2729
|
+
function generateInitDataItemOffsets(schema) {
|
|
2730
|
+
let out = `// =============================================================================
|
|
2731
|
+
// Init Data Item Offsets
|
|
2732
|
+
// =============================================================================
|
|
2733
|
+
|
|
2734
|
+
`;
|
|
2735
|
+
for (const item of schema.items) {
|
|
2736
|
+
const UPPER = toUpperSnake(item.name);
|
|
2737
|
+
out += `const INIT_${UPPER}_OFFSET = ${item.storageOffset};
|
|
2738
|
+
`;
|
|
2739
|
+
if (item.isBlob) {
|
|
2740
|
+
out += `export const INIT_${UPPER}_MAX_SIZE = ${item.blobMaxSize};
|
|
2741
|
+
`;
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
return out;
|
|
2745
|
+
}
|
|
2746
|
+
function generateInitDataAccessor(field, itemName, itemOffset) {
|
|
2747
|
+
const fieldPascal = toPascalCase(field.name);
|
|
2748
|
+
const funcName = `get${toPascalCase(itemName)}${fieldPascal}`;
|
|
2749
|
+
const offset = itemOffset + field.offset;
|
|
2750
|
+
const vec3Array = parseVec3ArrayType(field.type);
|
|
2751
|
+
if (vec3Array) {
|
|
2752
|
+
const count = vec3Array.length;
|
|
2753
|
+
return `
|
|
2754
|
+
/** Get ${itemName}.${field.name}[index].x (max index: ${count - 1}) */
|
|
2755
|
+
export function ${funcName}X(index: number): number {
|
|
2756
|
+
if (index < 0 || index >= ${count}) return 0;
|
|
2757
|
+
return getInitDataView().getFloat32(${offset} + index * 12, true);
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
/** Get ${itemName}.${field.name}[index].y (max index: ${count - 1}) */
|
|
2761
|
+
export function ${funcName}Y(index: number): number {
|
|
2762
|
+
if (index < 0 || index >= ${count}) return 0;
|
|
2763
|
+
return getInitDataView().getFloat32(${offset} + index * 12 + 4, true);
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
/** Get ${itemName}.${field.name}[index].z (max index: ${count - 1}) */
|
|
2767
|
+
export function ${funcName}Z(index: number): number {
|
|
2768
|
+
if (index < 0 || index >= ${count}) return 0;
|
|
2769
|
+
return getInitDataView().getFloat32(${offset} + index * 12 + 8, true);
|
|
2770
|
+
}
|
|
2771
|
+
`;
|
|
2772
|
+
}
|
|
2773
|
+
const vec2Array = parseVec2ArrayType(field.type);
|
|
2774
|
+
if (vec2Array) {
|
|
2775
|
+
const count = vec2Array.length;
|
|
2776
|
+
return `
|
|
2777
|
+
/** Get ${itemName}.${field.name}[index].x (max index: ${count - 1}) */
|
|
2778
|
+
export function ${funcName}X(index: number): number {
|
|
2779
|
+
if (index < 0 || index >= ${count}) return 0;
|
|
2780
|
+
return getInitDataView().getFloat32(${offset} + index * 8, true);
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2783
|
+
/** Get ${itemName}.${field.name}[index].y (max index: ${count - 1}) */
|
|
2784
|
+
export function ${funcName}Y(index: number): number {
|
|
2785
|
+
if (index < 0 || index >= ${count}) return 0;
|
|
2786
|
+
return getInitDataView().getFloat32(${offset} + index * 8 + 4, true);
|
|
2787
|
+
}
|
|
2788
|
+
`;
|
|
2789
|
+
}
|
|
2790
|
+
if (field.type === "vec3") {
|
|
2791
|
+
return `
|
|
2792
|
+
/** Get ${itemName}.${field.name}.x */
|
|
2793
|
+
export function ${funcName}X(): number {
|
|
2794
|
+
return getInitDataView().getFloat32(${offset}, true);
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
/** Get ${itemName}.${field.name}.y */
|
|
2798
|
+
export function ${funcName}Y(): number {
|
|
2799
|
+
return getInitDataView().getFloat32(${offset + 4}, true);
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
/** Get ${itemName}.${field.name}.z */
|
|
2803
|
+
export function ${funcName}Z(): number {
|
|
2804
|
+
return getInitDataView().getFloat32(${offset + 8}, true);
|
|
2805
|
+
}
|
|
2806
|
+
`;
|
|
2807
|
+
}
|
|
2808
|
+
if (field.type === "vec2") {
|
|
2809
|
+
return `
|
|
2810
|
+
/** Get ${itemName}.${field.name}.x */
|
|
2811
|
+
export function ${funcName}X(): number {
|
|
2812
|
+
return getInitDataView().getFloat32(${offset}, true);
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
/** Get ${itemName}.${field.name}.y */
|
|
2816
|
+
export function ${funcName}Y(): number {
|
|
2817
|
+
return getInitDataView().getFloat32(${offset + 4}, true);
|
|
2818
|
+
}
|
|
2819
|
+
`;
|
|
2820
|
+
}
|
|
2821
|
+
if (field.type === "quat") {
|
|
2822
|
+
return `
|
|
2823
|
+
/** Get ${itemName}.${field.name}.x */
|
|
2824
|
+
export function ${funcName}X(): number {
|
|
2825
|
+
return getInitDataView().getFloat32(${offset}, true);
|
|
1942
2826
|
}
|
|
1943
2827
|
|
|
1944
|
-
/** Get ${
|
|
1945
|
-
export function
|
|
1946
|
-
|
|
1947
|
-
if (offset < 0) {
|
|
1948
|
-
return 0;
|
|
1949
|
-
}
|
|
1950
|
-
return readF32(payload, offset + ${arg.offset} + 8);
|
|
2828
|
+
/** Get ${itemName}.${field.name}.y */
|
|
2829
|
+
export function ${funcName}Y(): number {
|
|
2830
|
+
return getInitDataView().getFloat32(${offset + 4}, true);
|
|
1951
2831
|
}
|
|
1952
2832
|
|
|
1953
|
-
/** Get ${
|
|
1954
|
-
export function
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
2833
|
+
/** Get ${itemName}.${field.name}.z */
|
|
2834
|
+
export function ${funcName}Z(): number {
|
|
2835
|
+
return getInitDataView().getFloat32(${offset + 8}, true);
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
/** Get ${itemName}.${field.name}.w */
|
|
2839
|
+
export function ${funcName}W(): number {
|
|
2840
|
+
return getInitDataView().getFloat32(${offset + 12}, true);
|
|
2841
|
+
}
|
|
2842
|
+
`;
|
|
1958
2843
|
}
|
|
1959
|
-
|
|
2844
|
+
if (field.arrayLength !== void 0 && field.arrayElementType) {
|
|
2845
|
+
const loadFn3 = tsLoadFnForType(field.arrayElementType);
|
|
2846
|
+
const elemSize = getElementSize(field.arrayElementType);
|
|
2847
|
+
const needsLE2 = needsEndian(field.arrayElementType);
|
|
2848
|
+
const tsTyp2 = tsTypeMapping(field.arrayElementType);
|
|
2849
|
+
const leArg2 = needsLE2 ? ", true" : "";
|
|
2850
|
+
const defaultVal = tsTyp2 === "boolean" ? "false" : "0";
|
|
2851
|
+
const count = field.arrayLength;
|
|
2852
|
+
return `
|
|
2853
|
+
/** Get ${itemName}.${field.name}[index] (max index: ${count - 1}) */
|
|
2854
|
+
export function ${funcName}(index: number): ${tsTyp2} {
|
|
2855
|
+
if (index < 0 || index >= ${count}) return ${defaultVal};
|
|
2856
|
+
return getInitDataView().${loadFn3}(${offset} + index * ${elemSize}${leArg2});
|
|
1960
2857
|
}
|
|
1961
2858
|
`;
|
|
1962
|
-
} else {
|
|
1963
|
-
const readFn = arg.type === "f32" ? "readF32" : arg.type === "uint8" ? "readU8" : arg.type === "int8" ? "readI8" : arg.type === "uint16" ? "readU16" : arg.type === "int16" ? "readI16" : arg.type === "uint32" ? "readU32" : arg.type === "int32" ? "readI32" : "readU8";
|
|
1964
|
-
out += `
|
|
1965
|
-
/** Get ${cmd.name}.${arg.name} value, or 0 if command not present. */
|
|
1966
|
-
export function get${pascal}${argPascal}FromPayload(payload: Uint8Array): ${tsInputType(arg.type)} {
|
|
1967
|
-
const offset = findControl(payload, ${cmd.index});
|
|
1968
|
-
if (offset < 0) {
|
|
1969
|
-
return 0;
|
|
1970
2859
|
}
|
|
1971
|
-
|
|
2860
|
+
const loadFn2 = tsLoadFnForType(field.type);
|
|
2861
|
+
const needsLE = needsEndian(field.type);
|
|
2862
|
+
const tsTyp = tsTypeMapping(field.type);
|
|
2863
|
+
const leArg = needsLE ? ", true" : "";
|
|
2864
|
+
const retExpr = field.type === "bool" ? `getInitDataView().${loadFn2}(${offset}) !== 0` : `getInitDataView().${loadFn2}(${offset}${leArg})`;
|
|
2865
|
+
return `
|
|
2866
|
+
/** Get ${itemName}.${field.name} */
|
|
2867
|
+
export function ${funcName}(): ${tsTyp} {
|
|
2868
|
+
return ${retExpr};
|
|
1972
2869
|
}
|
|
1973
2870
|
`;
|
|
1974
|
-
|
|
2871
|
+
}
|
|
2872
|
+
function generateInitDataItemAccessors(item) {
|
|
2873
|
+
if (item.isBlob) {
|
|
2874
|
+
const UPPER = toUpperSnake(item.name);
|
|
2875
|
+
const funcName = `get${toPascalCase(item.name)}`;
|
|
2876
|
+
return `
|
|
2877
|
+
// ---------- ${item.name} (blob) ----------
|
|
2878
|
+
|
|
2879
|
+
/** Get current length of ${item.name} blob */
|
|
2880
|
+
export function ${funcName}Length(): number {
|
|
2881
|
+
return getInitDataView().getUint32(INIT_${UPPER}_OFFSET, true);
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
/**
|
|
2885
|
+
* Get ${item.name} blob data as a Uint8Array view.
|
|
2886
|
+
*
|
|
2887
|
+
* WARNING: This returns a view into the init data buffer, not a copy.
|
|
2888
|
+
* Init data is read-only - do NOT mutate the returned array.
|
|
2889
|
+
* Modifying it will corrupt shared init data used by all game logic.
|
|
2890
|
+
*/
|
|
2891
|
+
export function ${funcName}(): Uint8Array {
|
|
2892
|
+
const view = getInitDataView();
|
|
2893
|
+
const length = view.getUint32(INIT_${UPPER}_OFFSET, true);
|
|
2894
|
+
return new Uint8Array(view.buffer, view.byteOffset + INIT_${UPPER}_OFFSET + 4, length);
|
|
2895
|
+
}
|
|
2896
|
+
`;
|
|
2897
|
+
}
|
|
2898
|
+
let out = `
|
|
2899
|
+
// ---------- ${item.name} ----------
|
|
2900
|
+
`;
|
|
2901
|
+
for (const field of item.fields) {
|
|
2902
|
+
out += generateInitDataAccessor(field, item.name, item.storageOffset);
|
|
2903
|
+
}
|
|
2904
|
+
return out;
|
|
2905
|
+
}
|
|
2906
|
+
function generateInitData(schema) {
|
|
2907
|
+
if (schema.items.length === 0) {
|
|
2908
|
+
return "";
|
|
2909
|
+
}
|
|
2910
|
+
let out = generateInitDataHeader(schema);
|
|
2911
|
+
out += "\n";
|
|
2912
|
+
out += generateInitDataItemOffsets(schema);
|
|
2913
|
+
out += "\n";
|
|
2914
|
+
for (const item of schema.items) {
|
|
2915
|
+
out += generateInitDataItemAccessors(item);
|
|
1975
2916
|
}
|
|
1976
2917
|
return out;
|
|
1977
2918
|
}
|
|
@@ -2059,8 +3000,18 @@ var TypeScriptGenerator = class {
|
|
|
2059
3000
|
}
|
|
2060
3001
|
return out;
|
|
2061
3002
|
}
|
|
2062
|
-
|
|
2063
|
-
|
|
3003
|
+
generateInitData(schema) {
|
|
3004
|
+
if (!schema || schema.items.length === 0) {
|
|
3005
|
+
return "";
|
|
3006
|
+
}
|
|
3007
|
+
return generateInitData(schema);
|
|
3008
|
+
}
|
|
3009
|
+
generateGame(state, input, initData) {
|
|
3010
|
+
let out = this.generateState(state) + "\n" + this.generateInput(input);
|
|
3011
|
+
if (initData && initData.items.length > 0) {
|
|
3012
|
+
out += "\n" + this.generateInitData(initData);
|
|
3013
|
+
}
|
|
3014
|
+
return out;
|
|
2064
3015
|
}
|
|
2065
3016
|
};
|
|
2066
3017
|
|
|
@@ -3420,611 +4371,357 @@ function generateGameAllocator() {
|
|
|
3420
4371
|
static ALLOCATOR: talc::TalckWasm = unsafe { talc::TalckWasm::new_global() };
|
|
3421
4372
|
`;
|
|
3422
4373
|
}
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
maxParticipants;
|
|
3427
|
-
constructor(opts = {}) {
|
|
3428
|
-
this.opts = opts;
|
|
3429
|
-
this.maxParticipants = opts.maxParticipants ?? 8;
|
|
3430
|
-
}
|
|
3431
|
-
generateState(schema) {
|
|
3432
|
-
let out = generateStateHeader2(schema, this.maxParticipants);
|
|
3433
|
-
out += generateComponentOffsets2(schema);
|
|
3434
|
-
out += generateMemoryHelpers();
|
|
3435
|
-
out += generateInternalHelpers();
|
|
3436
|
-
out += generateIterationHelpers2(schema);
|
|
3437
|
-
out += generateQueryHelpers2();
|
|
3438
|
-
out += generateLifecycle2(schema);
|
|
3439
|
-
out += `
|
|
3440
|
-
// =============================================================================
|
|
3441
|
-
// Component Operations
|
|
4374
|
+
function generateRustInitDataHeader(schema) {
|
|
4375
|
+
return `// =============================================================================
|
|
4376
|
+
// Init Data Bindings
|
|
3442
4377
|
// =============================================================================
|
|
4378
|
+
|
|
4379
|
+
// Init data is read-only constant data set once per session.
|
|
4380
|
+
// The data is shared by all clients and hashed into session identification.
|
|
4381
|
+
//
|
|
4382
|
+
// SAFETY: Init data accessors must only be called after set_init_data_ptr() has
|
|
4383
|
+
// been called. The host runtime guarantees this is done before apply() is called.
|
|
4384
|
+
// Calling accessors before initialization will result in null pointer access.
|
|
4385
|
+
//
|
|
4386
|
+
// To explicitly check if init data is available, use has_init_data().
|
|
4387
|
+
// This is not normally needed since the runtime guarantees initialization order.
|
|
4388
|
+
|
|
4389
|
+
/// Init data buffer magic number
|
|
4390
|
+
pub const INIT_DATA_MAGIC: u32 = 0x${INIT_DATA_MAGIC.toString(16)};
|
|
4391
|
+
|
|
4392
|
+
/// Init data header size in bytes
|
|
4393
|
+
pub const INIT_DATA_HEADER_SIZE: u32 = ${INIT_DATA_HEADER_SIZE};
|
|
4394
|
+
|
|
4395
|
+
/// Total init data buffer size in bytes
|
|
4396
|
+
pub const INIT_DATA_TOTAL_SIZE: u32 = ${schema.totalSize};
|
|
4397
|
+
|
|
4398
|
+
/// Static pointer to init data buffer - set via set_init_data_ptr()
|
|
4399
|
+
///
|
|
4400
|
+
/// SAFETY: This pointer is initialized to null and must be set via set_init_data_ptr()
|
|
4401
|
+
/// before any init data accessors are called. The runtime guarantees this happens
|
|
4402
|
+
/// before apply() is invoked.
|
|
4403
|
+
static mut INIT_DATA_PTR: *const u8 = core::ptr::null();
|
|
4404
|
+
|
|
4405
|
+
/// Set init data pointer. Called by host during WASM initialization.
|
|
4406
|
+
///
|
|
4407
|
+
/// # Safety
|
|
4408
|
+
/// The provided pointer must point to a valid init data buffer of size INIT_DATA_TOTAL_SIZE.
|
|
4409
|
+
/// This function must be called exactly once before any init data accessors are used.
|
|
4410
|
+
///
|
|
4411
|
+
/// # Panics (debug builds only)
|
|
4412
|
+
/// Panics if called more than once, indicating a bug in the runtime.
|
|
4413
|
+
#[no_mangle]
|
|
4414
|
+
pub unsafe extern "C" fn set_init_data_ptr(ptr: *const u8) {
|
|
4415
|
+
debug_assert!(INIT_DATA_PTR.is_null(), "set_init_data_ptr called more than once");
|
|
4416
|
+
INIT_DATA_PTR = ptr;
|
|
4417
|
+
}
|
|
4418
|
+
|
|
4419
|
+
/// Check if init data pointer has been set.
|
|
4420
|
+
///
|
|
4421
|
+
/// Returns true if init data is available, false otherwise.
|
|
4422
|
+
/// This can be used to guard against accessing init data before initialization,
|
|
4423
|
+
/// though this is not normally needed since the runtime guarantees initialization order.
|
|
4424
|
+
#[inline]
|
|
4425
|
+
pub fn has_init_data() -> bool {
|
|
4426
|
+
unsafe { !INIT_DATA_PTR.is_null() }
|
|
4427
|
+
}
|
|
3443
4428
|
`;
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
if (schema.singletonComponents.length > 0) {
|
|
3448
|
-
out += `
|
|
4429
|
+
}
|
|
4430
|
+
function generateRustInitDataItemOffsets(schema) {
|
|
4431
|
+
let out = `
|
|
3449
4432
|
// =============================================================================
|
|
3450
|
-
//
|
|
4433
|
+
// Init Data Item Offsets
|
|
3451
4434
|
// =============================================================================
|
|
4435
|
+
|
|
3452
4436
|
`;
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
} else {
|
|
3457
|
-
out += generateSingletonAccessors2(comp);
|
|
3458
|
-
}
|
|
3459
|
-
}
|
|
3460
|
-
}
|
|
3461
|
-
if (schema.events && schema.events.length > 0) {
|
|
3462
|
-
out += `
|
|
3463
|
-
// =============================================================================
|
|
3464
|
-
// Events
|
|
3465
|
-
// =============================================================================
|
|
4437
|
+
for (const item of schema.items) {
|
|
4438
|
+
const UPPER = toUpperSnake(item.name);
|
|
4439
|
+
out += `const INIT_${UPPER}_OFFSET: u32 = ${item.storageOffset};
|
|
3466
4440
|
`;
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
}
|
|
3470
|
-
}
|
|
3471
|
-
return out;
|
|
3472
|
-
}
|
|
3473
|
-
generateInput(schema) {
|
|
3474
|
-
validateInputSchemaForCodegen2(schema);
|
|
3475
|
-
let out = generateInputHeader2();
|
|
3476
|
-
if (schema.controls.length > 0) {
|
|
3477
|
-
out += generateControlConstants2(schema);
|
|
3478
|
-
out += generateFlagConstants2(schema);
|
|
3479
|
-
out += `
|
|
3480
|
-
// =============================================================================
|
|
3481
|
-
// Control Accessors (read from Player component)
|
|
3482
|
-
// =============================================================================
|
|
4441
|
+
if (item.isBlob) {
|
|
4442
|
+
out += `pub const INIT_${UPPER}_MAX_SIZE: u32 = ${item.blobMaxSize};
|
|
3483
4443
|
`;
|
|
3484
|
-
for (const ctrl of schema.controls) {
|
|
3485
|
-
out += generateControlAccessors2(ctrl);
|
|
3486
|
-
}
|
|
3487
|
-
}
|
|
3488
|
-
if (schema.commands.length > 0) {
|
|
3489
|
-
out += generateCommandConstants(schema);
|
|
3490
4444
|
}
|
|
3491
|
-
return out;
|
|
3492
4445
|
}
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
4446
|
+
return out;
|
|
4447
|
+
}
|
|
4448
|
+
function generateRustInitDataAccessor(field, itemName, itemOffset) {
|
|
4449
|
+
const ITEM_LOWER = toLowerSnake(itemName);
|
|
4450
|
+
const fieldLower = toLowerSnake(field.name);
|
|
4451
|
+
const funcName = `get_${ITEM_LOWER}_${fieldLower}`;
|
|
4452
|
+
const offset = itemOffset + field.offset;
|
|
4453
|
+
const vec3Array = parseVec3ArrayType(field.type);
|
|
4454
|
+
if (vec3Array) {
|
|
4455
|
+
const count = vec3Array.length;
|
|
4456
|
+
return `
|
|
4457
|
+
/// Get ${itemName}.${field.name}[index].x
|
|
4458
|
+
#[inline]
|
|
4459
|
+
pub unsafe fn ${funcName}_x(index: i32) -> f32 {
|
|
4460
|
+
if (index as u32) >= ${count} { return 0.0; }
|
|
4461
|
+
load_f32(INIT_DATA_PTR.add((${offset} + (index as u32) * 12) as usize))
|
|
4462
|
+
}
|
|
3497
4463
|
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
4464
|
+
/// Get ${itemName}.${field.name}[index].y
|
|
4465
|
+
#[inline]
|
|
4466
|
+
pub unsafe fn ${funcName}_y(index: i32) -> f32 {
|
|
4467
|
+
if (index as u32) >= ${count} { return 0.0; }
|
|
4468
|
+
load_f32(INIT_DATA_PTR.add((${offset} + (index as u32) * 12 + 4) as usize))
|
|
4469
|
+
}
|
|
4470
|
+
|
|
4471
|
+
/// Get ${itemName}.${field.name}[index].z
|
|
4472
|
+
#[inline]
|
|
4473
|
+
pub unsafe fn ${funcName}_z(index: i32) -> f32 {
|
|
4474
|
+
if (index as u32) >= ${count} { return 0.0; }
|
|
4475
|
+
load_f32(INIT_DATA_PTR.add((${offset} + (index as u32) * 12 + 8) as usize))
|
|
4476
|
+
}
|
|
3503
4477
|
`;
|
|
3504
|
-
out += this.generateInput(input);
|
|
3505
|
-
out += generateGameAllocator();
|
|
3506
|
-
return out;
|
|
3507
4478
|
}
|
|
3508
|
-
|
|
4479
|
+
const vec2Array = parseVec2ArrayType(field.type);
|
|
4480
|
+
if (vec2Array) {
|
|
4481
|
+
const count = vec2Array.length;
|
|
4482
|
+
return `
|
|
4483
|
+
/// Get ${itemName}.${field.name}[index].x
|
|
4484
|
+
#[inline]
|
|
4485
|
+
pub unsafe fn ${funcName}_x(index: i32) -> f32 {
|
|
4486
|
+
if (index as u32) >= ${count} { return 0.0; }
|
|
4487
|
+
load_f32(INIT_DATA_PTR.add((${offset} + (index as u32) * 8) as usize))
|
|
4488
|
+
}
|
|
3509
4489
|
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
int8: 1,
|
|
3516
|
-
uint16: 2,
|
|
3517
|
-
int16: 2,
|
|
3518
|
-
uint32: 4,
|
|
3519
|
-
int32: 4,
|
|
3520
|
-
entityRef: 4,
|
|
3521
|
-
f32: 4,
|
|
3522
|
-
vec2: 8,
|
|
3523
|
-
vec3: 12,
|
|
3524
|
-
quat: 16,
|
|
3525
|
-
enum: 1
|
|
3526
|
-
};
|
|
3527
|
-
var INPUT_TYPE_SIZES = {
|
|
3528
|
-
bool: 1,
|
|
3529
|
-
uint8: 1,
|
|
3530
|
-
int8: 1,
|
|
3531
|
-
uint16: 2,
|
|
3532
|
-
int16: 2,
|
|
3533
|
-
uint32: 4,
|
|
3534
|
-
int32: 4,
|
|
3535
|
-
f32: 4,
|
|
3536
|
-
flags8: 1,
|
|
3537
|
-
flags16: 2,
|
|
3538
|
-
flags32: 4,
|
|
3539
|
-
vec2: 8,
|
|
3540
|
-
vec3: 12,
|
|
3541
|
-
quat: 16
|
|
3542
|
-
};
|
|
3543
|
-
var PRIMITIVE_TYPES = /* @__PURE__ */ new Set([
|
|
3544
|
-
"bool",
|
|
3545
|
-
"uint8",
|
|
3546
|
-
"int8",
|
|
3547
|
-
"uint16",
|
|
3548
|
-
"int16",
|
|
3549
|
-
"uint32",
|
|
3550
|
-
"int32",
|
|
3551
|
-
"entityRef",
|
|
3552
|
-
"f32"
|
|
3553
|
-
]);
|
|
3554
|
-
var INPUT_PRIMITIVE_SCALAR_TYPES = /* @__PURE__ */ new Set([
|
|
3555
|
-
"bool",
|
|
3556
|
-
"uint8",
|
|
3557
|
-
"int8",
|
|
3558
|
-
"uint16",
|
|
3559
|
-
"int16",
|
|
3560
|
-
"uint32",
|
|
3561
|
-
"int32",
|
|
3562
|
-
"f32"
|
|
3563
|
-
]);
|
|
3564
|
-
var ENUM_VARIANT_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
3565
|
-
function parseArrayType2(type) {
|
|
3566
|
-
const match = type.match(/^(\w+)\[(\d+)\]$/);
|
|
3567
|
-
if (!match || !match[1] || !match[2]) return null;
|
|
3568
|
-
const elementType = match[1];
|
|
3569
|
-
const length = parseInt(match[2], 10);
|
|
3570
|
-
if (!PRIMITIVE_TYPES.has(elementType)) return null;
|
|
3571
|
-
return { elementType, length };
|
|
4490
|
+
/// Get ${itemName}.${field.name}[index].y
|
|
4491
|
+
#[inline]
|
|
4492
|
+
pub unsafe fn ${funcName}_y(index: i32) -> f32 {
|
|
4493
|
+
if (index as u32) >= ${count} { return 0.0; }
|
|
4494
|
+
load_f32(INIT_DATA_PTR.add((${offset} + (index as u32) * 8 + 4) as usize))
|
|
3572
4495
|
}
|
|
3573
|
-
|
|
3574
|
-
const match = type.match(/^(\w+)\[(\d+)\]$/);
|
|
3575
|
-
if (!match || !match[1] || !match[2]) return null;
|
|
3576
|
-
const elementType = match[1];
|
|
3577
|
-
const length = parseInt(match[2], 10);
|
|
3578
|
-
if (!INPUT_PRIMITIVE_SCALAR_TYPES.has(elementType)) {
|
|
3579
|
-
throw new Error(
|
|
3580
|
-
`Invalid array element type '${elementType}': arrays only support primitive scalar types (bool, uint8, int8, uint16, int16, uint32, int32, f32)`
|
|
3581
|
-
);
|
|
4496
|
+
`;
|
|
3582
4497
|
}
|
|
3583
|
-
|
|
4498
|
+
if (field.type === "vec3") {
|
|
4499
|
+
return `
|
|
4500
|
+
/// Get ${itemName}.${field.name}.x
|
|
4501
|
+
#[inline]
|
|
4502
|
+
pub unsafe fn ${funcName}_x() -> f32 {
|
|
4503
|
+
load_f32(INIT_DATA_PTR.add(${offset}))
|
|
3584
4504
|
}
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
throw new Error(`Unknown element type: ${arrayInfo.elementType}`);
|
|
3591
|
-
}
|
|
3592
|
-
return elementSize * arrayInfo.length;
|
|
3593
|
-
}
|
|
3594
|
-
const size = TYPE_SIZES[type];
|
|
3595
|
-
if (size === void 0) {
|
|
3596
|
-
throw new Error(`Unknown type: ${type}`);
|
|
3597
|
-
}
|
|
3598
|
-
return size;
|
|
4505
|
+
|
|
4506
|
+
/// Get ${itemName}.${field.name}.y
|
|
4507
|
+
#[inline]
|
|
4508
|
+
pub unsafe fn ${funcName}_y() -> f32 {
|
|
4509
|
+
load_f32(INIT_DATA_PTR.add(${offset + 4}))
|
|
3599
4510
|
}
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
return elementSize * arrayInfo.length;
|
|
3608
|
-
}
|
|
3609
|
-
const size = INPUT_TYPE_SIZES[type];
|
|
3610
|
-
if (size === void 0) {
|
|
3611
|
-
throw new Error(`Unknown type: ${type}`);
|
|
4511
|
+
|
|
4512
|
+
/// Get ${itemName}.${field.name}.z
|
|
4513
|
+
#[inline]
|
|
4514
|
+
pub unsafe fn ${funcName}_z() -> f32 {
|
|
4515
|
+
load_f32(INIT_DATA_PTR.add(${offset + 8}))
|
|
4516
|
+
}
|
|
4517
|
+
`;
|
|
3612
4518
|
}
|
|
3613
|
-
|
|
4519
|
+
if (field.type === "vec2") {
|
|
4520
|
+
return `
|
|
4521
|
+
/// Get ${itemName}.${field.name}.x
|
|
4522
|
+
#[inline]
|
|
4523
|
+
pub unsafe fn ${funcName}_x() -> f32 {
|
|
4524
|
+
load_f32(INIT_DATA_PTR.add(${offset}))
|
|
3614
4525
|
}
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
4526
|
+
|
|
4527
|
+
/// Get ${itemName}.${field.name}.y
|
|
4528
|
+
#[inline]
|
|
4529
|
+
pub unsafe fn ${funcName}_y() -> f32 {
|
|
4530
|
+
load_f32(INIT_DATA_PTR.add(${offset + 4}))
|
|
4531
|
+
}
|
|
4532
|
+
`;
|
|
3622
4533
|
}
|
|
4534
|
+
if (field.type === "quat") {
|
|
4535
|
+
return `
|
|
4536
|
+
/// Get ${itemName}.${field.name}.x
|
|
4537
|
+
#[inline]
|
|
4538
|
+
pub unsafe fn ${funcName}_x() -> f32 {
|
|
4539
|
+
load_f32(INIT_DATA_PTR.add(${offset}))
|
|
3623
4540
|
}
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
4541
|
+
|
|
4542
|
+
/// Get ${itemName}.${field.name}.y
|
|
4543
|
+
#[inline]
|
|
4544
|
+
pub unsafe fn ${funcName}_y() -> f32 {
|
|
4545
|
+
load_f32(INIT_DATA_PTR.add(${offset + 4}))
|
|
4546
|
+
}
|
|
4547
|
+
|
|
4548
|
+
/// Get ${itemName}.${field.name}.z
|
|
4549
|
+
#[inline]
|
|
4550
|
+
pub unsafe fn ${funcName}_z() -> f32 {
|
|
4551
|
+
load_f32(INIT_DATA_PTR.add(${offset + 8}))
|
|
4552
|
+
}
|
|
4553
|
+
|
|
4554
|
+
/// Get ${itemName}.${field.name}.w
|
|
4555
|
+
#[inline]
|
|
4556
|
+
pub unsafe fn ${funcName}_w() -> f32 {
|
|
4557
|
+
load_f32(INIT_DATA_PTR.add(${offset + 12}))
|
|
4558
|
+
}
|
|
4559
|
+
`;
|
|
3628
4560
|
}
|
|
3629
|
-
if (
|
|
3630
|
-
|
|
4561
|
+
if (field.arrayLength !== void 0 && field.arrayElementType) {
|
|
4562
|
+
const loadFunc2 = loadFnForType(field.arrayElementType);
|
|
4563
|
+
const elemSize = getElementSize(field.arrayElementType);
|
|
4564
|
+
const rustTyp2 = rustTypeMapping(field.arrayElementType);
|
|
4565
|
+
const defaultVal = rustTyp2 === "bool" ? "false" : `0${rustTyp2 === "f32" ? ".0" : ""}`;
|
|
4566
|
+
const count = field.arrayLength;
|
|
4567
|
+
return `
|
|
4568
|
+
/// Get ${itemName}.${field.name}[index]
|
|
4569
|
+
#[inline]
|
|
4570
|
+
pub unsafe fn ${funcName}(index: i32) -> ${rustTyp2} {
|
|
4571
|
+
if (index as u32) >= ${count} { return ${defaultVal}; }
|
|
4572
|
+
${loadFunc2}(INIT_DATA_PTR.add((${offset} + (index as u32) * ${elemSize}) as usize))
|
|
4573
|
+
}
|
|
4574
|
+
`;
|
|
3631
4575
|
}
|
|
3632
|
-
|
|
3633
|
-
|
|
4576
|
+
const loadFunc = loadFnForType(field.type);
|
|
4577
|
+
const rustTyp = rustTypeMapping(field.type);
|
|
4578
|
+
return `
|
|
4579
|
+
/// Get ${itemName}.${field.name}
|
|
4580
|
+
#[inline]
|
|
4581
|
+
pub unsafe fn ${funcName}() -> ${rustTyp} {
|
|
4582
|
+
${loadFunc}(INIT_DATA_PTR.add(${offset}))
|
|
4583
|
+
}
|
|
4584
|
+
`;
|
|
4585
|
+
}
|
|
4586
|
+
function generateRustInitDataItemAccessors(item) {
|
|
4587
|
+
if (item.isBlob) {
|
|
4588
|
+
const UPPER = toUpperSnake(item.name);
|
|
4589
|
+
const funcNameLower = toLowerSnake(item.name);
|
|
4590
|
+
return `
|
|
4591
|
+
// ---------- ${item.name} (blob) ----------
|
|
4592
|
+
|
|
4593
|
+
/// Get current length of ${item.name} blob
|
|
4594
|
+
#[inline]
|
|
4595
|
+
pub unsafe fn get_${funcNameLower}_len() -> u32 {
|
|
4596
|
+
load_u32(INIT_DATA_PTR.add(INIT_${UPPER}_OFFSET as usize))
|
|
4597
|
+
}
|
|
4598
|
+
|
|
4599
|
+
/// Get pointer to ${item.name} blob data
|
|
4600
|
+
#[inline]
|
|
4601
|
+
pub unsafe fn get_${funcNameLower}_ptr() -> *const u8 {
|
|
4602
|
+
INIT_DATA_PTR.add(INIT_${UPPER}_OFFSET as usize + 4)
|
|
4603
|
+
}
|
|
4604
|
+
`;
|
|
3634
4605
|
}
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
}
|
|
3641
|
-
if (!ENUM_VARIANT_NAME_RE.test(v)) {
|
|
3642
|
-
throw new Error(
|
|
3643
|
-
`enum ${subject} has invalid variant name ${JSON.stringify(v)} (must match ${ENUM_VARIANT_NAME_RE})`
|
|
3644
|
-
);
|
|
3645
|
-
}
|
|
3646
|
-
if (seen.has(v)) {
|
|
3647
|
-
throw new Error(`enum ${subject} has duplicate variant '${v}'`);
|
|
3648
|
-
}
|
|
3649
|
-
seen.add(v);
|
|
3650
|
-
const normalized = toUpperSnake2(v);
|
|
3651
|
-
const prev = seenNormalized.get(normalized);
|
|
3652
|
-
if (prev !== void 0) {
|
|
3653
|
-
throw new Error(
|
|
3654
|
-
`enum ${subject} has variants ${JSON.stringify(prev)} and ${JSON.stringify(v)} which collide as ${JSON.stringify(normalized)}`
|
|
3655
|
-
);
|
|
3656
|
-
}
|
|
3657
|
-
seenNormalized.set(normalized, v);
|
|
4606
|
+
let out = `
|
|
4607
|
+
// ---------- ${item.name} ----------
|
|
4608
|
+
`;
|
|
4609
|
+
for (const field of item.fields) {
|
|
4610
|
+
out += generateRustInitDataAccessor(field, item.name, item.storageOffset);
|
|
3658
4611
|
}
|
|
3659
|
-
return
|
|
4612
|
+
return out;
|
|
3660
4613
|
}
|
|
3661
|
-
function
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
const c = s[i];
|
|
3665
|
-
if (i > 0 && c >= "A" && c <= "Z") {
|
|
3666
|
-
result += "_";
|
|
3667
|
-
}
|
|
3668
|
-
result += c.toUpperCase();
|
|
4614
|
+
function generateRustInitData(schema) {
|
|
4615
|
+
if (schema.items.length === 0) {
|
|
4616
|
+
return "";
|
|
3669
4617
|
}
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
4618
|
+
let out = generateRustInitDataHeader(schema);
|
|
4619
|
+
out += "\n";
|
|
4620
|
+
out += generateRustInitDataItemOffsets(schema);
|
|
4621
|
+
out += "\n";
|
|
4622
|
+
for (const item of schema.items) {
|
|
4623
|
+
out += generateRustInitDataItemAccessors(item);
|
|
3676
4624
|
}
|
|
3677
|
-
|
|
3678
|
-
|
|
4625
|
+
return out;
|
|
4626
|
+
}
|
|
4627
|
+
var RustGenerator = class {
|
|
4628
|
+
language = "rust";
|
|
4629
|
+
opts;
|
|
4630
|
+
maxParticipants;
|
|
4631
|
+
constructor(opts = {}) {
|
|
4632
|
+
this.opts = opts;
|
|
4633
|
+
this.maxParticipants = opts.maxParticipants ?? 8;
|
|
3679
4634
|
}
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
const seenComponentNames = /* @__PURE__ */ new Set();
|
|
3696
|
-
let storageOffset = componentStorageOffset;
|
|
3697
|
-
const pendingSingletons = [];
|
|
3698
|
-
let bitIndex = 0;
|
|
3699
|
-
for (let i = 0; i < componentDefs.length; i++) {
|
|
3700
|
-
const componentDef = componentDefs[i];
|
|
3701
|
-
if (componentDef === void 0 || componentDef === null || typeof componentDef !== "object") {
|
|
3702
|
-
throw new Error(`Component at index ${i} must be an object`);
|
|
3703
|
-
}
|
|
3704
|
-
const rawName = componentDef.name;
|
|
3705
|
-
if (typeof rawName !== "string" || rawName.trim().length === 0) {
|
|
3706
|
-
throw new Error(`Component at index ${i} must have a non-empty string 'name'`);
|
|
3707
|
-
}
|
|
3708
|
-
const name = rawName;
|
|
3709
|
-
if (seenComponentNames.has(name)) {
|
|
3710
|
-
throw new Error(`Duplicate component name '${name}' in schema`);
|
|
3711
|
-
}
|
|
3712
|
-
seenComponentNames.add(name);
|
|
3713
|
-
const rawType = componentDef.type;
|
|
3714
|
-
if (typeof rawType !== "string" || rawType.trim().length === 0) {
|
|
3715
|
-
throw new Error(`Component '${name}' must have a string 'type'`);
|
|
3716
|
-
}
|
|
3717
|
-
const componentType = rawType;
|
|
3718
|
-
const fields = [];
|
|
3719
|
-
let componentSize = 0;
|
|
3720
|
-
let isTag = false;
|
|
3721
|
-
let isBlob = false;
|
|
3722
|
-
let blobMaxSize;
|
|
3723
|
-
const isSingleton = componentDef.singleton === true;
|
|
3724
|
-
if (componentType === "tag") {
|
|
3725
|
-
isTag = true;
|
|
3726
|
-
if (isSingleton) {
|
|
3727
|
-
throw new Error(`Component '${name}' cannot be a singleton tag`);
|
|
3728
|
-
}
|
|
3729
|
-
} else if (componentType === "compound") {
|
|
3730
|
-
const rawFields = componentDef.fields;
|
|
3731
|
-
if (!Array.isArray(rawFields)) {
|
|
3732
|
-
throw new Error(`Component '${name}' with type 'compound' must declare a 'fields' array`);
|
|
3733
|
-
}
|
|
3734
|
-
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
3735
|
-
let fieldOffset = 0;
|
|
3736
|
-
for (let fieldIndex = 0; fieldIndex < rawFields.length; fieldIndex++) {
|
|
3737
|
-
const fieldDef = rawFields[fieldIndex];
|
|
3738
|
-
if (fieldDef === void 0 || fieldDef === null || typeof fieldDef !== "object") {
|
|
3739
|
-
throw new Error(`Component '${name}' has an invalid field at index ${fieldIndex}`);
|
|
3740
|
-
}
|
|
3741
|
-
const rawFieldName = fieldDef.name;
|
|
3742
|
-
if (typeof rawFieldName !== "string" || rawFieldName.trim().length === 0) {
|
|
3743
|
-
throw new Error(`Component '${name}' has a field with a missing or empty 'name'`);
|
|
3744
|
-
}
|
|
3745
|
-
if (seenFieldNames.has(rawFieldName)) {
|
|
3746
|
-
throw new Error(`Component '${name}' has duplicate field '${rawFieldName}'`);
|
|
3747
|
-
}
|
|
3748
|
-
seenFieldNames.add(rawFieldName);
|
|
3749
|
-
const rawFieldType = fieldDef.type;
|
|
3750
|
-
if (typeof rawFieldType !== "string" || rawFieldType.trim().length === 0) {
|
|
3751
|
-
throw new Error(`Field '${name}.${rawFieldName}' must have a string 'type'`);
|
|
3752
|
-
}
|
|
3753
|
-
const size = getValidatedTypeSize(rawFieldType, name, rawFieldName);
|
|
3754
|
-
const arrayInfo = parseArrayType2(rawFieldType);
|
|
3755
|
-
const compiledField = {
|
|
3756
|
-
name: rawFieldName,
|
|
3757
|
-
type: rawFieldType,
|
|
3758
|
-
size,
|
|
3759
|
-
offset: fieldOffset
|
|
3760
|
-
};
|
|
3761
|
-
if (arrayInfo) {
|
|
3762
|
-
compiledField.arrayLength = arrayInfo.length;
|
|
3763
|
-
compiledField.arrayElementType = arrayInfo.elementType;
|
|
3764
|
-
}
|
|
3765
|
-
if (rawFieldType === "enum") {
|
|
3766
|
-
compiledField.variants = validateEnumVariants(
|
|
3767
|
-
fieldDef.variants,
|
|
3768
|
-
`field '${name}.${rawFieldName}'`
|
|
3769
|
-
);
|
|
3770
|
-
}
|
|
3771
|
-
fields.push(compiledField);
|
|
3772
|
-
fieldOffset += size;
|
|
3773
|
-
componentSize += size;
|
|
3774
|
-
}
|
|
3775
|
-
} else if (componentType === "blob") {
|
|
3776
|
-
if (!isSingleton) {
|
|
3777
|
-
throw new Error(`Component '${name}' with type 'blob' must be a singleton`);
|
|
3778
|
-
}
|
|
3779
|
-
const maxSize = componentDef.maxSize;
|
|
3780
|
-
if (typeof maxSize !== "number" || maxSize <= 0) {
|
|
3781
|
-
throw new Error(`Component '${name}' with type 'blob' requires positive 'maxSize'`);
|
|
3782
|
-
}
|
|
3783
|
-
componentSize = 4 + maxSize;
|
|
3784
|
-
isBlob = true;
|
|
3785
|
-
blobMaxSize = maxSize;
|
|
3786
|
-
} else {
|
|
3787
|
-
const fieldType = componentType;
|
|
3788
|
-
const size = getValidatedTypeSize(fieldType, name);
|
|
3789
|
-
const arrayInfo = parseArrayType2(fieldType);
|
|
3790
|
-
const compiledField = {
|
|
3791
|
-
name: "",
|
|
3792
|
-
type: fieldType,
|
|
3793
|
-
size,
|
|
3794
|
-
offset: 0
|
|
3795
|
-
};
|
|
3796
|
-
if (arrayInfo) {
|
|
3797
|
-
compiledField.arrayLength = arrayInfo.length;
|
|
3798
|
-
compiledField.arrayElementType = arrayInfo.elementType;
|
|
3799
|
-
}
|
|
3800
|
-
if (fieldType === "enum") {
|
|
3801
|
-
compiledField.variants = validateEnumVariants(
|
|
3802
|
-
componentDef.variants,
|
|
3803
|
-
`component '${name}'`
|
|
3804
|
-
);
|
|
3805
|
-
}
|
|
3806
|
-
fields.push(compiledField);
|
|
3807
|
-
componentSize = size;
|
|
3808
|
-
}
|
|
3809
|
-
const compiled = {
|
|
3810
|
-
name,
|
|
3811
|
-
index: isSingleton ? -1 : bitIndex,
|
|
3812
|
-
isTag,
|
|
3813
|
-
isSingleton,
|
|
3814
|
-
size: componentSize,
|
|
3815
|
-
storageOffset: 0,
|
|
3816
|
-
fields
|
|
3817
|
-
};
|
|
3818
|
-
if (isBlob) {
|
|
3819
|
-
compiled.isBlob = true;
|
|
3820
|
-
compiled.blobMaxSize = blobMaxSize;
|
|
4635
|
+
generateState(schema) {
|
|
4636
|
+
let out = generateStateHeader2(schema, this.maxParticipants);
|
|
4637
|
+
out += generateComponentOffsets2(schema);
|
|
4638
|
+
out += generateMemoryHelpers();
|
|
4639
|
+
out += generateInternalHelpers();
|
|
4640
|
+
out += generateIterationHelpers2(schema);
|
|
4641
|
+
out += generateQueryHelpers2();
|
|
4642
|
+
out += generateLifecycle2(schema);
|
|
4643
|
+
out += `
|
|
4644
|
+
// =============================================================================
|
|
4645
|
+
// Component Operations
|
|
4646
|
+
// =============================================================================
|
|
4647
|
+
`;
|
|
4648
|
+
for (const comp of schema.entityComponents) {
|
|
4649
|
+
out += generateComponentAccessors2(comp);
|
|
3821
4650
|
}
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
4651
|
+
if (schema.singletonComponents.length > 0) {
|
|
4652
|
+
out += `
|
|
4653
|
+
// =============================================================================
|
|
4654
|
+
// Singleton Components
|
|
4655
|
+
// =============================================================================
|
|
4656
|
+
`;
|
|
4657
|
+
for (const comp of schema.singletonComponents) {
|
|
4658
|
+
if (comp.isBlob) {
|
|
4659
|
+
out += generateBlobAccessors2(comp);
|
|
4660
|
+
} else {
|
|
4661
|
+
out += generateSingletonAccessors2(comp);
|
|
4662
|
+
}
|
|
3833
4663
|
}
|
|
3834
|
-
entityComponents.push(compiled);
|
|
3835
|
-
bitIndex += 1;
|
|
3836
4664
|
}
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
let totalSize = singletonCursor;
|
|
3846
|
-
let events;
|
|
3847
|
-
let eventByName;
|
|
3848
|
-
let eventStorageOffset;
|
|
3849
|
-
let eventStorageSize;
|
|
3850
|
-
if (eventDefs && eventDefs.length > 0) {
|
|
3851
|
-
events = [];
|
|
3852
|
-
eventByName = /* @__PURE__ */ new Map();
|
|
3853
|
-
eventStorageOffset = totalSize;
|
|
3854
|
-
let eventCursor = eventStorageOffset;
|
|
3855
|
-
const seenEventNames = /* @__PURE__ */ new Set();
|
|
3856
|
-
for (let i = 0; i < eventDefs.length; i++) {
|
|
3857
|
-
const eventDef = eventDefs[i];
|
|
3858
|
-
if (typeof eventDef.name !== "string" || eventDef.name.trim().length === 0) {
|
|
3859
|
-
throw new Error(`Event at index ${i} must have a non-empty string 'name'`);
|
|
3860
|
-
}
|
|
3861
|
-
if (seenEventNames.has(eventDef.name)) {
|
|
3862
|
-
throw new Error(`Duplicate event name '${eventDef.name}' in schema`);
|
|
3863
|
-
}
|
|
3864
|
-
seenEventNames.add(eventDef.name);
|
|
3865
|
-
if (seenComponentNames.has(eventDef.name)) {
|
|
3866
|
-
throw new Error(`Event name '${eventDef.name}' conflicts with component name`);
|
|
3867
|
-
}
|
|
3868
|
-
if (!Array.isArray(eventDef.fields) || eventDef.fields.length === 0) {
|
|
3869
|
-
throw new Error(`Event '${eventDef.name}' must have at least one field`);
|
|
3870
|
-
}
|
|
3871
|
-
const maxEvents = eventDef.maxEvents && eventDef.maxEvents > 0 ? eventDef.maxEvents : 64;
|
|
3872
|
-
const eventFields = [];
|
|
3873
|
-
let fieldOffset = 0;
|
|
3874
|
-
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
3875
|
-
for (let fi = 0; fi < eventDef.fields.length; fi++) {
|
|
3876
|
-
const fieldDef = eventDef.fields[fi];
|
|
3877
|
-
if (typeof fieldDef.name !== "string" || fieldDef.name.trim().length === 0) {
|
|
3878
|
-
throw new Error(`Event '${eventDef.name}' has a field with a missing or empty 'name' at index ${fi}`);
|
|
3879
|
-
}
|
|
3880
|
-
if (seenFieldNames.has(fieldDef.name)) {
|
|
3881
|
-
throw new Error(`Event '${eventDef.name}' has duplicate field '${fieldDef.name}'`);
|
|
3882
|
-
}
|
|
3883
|
-
seenFieldNames.add(fieldDef.name);
|
|
3884
|
-
if (typeof fieldDef.type !== "string" || fieldDef.type.trim().length === 0) {
|
|
3885
|
-
throw new Error(`Event field '${eventDef.name}.${fieldDef.name}' must have a string 'type'`);
|
|
3886
|
-
}
|
|
3887
|
-
const arrayInfo = parseArrayType2(fieldDef.type);
|
|
3888
|
-
if (arrayInfo) {
|
|
3889
|
-
throw new Error(
|
|
3890
|
-
`Event field '${eventDef.name}.${fieldDef.name}' has array type '${fieldDef.type}'; events do not support array types`
|
|
3891
|
-
);
|
|
3892
|
-
}
|
|
3893
|
-
const size = getValidatedTypeSize(fieldDef.type, eventDef.name, fieldDef.name);
|
|
3894
|
-
const compiledField = {
|
|
3895
|
-
name: fieldDef.name,
|
|
3896
|
-
type: fieldDef.type,
|
|
3897
|
-
size,
|
|
3898
|
-
offset: fieldOffset
|
|
3899
|
-
};
|
|
3900
|
-
if (fieldDef.type === "enum" && fieldDef.variants) {
|
|
3901
|
-
compiledField.variants = validateEnumVariants(fieldDef.variants, `event field '${eventDef.name}.${fieldDef.name}'`);
|
|
3902
|
-
}
|
|
3903
|
-
eventFields.push(compiledField);
|
|
3904
|
-
fieldOffset += size;
|
|
4665
|
+
if (schema.events && schema.events.length > 0) {
|
|
4666
|
+
out += `
|
|
4667
|
+
// =============================================================================
|
|
4668
|
+
// Events
|
|
4669
|
+
// =============================================================================
|
|
4670
|
+
`;
|
|
4671
|
+
for (const event of schema.events) {
|
|
4672
|
+
out += generateEventAccessors2(event);
|
|
3905
4673
|
}
|
|
3906
|
-
const recordSize = fieldOffset;
|
|
3907
|
-
const bufferSize = 2 + maxEvents * recordSize;
|
|
3908
|
-
const compiledEvent = {
|
|
3909
|
-
name: eventDef.name,
|
|
3910
|
-
index: i,
|
|
3911
|
-
maxEvents,
|
|
3912
|
-
recordSize,
|
|
3913
|
-
storageOffset: eventCursor,
|
|
3914
|
-
bufferSize,
|
|
3915
|
-
fields: eventFields
|
|
3916
|
-
};
|
|
3917
|
-
events.push(compiledEvent);
|
|
3918
|
-
eventByName.set(eventDef.name, compiledEvent);
|
|
3919
|
-
eventCursor += bufferSize;
|
|
3920
4674
|
}
|
|
3921
|
-
|
|
3922
|
-
totalSize = eventCursor;
|
|
4675
|
+
return out;
|
|
3923
4676
|
}
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
freeStackSize,
|
|
3939
|
-
componentStorageOffset,
|
|
3940
|
-
singletonStorageOffset,
|
|
3941
|
-
singletonStorageSize,
|
|
3942
|
-
eventStorageOffset,
|
|
3943
|
-
eventStorageSize,
|
|
3944
|
-
totalSize,
|
|
3945
|
-
bitmaskSize
|
|
3946
|
-
};
|
|
3947
|
-
}
|
|
3948
|
-
function compileInputSchema(schema) {
|
|
3949
|
-
const controls = [];
|
|
3950
|
-
const commands = [];
|
|
3951
|
-
const controlByName = /* @__PURE__ */ new Map();
|
|
3952
|
-
const commandByName = /* @__PURE__ */ new Map();
|
|
3953
|
-
const seenNames = /* @__PURE__ */ new Set();
|
|
3954
|
-
let index = 0;
|
|
3955
|
-
for (let i = 0; i < schema.controls.length; i++) {
|
|
3956
|
-
const controlDef = schema.controls[i];
|
|
3957
|
-
if (typeof controlDef.name !== "string" || controlDef.name.trim().length === 0) {
|
|
3958
|
-
throw new Error(`Control at index ${i} must have a non-empty string 'name'`);
|
|
3959
|
-
}
|
|
3960
|
-
if (seenNames.has(controlDef.name)) {
|
|
3961
|
-
throw new Error(`Duplicate control/command name '${controlDef.name}' in schema`);
|
|
3962
|
-
}
|
|
3963
|
-
seenNames.add(controlDef.name);
|
|
3964
|
-
if (typeof controlDef.type !== "string" || controlDef.type.trim().length === 0) {
|
|
3965
|
-
throw new Error(`Control '${controlDef.name}' must have a string 'type'`);
|
|
4677
|
+
generateInput(schema) {
|
|
4678
|
+
validateInputSchemaForCodegen2(schema);
|
|
4679
|
+
let out = generateInputHeader2();
|
|
4680
|
+
if (schema.controls.length > 0) {
|
|
4681
|
+
out += generateControlConstants2(schema);
|
|
4682
|
+
out += generateFlagConstants2(schema);
|
|
4683
|
+
out += `
|
|
4684
|
+
// =============================================================================
|
|
4685
|
+
// Control Accessors (read from Player component)
|
|
4686
|
+
// =============================================================================
|
|
4687
|
+
`;
|
|
4688
|
+
for (const ctrl of schema.controls) {
|
|
4689
|
+
out += generateControlAccessors2(ctrl);
|
|
4690
|
+
}
|
|
3966
4691
|
}
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
name: controlDef.name,
|
|
3970
|
-
index: index++,
|
|
3971
|
-
type: controlDef.type,
|
|
3972
|
-
size,
|
|
3973
|
-
options: controlDef.options,
|
|
3974
|
-
hint: controlDef.hint,
|
|
3975
|
-
retain: controlDef.retain === "always" ? "always" : "tick"
|
|
3976
|
-
};
|
|
3977
|
-
controls.push(compiled);
|
|
3978
|
-
controlByName.set(controlDef.name, compiled);
|
|
3979
|
-
if (index > 255) {
|
|
3980
|
-
throw new Error(`Too many controls/commands: index ${index} exceeds u8 limit (255)`);
|
|
4692
|
+
if (schema.commands.length > 0) {
|
|
4693
|
+
out += generateCommandConstants(schema);
|
|
3981
4694
|
}
|
|
4695
|
+
return out;
|
|
3982
4696
|
}
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
throw new Error(`Command at index ${i} must have a non-empty string 'name'`);
|
|
3987
|
-
}
|
|
3988
|
-
if (seenNames.has(commandDef.name)) {
|
|
3989
|
-
throw new Error(`Duplicate control/command name '${commandDef.name}' in schema`);
|
|
3990
|
-
}
|
|
3991
|
-
seenNames.add(commandDef.name);
|
|
3992
|
-
const args = [];
|
|
3993
|
-
let offset = 0;
|
|
3994
|
-
for (let ai = 0; ai < commandDef.args.length; ai++) {
|
|
3995
|
-
const argDef = commandDef.args[ai];
|
|
3996
|
-
if (typeof argDef.name !== "string" || argDef.name.trim().length === 0) {
|
|
3997
|
-
throw new Error(`Command '${commandDef.name}' has an argument with missing 'name' at index ${ai}`);
|
|
3998
|
-
}
|
|
3999
|
-
const size = getInputTypeSize(argDef.type);
|
|
4000
|
-
const arrayInfo = parseInputArrayType(argDef.type);
|
|
4001
|
-
const arg = {
|
|
4002
|
-
name: argDef.name,
|
|
4003
|
-
type: argDef.type,
|
|
4004
|
-
size,
|
|
4005
|
-
offset
|
|
4006
|
-
};
|
|
4007
|
-
if (arrayInfo) {
|
|
4008
|
-
arg.arrayLength = arrayInfo.length;
|
|
4009
|
-
arg.arrayElementType = arrayInfo.elementType;
|
|
4010
|
-
}
|
|
4011
|
-
args.push(arg);
|
|
4012
|
-
offset += size;
|
|
4697
|
+
generateInitData(schema) {
|
|
4698
|
+
if (!schema || schema.items.length === 0) {
|
|
4699
|
+
return "";
|
|
4013
4700
|
}
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4701
|
+
return generateRustInitData(schema);
|
|
4702
|
+
}
|
|
4703
|
+
generateGame(state, input, initData) {
|
|
4704
|
+
let out = `// Combined Rust module: state accessors + input helpers + transition
|
|
4705
|
+
//
|
|
4706
|
+
// Generated code - do not edit manually.
|
|
4707
|
+
|
|
4708
|
+
// --- State accessors --------------------------------------------------------
|
|
4709
|
+
`;
|
|
4710
|
+
out += this.generateState(state);
|
|
4711
|
+
out += `
|
|
4712
|
+
// --- Input helpers ---------------------------------------------------------
|
|
4713
|
+
`;
|
|
4714
|
+
out += this.generateInput(input);
|
|
4715
|
+
if (initData && initData.items.length > 0) {
|
|
4716
|
+
out += `
|
|
4717
|
+
// --- Init data accessors ---------------------------------------------------
|
|
4718
|
+
`;
|
|
4719
|
+
out += this.generateInitData(initData);
|
|
4024
4720
|
}
|
|
4721
|
+
out += generateGameAllocator();
|
|
4722
|
+
return out;
|
|
4025
4723
|
}
|
|
4026
|
-
|
|
4027
|
-
}
|
|
4724
|
+
};
|
|
4028
4725
|
|
|
4029
4726
|
// src/vite/codegen-runner.ts
|
|
4030
4727
|
async function runCodegen(configPath, options) {
|
|
@@ -4038,11 +4735,15 @@ async function runCodegen(configPath, options) {
|
|
|
4038
4735
|
const compiledInput = compileInputSchema(merged.config.input);
|
|
4039
4736
|
const preparedState = prepareStateSchema(merged.config.state, compiledInput);
|
|
4040
4737
|
const compiledState = compileStateSchema(preparedState);
|
|
4738
|
+
const compiledInitData = merged.config.data && merged.config.data.length > 0 ? compileInitDataSchema(merged.config.data) : void 0;
|
|
4041
4739
|
if (verbose) {
|
|
4042
4740
|
console.log(`[multitap] State buffer size: ${compiledState.totalSize} bytes`);
|
|
4741
|
+
if (compiledInitData) {
|
|
4742
|
+
console.log(`[multitap] Init data buffer size: ${compiledInitData.totalSize} bytes`);
|
|
4743
|
+
}
|
|
4043
4744
|
}
|
|
4044
4745
|
const tsGen = new TypeScriptGenerator({ maxParticipants: merged.config.maxParticipants });
|
|
4045
|
-
const tsCode = tsGen.generateGame(compiledState, compiledInput);
|
|
4746
|
+
const tsCode = tsGen.generateGame(compiledState, compiledInput, compiledInitData);
|
|
4046
4747
|
await mkdir(generatedDir, { recursive: true });
|
|
4047
4748
|
const tsPath = join2(generatedDir, "state.ts");
|
|
4048
4749
|
await writeFile(tsPath, tsCode, "utf-8");
|
|
@@ -4050,7 +4751,7 @@ async function runCodegen(configPath, options) {
|
|
|
4050
4751
|
console.log(`[multitap] Generated ${tsPath}`);
|
|
4051
4752
|
}
|
|
4052
4753
|
const rustGen = new RustGenerator({ maxParticipants: merged.config.maxParticipants });
|
|
4053
|
-
const rustCode = rustGen.generateGame(compiledState, compiledInput);
|
|
4754
|
+
const rustCode = rustGen.generateGame(compiledState, compiledInput, compiledInitData);
|
|
4054
4755
|
for (const pluginPath of merged.pluginOrder) {
|
|
4055
4756
|
const rustDir = join2(pluginPath, "src");
|
|
4056
4757
|
await mkdir(rustDir, { recursive: true });
|
|
@@ -4063,6 +4764,7 @@ async function runCodegen(configPath, options) {
|
|
|
4063
4764
|
return {
|
|
4064
4765
|
compiledState,
|
|
4065
4766
|
compiledInput,
|
|
4767
|
+
compiledInitData,
|
|
4066
4768
|
mergedConfig: merged,
|
|
4067
4769
|
graph
|
|
4068
4770
|
};
|
|
@@ -4186,8 +4888,15 @@ function prepareStateSchemaForSerialization(schema) {
|
|
|
4186
4888
|
bitmaskSize: schema.bitmaskSize
|
|
4187
4889
|
};
|
|
4188
4890
|
}
|
|
4891
|
+
function prepareInitDataSchemaForSerialization(schema) {
|
|
4892
|
+
return {
|
|
4893
|
+
items: schema.items,
|
|
4894
|
+
headerSize: schema.headerSize,
|
|
4895
|
+
totalSize: schema.totalSize
|
|
4896
|
+
};
|
|
4897
|
+
}
|
|
4189
4898
|
function buildVirtualModule(options) {
|
|
4190
|
-
const { config, plugins, compiledState } = options;
|
|
4899
|
+
const { config, plugins, compiledState, compiledInitData } = options;
|
|
4191
4900
|
const lines = [];
|
|
4192
4901
|
lines.push("function base64ToUint8Array(b64) {");
|
|
4193
4902
|
lines.push(" const binary = atob(b64);");
|
|
@@ -4206,7 +4915,8 @@ function buildVirtualModule(options) {
|
|
|
4206
4915
|
lines.push("");
|
|
4207
4916
|
const stateJSON = JSON.stringify(prepareStateSchemaForSerialization(compiledState));
|
|
4208
4917
|
const inputJSON = JSON.stringify(config.input);
|
|
4209
|
-
|
|
4918
|
+
const initDataJSON = compiledInitData ? JSON.stringify(prepareInitDataSchemaForSerialization(compiledInitData)) : "undefined";
|
|
4919
|
+
lines.push(`const schema = { state: ${stateJSON}, input: ${inputJSON}, initData: ${initDataJSON} };`);
|
|
4210
4920
|
lines.push("");
|
|
4211
4921
|
lines.push("export default {");
|
|
4212
4922
|
lines.push(` appID: ${JSON.stringify(config.appID)},`);
|
|
@@ -4275,7 +4985,8 @@ function multitapPlugin(options = {}) {
|
|
|
4275
4985
|
const moduleCode = buildVirtualModule({
|
|
4276
4986
|
config: codegenResult.mergedConfig.config,
|
|
4277
4987
|
plugins,
|
|
4278
|
-
compiledState: codegenResult.compiledState
|
|
4988
|
+
compiledState: codegenResult.compiledState,
|
|
4989
|
+
compiledInitData: codegenResult.compiledInitData
|
|
4279
4990
|
});
|
|
4280
4991
|
if (verbose) {
|
|
4281
4992
|
console.log(`[multitap] Build complete`);
|