@series-inc/rundot-syncplay 5.23.0-beta.7
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/README.md +610 -0
- package/dist/animation.d.ts +119 -0
- package/dist/animation.js +488 -0
- package/dist/asset-cooking.d.ts +82 -0
- package/dist/asset-cooking.js +364 -0
- package/dist/authority-room.d.ts +217 -0
- package/dist/authority-room.js +701 -0
- package/dist/bot-documents.d.ts +115 -0
- package/dist/bot-documents.js +549 -0
- package/dist/bot-primitives.d.ts +152 -0
- package/dist/bot-primitives.js +472 -0
- package/dist/browser.d.ts +57 -0
- package/dist/browser.js +53 -0
- package/dist/canonical.d.ts +12 -0
- package/dist/canonical.js +288 -0
- package/dist/certification.d.ts +455 -0
- package/dist/certification.js +1855 -0
- package/dist/chaos-certification.d.ts +56 -0
- package/dist/chaos-certification.js +1397 -0
- package/dist/cjs/animation.js +495 -0
- package/dist/cjs/asset-cooking.js +372 -0
- package/dist/cjs/authority-room.js +704 -0
- package/dist/cjs/bot-documents.js +556 -0
- package/dist/cjs/bot-primitives.js +482 -0
- package/dist/cjs/browser.js +96 -0
- package/dist/cjs/canonical.js +299 -0
- package/dist/cjs/certification.js +1860 -0
- package/dist/cjs/chaos-certification.js +1402 -0
- package/dist/cjs/collections.js +36 -0
- package/dist/cjs/collision.js +96 -0
- package/dist/cjs/commands.js +987 -0
- package/dist/cjs/config-bundle.js +491 -0
- package/dist/cjs/creator.js +30 -0
- package/dist/cjs/deterministic-primitives.js +134 -0
- package/dist/cjs/deterministic-session.js +1410 -0
- package/dist/cjs/ecs-lite.js +155 -0
- package/dist/cjs/effects-adapter.js +97 -0
- package/dist/cjs/engine-complete.js +2297 -0
- package/dist/cjs/engine-core-proof.js +70 -0
- package/dist/cjs/engine-parity-matrix.js +874 -0
- package/dist/cjs/engine-parity.js +3183 -0
- package/dist/cjs/engine-runtime.js +3141 -0
- package/dist/cjs/errors.js +22 -0
- package/dist/cjs/events.js +247 -0
- package/dist/cjs/examples/counter-duel.js +157 -0
- package/dist/cjs/examples/fixed-arena.js +368 -0
- package/dist/cjs/index.js +229 -0
- package/dist/cjs/input-authority.js +150 -0
- package/dist/cjs/input-button.js +256 -0
- package/dist/cjs/input-timeline.js +418 -0
- package/dist/cjs/instant-replay.js +66 -0
- package/dist/cjs/lag-compensation3d.js +837 -0
- package/dist/cjs/local-authority-server-runner.js +1121 -0
- package/dist/cjs/local-authority-wire.js +1497 -0
- package/dist/cjs/local-authority.js +817 -0
- package/dist/cjs/local-ws-adapter.js +150 -0
- package/dist/cjs/match-log.js +61 -0
- package/dist/cjs/math.js +157 -0
- package/dist/cjs/mock-network-adapter.js +26 -0
- package/dist/cjs/movement.js +703 -0
- package/dist/cjs/movement3d.js +1246 -0
- package/dist/cjs/multiclient-authority.js +308 -0
- package/dist/cjs/multiplayer-service.js +434 -0
- package/dist/cjs/navigation.js +347 -0
- package/dist/cjs/navmesh.js +1437 -0
- package/dist/cjs/networked-client.js +691 -0
- package/dist/cjs/networked-input-authority.js +443 -0
- package/dist/cjs/node.js +47 -0
- package/dist/cjs/number-lab.js +78 -0
- package/dist/cjs/offline-session.js +370 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/pause-resume.js +43 -0
- package/dist/cjs/physics-cert.js +426 -0
- package/dist/cjs/physics2d.js +751 -0
- package/dist/cjs/physics3d.js +1996 -0
- package/dist/cjs/prediction-culling.js +203 -0
- package/dist/cjs/protocol-lab.js +1299 -0
- package/dist/cjs/r3f-render-adapter.js +209 -0
- package/dist/cjs/random.js +59 -0
- package/dist/cjs/replay-bundle.js +1598 -0
- package/dist/cjs/replay-inspect.js +236 -0
- package/dist/cjs/replay-scoring.js +68 -0
- package/dist/cjs/replay.js +356 -0
- package/dist/cjs/reports.js +181 -0
- package/dist/cjs/rollback-history.js +251 -0
- package/dist/cjs/runtime-guards.js +111 -0
- package/dist/cjs/sample-lab.js +145 -0
- package/dist/cjs/sample-scenes.js +752 -0
- package/dist/cjs/schema-artifacts.js +359 -0
- package/dist/cjs/schema-codegen.js +544 -0
- package/dist/cjs/schema-dsl-binary.js +655 -0
- package/dist/cjs/schema.js +319 -0
- package/dist/cjs/sdk-offline.js +99 -0
- package/dist/cjs/session-promotion.js +74 -0
- package/dist/cjs/session-snapshot.js +265 -0
- package/dist/cjs/session-wire.js +216 -0
- package/dist/cjs/signals.js +103 -0
- package/dist/cjs/sparse-set.js +169 -0
- package/dist/cjs/synctest.js +136 -0
- package/dist/cjs/system-lifecycle.js +286 -0
- package/dist/cjs/timers.js +61 -0
- package/dist/cjs/tools/static-checker.js +1468 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/venus-mp-budget.js +125 -0
- package/dist/cjs/ws-frame.js +255 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +11379 -0
- package/dist/collections.d.ts +9 -0
- package/dist/collections.js +30 -0
- package/dist/collision.d.ts +25 -0
- package/dist/collision.js +85 -0
- package/dist/commands.d.ts +137 -0
- package/dist/commands.js +977 -0
- package/dist/config-bundle.d.ts +89 -0
- package/dist/config-bundle.js +483 -0
- package/dist/creator.d.ts +20 -0
- package/dist/creator.js +11 -0
- package/dist/deterministic-primitives.d.ts +59 -0
- package/dist/deterministic-primitives.js +126 -0
- package/dist/deterministic-session.d.ts +197 -0
- package/dist/deterministic-session.js +1401 -0
- package/dist/ecs-lite.d.ts +26 -0
- package/dist/ecs-lite.js +152 -0
- package/dist/effects-adapter.d.ts +21 -0
- package/dist/effects-adapter.js +94 -0
- package/dist/engine-complete.d.ts +45 -0
- package/dist/engine-complete.js +2292 -0
- package/dist/engine-core-proof.d.ts +26 -0
- package/dist/engine-core-proof.js +66 -0
- package/dist/engine-parity-matrix.d.ts +300 -0
- package/dist/engine-parity-matrix.js +869 -0
- package/dist/engine-parity.d.ts +74 -0
- package/dist/engine-parity.js +3178 -0
- package/dist/engine-runtime.d.ts +351 -0
- package/dist/engine-runtime.js +3123 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +17 -0
- package/dist/events.d.ts +45 -0
- package/dist/events.js +242 -0
- package/dist/examples/counter-duel.d.ts +43 -0
- package/dist/examples/counter-duel.js +150 -0
- package/dist/examples/fixed-arena.d.ts +110 -0
- package/dist/examples/fixed-arena.js +356 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +66 -0
- package/dist/input-authority.d.ts +82 -0
- package/dist/input-authority.js +147 -0
- package/dist/input-button.d.ts +36 -0
- package/dist/input-button.js +249 -0
- package/dist/input-timeline.d.ts +65 -0
- package/dist/input-timeline.js +412 -0
- package/dist/instant-replay.d.ts +59 -0
- package/dist/instant-replay.js +62 -0
- package/dist/lag-compensation3d.d.ts +146 -0
- package/dist/lag-compensation3d.js +827 -0
- package/dist/local-authority-server-runner.d.ts +79 -0
- package/dist/local-authority-server-runner.js +1118 -0
- package/dist/local-authority-wire.d.ts +142 -0
- package/dist/local-authority-wire.js +1477 -0
- package/dist/local-authority.d.ts +160 -0
- package/dist/local-authority.js +810 -0
- package/dist/local-ws-adapter.d.ts +14 -0
- package/dist/local-ws-adapter.js +146 -0
- package/dist/match-log.d.ts +74 -0
- package/dist/match-log.js +57 -0
- package/dist/math.d.ts +41 -0
- package/dist/math.js +154 -0
- package/dist/mock-network-adapter.d.ts +13 -0
- package/dist/mock-network-adapter.js +23 -0
- package/dist/movement.d.ts +155 -0
- package/dist/movement.js +694 -0
- package/dist/movement3d.d.ts +195 -0
- package/dist/movement3d.js +1238 -0
- package/dist/multiclient-authority.d.ts +30 -0
- package/dist/multiclient-authority.js +305 -0
- package/dist/multiplayer-service.d.ts +153 -0
- package/dist/multiplayer-service.js +429 -0
- package/dist/navigation.d.ts +91 -0
- package/dist/navigation.js +338 -0
- package/dist/navmesh.d.ts +236 -0
- package/dist/navmesh.js +1423 -0
- package/dist/networked-client.d.ts +124 -0
- package/dist/networked-client.js +688 -0
- package/dist/networked-input-authority.d.ts +51 -0
- package/dist/networked-input-authority.js +439 -0
- package/dist/node.d.ts +22 -0
- package/dist/node.js +19 -0
- package/dist/number-lab.d.ts +15 -0
- package/dist/number-lab.js +75 -0
- package/dist/offline-session.d.ts +2 -0
- package/dist/offline-session.js +367 -0
- package/dist/pause-resume.d.ts +48 -0
- package/dist/pause-resume.js +40 -0
- package/dist/physics-cert.d.ts +40 -0
- package/dist/physics-cert.js +389 -0
- package/dist/physics2d.d.ts +142 -0
- package/dist/physics2d.js +741 -0
- package/dist/physics3d.d.ts +285 -0
- package/dist/physics3d.js +1979 -0
- package/dist/prediction-culling.d.ts +34 -0
- package/dist/prediction-culling.js +200 -0
- package/dist/protocol-lab.d.ts +115 -0
- package/dist/protocol-lab.js +1295 -0
- package/dist/r3f-render-adapter.d.ts +61 -0
- package/dist/r3f-render-adapter.js +203 -0
- package/dist/random.d.ts +13 -0
- package/dist/random.js +55 -0
- package/dist/replay-bundle.d.ts +150 -0
- package/dist/replay-bundle.js +1586 -0
- package/dist/replay-inspect.d.ts +60 -0
- package/dist/replay-inspect.js +228 -0
- package/dist/replay-scoring.d.ts +28 -0
- package/dist/replay-scoring.js +65 -0
- package/dist/replay.d.ts +17 -0
- package/dist/replay.js +351 -0
- package/dist/reports.d.ts +176 -0
- package/dist/reports.js +174 -0
- package/dist/rollback-history.d.ts +46 -0
- package/dist/rollback-history.js +247 -0
- package/dist/runtime-guards.d.ts +1 -0
- package/dist/runtime-guards.js +108 -0
- package/dist/sample-lab.d.ts +32 -0
- package/dist/sample-lab.js +134 -0
- package/dist/sample-scenes.d.ts +86 -0
- package/dist/sample-scenes.js +745 -0
- package/dist/schema-artifacts.d.ts +49 -0
- package/dist/schema-artifacts.js +356 -0
- package/dist/schema-codegen.d.ts +21 -0
- package/dist/schema-codegen.js +538 -0
- package/dist/schema-dsl-binary.d.ts +75 -0
- package/dist/schema-dsl-binary.js +648 -0
- package/dist/schema.d.ts +87 -0
- package/dist/schema.js +315 -0
- package/dist/sdk-offline.d.ts +34 -0
- package/dist/sdk-offline.js +96 -0
- package/dist/session-promotion.d.ts +26 -0
- package/dist/session-promotion.js +70 -0
- package/dist/session-snapshot.d.ts +95 -0
- package/dist/session-snapshot.js +255 -0
- package/dist/session-wire.d.ts +89 -0
- package/dist/session-wire.js +211 -0
- package/dist/signals.d.ts +31 -0
- package/dist/signals.js +99 -0
- package/dist/sparse-set.d.ts +34 -0
- package/dist/sparse-set.js +165 -0
- package/dist/synctest.d.ts +31 -0
- package/dist/synctest.js +133 -0
- package/dist/system-lifecycle.d.ts +72 -0
- package/dist/system-lifecycle.js +281 -0
- package/dist/timers.d.ts +17 -0
- package/dist/timers.js +53 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/static-checker.d.ts +31 -0
- package/dist/tools/static-checker.js +1462 -0
- package/dist/tools/vite-plugin.d.ts +69 -0
- package/dist/tools/vite-plugin.js +203 -0
- package/dist/types.d.ts +148 -0
- package/dist/types.js +1 -0
- package/dist/venus-mp-budget.d.ts +29 -0
- package/dist/venus-mp-budget.js +122 -0
- package/dist/ws-frame.d.ts +49 -0
- package/dist/ws-frame.js +247 -0
- package/package.json +169 -0
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deterministicDslMaxPlayers = deterministicDslMaxPlayers;
|
|
4
|
+
exports.parseDeterministicSchemaDsl = parseDeterministicSchemaDsl;
|
|
5
|
+
exports.createDeterministicBinarySerializer = createDeterministicBinarySerializer;
|
|
6
|
+
exports.runDeterministicDslBinaryFixture = runDeterministicDslBinaryFixture;
|
|
7
|
+
exports.stableDeterministicDslSchemaJson = stableDeterministicDslSchemaJson;
|
|
8
|
+
const canonical_js_1 = require("./canonical.js");
|
|
9
|
+
function deterministicDslMaxPlayers(schema) {
|
|
10
|
+
return Number(schema.pragmas['max-players'] ?? 0);
|
|
11
|
+
}
|
|
12
|
+
function parseDeterministicSchemaDsl(source) {
|
|
13
|
+
const imports = [...source.matchAll(/\bimport\s+(?:"([^"]+)"|'([^']+)'|([A-Za-z_][A-Za-z0-9_./-]*))\s*;/g)]
|
|
14
|
+
.map((match) => match[1] ?? match[2] ?? match[3])
|
|
15
|
+
.sort(compareStrings);
|
|
16
|
+
const pragmaEntries = [...source.matchAll(/#pragma\s+([A-Za-z_][A-Za-z0-9_-]*)\s+([A-Za-z0-9_.:/-]+)/g)]
|
|
17
|
+
.map((match) => [match[1], match[2]])
|
|
18
|
+
.sort((left, right) => compareStrings(left[0], right[0]));
|
|
19
|
+
const pragmas = Object.fromEntries(pragmaEntries);
|
|
20
|
+
assertUniqueStrings(imports, 'DSL import');
|
|
21
|
+
assertUniqueStrings(pragmaEntries.map((entry) => entry[0]), 'DSL pragma');
|
|
22
|
+
const declarations = [...source.matchAll(/\b((?:(?:synced|abstract|local|remote|server|client)\s+)*)(component|singleton|global|input|command|event|signal|struct|enum|flags|union)\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([^}]*)\}/g)]
|
|
23
|
+
.map((match) => ({
|
|
24
|
+
kind: match[2],
|
|
25
|
+
name: match[3],
|
|
26
|
+
fields: match[2] === 'enum' || match[2] === 'flags' ? parseDslSymbolFields(match[4]) : parseDslFields(match[4]),
|
|
27
|
+
modifiers: parseDslDeclarationModifiers(match[1]),
|
|
28
|
+
}))
|
|
29
|
+
.sort(compareDeclarations);
|
|
30
|
+
if (declarations.length === 0) {
|
|
31
|
+
throw new Error('Schema DSL did not contain any declarations');
|
|
32
|
+
}
|
|
33
|
+
const names = new Set();
|
|
34
|
+
for (const declaration of declarations) {
|
|
35
|
+
if (names.has(declaration.name)) {
|
|
36
|
+
throw new Error(`Duplicate DSL declaration ${declaration.name}`);
|
|
37
|
+
}
|
|
38
|
+
names.add(declaration.name);
|
|
39
|
+
}
|
|
40
|
+
const body = { imports, pragmas, declarations };
|
|
41
|
+
return { ...body, schemaHash: (0, canonical_js_1.defaultChecksum)(body) };
|
|
42
|
+
}
|
|
43
|
+
function createDeterministicBinarySerializer(schema) {
|
|
44
|
+
const declarations = new Map(schema.declarations.map((declaration) => [declaration.name, declaration]));
|
|
45
|
+
return {
|
|
46
|
+
encode(declarationName, value) {
|
|
47
|
+
const declaration = declarations.get(declarationName);
|
|
48
|
+
if (declaration === undefined) {
|
|
49
|
+
throw new Error(`Unknown DSL declaration ${declarationName}`);
|
|
50
|
+
}
|
|
51
|
+
return new Uint8Array(encodeFields(declaration, value));
|
|
52
|
+
},
|
|
53
|
+
decode(declarationName, bytes) {
|
|
54
|
+
const declaration = declarations.get(declarationName);
|
|
55
|
+
if (declaration === undefined) {
|
|
56
|
+
throw new Error(`Unknown DSL declaration ${declarationName}`);
|
|
57
|
+
}
|
|
58
|
+
return decodeFields(declaration, bytes);
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function runDeterministicDslBinaryFixture() {
|
|
63
|
+
const source = `
|
|
64
|
+
#pragma max-players 128
|
|
65
|
+
import "shared/loadouts";
|
|
66
|
+
enum Team { Red; Blue; }
|
|
67
|
+
flags AbilityFlags { Flying; Shielded; }
|
|
68
|
+
component Position { x: fixed; y: fixed; z: fixed; }
|
|
69
|
+
singleton MatchState { tick: int; overtime: bool; }
|
|
70
|
+
global RuntimeConfig { maxPlayers: int; modes: set<string>; weights: dictionary<string,int>; }
|
|
71
|
+
input Move { dx: int; dz: int; jump: button; }
|
|
72
|
+
command Spawn { prototype: asset_ref<Prototype>; owner: player_ref; targets: entity-ref[2]; count: int; }
|
|
73
|
+
synced event DamageEvent { source: entity-ref; target: entity-ref; amount: int; team: enum<Team>; flags: bitset<AbilityFlags>; }
|
|
74
|
+
signal ScoreSignal { slot: int; points: int; }
|
|
75
|
+
union TargetUnion { entity: entity_ref; owner: player_ref; }
|
|
76
|
+
struct Loadout { weapons: asset-ref[]; flags: bool[3]; inventory: list<asset_ref<Prototype>>; tags: hash_set<string>; ammo: dictionary<string,int>; }
|
|
77
|
+
`;
|
|
78
|
+
const permutedSource = `
|
|
79
|
+
#pragma max-players 128
|
|
80
|
+
import shared/loadouts;
|
|
81
|
+
struct Loadout { tags: hash_set<string>; inventory: list<asset_ref<Prototype>>; flags: bool[3]; ammo: dictionary<string,int>; weapons: asset-ref[]; }
|
|
82
|
+
union TargetUnion { owner: player_ref; entity: entity_ref; }
|
|
83
|
+
signal ScoreSignal { points: int; slot: int; }
|
|
84
|
+
synced event DamageEvent { amount: int; flags: bitset<AbilityFlags>; source: entity-ref; target: entity-ref; team: enum<Team>; }
|
|
85
|
+
command Spawn { count: int; owner: player_ref; prototype: asset_ref<Prototype>; targets: entity-ref[2]; }
|
|
86
|
+
input Move { dx: int; dz: int; jump: button; }
|
|
87
|
+
global RuntimeConfig { weights: dictionary<string, int>; modes: set<string>; maxPlayers: int; }
|
|
88
|
+
singleton MatchState { tick: int; overtime: bool; }
|
|
89
|
+
component Position { x: fixed; y: fixed; z: fixed; }
|
|
90
|
+
flags AbilityFlags { Shielded; Flying; }
|
|
91
|
+
enum Team { Blue; Red; }
|
|
92
|
+
`;
|
|
93
|
+
const schema = parseDeterministicSchemaDsl(source);
|
|
94
|
+
const permuted = parseDeterministicSchemaDsl(permutedSource);
|
|
95
|
+
const serializer = createDeterministicBinarySerializer(schema);
|
|
96
|
+
const samples = [
|
|
97
|
+
serializer.encode('Position', { x: 1.25, y: -2.5, z: 3 }),
|
|
98
|
+
serializer.encode('MatchState', { tick: 120, overtime: true }),
|
|
99
|
+
serializer.encode('RuntimeConfig', {
|
|
100
|
+
maxPlayers: 128,
|
|
101
|
+
modes: ['duel', 'arena'],
|
|
102
|
+
weights: [{ key: 'duel', value: 2 }, { key: 'arena', value: 1 }],
|
|
103
|
+
}),
|
|
104
|
+
serializer.encode('Move', { dx: -1, dz: 1, jump: true }),
|
|
105
|
+
serializer.encode('Spawn', { prototype: 'enemy.basic', owner: 4, targets: [11, 12], count: 3 }),
|
|
106
|
+
serializer.encode('DamageEvent', { source: 1, target: 2, amount: 7, team: 1, flags: 3 }),
|
|
107
|
+
serializer.encode('ScoreSignal', { slot: 3, points: 25 }),
|
|
108
|
+
serializer.encode('TargetUnion', { entity: 12, owner: 4 }),
|
|
109
|
+
serializer.encode('Loadout', {
|
|
110
|
+
weapons: ['weapon.a', 'weapon.b'],
|
|
111
|
+
flags: [true, false, true],
|
|
112
|
+
inventory: ['item.z', 'item.a'],
|
|
113
|
+
tags: ['rare', 'starter'],
|
|
114
|
+
ammo: [{ key: 'rocket', value: 2 }, { key: 'bullet', value: 30 }],
|
|
115
|
+
}),
|
|
116
|
+
];
|
|
117
|
+
const decoded = [
|
|
118
|
+
serializer.decode('Position', samples[0]),
|
|
119
|
+
serializer.decode('MatchState', samples[1]),
|
|
120
|
+
serializer.decode('RuntimeConfig', samples[2]),
|
|
121
|
+
serializer.decode('Move', samples[3]),
|
|
122
|
+
serializer.decode('Spawn', samples[4]),
|
|
123
|
+
serializer.decode('DamageEvent', samples[5]),
|
|
124
|
+
serializer.decode('ScoreSignal', samples[6]),
|
|
125
|
+
serializer.decode('TargetUnion', samples[7]),
|
|
126
|
+
serializer.decode('Loadout', samples[8]),
|
|
127
|
+
];
|
|
128
|
+
let rejectedBadField = false;
|
|
129
|
+
try {
|
|
130
|
+
serializer.encode('Move', { dx: 1, jump: false });
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
rejectedBadField = true;
|
|
134
|
+
}
|
|
135
|
+
let rejectedExtraField = false;
|
|
136
|
+
try {
|
|
137
|
+
serializer.encode('Move', { dx: 1, dz: 0, jump: false, extra: 1 });
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
rejectedExtraField = true;
|
|
141
|
+
}
|
|
142
|
+
let rejectedBadDeclaration = false;
|
|
143
|
+
try {
|
|
144
|
+
serializer.encode('Missing', {});
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
rejectedBadDeclaration = true;
|
|
148
|
+
}
|
|
149
|
+
let rejectedDuplicateSetValue = false;
|
|
150
|
+
try {
|
|
151
|
+
serializer.encode('RuntimeConfig', {
|
|
152
|
+
maxPlayers: 128,
|
|
153
|
+
modes: ['arena', 'arena'],
|
|
154
|
+
weights: [{ key: 'arena', value: 1 }],
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
rejectedDuplicateSetValue = true;
|
|
159
|
+
}
|
|
160
|
+
let rejectedDuplicateDictionaryKey = false;
|
|
161
|
+
try {
|
|
162
|
+
serializer.encode('RuntimeConfig', {
|
|
163
|
+
maxPlayers: 128,
|
|
164
|
+
modes: ['arena'],
|
|
165
|
+
weights: [{ key: 'arena', value: 1 }, { key: 'arena', value: 2 }],
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
rejectedDuplicateDictionaryKey = true;
|
|
170
|
+
}
|
|
171
|
+
let rejectedWrongFixedArrayLength = false;
|
|
172
|
+
try {
|
|
173
|
+
serializer.encode('Spawn', { prototype: 'enemy.basic', owner: 4, targets: [11], count: 3 });
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
rejectedWrongFixedArrayLength = true;
|
|
177
|
+
}
|
|
178
|
+
const fields = schema.declarations.flatMap((declaration) => declaration.fields);
|
|
179
|
+
return {
|
|
180
|
+
schemaHash: schema.schemaHash,
|
|
181
|
+
declarations: schema.declarations.length,
|
|
182
|
+
fieldKinds: [...new Set(fields.map((field) => field.kind))].sort(compareStrings),
|
|
183
|
+
eventDeclarations: schema.declarations.filter((declaration) => declaration.kind === 'event').length,
|
|
184
|
+
signalDeclarations: schema.declarations.filter((declaration) => declaration.kind === 'signal').length,
|
|
185
|
+
structDeclarations: schema.declarations.filter((declaration) => declaration.kind === 'struct').length,
|
|
186
|
+
globalDeclarations: schema.declarations.filter((declaration) => declaration.kind === 'global').length,
|
|
187
|
+
importCount: schema.imports.length,
|
|
188
|
+
pragmaCount: stableRecordKeys(schema.pragmas).length,
|
|
189
|
+
maxPlayers: deterministicDslMaxPlayers(schema),
|
|
190
|
+
assetRefFields: fields.filter((field) => scalarKindForCollection(field.kind) === 'asset-ref').length,
|
|
191
|
+
entityRefFields: fields.filter((field) => scalarKindForCollection(field.kind) === 'entity-ref').length,
|
|
192
|
+
arrayFields: fields.filter((field) => isArrayKind(field.kind) || isFixedArrayKind(field.kind)).length,
|
|
193
|
+
fixedArrayFields: fields.filter((field) => isFixedArrayKind(field.kind)).length,
|
|
194
|
+
listFields: fields.filter((field) => isListKind(field.kind)).length,
|
|
195
|
+
setFields: fields.filter((field) => isSetKind(field.kind)).length,
|
|
196
|
+
dictionaryFields: fields.filter((field) => isDictionaryKind(field.kind)).length,
|
|
197
|
+
buttonFields: fields.filter((field) => field.kind === 'button').length,
|
|
198
|
+
enumDeclarations: schema.declarations.filter((declaration) => declaration.kind === 'enum').length,
|
|
199
|
+
flagsDeclarations: schema.declarations.filter((declaration) => declaration.kind === 'flags').length,
|
|
200
|
+
unionDeclarations: schema.declarations.filter((declaration) => declaration.kind === 'union').length,
|
|
201
|
+
playerRefFields: fields.filter((field) => scalarKindForCollection(field.kind) === 'player-ref').length,
|
|
202
|
+
typedAssetRefFields: fields.filter((field) => isTypedAssetRefKind(scalarKindForCollection(field.kind))).length,
|
|
203
|
+
bitsetFields: fields.filter((field) => isBitsetKind(scalarKindForCollection(field.kind))).length,
|
|
204
|
+
declarationModifiers: schema.declarations.reduce((sum, declaration) => sum + declaration.modifiers.length, 0),
|
|
205
|
+
hashSetAliasFields: fields.filter((field) => isSetKind(field.kind)).length,
|
|
206
|
+
encodedBytes: samples.reduce((sum, bytes) => sum + bytes.byteLength, 0),
|
|
207
|
+
roundTripChecksum: (0, canonical_js_1.defaultChecksum)(decoded),
|
|
208
|
+
permutationStable: schema.schemaHash === permuted.schemaHash,
|
|
209
|
+
rejectedBadField,
|
|
210
|
+
rejectedExtraField,
|
|
211
|
+
rejectedBadDeclaration,
|
|
212
|
+
rejectedDuplicateSetValue,
|
|
213
|
+
rejectedDuplicateDictionaryKey,
|
|
214
|
+
rejectedWrongFixedArrayLength,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function parseDslFields(body) {
|
|
218
|
+
const fields = body.split(';')
|
|
219
|
+
.map((part) => part.trim())
|
|
220
|
+
.filter((part) => part.length > 0)
|
|
221
|
+
.map(parseDslField)
|
|
222
|
+
.sort(compareFields);
|
|
223
|
+
const names = new Set();
|
|
224
|
+
for (const field of fields) {
|
|
225
|
+
if (names.has(field.name)) {
|
|
226
|
+
throw new Error(`Duplicate DSL field ${field.name}`);
|
|
227
|
+
}
|
|
228
|
+
names.add(field.name);
|
|
229
|
+
}
|
|
230
|
+
return fields;
|
|
231
|
+
}
|
|
232
|
+
function parseDslField(part) {
|
|
233
|
+
const colonIndex = part.indexOf(':');
|
|
234
|
+
if (colonIndex >= 0) {
|
|
235
|
+
const name = part.slice(0, colonIndex).trim();
|
|
236
|
+
const kind = part.slice(colonIndex + 1).trim();
|
|
237
|
+
if (isDslIdentifier(name) && kind.length > 0) {
|
|
238
|
+
return { name, kind: parseDslFieldKind(kind) };
|
|
239
|
+
}
|
|
240
|
+
throw new Error(`Invalid DSL field ${part}`);
|
|
241
|
+
}
|
|
242
|
+
const separatorIndex = lastWhitespaceRunStart(part);
|
|
243
|
+
if (separatorIndex < 0) {
|
|
244
|
+
throw new Error(`Invalid DSL field ${part}`);
|
|
245
|
+
}
|
|
246
|
+
const kind = part.slice(0, separatorIndex).trim();
|
|
247
|
+
const name = part.slice(separatorIndex).trim();
|
|
248
|
+
if (kind.length === 0 || !isDslIdentifier(name)) {
|
|
249
|
+
throw new Error(`Invalid DSL field ${part}`);
|
|
250
|
+
}
|
|
251
|
+
return { name, kind: parseDslFieldKind(kind) };
|
|
252
|
+
}
|
|
253
|
+
function lastWhitespaceRunStart(value) {
|
|
254
|
+
let index = value.length - 1;
|
|
255
|
+
while (index >= 0 && !isWhitespace(value[index])) {
|
|
256
|
+
index -= 1;
|
|
257
|
+
}
|
|
258
|
+
if (index < 0) {
|
|
259
|
+
return -1;
|
|
260
|
+
}
|
|
261
|
+
while (index > 0 && isWhitespace(value[index - 1])) {
|
|
262
|
+
index -= 1;
|
|
263
|
+
}
|
|
264
|
+
return index;
|
|
265
|
+
}
|
|
266
|
+
function isDslIdentifier(value) {
|
|
267
|
+
if (value.length === 0 || !isIdentifierStart(value.charCodeAt(0))) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
for (let index = 1; index < value.length; index += 1) {
|
|
271
|
+
if (!isIdentifierPart(value.charCodeAt(index))) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
function isIdentifierStart(code) {
|
|
278
|
+
return (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code === 95;
|
|
279
|
+
}
|
|
280
|
+
function isIdentifierPart(code) {
|
|
281
|
+
return isIdentifierStart(code) || (code >= 48 && code <= 57);
|
|
282
|
+
}
|
|
283
|
+
function isWhitespace(value) {
|
|
284
|
+
return value === ' ' || value === '\n' || value === '\r' || value === '\t' || value === '\f';
|
|
285
|
+
}
|
|
286
|
+
function parseDslSymbolFields(body) {
|
|
287
|
+
const fields = body.split(';')
|
|
288
|
+
.map((part) => part.trim())
|
|
289
|
+
.filter((part) => part.length > 0)
|
|
290
|
+
.map((part) => {
|
|
291
|
+
const match = /^([A-Za-z_][A-Za-z0-9_]*)(?:\s*=\s*-?[0-9]+)?$/.exec(part);
|
|
292
|
+
if (match === null) {
|
|
293
|
+
throw new Error(`Invalid DSL symbol ${part}`);
|
|
294
|
+
}
|
|
295
|
+
return { name: match[1], kind: 'int' };
|
|
296
|
+
})
|
|
297
|
+
.sort(compareFields);
|
|
298
|
+
const names = new Set();
|
|
299
|
+
for (const field of fields) {
|
|
300
|
+
if (names.has(field.name)) {
|
|
301
|
+
throw new Error(`Duplicate DSL symbol ${field.name}`);
|
|
302
|
+
}
|
|
303
|
+
names.add(field.name);
|
|
304
|
+
}
|
|
305
|
+
return fields;
|
|
306
|
+
}
|
|
307
|
+
function parseDslDeclarationModifiers(raw) {
|
|
308
|
+
const modifiers = raw.trim().length === 0
|
|
309
|
+
? []
|
|
310
|
+
: raw.trim().split(/\s+/).filter((modifier) => modifier.length > 0).sort(compareStrings);
|
|
311
|
+
assertUniqueStrings(modifiers, 'DSL declaration modifier');
|
|
312
|
+
return modifiers;
|
|
313
|
+
}
|
|
314
|
+
function encodeFields(declaration, value) {
|
|
315
|
+
const bytes = [];
|
|
316
|
+
const fieldNames = new Set(declaration.fields.map((field) => field.name));
|
|
317
|
+
for (const key of Object.keys(value).sort(compareStrings)) {
|
|
318
|
+
if (!fieldNames.has(key)) {
|
|
319
|
+
throw new Error(`Unknown field ${key} for ${declaration.name}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
for (const field of declaration.fields) {
|
|
323
|
+
if (!(field.name in value)) {
|
|
324
|
+
throw new Error(`Missing field ${field.name} for ${declaration.name}`);
|
|
325
|
+
}
|
|
326
|
+
appendField(bytes, field.kind, value[field.name]);
|
|
327
|
+
}
|
|
328
|
+
return bytes;
|
|
329
|
+
}
|
|
330
|
+
function appendField(bytes, kind, value) {
|
|
331
|
+
if (isDictionaryKind(kind)) {
|
|
332
|
+
appendDictionaryField(bytes, kind, value);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (isArrayLikeKind(kind)) {
|
|
336
|
+
if (!Array.isArray(value)) {
|
|
337
|
+
throw new Error(`Expected array for ${kind} field`);
|
|
338
|
+
}
|
|
339
|
+
const fixedLength = fixedArrayLength(kind);
|
|
340
|
+
if (fixedLength !== undefined && value.length !== fixedLength) {
|
|
341
|
+
throw new Error(`Fixed array ${kind} field requires exactly ${fixedLength} values`);
|
|
342
|
+
}
|
|
343
|
+
if (fixedLength === undefined && value.length > 65535) {
|
|
344
|
+
throw new Error('Array field too large');
|
|
345
|
+
}
|
|
346
|
+
const scalarKind = scalarKindForCollection(kind);
|
|
347
|
+
const values = isSetKind(kind) ? canonicalSetValues(value, scalarKind) : value;
|
|
348
|
+
if (fixedLength === undefined) {
|
|
349
|
+
appendUint16(bytes, values.length);
|
|
350
|
+
}
|
|
351
|
+
for (const item of values) {
|
|
352
|
+
if (isDictionaryEntry(item)) {
|
|
353
|
+
throw new Error(`Invalid ${kind} field value`);
|
|
354
|
+
}
|
|
355
|
+
appendScalarField(bytes, scalarKind, item);
|
|
356
|
+
}
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (Array.isArray(value)) {
|
|
360
|
+
throw new Error(`Unexpected array for ${kind} field`);
|
|
361
|
+
}
|
|
362
|
+
if (typeof value !== 'boolean' && typeof value !== 'number' && typeof value !== 'string') {
|
|
363
|
+
throw new Error(`Invalid ${kind} field value`);
|
|
364
|
+
}
|
|
365
|
+
appendScalarField(bytes, kind, value);
|
|
366
|
+
}
|
|
367
|
+
function appendScalarField(bytes, kind, value) {
|
|
368
|
+
if (kind === 'bool' || kind === 'button') {
|
|
369
|
+
if (typeof value !== 'boolean') {
|
|
370
|
+
throw new Error(`${kind} field requires boolean value`);
|
|
371
|
+
}
|
|
372
|
+
bytes.push(value ? 1 : 0);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (kind === 'string' || kind === 'asset-ref' || isTypedAssetRefKind(kind)) {
|
|
376
|
+
if (typeof value !== 'string') {
|
|
377
|
+
throw new Error(`${kind} field requires string value`);
|
|
378
|
+
}
|
|
379
|
+
const encoded = (0, canonical_js_1.encodeUtf8)(value);
|
|
380
|
+
if (encoded.byteLength > 65535) {
|
|
381
|
+
throw new Error('String field too large');
|
|
382
|
+
}
|
|
383
|
+
appendUint16(bytes, encoded.byteLength);
|
|
384
|
+
for (const byte of encoded) {
|
|
385
|
+
bytes.push(byte);
|
|
386
|
+
}
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const numeric = kind === 'fixed' ? Math.round(Number(value) * 65536) : Number(value);
|
|
390
|
+
if (!Number.isInteger(numeric)) {
|
|
391
|
+
throw new Error(`Non-integer ${kind} value`);
|
|
392
|
+
}
|
|
393
|
+
if (numeric < -2147483648 || numeric > 2147483647) {
|
|
394
|
+
throw new Error(`${kind} value outside int32 range`);
|
|
395
|
+
}
|
|
396
|
+
appendInt32(bytes, numeric);
|
|
397
|
+
}
|
|
398
|
+
function decodeFields(declaration, bytes) {
|
|
399
|
+
let offset = 0;
|
|
400
|
+
const value = {};
|
|
401
|
+
for (const field of declaration.fields) {
|
|
402
|
+
const decoded = readField(bytes, offset, field.kind);
|
|
403
|
+
value[field.name] = decoded.value;
|
|
404
|
+
offset = decoded.offset;
|
|
405
|
+
}
|
|
406
|
+
if (offset !== bytes.byteLength) {
|
|
407
|
+
throw new Error(`Trailing bytes for ${declaration.name}`);
|
|
408
|
+
}
|
|
409
|
+
return value;
|
|
410
|
+
}
|
|
411
|
+
function readField(bytes, offset, kind) {
|
|
412
|
+
if (isDictionaryKind(kind)) {
|
|
413
|
+
return readDictionaryField(bytes, offset, kind);
|
|
414
|
+
}
|
|
415
|
+
if (isArrayLikeKind(kind)) {
|
|
416
|
+
const fixedLength = fixedArrayLength(kind);
|
|
417
|
+
const length = fixedLength ?? readUint16(bytes, offset);
|
|
418
|
+
let nextOffset = fixedLength === undefined ? offset + 2 : offset;
|
|
419
|
+
const scalarKind = scalarKindForCollection(kind);
|
|
420
|
+
const value = [];
|
|
421
|
+
for (let index = 0; index < length; index += 1) {
|
|
422
|
+
const decoded = readScalarField(bytes, nextOffset, scalarKind);
|
|
423
|
+
value.push(decoded.value);
|
|
424
|
+
nextOffset = decoded.offset;
|
|
425
|
+
}
|
|
426
|
+
return { value, offset: nextOffset };
|
|
427
|
+
}
|
|
428
|
+
return readScalarField(bytes, offset, kind);
|
|
429
|
+
}
|
|
430
|
+
function readScalarField(bytes, offset, kind) {
|
|
431
|
+
if (kind === 'bool' || kind === 'button') {
|
|
432
|
+
assertAvailable(bytes, offset, 1);
|
|
433
|
+
if (bytes[offset] !== 0 && bytes[offset] !== 1) {
|
|
434
|
+
throw new Error(`Invalid ${kind} byte`);
|
|
435
|
+
}
|
|
436
|
+
return { value: bytes[offset] === 1, offset: offset + 1 };
|
|
437
|
+
}
|
|
438
|
+
if (kind === 'string' || kind === 'asset-ref' || isTypedAssetRefKind(kind)) {
|
|
439
|
+
const length = readUint16(bytes, offset);
|
|
440
|
+
const start = offset + 2;
|
|
441
|
+
const end = start + length;
|
|
442
|
+
assertAvailable(bytes, start, length);
|
|
443
|
+
return {
|
|
444
|
+
value: (0, canonical_js_1.decodeUtf8)(bytes.slice(start, end)),
|
|
445
|
+
offset: end,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
const int = readInt32(bytes, offset);
|
|
449
|
+
return { value: kind === 'fixed' ? int / 65536 : int, offset: offset + 4 };
|
|
450
|
+
}
|
|
451
|
+
function appendInt32(bytes, value) {
|
|
452
|
+
bytes.push(value & 0xff, (value >> 8) & 0xff, (value >> 16) & 0xff, (value >> 24) & 0xff);
|
|
453
|
+
}
|
|
454
|
+
function appendUint16(bytes, value) {
|
|
455
|
+
bytes.push(value & 0xff, (value >> 8) & 0xff);
|
|
456
|
+
}
|
|
457
|
+
function readInt32(bytes, offset) {
|
|
458
|
+
assertAvailable(bytes, offset, 4);
|
|
459
|
+
return (bytes[offset] | (bytes[offset + 1] << 8) | (bytes[offset + 2] << 16) | (bytes[offset + 3] << 24)) | 0;
|
|
460
|
+
}
|
|
461
|
+
function readUint16(bytes, offset) {
|
|
462
|
+
assertAvailable(bytes, offset, 2);
|
|
463
|
+
return bytes[offset] | (bytes[offset + 1] << 8);
|
|
464
|
+
}
|
|
465
|
+
function assertAvailable(bytes, offset, length) {
|
|
466
|
+
if (offset < 0 || offset + length > bytes.byteLength) {
|
|
467
|
+
throw new Error('Truncated DSL binary payload');
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function isArrayKind(kind) {
|
|
471
|
+
return kind.endsWith('[]');
|
|
472
|
+
}
|
|
473
|
+
function isFixedArrayKind(kind) {
|
|
474
|
+
return new RegExp(`^${scalarKindPattern()}\\[[1-9][0-9]*\\]$`).test(kind);
|
|
475
|
+
}
|
|
476
|
+
function isListKind(kind) {
|
|
477
|
+
return kind.startsWith('list<') && kind.endsWith('>');
|
|
478
|
+
}
|
|
479
|
+
function isSetKind(kind) {
|
|
480
|
+
return kind.startsWith('set<') && kind.endsWith('>');
|
|
481
|
+
}
|
|
482
|
+
function isDictionaryKind(kind) {
|
|
483
|
+
return kind.startsWith('dictionary<') && kind.endsWith('>');
|
|
484
|
+
}
|
|
485
|
+
function isArrayLikeKind(kind) {
|
|
486
|
+
return isArrayKind(kind) || isFixedArrayKind(kind) || isListKind(kind) || isSetKind(kind);
|
|
487
|
+
}
|
|
488
|
+
function scalarKindForCollection(kind) {
|
|
489
|
+
if (isArrayKind(kind)) {
|
|
490
|
+
return kind.slice(0, -2);
|
|
491
|
+
}
|
|
492
|
+
if (isFixedArrayKind(kind)) {
|
|
493
|
+
return kind.slice(0, kind.indexOf('['));
|
|
494
|
+
}
|
|
495
|
+
if (isListKind(kind) || isSetKind(kind)) {
|
|
496
|
+
return kind.slice(kind.indexOf('<') + 1, -1);
|
|
497
|
+
}
|
|
498
|
+
if (isDictionaryKind(kind)) {
|
|
499
|
+
return kind.slice(kind.indexOf('<') + 1, kind.indexOf(','));
|
|
500
|
+
}
|
|
501
|
+
return kind;
|
|
502
|
+
}
|
|
503
|
+
function parseDslFieldKind(rawKind) {
|
|
504
|
+
const normalized = normalizeDslFieldKind(rawKind);
|
|
505
|
+
const scalar = scalarKindPattern();
|
|
506
|
+
if (new RegExp(`^${scalar}(\\[\\])?$`).test(normalized)) {
|
|
507
|
+
return normalized;
|
|
508
|
+
}
|
|
509
|
+
if (new RegExp(`^${scalar}\\[[1-9][0-9]*\\]$`).test(normalized)) {
|
|
510
|
+
return normalized;
|
|
511
|
+
}
|
|
512
|
+
if (new RegExp(`^(list|set)<${scalar}>$`).test(normalized)) {
|
|
513
|
+
return normalized;
|
|
514
|
+
}
|
|
515
|
+
if (new RegExp(`^dictionary<${scalar},${scalar}>$`).test(normalized)) {
|
|
516
|
+
return normalized;
|
|
517
|
+
}
|
|
518
|
+
throw new Error(`Invalid DSL field kind ${rawKind}`);
|
|
519
|
+
}
|
|
520
|
+
function normalizeDslFieldKind(rawKind) {
|
|
521
|
+
return rawKind
|
|
522
|
+
.replace(/\bFP\b/g, 'fixed')
|
|
523
|
+
.replace(/\bInt32\b/g, 'int')
|
|
524
|
+
.replace(/\bBoolean\b/g, 'bool')
|
|
525
|
+
.replace(/\bEntityRef\b/g, 'entity-ref')
|
|
526
|
+
.replace(/\bPlayerRef\b/g, 'player-ref')
|
|
527
|
+
.replace(/\bAssetRef<([A-Za-z_][A-Za-z0-9_]*)>/g, 'asset-ref:$1')
|
|
528
|
+
.replace(/\s+/g, '')
|
|
529
|
+
.replace(/\bentity_ref\b/g, 'entity-ref')
|
|
530
|
+
.replace(/\bplayer_ref\b/g, 'player-ref')
|
|
531
|
+
.replace(/\bhash_set</g, 'set<')
|
|
532
|
+
.replace(/\basset_ref<([A-Za-z_][A-Za-z0-9_]*)>/g, 'asset-ref:$1')
|
|
533
|
+
.replace(/\basset-ref<([A-Za-z_][A-Za-z0-9_]*)>/g, 'asset-ref:$1')
|
|
534
|
+
.replace(/\benum<([A-Za-z_][A-Za-z0-9_]*)>/g, 'enum:$1')
|
|
535
|
+
.replace(/\bflags<([A-Za-z_][A-Za-z0-9_]*)>/g, 'flags:$1')
|
|
536
|
+
.replace(/\bbitset<([A-Za-z_][A-Za-z0-9_]*)>/g, 'bitset:$1');
|
|
537
|
+
}
|
|
538
|
+
function scalarKindPattern() {
|
|
539
|
+
return '(?:int|fixed|bool|button|string|asset-ref|entity-ref|player-ref|asset-ref:[A-Za-z_][A-Za-z0-9_]*|enum:[A-Za-z_][A-Za-z0-9_]*|flags:[A-Za-z_][A-Za-z0-9_]*|bitset:[A-Za-z_][A-Za-z0-9_]*)';
|
|
540
|
+
}
|
|
541
|
+
function isTypedAssetRefKind(kind) {
|
|
542
|
+
return kind.startsWith('asset-ref:');
|
|
543
|
+
}
|
|
544
|
+
function isBitsetKind(kind) {
|
|
545
|
+
return kind.startsWith('bitset:');
|
|
546
|
+
}
|
|
547
|
+
function fixedArrayLength(kind) {
|
|
548
|
+
if (!isFixedArrayKind(kind)) {
|
|
549
|
+
return undefined;
|
|
550
|
+
}
|
|
551
|
+
return Number(kind.slice(kind.indexOf('[') + 1, -1));
|
|
552
|
+
}
|
|
553
|
+
function appendDictionaryField(bytes, kind, value) {
|
|
554
|
+
if (!Array.isArray(value)) {
|
|
555
|
+
throw new Error(`Expected dictionary entries for ${kind} field`);
|
|
556
|
+
}
|
|
557
|
+
const entries = canonicalDictionaryEntries(value, kind);
|
|
558
|
+
if (entries.length > 65535) {
|
|
559
|
+
throw new Error('Dictionary field too large');
|
|
560
|
+
}
|
|
561
|
+
appendUint16(bytes, entries.length);
|
|
562
|
+
const [keyKind, valueKind] = dictionaryKinds(kind);
|
|
563
|
+
for (const entry of entries) {
|
|
564
|
+
appendScalarField(bytes, keyKind, entry.key);
|
|
565
|
+
appendScalarField(bytes, valueKind, entry.value);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function readDictionaryField(bytes, offset, kind) {
|
|
569
|
+
const length = readUint16(bytes, offset);
|
|
570
|
+
let nextOffset = offset + 2;
|
|
571
|
+
const [keyKind, valueKind] = dictionaryKinds(kind);
|
|
572
|
+
const value = [];
|
|
573
|
+
for (let index = 0; index < length; index += 1) {
|
|
574
|
+
const key = readScalarField(bytes, nextOffset, keyKind);
|
|
575
|
+
const decodedValue = readScalarField(bytes, key.offset, valueKind);
|
|
576
|
+
value.push({ key: key.value, value: decodedValue.value });
|
|
577
|
+
nextOffset = decodedValue.offset;
|
|
578
|
+
}
|
|
579
|
+
return { value, offset: nextOffset };
|
|
580
|
+
}
|
|
581
|
+
function canonicalSetValues(value, scalarKind) {
|
|
582
|
+
const values = value.map((item) => {
|
|
583
|
+
if (isDictionaryEntry(item)) {
|
|
584
|
+
throw new Error('Set field cannot contain dictionary entries');
|
|
585
|
+
}
|
|
586
|
+
return item;
|
|
587
|
+
}).sort(compareScalarValues);
|
|
588
|
+
let previous;
|
|
589
|
+
for (const item of values) {
|
|
590
|
+
const key = scalarValueKey(item);
|
|
591
|
+
if (previous === key) {
|
|
592
|
+
throw new Error(`Duplicate ${scalarKind} set value ${key}`);
|
|
593
|
+
}
|
|
594
|
+
previous = key;
|
|
595
|
+
}
|
|
596
|
+
return values;
|
|
597
|
+
}
|
|
598
|
+
function canonicalDictionaryEntries(value, kind) {
|
|
599
|
+
const entries = value.map((entry) => {
|
|
600
|
+
if (!isDictionaryEntry(entry)) {
|
|
601
|
+
throw new Error(`Invalid ${kind} dictionary entry`);
|
|
602
|
+
}
|
|
603
|
+
return entry;
|
|
604
|
+
}).sort(compareDictionaryEntries);
|
|
605
|
+
let previous;
|
|
606
|
+
for (const entry of entries) {
|
|
607
|
+
const key = scalarValueKey(entry.key);
|
|
608
|
+
if (previous === key) {
|
|
609
|
+
throw new Error(`Duplicate dictionary key ${key}`);
|
|
610
|
+
}
|
|
611
|
+
previous = key;
|
|
612
|
+
}
|
|
613
|
+
return entries;
|
|
614
|
+
}
|
|
615
|
+
function dictionaryKinds(kind) {
|
|
616
|
+
const body = kind.slice('dictionary<'.length, -1);
|
|
617
|
+
const [keyKind, valueKind] = body.split(',');
|
|
618
|
+
return [keyKind, valueKind];
|
|
619
|
+
}
|
|
620
|
+
function isDictionaryEntry(value) {
|
|
621
|
+
return typeof value === 'object' && value !== null && 'key' in value && 'value' in value;
|
|
622
|
+
}
|
|
623
|
+
function assertUniqueStrings(values, label) {
|
|
624
|
+
const seen = new Set();
|
|
625
|
+
for (const value of values) {
|
|
626
|
+
if (seen.has(value)) {
|
|
627
|
+
throw new Error(`Duplicate ${label} ${value}`);
|
|
628
|
+
}
|
|
629
|
+
seen.add(value);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function stableRecordKeys(record) {
|
|
633
|
+
return Object.keys(record).sort(compareStrings);
|
|
634
|
+
}
|
|
635
|
+
function compareScalarValues(left, right) {
|
|
636
|
+
return scalarValueKey(left) < scalarValueKey(right) ? -1 : scalarValueKey(left) > scalarValueKey(right) ? 1 : 0;
|
|
637
|
+
}
|
|
638
|
+
function compareDictionaryEntries(left, right) {
|
|
639
|
+
return compareScalarValues(left.key, right.key) || compareScalarValues(left.value, right.value);
|
|
640
|
+
}
|
|
641
|
+
function scalarValueKey(value) {
|
|
642
|
+
return `${typeof value}:${String(value)}`;
|
|
643
|
+
}
|
|
644
|
+
function compareDeclarations(left, right) {
|
|
645
|
+
return compareStrings(left.kind, right.kind) || compareStrings(left.name, right.name);
|
|
646
|
+
}
|
|
647
|
+
function compareFields(left, right) {
|
|
648
|
+
return compareStrings(left.name, right.name);
|
|
649
|
+
}
|
|
650
|
+
function compareStrings(left, right) {
|
|
651
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
652
|
+
}
|
|
653
|
+
function stableDeterministicDslSchemaJson(schema) {
|
|
654
|
+
return `${(0, canonical_js_1.canonicalStringify)(schema)}\n`;
|
|
655
|
+
}
|