@lagless/core 0.0.36 → 0.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +26 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/lib/di/di-container.d.ts +7 -0
- package/dist/lib/di/di-container.d.ts.map +1 -0
- package/dist/lib/di/di-decorators.d.ts +4 -0
- package/dist/lib/di/di-decorators.d.ts.map +1 -0
- package/dist/lib/di/index.d.ts +3 -0
- package/dist/lib/di/index.d.ts.map +1 -0
- package/dist/lib/ecs-config.d.ts +16 -0
- package/dist/lib/ecs-config.d.ts.map +1 -0
- package/dist/lib/ecs-runner.d.ts +20 -0
- package/dist/lib/ecs-runner.d.ts.map +1 -0
- package/dist/lib/ecs-runner.js +8 -2
- package/dist/lib/ecs-runner.js.map +1 -1
- package/dist/lib/ecs-simulation.d.ts +64 -0
- package/dist/lib/ecs-simulation.d.ts.map +1 -0
- package/dist/lib/ecs-simulation.js +57 -1
- package/dist/lib/ecs-simulation.js.map +1 -1
- package/dist/lib/hash-verification/abstract-hash-verification.system.d.ts +32 -0
- package/dist/lib/hash-verification/abstract-hash-verification.system.d.ts.map +1 -0
- package/dist/lib/hash-verification/abstract-hash-verification.system.js +5 -0
- package/dist/lib/hash-verification/abstract-hash-verification.system.js.map +1 -1
- package/dist/lib/hash-verification/create-hash-reporter.d.ts +16 -0
- package/dist/lib/hash-verification/create-hash-reporter.d.ts.map +1 -0
- package/dist/lib/hash-verification/create-hash-reporter.js +11 -8
- package/dist/lib/hash-verification/create-hash-reporter.js.map +1 -1
- package/dist/lib/hash-verification/divergence.signal.d.ts +11 -0
- package/dist/lib/hash-verification/divergence.signal.d.ts.map +1 -0
- package/dist/lib/hash-verification/index.d.ts +4 -0
- package/dist/lib/hash-verification/index.d.ts.map +1 -0
- package/dist/lib/input/abstract-input-provider.d.ts +62 -0
- package/dist/lib/input/abstract-input-provider.d.ts.map +1 -0
- package/dist/lib/input/abstract-input-provider.js +8 -1
- package/dist/lib/input/abstract-input-provider.js.map +1 -1
- package/dist/lib/input/index.d.ts +8 -0
- package/dist/lib/input/index.d.ts.map +1 -0
- package/dist/lib/input/input-provider-di-token.d.ts +7 -0
- package/dist/lib/input/input-provider-di-token.d.ts.map +1 -0
- package/dist/lib/input/input-provider-di-token.js +3 -0
- package/dist/lib/input/input-provider-di-token.js.map +1 -1
- package/dist/lib/input/input-registry.d.ts +8 -0
- package/dist/lib/input/input-registry.d.ts.map +1 -0
- package/dist/lib/input/local-input-provider.d.ts +7 -0
- package/dist/lib/input/local-input-provider.d.ts.map +1 -0
- package/dist/lib/input/local-input-provider.js +5 -0
- package/dist/lib/input/local-input-provider.js.map +1 -1
- package/dist/lib/input/replay-input-provider.d.ts +13 -0
- package/dist/lib/input/replay-input-provider.d.ts.map +1 -0
- package/dist/lib/input/replay-input-provider.js +5 -0
- package/dist/lib/input/replay-input-provider.js.map +1 -1
- package/dist/lib/input/rpc-history.d.ts +40 -0
- package/dist/lib/input/rpc-history.d.ts.map +1 -0
- package/dist/lib/input/rpc.d.ts +8 -0
- package/dist/lib/input/rpc.d.ts.map +1 -0
- package/dist/lib/mem/abstract-memory.interface.d.ts +6 -0
- package/dist/lib/mem/abstract-memory.interface.d.ts.map +1 -0
- package/dist/lib/mem/index.d.ts +5 -0
- package/dist/lib/mem/index.d.ts.map +1 -0
- package/dist/lib/mem/managers/components-manager.d.ts +15 -0
- package/dist/lib/mem/managers/components-manager.d.ts.map +1 -0
- package/dist/lib/mem/managers/entities-manager.d.ts +32 -0
- package/dist/lib/mem/managers/entities-manager.d.ts.map +1 -0
- package/dist/lib/mem/managers/entities-manager.js +65 -23
- package/dist/lib/mem/managers/entities-manager.js.map +1 -1
- package/dist/lib/mem/managers/filters-manager.d.ts +17 -0
- package/dist/lib/mem/managers/filters-manager.d.ts.map +1 -0
- package/dist/lib/mem/managers/filters-manager.js +19 -4
- package/dist/lib/mem/managers/filters-manager.js.map +1 -1
- package/dist/lib/mem/managers/player-resources-manager.d.ts +21 -0
- package/dist/lib/mem/managers/player-resources-manager.d.ts.map +1 -0
- package/dist/lib/mem/managers/prng-manager.d.ts +36 -0
- package/dist/lib/mem/managers/prng-manager.d.ts.map +1 -0
- package/dist/lib/mem/managers/singletons-manager.d.ts +13 -0
- package/dist/lib/mem/managers/singletons-manager.d.ts.map +1 -0
- package/dist/lib/mem/managers/tick-manager.d.ts +10 -0
- package/dist/lib/mem/managers/tick-manager.d.ts.map +1 -0
- package/dist/lib/mem/mem.d.ts +28 -0
- package/dist/lib/mem/mem.d.ts.map +1 -0
- package/dist/lib/mem/mem.js +4 -2
- package/dist/lib/mem/mem.js.map +1 -1
- package/dist/lib/prefab.d.ts +16 -0
- package/dist/lib/prefab.d.ts.map +1 -0
- package/dist/lib/signals/event-emitter.d.ts +9 -0
- package/dist/lib/signals/event-emitter.d.ts.map +1 -0
- package/dist/lib/signals/signal.d.ts +41 -0
- package/dist/lib/signals/signal.d.ts.map +1 -0
- package/dist/lib/signals/signal.js +34 -34
- package/dist/lib/signals/signal.js.map +1 -1
- package/dist/lib/signals/signals.registry.d.ts +9 -0
- package/dist/lib/signals/signals.registry.d.ts.map +1 -0
- package/dist/lib/signals/signals.registry.js +2 -2
- package/dist/lib/signals/signals.registry.js.map +1 -1
- package/dist/lib/types/abstract-filter.d.ts +17 -0
- package/dist/lib/types/abstract-filter.d.ts.map +1 -0
- package/dist/lib/types/abstract-filter.js +22 -21
- package/dist/lib/types/abstract-filter.js.map +1 -1
- package/dist/lib/types/ecs-types.d.ts +111 -0
- package/dist/lib/types/ecs-types.d.ts.map +1 -0
- package/dist/lib/types/ecs-types.js.map +1 -1
- package/dist/lib/types/index.d.ts +3 -0
- package/dist/lib/types/index.d.ts.map +1 -0
- package/package.json +5 -6
package/LICENSE
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Creative Commons Attribution-NonCommercial 4.0 International
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Lagless
|
|
4
|
+
|
|
5
|
+
This work is licensed under the Creative Commons
|
|
6
|
+
Attribution-NonCommercial 4.0 International License.
|
|
7
|
+
|
|
8
|
+
You are free to:
|
|
9
|
+
|
|
10
|
+
Share — copy and redistribute the material in any medium or format
|
|
11
|
+
Adapt — remix, transform, and build upon the material
|
|
12
|
+
|
|
13
|
+
Under the following terms:
|
|
14
|
+
|
|
15
|
+
Attribution — You must give appropriate credit, provide a link to
|
|
16
|
+
the license, and indicate if changes were made. You may do so in
|
|
17
|
+
any reasonable manner, but not in any way that suggests the licensor
|
|
18
|
+
endorses you or your use.
|
|
19
|
+
|
|
20
|
+
NonCommercial — You may not use the material for commercial purposes.
|
|
21
|
+
|
|
22
|
+
No additional restrictions — You may not apply legal terms or
|
|
23
|
+
technological measures that legally restrict others from doing
|
|
24
|
+
anything the license permits.
|
|
25
|
+
|
|
26
|
+
Full license text: https://creativecommons.org/licenses/by-nc/4.0/legalcode
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './lib/prefab.js';
|
|
2
|
+
export * from './lib/di/index.js';
|
|
3
|
+
export * from './lib/mem/index.js';
|
|
4
|
+
export * from './lib/ecs-config.js';
|
|
5
|
+
export * from './lib/ecs-runner.js';
|
|
6
|
+
export * from './lib/types/index.js';
|
|
7
|
+
export * from './lib/input/index.js';
|
|
8
|
+
export * from './lib/ecs-simulation.js';
|
|
9
|
+
export * from './lib/signals/signal.js';
|
|
10
|
+
export * from './lib/hash-verification/index.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kCAAkC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"di-container.d.ts","sourceRoot":"","sources":["../../../src/lib/di/di-container.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,KAAK,CAAC,CAAC,GAAG,GAAG,IAAI,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;AAEvD,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IAE9C,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;IAgB5B,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI;CAGrD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"di-decorators.d.ts","sourceRoot":"","sources":["../../../src/lib/di/di-decorators.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAO/C,wBAAgB,SAAS,CAAC,GAAG,YAAY,EAAE,KAAK,EAAE,GAAG,cAAc,CAiElE;AAED,wBAAgB,SAAS,CAAC,GAAG,YAAY,EAAE,KAAK,EAAE,GAAG,cAAc,CAoElE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/di/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type RawSeed = Uint8Array;
|
|
2
|
+
export declare class ECSConfig {
|
|
3
|
+
readonly seed: RawSeed;
|
|
4
|
+
readonly maxEntities: number;
|
|
5
|
+
readonly maxPlayers: number;
|
|
6
|
+
readonly initialInputDelayTick: number;
|
|
7
|
+
readonly minInputDelayTick: number;
|
|
8
|
+
readonly maxInputDelayTick: number;
|
|
9
|
+
readonly fps: number;
|
|
10
|
+
readonly frameLength: number;
|
|
11
|
+
readonly snapshotRate: number;
|
|
12
|
+
readonly snapshotHistorySize: number;
|
|
13
|
+
readonly maxNudgePerFrame: number;
|
|
14
|
+
constructor(options?: Partial<ECSConfig>);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=ecs-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ecs-config.d.ts","sourceRoot":"","sources":["../../src/lib/ecs-config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG,UAAU,CAAC;AAIjC,qBAAa,SAAS;IACpB,SAAgB,IAAI,EAAE,OAAO,CAAC;IAC9B,SAAgB,WAAW,EAAE,MAAM,CAAC;IACpC,SAAgB,UAAU,EAAE,MAAM,CAAC;IACnC,SAAgB,qBAAqB,EAAE,MAAM,CAAC;IAC9C,SAAgB,iBAAiB,EAAE,MAAM,CAAC;IAC1C,SAAgB,iBAAiB,EAAE,MAAM,CAAC;IAC1C,SAAgB,GAAG,EAAE,MAAM,CAAC;IAC5B,SAAgB,WAAW,EAAE,MAAM,CAAC;IACpC,SAAgB,YAAY,EAAE,MAAM,CAAC;IACrC,SAAgB,mBAAmB,EAAE,MAAM,CAAC;IAC5C,SAAgB,gBAAgB,EAAE,MAAM,CAAC;gBAE7B,OAAO,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC;CAazC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Container } from './di/index.js';
|
|
2
|
+
import { ECSConfig } from './ecs-config.js';
|
|
3
|
+
import { ECSSimulation } from './ecs-simulation.js';
|
|
4
|
+
import { AbstractInputProvider } from './input/index.js';
|
|
5
|
+
import { ECSDeps, IECSSystemConstructor } from './types/index.js';
|
|
6
|
+
import { ISignalConstructor } from './signals/signal.js';
|
|
7
|
+
export declare abstract class ECSRunner {
|
|
8
|
+
readonly Config: ECSConfig;
|
|
9
|
+
readonly InputProviderInstance: AbstractInputProvider;
|
|
10
|
+
readonly Systems: Array<IECSSystemConstructor>;
|
|
11
|
+
readonly Signals: Array<ISignalConstructor>;
|
|
12
|
+
readonly Deps: ECSDeps;
|
|
13
|
+
readonly DIContainer: Container;
|
|
14
|
+
readonly Simulation: ECSSimulation;
|
|
15
|
+
protected constructor(Config: ECSConfig, InputProviderInstance: AbstractInputProvider, Systems: Array<IECSSystemConstructor>, Signals: Array<ISignalConstructor> | undefined, Deps: ECSDeps, simulation?: ECSSimulation, extraRegistrations?: Array<[unknown, unknown]>);
|
|
16
|
+
start(): void;
|
|
17
|
+
update(dt: number): void;
|
|
18
|
+
dispose(): void;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=ecs-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ecs-runner.d.ts","sourceRoot":"","sources":["../../src/lib/ecs-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAGlE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,8BAAsB,SAAS;aAKX,MAAM,EAAE,SAAS;aACjB,qBAAqB,EAAE,qBAAqB;aAC5C,OAAO,EAAE,KAAK,CAAC,qBAAqB,CAAC;aACrC,OAAO,EAAE,KAAK,CAAC,kBAAkB,CAAC;aAClC,IAAI,EAAE,OAAO;IAR/B,SAAgB,WAAW,EAAE,SAAS,CAAC;IACvC,SAAgB,UAAU,EAAE,aAAa,CAAC;IAE1C,SAAS,aACS,MAAM,EAAE,SAAS,EACjB,qBAAqB,EAAE,qBAAqB,EAC5C,OAAO,EAAE,KAAK,CAAC,qBAAqB,CAAC,EACrC,OAAO,EAAE,KAAK,CAAC,kBAAkB,CAAC,YAAK,EACvC,IAAI,EAAE,OAAO,EAC7B,UAAU,CAAC,EAAE,aAAa,EAC1B,kBAAkB,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAkDzC,KAAK,IAAI,IAAI;IAIb,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIxB,OAAO,IAAI,IAAI;CAMvB"}
|
package/dist/lib/ecs-runner.js
CHANGED
|
@@ -15,14 +15,14 @@ export class ECSRunner {
|
|
|
15
15
|
this.InputProviderInstance.dispose();
|
|
16
16
|
this.Simulation.disposeSignals();
|
|
17
17
|
}
|
|
18
|
-
constructor(Config, InputProviderInstance, Systems, Signals = [], Deps){
|
|
18
|
+
constructor(Config, InputProviderInstance, Systems, Signals = [], Deps, simulation, extraRegistrations){
|
|
19
19
|
this.Config = Config;
|
|
20
20
|
this.InputProviderInstance = InputProviderInstance;
|
|
21
21
|
this.Systems = Systems;
|
|
22
22
|
this.Signals = Signals;
|
|
23
23
|
this.Deps = Deps;
|
|
24
24
|
this.DIContainer = new Container();
|
|
25
|
-
this.Simulation = new ECSSimulation(this.Config, this.Deps, this.InputProviderInstance);
|
|
25
|
+
this.Simulation = simulation != null ? simulation : new ECSSimulation(this.Config, this.Deps, this.InputProviderInstance);
|
|
26
26
|
this.InputProviderInstance.init(this.Simulation);
|
|
27
27
|
const mem = this.Simulation.mem;
|
|
28
28
|
this.DIContainer.register(ECSConfig, this.Config);
|
|
@@ -44,6 +44,12 @@ export class ECSRunner {
|
|
|
44
44
|
}
|
|
45
45
|
// player resources
|
|
46
46
|
this.DIContainer.register(PlayerResources, mem.playerResourcesManager.PlayerResources);
|
|
47
|
+
// extra registrations (from subclasses like PhysicsRunner3d)
|
|
48
|
+
if (extraRegistrations) {
|
|
49
|
+
for (const [token, instance] of extraRegistrations){
|
|
50
|
+
this.DIContainer.register(token, instance);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
47
53
|
// signals
|
|
48
54
|
const signalInstances = this.Signals.map((SignalConstructor)=>{
|
|
49
55
|
return this.DIContainer.resolve(SignalConstructor);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/ecs-runner.ts"],"sourcesContent":["import { Container } from './di/index.js';\nimport { ECSConfig } from './ecs-config.js';\nimport { ECSSimulation } from './ecs-simulation.js';\nimport { AbstractInputProvider } from './input/index.js';\nimport { ECSDeps, IECSSystemConstructor } from './types/index.js';\nimport { InputProvider } from './input/index.js';\nimport { EntitiesManager, PlayerResources, PRNG } from './mem/index.js';\nimport { ISignalConstructor } from './signals/signal.js';\n\nexport abstract class ECSRunner {\n public readonly DIContainer: Container;\n public readonly Simulation: ECSSimulation;\n\n protected constructor(\n public readonly Config: ECSConfig,\n public readonly InputProviderInstance: AbstractInputProvider,\n public readonly Systems: Array<IECSSystemConstructor>,\n public readonly Signals: Array<ISignalConstructor> = [],\n public readonly Deps: ECSDeps,\n ) {\n this.DIContainer = new Container();\n this.Simulation = new ECSSimulation(this.Config, this.Deps, this.InputProviderInstance);\n this.InputProviderInstance.init(this.Simulation);\n\n const mem = this.Simulation.mem;\n\n this.DIContainer.register(ECSConfig, this.Config);\n this.DIContainer.register(InputProvider, this.InputProviderInstance);\n this.DIContainer.register(ECSSimulation, this.Simulation);\n this.DIContainer.register(EntitiesManager, mem.entitiesManager);\n this.DIContainer.register(PRNG, mem.prngManager.prng);\n\n // components\n for (const [ ComponentConstructor, ComponentInstance ] of mem.componentsManager) {\n this.DIContainer.register(ComponentConstructor, ComponentInstance);\n }\n // singletons\n for (const [ SingletonConstructor, SingletonInstance ] of mem.singletonsManager) {\n this.DIContainer.register(SingletonConstructor, SingletonInstance);\n }\n // filters\n for (const [ FilterConstructor, FilterInstance ] of mem.filtersManager) {\n this.DIContainer.register(FilterConstructor, FilterInstance);\n }\n // player resources\n this.DIContainer.register(PlayerResources, mem.playerResourcesManager.PlayerResources);\n\n // signals\n const signalInstances = this.Signals.map((SignalConstructor) => {\n return this.DIContainer.resolve(SignalConstructor);\n });\n this.Simulation.initSignals(signalInstances);\n\n // systems\n const systemInstances = this.Systems.map((SystemConstructor) => {\n return this.DIContainer.resolve(SystemConstructor);\n });\n\n this.Simulation.registerSystems(systemInstances);\n }\n\n public start(): void {\n this.Simulation.start();\n }\n\n public update(dt: number): void {\n this.Simulation.update(dt);\n }\n\n public dispose(): void {\n // ECSRunner disposed\n this.InputProviderInstance.dispose();\n this.Simulation.disposeSignals();\n\n }\n}\n"],"names":["Container","ECSConfig","ECSSimulation","InputProvider","EntitiesManager","PlayerResources","PRNG","ECSRunner","start","Simulation","update","dt","dispose","InputProviderInstance","disposeSignals","Config","Systems","Signals","Deps","DIContainer","init","mem","register","entitiesManager","prngManager","prng","ComponentConstructor","ComponentInstance","componentsManager","SingletonConstructor","SingletonInstance","singletonsManager","FilterConstructor","FilterInstance","filtersManager","playerResourcesManager","signalInstances","map","SignalConstructor","resolve","initSignals","systemInstances","SystemConstructor","registerSystems"],"rangeMappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/lib/ecs-runner.ts"],"sourcesContent":["import { Container } from './di/index.js';\nimport { ECSConfig } from './ecs-config.js';\nimport { ECSSimulation } from './ecs-simulation.js';\nimport { AbstractInputProvider } from './input/index.js';\nimport { ECSDeps, IECSSystemConstructor } from './types/index.js';\nimport { InputProvider } from './input/index.js';\nimport { EntitiesManager, PlayerResources, PRNG } from './mem/index.js';\nimport { ISignalConstructor } from './signals/signal.js';\n\nexport abstract class ECSRunner {\n public readonly DIContainer: Container;\n public readonly Simulation: ECSSimulation;\n\n protected constructor(\n public readonly Config: ECSConfig,\n public readonly InputProviderInstance: AbstractInputProvider,\n public readonly Systems: Array<IECSSystemConstructor>,\n public readonly Signals: Array<ISignalConstructor> = [],\n public readonly Deps: ECSDeps,\n simulation?: ECSSimulation,\n extraRegistrations?: Array<[unknown, unknown]>,\n ) {\n this.DIContainer = new Container();\n this.Simulation = simulation ?? new ECSSimulation(this.Config, this.Deps, this.InputProviderInstance);\n this.InputProviderInstance.init(this.Simulation);\n\n const mem = this.Simulation.mem;\n\n this.DIContainer.register(ECSConfig, this.Config);\n this.DIContainer.register(InputProvider, this.InputProviderInstance);\n this.DIContainer.register(ECSSimulation, this.Simulation);\n this.DIContainer.register(EntitiesManager, mem.entitiesManager);\n this.DIContainer.register(PRNG, mem.prngManager.prng);\n\n // components\n for (const [ ComponentConstructor, ComponentInstance ] of mem.componentsManager) {\n this.DIContainer.register(ComponentConstructor, ComponentInstance);\n }\n // singletons\n for (const [ SingletonConstructor, SingletonInstance ] of mem.singletonsManager) {\n this.DIContainer.register(SingletonConstructor, SingletonInstance);\n }\n // filters\n for (const [ FilterConstructor, FilterInstance ] of mem.filtersManager) {\n this.DIContainer.register(FilterConstructor, FilterInstance);\n }\n // player resources\n this.DIContainer.register(PlayerResources, mem.playerResourcesManager.PlayerResources);\n\n // extra registrations (from subclasses like PhysicsRunner3d)\n if (extraRegistrations) {\n for (const [token, instance] of extraRegistrations) {\n this.DIContainer.register(token as new (...args: unknown[]) => unknown, instance);\n }\n }\n\n // signals\n const signalInstances = this.Signals.map((SignalConstructor) => {\n return this.DIContainer.resolve(SignalConstructor);\n });\n this.Simulation.initSignals(signalInstances);\n\n // systems\n const systemInstances = this.Systems.map((SystemConstructor) => {\n return this.DIContainer.resolve(SystemConstructor);\n });\n\n this.Simulation.registerSystems(systemInstances);\n }\n\n public start(): void {\n this.Simulation.start();\n }\n\n public update(dt: number): void {\n this.Simulation.update(dt);\n }\n\n public dispose(): void {\n // ECSRunner disposed\n this.InputProviderInstance.dispose();\n this.Simulation.disposeSignals();\n\n }\n}\n"],"names":["Container","ECSConfig","ECSSimulation","InputProvider","EntitiesManager","PlayerResources","PRNG","ECSRunner","start","Simulation","update","dt","dispose","InputProviderInstance","disposeSignals","Config","Systems","Signals","Deps","simulation","extraRegistrations","DIContainer","init","mem","register","entitiesManager","prngManager","prng","ComponentConstructor","ComponentInstance","componentsManager","SingletonConstructor","SingletonInstance","singletonsManager","FilterConstructor","FilterInstance","filtersManager","playerResourcesManager","token","instance","signalInstances","map","SignalConstructor","resolve","initSignals","systemInstances","SystemConstructor","registerSystems"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Mem } from './mem/index.js';
|
|
2
|
+
import { ECSConfig } from './ecs-config.js';
|
|
3
|
+
import { SimulationClock, SnapshotHistory } from '@lagless/misc';
|
|
4
|
+
import { ECSDeps, IECSSystem } from './types/index.js';
|
|
5
|
+
import { AbstractInputProvider } from './input/index.js';
|
|
6
|
+
export declare class ECSSimulation {
|
|
7
|
+
protected readonly _ECSConfig: ECSConfig;
|
|
8
|
+
private readonly _ECSDeps;
|
|
9
|
+
private readonly _inputProvider;
|
|
10
|
+
readonly mem: Mem;
|
|
11
|
+
readonly clock: SimulationClock;
|
|
12
|
+
private readonly _signalsRegistry;
|
|
13
|
+
private readonly _frameLength;
|
|
14
|
+
private readonly _snapshotRate;
|
|
15
|
+
protected readonly _initialSnapshot: ArrayBuffer;
|
|
16
|
+
private readonly _systems;
|
|
17
|
+
private readonly _onTickHandlers;
|
|
18
|
+
private readonly _onRollbackHandlers;
|
|
19
|
+
private readonly _onStateTransferHandlers;
|
|
20
|
+
private _interpolationFactor;
|
|
21
|
+
protected _snapshotHistory: SnapshotHistory<ArrayBuffer>;
|
|
22
|
+
private _hashTrackingInterval;
|
|
23
|
+
private _hashHistory;
|
|
24
|
+
get tick(): number;
|
|
25
|
+
get interpolationFactor(): number;
|
|
26
|
+
constructor(_ECSConfig: ECSConfig, _ECSDeps: ECSDeps, _inputProvider: AbstractInputProvider);
|
|
27
|
+
addTickHandler(handler: (tick: number) => void): () => void;
|
|
28
|
+
removeTickHandler(handler: (tick: number) => void): void;
|
|
29
|
+
addRollbackHandler(handler: (tick: number) => void): () => void;
|
|
30
|
+
addStateTransferHandler(handler: (tick: number) => void): () => void;
|
|
31
|
+
registerSystems(systems: IECSSystem[]): void;
|
|
32
|
+
initSignals(signals: import('./signals/signal.js').Signal[]): void;
|
|
33
|
+
disposeSignals(): void;
|
|
34
|
+
throwIfSystemsNotRegistered(): void;
|
|
35
|
+
get frameLength(): number;
|
|
36
|
+
get inputProvider(): AbstractInputProvider;
|
|
37
|
+
enableHashTracking(interval: number): void;
|
|
38
|
+
getHashAtTick(tick: number): number | undefined;
|
|
39
|
+
start(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Apply external state received from another client (late-join / reconnect).
|
|
42
|
+
* Replaces the entire simulation state, sets the tick, and resets history.
|
|
43
|
+
*/
|
|
44
|
+
applyExternalState(state: ArrayBuffer, tick: number): void;
|
|
45
|
+
/**
|
|
46
|
+
* Export simulation state for network state transfer (late-join / reconnect).
|
|
47
|
+
* Override in subclasses to include additional state (e.g. physics world).
|
|
48
|
+
*/
|
|
49
|
+
exportStateForTransfer(): ArrayBuffer;
|
|
50
|
+
/**
|
|
51
|
+
* Apply state received from network state transfer (late-join / reconnect).
|
|
52
|
+
* Override in subclasses to restore additional state (e.g. physics world).
|
|
53
|
+
*/
|
|
54
|
+
applyStateFromTransfer(blob: ArrayBuffer, tick: number): void;
|
|
55
|
+
protected notifyStateTransferHandlers(tick: number): void;
|
|
56
|
+
update(dt: number): void;
|
|
57
|
+
private checkAndRollback;
|
|
58
|
+
private simulationTicks;
|
|
59
|
+
protected rollback(tick: number): void;
|
|
60
|
+
protected simulate(tick: number): void;
|
|
61
|
+
private storeSnapshotIfNeeded;
|
|
62
|
+
protected saveSnapshot(tick: number): void;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=ecs-simulation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ecs-simulation.d.ts","sourceRoot":"","sources":["../../src/lib/ecs-simulation.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,18 @@ export class ECSSimulation {
|
|
|
19
19
|
removeTickHandler(handler) {
|
|
20
20
|
this._onTickHandlers.delete(handler);
|
|
21
21
|
}
|
|
22
|
+
addRollbackHandler(handler) {
|
|
23
|
+
this._onRollbackHandlers.add(handler);
|
|
24
|
+
return ()=>{
|
|
25
|
+
this._onRollbackHandlers.delete(handler);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
addStateTransferHandler(handler) {
|
|
29
|
+
this._onStateTransferHandlers.add(handler);
|
|
30
|
+
return ()=>{
|
|
31
|
+
this._onStateTransferHandlers.delete(handler);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
22
34
|
registerSystems(systems) {
|
|
23
35
|
if (this._systems.length !== 0) throw new Error('Systems already registered');
|
|
24
36
|
for (const system of systems){
|
|
@@ -39,6 +51,15 @@ export class ECSSimulation {
|
|
|
39
51
|
get frameLength() {
|
|
40
52
|
return this._frameLength;
|
|
41
53
|
}
|
|
54
|
+
get inputProvider() {
|
|
55
|
+
return this._inputProvider;
|
|
56
|
+
}
|
|
57
|
+
enableHashTracking(interval) {
|
|
58
|
+
this._hashTrackingInterval = interval;
|
|
59
|
+
}
|
|
60
|
+
getHashAtTick(tick) {
|
|
61
|
+
return this._hashHistory.get(tick);
|
|
62
|
+
}
|
|
42
63
|
start() {
|
|
43
64
|
this.clock.start();
|
|
44
65
|
}
|
|
@@ -59,6 +80,24 @@ export class ECSSimulation {
|
|
|
59
80
|
this.clock.setAccumulatedTime(tick * this._frameLength);
|
|
60
81
|
// Reset signals — old predictions are invalid
|
|
61
82
|
this._signalsRegistry.dispose();
|
|
83
|
+
// Clear hash history — old hashes are from a different timeline
|
|
84
|
+
this._hashHistory.clear();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Export simulation state for network state transfer (late-join / reconnect).
|
|
88
|
+
* Override in subclasses to include additional state (e.g. physics world).
|
|
89
|
+
*/ exportStateForTransfer() {
|
|
90
|
+
return this.mem.exportSnapshot();
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Apply state received from network state transfer (late-join / reconnect).
|
|
94
|
+
* Override in subclasses to restore additional state (e.g. physics world).
|
|
95
|
+
*/ applyStateFromTransfer(blob, tick) {
|
|
96
|
+
this.applyExternalState(blob, tick);
|
|
97
|
+
this.notifyStateTransferHandlers(tick);
|
|
98
|
+
}
|
|
99
|
+
notifyStateTransferHandlers(tick) {
|
|
100
|
+
for (const handler of this._onStateTransferHandlers)handler(tick);
|
|
62
101
|
}
|
|
63
102
|
update(dt) {
|
|
64
103
|
this.clock.update(dt);
|
|
@@ -75,6 +114,7 @@ export class ECSSimulation {
|
|
|
75
114
|
const rollbackTick = this._inputProvider.getInvalidateRollbackTick();
|
|
76
115
|
if (rollbackTick === undefined || rollbackTick > currentTick) return;
|
|
77
116
|
this.rollback(rollbackTick);
|
|
117
|
+
for (const handler of this._onRollbackHandlers)handler(this.tick);
|
|
78
118
|
}
|
|
79
119
|
simulationTicks(currentTick, toTick) {
|
|
80
120
|
if (toTick - currentTick > 1) {
|
|
@@ -83,10 +123,22 @@ export class ECSSimulation {
|
|
|
83
123
|
while(currentTick < toTick){
|
|
84
124
|
this.mem.tickManager.setTick(++currentTick);
|
|
85
125
|
this.simulate(currentTick);
|
|
86
|
-
this.
|
|
126
|
+
if (this._hashTrackingInterval > 0 && currentTick % this._hashTrackingInterval === 0) {
|
|
127
|
+
this._hashHistory.set(currentTick, this.mem.getHash());
|
|
128
|
+
}
|
|
129
|
+
this._signalsRegistry.onTick(this._inputProvider.verifiedTick);
|
|
87
130
|
this.storeSnapshotIfNeeded(currentTick);
|
|
88
131
|
for (const handler of this._onTickHandlers)handler(currentTick);
|
|
89
132
|
}
|
|
133
|
+
// Prune old hash entries
|
|
134
|
+
if (this._hashHistory.size > 0) {
|
|
135
|
+
const pruneBelow = this._inputProvider.verifiedTick - 600;
|
|
136
|
+
if (pruneBelow > 0) {
|
|
137
|
+
for (const tick of this._hashHistory.keys()){
|
|
138
|
+
if (tick < pruneBelow) this._hashHistory.delete(tick);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
90
142
|
}
|
|
91
143
|
rollback(tick) {
|
|
92
144
|
this._signalsRegistry.onBeforeRollback(tick);
|
|
@@ -124,7 +176,11 @@ export class ECSSimulation {
|
|
|
124
176
|
this._inputProvider = _inputProvider;
|
|
125
177
|
this._systems = new Array();
|
|
126
178
|
this._onTickHandlers = new Set();
|
|
179
|
+
this._onRollbackHandlers = new Set();
|
|
180
|
+
this._onStateTransferHandlers = new Set();
|
|
127
181
|
this._interpolationFactor = 0;
|
|
182
|
+
this._hashTrackingInterval = 0;
|
|
183
|
+
this._hashHistory = new Map();
|
|
128
184
|
this.mem = new Mem(this._ECSConfig, this._ECSDeps);
|
|
129
185
|
this._frameLength = this._ECSConfig.frameLength;
|
|
130
186
|
this._snapshotRate = this._ECSConfig.snapshotRate;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/ecs-simulation.ts"],"sourcesContent":["import { MathOps } from '@lagless/math';\nimport { Mem } from './mem/index.js';\nimport { ECSConfig } from './ecs-config.js';\nimport { SimulationClock, SnapshotHistory, createLogger } from '@lagless/misc';\nimport { ECSDeps, IECSSystem } from './types/index.js';\nimport { AbstractInputProvider } from './input/index.js';\nimport { SignalsRegistry } from './signals/signals.registry.js';\n\nconst log = createLogger('ECSSimulation');\n\nexport class ECSSimulation {\n public readonly mem: Mem;\n public readonly clock: SimulationClock;\n private readonly _signalsRegistry: SignalsRegistry;\n private readonly _frameLength: number;\n private readonly _snapshotRate: number;\n private readonly _initialSnapshot!: ArrayBuffer;\n private readonly _systems = new Array<IECSSystem>();\n private readonly _onTickHandlers = new Set<(tick: number) => void>();\n\n private _interpolationFactor = 0;\n private _snapshotHistory: SnapshotHistory<ArrayBuffer>;\n\n public get tick(): number {\n return this.mem.tickManager.tick;\n }\n\n public get interpolationFactor(): number {\n return this._interpolationFactor;\n }\n\n constructor(\n private readonly _ECSConfig: ECSConfig,\n private readonly _ECSDeps: ECSDeps,\n private readonly _inputProvider: AbstractInputProvider,\n ) {\n this.mem = new Mem(this._ECSConfig, this._ECSDeps);\n this._frameLength = this._ECSConfig.frameLength;\n this._snapshotRate = this._ECSConfig.snapshotRate;\n this._snapshotHistory = new SnapshotHistory<ArrayBuffer>(this._ECSConfig.snapshotHistorySize);\n this._initialSnapshot = this.mem.exportSnapshot();\n this.clock = new SimulationClock(_ECSConfig.frameLength, _ECSConfig.maxNudgePerFrame);\n this._signalsRegistry = new SignalsRegistry();\n }\n\n public addTickHandler(handler: (tick: number) => void): () => void {\n this._onTickHandlers.add(handler);\n return () => {\n this._onTickHandlers.delete(handler);\n };\n }\n\n public removeTickHandler(handler: (tick: number) => void): void {\n this._onTickHandlers.delete(handler);\n }\n\n public registerSystems(systems: IECSSystem[]): void {\n if (this._systems.length !== 0) throw new Error('Systems already registered');\n\n for (const system of systems) {\n this._systems.push(system);\n }\n }\n\n public initSignals(signals: import('./signals/signal.js').Signal[]): void {\n this._signalsRegistry.init(signals);\n }\n\n public disposeSignals(): void {\n this._signalsRegistry.dispose();\n }\n\n public throwIfSystemsNotRegistered(): void {\n if (this._systems.length === 0) {\n throw new Error('No systems registered.');\n }\n }\n\n public get frameLength(): number {\n return this._frameLength;\n }\n\n public start(): void {\n this.clock.start();\n }\n\n /**\n * Apply external state received from another client (late-join / reconnect).\n * Replaces the entire simulation state, sets the tick, and resets history.\n */\n public applyExternalState(state: ArrayBuffer, tick: number): void {\n log.info(`Applying external state at tick ${tick} (${state.byteLength} bytes)`);\n\n // Apply the snapshot to memory\n this.mem.applySnapshot(state);\n\n // Set tick (snapshot may have been taken at a different tick than requested)\n this.mem.tickManager.setTick(tick);\n\n // Reset snapshot history — old snapshots are from a different timeline\n this._snapshotHistory = new SnapshotHistory<ArrayBuffer>(this._ECSConfig.snapshotHistorySize);\n\n // Save the received state as the new baseline snapshot\n this.saveSnapshot(tick);\n\n // Adjust clock to match the new tick\n this.clock.setAccumulatedTime(tick * this._frameLength);\n\n // Reset signals — old predictions are invalid\n this._signalsRegistry.dispose();\n }\n\n public update(dt: number) {\n this.clock.update(dt);\n\n const targetTick = Math.floor(this.clock.accumulatedTime / this._frameLength);\n\n this.checkAndRollback(this.mem.tickManager.tick);\n this.simulationTicks(this.mem.tickManager.tick, targetTick);\n\n this._inputProvider.update();\n\n const simTick = this.mem.tickManager.tick;\n const tickTime = simTick * this._frameLength;\n const leftover = this.clock.accumulatedTime - tickTime;\n\n this._interpolationFactor = MathOps.clamp01(leftover / this._frameLength);\n }\n\n private checkAndRollback(currentTick: number) {\n const rollbackTick = this._inputProvider.getInvalidateRollbackTick();\n\n if (rollbackTick === undefined || rollbackTick > currentTick) return;\n\n this.rollback(rollbackTick);\n }\n\n private simulationTicks(currentTick: number, toTick: number): void {\n if (toTick - currentTick > 1) {\n log.warn(`Simulation ticks: ${currentTick} -> ${toTick} (simulate ${toTick - currentTick})`);\n }\n\n while (currentTick < toTick) {\n this.mem.tickManager.setTick(++currentTick);\n this.simulate(currentTick);\n this._signalsRegistry.onTick(currentTick);\n this.storeSnapshotIfNeeded(currentTick);\n for (const handler of this._onTickHandlers) handler(currentTick);\n }\n }\n\n protected rollback(tick: number): void {\n this._signalsRegistry.onBeforeRollback(tick);\n let snapshot: ArrayBuffer;\n\n try {\n snapshot = this._snapshotHistory.getNearest(tick);\n log.warn(`Rollback to tick ${tick} succeeded`);\n } catch {\n snapshot = this._initialSnapshot;\n log.warn(`Rollback to tick ${tick} failed, using initial snapshot`);\n }\n\n this.mem.applySnapshot(snapshot);\n this._snapshotHistory.rollback(this.mem.tickManager.tick);\n }\n\n protected simulate(tick: number): void {\n for (let i = 0; i < this._systems.length; i++) {\n this._systems[i].update(tick);\n }\n }\n\n private storeSnapshotIfNeeded(tick: number): void {\n if (this._snapshotRate === 0) return;\n\n if (tick % this._snapshotRate === 0) {\n this.saveSnapshot(tick);\n }\n\n if (tick % 200 === 0) {\n log.debug(`Mem Hash at tick ${tick}: ${this.mem.getHash()}`);\n }\n }\n\n protected saveSnapshot(tick: number): void {\n this._snapshotHistory.set(tick, this.mem.exportSnapshot());\n }\n}\n"],"names":["MathOps","Mem","SimulationClock","SnapshotHistory","createLogger","SignalsRegistry","log","ECSSimulation","tick","mem","tickManager","interpolationFactor","_interpolationFactor","addTickHandler","handler","_onTickHandlers","add","delete","removeTickHandler","registerSystems","systems","_systems","length","Error","system","push","initSignals","signals","_signalsRegistry","init","disposeSignals","dispose","throwIfSystemsNotRegistered","frameLength","_frameLength","start","clock","applyExternalState","state","info","byteLength","applySnapshot","setTick","_snapshotHistory","_ECSConfig","snapshotHistorySize","saveSnapshot","setAccumulatedTime","update","dt","targetTick","Math","floor","accumulatedTime","checkAndRollback","simulationTicks","_inputProvider","simTick","tickTime","leftover","clamp01","currentTick","rollbackTick","getInvalidateRollbackTick","undefined","rollback","toTick","warn","simulate","onTick","storeSnapshotIfNeeded","onBeforeRollback","snapshot","getNearest","_initialSnapshot","i","_snapshotRate","debug","getHash","set","exportSnapshot","constructor","_ECSDeps","Array","Set","snapshotRate","maxNudgePerFrame"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/lib/ecs-simulation.ts"],"sourcesContent":["import { MathOps } from '@lagless/math';\nimport { Mem } from './mem/index.js';\nimport { ECSConfig } from './ecs-config.js';\nimport { SimulationClock, SnapshotHistory, createLogger } from '@lagless/misc';\nimport { ECSDeps, IECSSystem } from './types/index.js';\nimport { AbstractInputProvider } from './input/index.js';\nimport { SignalsRegistry } from './signals/signals.registry.js';\n\nconst log = createLogger('ECSSimulation');\n\nexport class ECSSimulation {\n public readonly mem: Mem;\n public readonly clock: SimulationClock;\n private readonly _signalsRegistry: SignalsRegistry;\n private readonly _frameLength: number;\n private readonly _snapshotRate: number;\n protected readonly _initialSnapshot!: ArrayBuffer;\n private readonly _systems = new Array<IECSSystem>();\n private readonly _onTickHandlers = new Set<(tick: number) => void>();\n private readonly _onRollbackHandlers = new Set<(tick: number) => void>();\n private readonly _onStateTransferHandlers = new Set<(tick: number) => void>();\n\n private _interpolationFactor = 0;\n protected _snapshotHistory: SnapshotHistory<ArrayBuffer>;\n private _hashTrackingInterval = 0;\n private _hashHistory = new Map<number, number>();\n\n public get tick(): number {\n return this.mem.tickManager.tick;\n }\n\n public get interpolationFactor(): number {\n return this._interpolationFactor;\n }\n\n constructor(\n protected readonly _ECSConfig: ECSConfig,\n private readonly _ECSDeps: ECSDeps,\n private readonly _inputProvider: AbstractInputProvider,\n ) {\n this.mem = new Mem(this._ECSConfig, this._ECSDeps);\n this._frameLength = this._ECSConfig.frameLength;\n this._snapshotRate = this._ECSConfig.snapshotRate;\n this._snapshotHistory = new SnapshotHistory<ArrayBuffer>(this._ECSConfig.snapshotHistorySize);\n this._initialSnapshot = this.mem.exportSnapshot();\n this.clock = new SimulationClock(_ECSConfig.frameLength, _ECSConfig.maxNudgePerFrame);\n this._signalsRegistry = new SignalsRegistry();\n }\n\n public addTickHandler(handler: (tick: number) => void): () => void {\n this._onTickHandlers.add(handler);\n return () => {\n this._onTickHandlers.delete(handler);\n };\n }\n\n public removeTickHandler(handler: (tick: number) => void): void {\n this._onTickHandlers.delete(handler);\n }\n\n public addRollbackHandler(handler: (tick: number) => void): () => void {\n this._onRollbackHandlers.add(handler);\n return () => { this._onRollbackHandlers.delete(handler); };\n }\n\n public addStateTransferHandler(handler: (tick: number) => void): () => void {\n this._onStateTransferHandlers.add(handler);\n return () => { this._onStateTransferHandlers.delete(handler); };\n }\n\n public registerSystems(systems: IECSSystem[]): void {\n if (this._systems.length !== 0) throw new Error('Systems already registered');\n\n for (const system of systems) {\n this._systems.push(system);\n }\n }\n\n public initSignals(signals: import('./signals/signal.js').Signal[]): void {\n this._signalsRegistry.init(signals);\n }\n\n public disposeSignals(): void {\n this._signalsRegistry.dispose();\n }\n\n public throwIfSystemsNotRegistered(): void {\n if (this._systems.length === 0) {\n throw new Error('No systems registered.');\n }\n }\n\n public get frameLength(): number {\n return this._frameLength;\n }\n\n public get inputProvider(): AbstractInputProvider {\n return this._inputProvider;\n }\n\n public enableHashTracking(interval: number): void {\n this._hashTrackingInterval = interval;\n }\n\n public getHashAtTick(tick: number): number | undefined {\n return this._hashHistory.get(tick);\n }\n\n public start(): void {\n this.clock.start();\n }\n\n /**\n * Apply external state received from another client (late-join / reconnect).\n * Replaces the entire simulation state, sets the tick, and resets history.\n */\n public applyExternalState(state: ArrayBuffer, tick: number): void {\n log.info(`Applying external state at tick ${tick} (${state.byteLength} bytes)`);\n\n // Apply the snapshot to memory\n this.mem.applySnapshot(state);\n\n // Set tick (snapshot may have been taken at a different tick than requested)\n this.mem.tickManager.setTick(tick);\n\n // Reset snapshot history — old snapshots are from a different timeline\n this._snapshotHistory = new SnapshotHistory<ArrayBuffer>(this._ECSConfig.snapshotHistorySize);\n\n // Save the received state as the new baseline snapshot\n this.saveSnapshot(tick);\n\n // Adjust clock to match the new tick\n this.clock.setAccumulatedTime(tick * this._frameLength);\n\n // Reset signals — old predictions are invalid\n this._signalsRegistry.dispose();\n\n // Clear hash history — old hashes are from a different timeline\n this._hashHistory.clear();\n }\n\n /**\n * Export simulation state for network state transfer (late-join / reconnect).\n * Override in subclasses to include additional state (e.g. physics world).\n */\n public exportStateForTransfer(): ArrayBuffer {\n return this.mem.exportSnapshot();\n }\n\n /**\n * Apply state received from network state transfer (late-join / reconnect).\n * Override in subclasses to restore additional state (e.g. physics world).\n */\n public applyStateFromTransfer(blob: ArrayBuffer, tick: number): void {\n this.applyExternalState(blob, tick);\n this.notifyStateTransferHandlers(tick);\n }\n\n protected notifyStateTransferHandlers(tick: number): void {\n for (const handler of this._onStateTransferHandlers) handler(tick);\n }\n\n public update(dt: number) {\n this.clock.update(dt);\n\n const targetTick = Math.floor(this.clock.accumulatedTime / this._frameLength);\n\n this.checkAndRollback(this.mem.tickManager.tick);\n this.simulationTicks(this.mem.tickManager.tick, targetTick);\n\n this._inputProvider.update();\n\n const simTick = this.mem.tickManager.tick;\n const tickTime = simTick * this._frameLength;\n const leftover = this.clock.accumulatedTime - tickTime;\n\n this._interpolationFactor = MathOps.clamp01(leftover / this._frameLength);\n }\n\n private checkAndRollback(currentTick: number) {\n const rollbackTick = this._inputProvider.getInvalidateRollbackTick();\n\n if (rollbackTick === undefined || rollbackTick > currentTick) return;\n\n this.rollback(rollbackTick);\n\n for (const handler of this._onRollbackHandlers) handler(this.tick);\n }\n\n private simulationTicks(currentTick: number, toTick: number): void {\n if (toTick - currentTick > 1) {\n log.warn(`Simulation ticks: ${currentTick} -> ${toTick} (simulate ${toTick - currentTick})`);\n }\n\n while (currentTick < toTick) {\n this.mem.tickManager.setTick(++currentTick);\n this.simulate(currentTick);\n\n if (this._hashTrackingInterval > 0 && currentTick % this._hashTrackingInterval === 0) {\n this._hashHistory.set(currentTick, this.mem.getHash());\n }\n\n this._signalsRegistry.onTick(this._inputProvider.verifiedTick);\n this.storeSnapshotIfNeeded(currentTick);\n for (const handler of this._onTickHandlers) handler(currentTick);\n }\n\n // Prune old hash entries\n if (this._hashHistory.size > 0) {\n const pruneBelow = this._inputProvider.verifiedTick - 600;\n if (pruneBelow > 0) {\n for (const tick of this._hashHistory.keys()) {\n if (tick < pruneBelow) this._hashHistory.delete(tick);\n }\n }\n }\n }\n\n protected rollback(tick: number): void {\n this._signalsRegistry.onBeforeRollback(tick);\n let snapshot: ArrayBuffer;\n\n try {\n snapshot = this._snapshotHistory.getNearest(tick);\n log.warn(`Rollback to tick ${tick} succeeded`);\n } catch {\n snapshot = this._initialSnapshot;\n log.warn(`Rollback to tick ${tick} failed, using initial snapshot`);\n }\n\n this.mem.applySnapshot(snapshot);\n this._snapshotHistory.rollback(this.mem.tickManager.tick);\n }\n\n protected simulate(tick: number): void {\n for (let i = 0; i < this._systems.length; i++) {\n this._systems[i].update(tick);\n }\n }\n\n private storeSnapshotIfNeeded(tick: number): void {\n if (this._snapshotRate === 0) return;\n\n if (tick % this._snapshotRate === 0) {\n this.saveSnapshot(tick);\n }\n\n if (tick % 200 === 0) {\n log.debug(`Mem Hash at tick ${tick}: ${this.mem.getHash()}`);\n }\n }\n\n protected saveSnapshot(tick: number): void {\n this._snapshotHistory.set(tick, this.mem.exportSnapshot());\n }\n}\n"],"names":["MathOps","Mem","SimulationClock","SnapshotHistory","createLogger","SignalsRegistry","log","ECSSimulation","tick","mem","tickManager","interpolationFactor","_interpolationFactor","addTickHandler","handler","_onTickHandlers","add","delete","removeTickHandler","addRollbackHandler","_onRollbackHandlers","addStateTransferHandler","_onStateTransferHandlers","registerSystems","systems","_systems","length","Error","system","push","initSignals","signals","_signalsRegistry","init","disposeSignals","dispose","throwIfSystemsNotRegistered","frameLength","_frameLength","inputProvider","_inputProvider","enableHashTracking","interval","_hashTrackingInterval","getHashAtTick","_hashHistory","get","start","clock","applyExternalState","state","info","byteLength","applySnapshot","setTick","_snapshotHistory","_ECSConfig","snapshotHistorySize","saveSnapshot","setAccumulatedTime","clear","exportStateForTransfer","exportSnapshot","applyStateFromTransfer","blob","notifyStateTransferHandlers","update","dt","targetTick","Math","floor","accumulatedTime","checkAndRollback","simulationTicks","simTick","tickTime","leftover","clamp01","currentTick","rollbackTick","getInvalidateRollbackTick","undefined","rollback","toTick","warn","simulate","set","getHash","onTick","verifiedTick","storeSnapshotIfNeeded","size","pruneBelow","keys","onBeforeRollback","snapshot","getNearest","_initialSnapshot","i","_snapshotRate","debug","constructor","_ECSDeps","Array","Set","Map","snapshotRate","maxNudgePerFrame"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { IECSSystem, IAbstractInputConstructor, IPlayerResourceConstructor } from '../types/index.js';
|
|
2
|
+
import type { ECSConfig } from '../ecs-config.js';
|
|
3
|
+
import type { AbstractInputProvider } from '../input/abstract-input-provider.js';
|
|
4
|
+
import type { PlayerResources } from '../mem/managers/player-resources-manager.js';
|
|
5
|
+
import type { DivergenceSignal } from './divergence.signal.js';
|
|
6
|
+
/**
|
|
7
|
+
* Abstract base class for ECS hash verification systems.
|
|
8
|
+
*
|
|
9
|
+
* Subclass this in your game simulation and provide the concrete
|
|
10
|
+
* `_reportHashRpc` and `_playerResourceClass` from your codegen.
|
|
11
|
+
*
|
|
12
|
+
* The subclass must use `@ECSSystem()` decorator and declare constructor
|
|
13
|
+
* params for DI injection matching the base constructor signature.
|
|
14
|
+
*/
|
|
15
|
+
export declare abstract class AbstractHashVerificationSystem implements IECSSystem {
|
|
16
|
+
protected readonly _ECSConfig: ECSConfig;
|
|
17
|
+
protected readonly _InputProvider: AbstractInputProvider;
|
|
18
|
+
protected readonly _PlayerResources: PlayerResources;
|
|
19
|
+
protected readonly _DivergenceSignal: DivergenceSignal;
|
|
20
|
+
constructor(_ECSConfig: ECSConfig, _InputProvider: AbstractInputProvider, _PlayerResources: PlayerResources, _DivergenceSignal: DivergenceSignal);
|
|
21
|
+
/**
|
|
22
|
+
* The codegen-generated ReportHash input constructor.
|
|
23
|
+
* Must have `{ readonly id: number }` and conform to `IAbstractInputConstructor`.
|
|
24
|
+
*/
|
|
25
|
+
protected abstract readonly _reportHashRpc: IAbstractInputConstructor;
|
|
26
|
+
/**
|
|
27
|
+
* The codegen-generated PlayerResource class constructor.
|
|
28
|
+
*/
|
|
29
|
+
protected abstract readonly _playerResourceClass: IPlayerResourceConstructor;
|
|
30
|
+
update(tick: number): void;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=abstract-hash-verification.system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abstract-hash-verification.system.d.ts","sourceRoot":"","sources":["../../../src/lib/hash-verification/abstract-hash-verification.system.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC3G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6CAA6C,CAAC;AACnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAY/D;;;;;;;;GAQG;AACH,8BAAsB,8BAA+B,YAAW,UAAU;IAEtE,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,SAAS;IACxC,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,qBAAqB;IACxD,SAAS,CAAC,QAAQ,CAAC,gBAAgB,EAAE,eAAe;IACpD,SAAS,CAAC,QAAQ,CAAC,iBAAiB,EAAE,gBAAgB;gBAHnC,UAAU,EAAE,SAAS,EACrB,cAAc,EAAE,qBAAqB,EACrC,gBAAgB,EAAE,eAAe,EACjC,iBAAiB,EAAE,gBAAgB;IAGxD;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc,EAAE,yBAAyB,CAAC;IAEtE;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,oBAAoB,EAAE,0BAA0B,CAAC;IAEtE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;CA2ClC"}
|
|
@@ -15,15 +15,20 @@
|
|
|
15
15
|
safe.lastReportedHash = rpc.data.hash;
|
|
16
16
|
safe.lastReportedHashTick = rpc.data.atTick;
|
|
17
17
|
}
|
|
18
|
+
// Only compare hashes for ticks that are verified (server-confirmed, no
|
|
19
|
+
// future rollback possible). This replaces the old heuristic delay.
|
|
20
|
+
const verifiedTick = this._InputProvider.verifiedTick;
|
|
18
21
|
const maxPlayers = this._ECSConfig.maxPlayers;
|
|
19
22
|
for(let a = 0; a < maxPlayers; a++){
|
|
20
23
|
const pa = this._PlayerResources.get(this._playerResourceClass, a);
|
|
21
24
|
const safeA = pa.safe;
|
|
22
25
|
if (safeA.connected === 0 || safeA.lastReportedHashTick === 0) continue;
|
|
26
|
+
if (safeA.lastReportedHashTick > verifiedTick) continue;
|
|
23
27
|
for(let b = a + 1; b < maxPlayers; b++){
|
|
24
28
|
const pb = this._PlayerResources.get(this._playerResourceClass, b);
|
|
25
29
|
const safeB = pb.safe;
|
|
26
30
|
if (safeB.connected === 0 || safeB.lastReportedHashTick === 0) continue;
|
|
31
|
+
if (safeB.lastReportedHashTick > verifiedTick) continue;
|
|
27
32
|
if (safeA.lastReportedHashTick === safeB.lastReportedHashTick && safeA.lastReportedHash !== safeB.lastReportedHash) {
|
|
28
33
|
safeA.hashMismatchCount++;
|
|
29
34
|
safeB.hashMismatchCount++;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/hash-verification/abstract-hash-verification.system.ts"],"sourcesContent":["import type { IECSSystem, IAbstractInputConstructor, IPlayerResourceConstructor } from '../types/index.js';\nimport type { ECSConfig } from '../ecs-config.js';\nimport type { AbstractInputProvider } from '../input/abstract-input-provider.js';\nimport type { PlayerResources } from '../mem/managers/player-resources-manager.js';\nimport type { DivergenceSignal } from './divergence.signal.js';\n\n/**\n * Minimum fields that a PlayerResource must expose for hash verification.\n */\ninterface HashPlayerResourceProxy {\n connected: number;\n lastReportedHash: number;\n lastReportedHashTick: number;\n hashMismatchCount: number;\n}\n\n/**\n * Abstract base class for ECS hash verification systems.\n *\n * Subclass this in your game simulation and provide the concrete\n * `_reportHashRpc` and `_playerResourceClass` from your codegen.\n *\n * The subclass must use `@ECSSystem()` decorator and declare constructor\n * params for DI injection matching the base constructor signature.\n */\nexport abstract class AbstractHashVerificationSystem implements IECSSystem {\n constructor(\n protected readonly _ECSConfig: ECSConfig,\n protected readonly _InputProvider: AbstractInputProvider,\n protected readonly _PlayerResources: PlayerResources,\n protected readonly _DivergenceSignal: DivergenceSignal,\n ) {}\n\n /**\n * The codegen-generated ReportHash input constructor.\n * Must have `{ readonly id: number }` and conform to `IAbstractInputConstructor`.\n */\n protected abstract readonly _reportHashRpc: IAbstractInputConstructor;\n\n /**\n * The codegen-generated PlayerResource class constructor.\n */\n protected abstract readonly _playerResourceClass: IPlayerResourceConstructor;\n\n public update(tick: number): void {\n const rpcs = this._InputProvider.collectTickRPCs(tick, this._reportHashRpc) as unknown as Array<{ meta: { playerSlot: number }, data: { hash: number, atTick: number } }>;\n\n for (const rpc of rpcs) {\n const playerResource = this._PlayerResources.get(this._playerResourceClass, rpc.meta.playerSlot) as unknown as { safe: HashPlayerResourceProxy };\n const safe = playerResource.safe as HashPlayerResourceProxy;\n safe.lastReportedHash = rpc.data.hash;\n safe.lastReportedHashTick = rpc.data.atTick;\n }\n\n const maxPlayers = this._ECSConfig.maxPlayers;\n for (let a = 0; a < maxPlayers; a++) {\n const pa = this._PlayerResources.get(this._playerResourceClass, a) as unknown as { safe: HashPlayerResourceProxy };\n const safeA = pa.safe as HashPlayerResourceProxy;\n if (safeA.connected === 0 || safeA.lastReportedHashTick === 0) continue;\n\n for (let b = a + 1; b < maxPlayers; b++) {\n const pb = this._PlayerResources.get(this._playerResourceClass, b) as unknown as { safe: HashPlayerResourceProxy };\n const safeB = pb.safe as HashPlayerResourceProxy;\n if (safeB.connected === 0 || safeB.lastReportedHashTick === 0) continue;\n\n if (safeA.lastReportedHashTick === safeB.lastReportedHashTick &&\n safeA.lastReportedHash !== safeB.lastReportedHash) {\n safeA.hashMismatchCount++;\n safeB.hashMismatchCount++;\n\n this._DivergenceSignal.emit(tick, {\n slotA: a,\n slotB: b,\n hashA: safeA.lastReportedHash,\n hashB: safeB.lastReportedHash,\n atTick: safeA.lastReportedHashTick,\n });\n }\n }\n }\n }\n}\n"],"names":["AbstractHashVerificationSystem","update","tick","rpcs","_InputProvider","collectTickRPCs","_reportHashRpc","rpc","playerResource","_PlayerResources","get","_playerResourceClass","meta","playerSlot","safe","lastReportedHash","data","hash","lastReportedHashTick","atTick","maxPlayers","_ECSConfig","a","pa","safeA","connected","b","pb","safeB","hashMismatchCount","_DivergenceSignal","emit","slotA","slotB","hashA","hashB","constructor"],"rangeMappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/hash-verification/abstract-hash-verification.system.ts"],"sourcesContent":["import type { IECSSystem, IAbstractInputConstructor, IPlayerResourceConstructor } from '../types/index.js';\nimport type { ECSConfig } from '../ecs-config.js';\nimport type { AbstractInputProvider } from '../input/abstract-input-provider.js';\nimport type { PlayerResources } from '../mem/managers/player-resources-manager.js';\nimport type { DivergenceSignal } from './divergence.signal.js';\n\n/**\n * Minimum fields that a PlayerResource must expose for hash verification.\n */\ninterface HashPlayerResourceProxy {\n connected: number;\n lastReportedHash: number;\n lastReportedHashTick: number;\n hashMismatchCount: number;\n}\n\n/**\n * Abstract base class for ECS hash verification systems.\n *\n * Subclass this in your game simulation and provide the concrete\n * `_reportHashRpc` and `_playerResourceClass` from your codegen.\n *\n * The subclass must use `@ECSSystem()` decorator and declare constructor\n * params for DI injection matching the base constructor signature.\n */\nexport abstract class AbstractHashVerificationSystem implements IECSSystem {\n constructor(\n protected readonly _ECSConfig: ECSConfig,\n protected readonly _InputProvider: AbstractInputProvider,\n protected readonly _PlayerResources: PlayerResources,\n protected readonly _DivergenceSignal: DivergenceSignal,\n ) {}\n\n /**\n * The codegen-generated ReportHash input constructor.\n * Must have `{ readonly id: number }` and conform to `IAbstractInputConstructor`.\n */\n protected abstract readonly _reportHashRpc: IAbstractInputConstructor;\n\n /**\n * The codegen-generated PlayerResource class constructor.\n */\n protected abstract readonly _playerResourceClass: IPlayerResourceConstructor;\n\n public update(tick: number): void {\n const rpcs = this._InputProvider.collectTickRPCs(tick, this._reportHashRpc) as unknown as Array<{ meta: { playerSlot: number }, data: { hash: number, atTick: number } }>;\n\n for (const rpc of rpcs) {\n const playerResource = this._PlayerResources.get(this._playerResourceClass, rpc.meta.playerSlot) as unknown as { safe: HashPlayerResourceProxy };\n const safe = playerResource.safe as HashPlayerResourceProxy;\n safe.lastReportedHash = rpc.data.hash;\n safe.lastReportedHashTick = rpc.data.atTick;\n }\n\n // Only compare hashes for ticks that are verified (server-confirmed, no\n // future rollback possible). This replaces the old heuristic delay.\n const verifiedTick = this._InputProvider.verifiedTick;\n\n const maxPlayers = this._ECSConfig.maxPlayers;\n for (let a = 0; a < maxPlayers; a++) {\n const pa = this._PlayerResources.get(this._playerResourceClass, a) as unknown as { safe: HashPlayerResourceProxy };\n const safeA = pa.safe as HashPlayerResourceProxy;\n if (safeA.connected === 0 || safeA.lastReportedHashTick === 0) continue;\n if (safeA.lastReportedHashTick > verifiedTick) continue;\n\n for (let b = a + 1; b < maxPlayers; b++) {\n const pb = this._PlayerResources.get(this._playerResourceClass, b) as unknown as { safe: HashPlayerResourceProxy };\n const safeB = pb.safe as HashPlayerResourceProxy;\n if (safeB.connected === 0 || safeB.lastReportedHashTick === 0) continue;\n if (safeB.lastReportedHashTick > verifiedTick) continue;\n\n if (safeA.lastReportedHashTick === safeB.lastReportedHashTick &&\n safeA.lastReportedHash !== safeB.lastReportedHash) {\n safeA.hashMismatchCount++;\n safeB.hashMismatchCount++;\n\n this._DivergenceSignal.emit(tick, {\n slotA: a,\n slotB: b,\n hashA: safeA.lastReportedHash,\n hashB: safeB.lastReportedHash,\n atTick: safeA.lastReportedHashTick,\n });\n }\n }\n }\n }\n}\n"],"names":["AbstractHashVerificationSystem","update","tick","rpcs","_InputProvider","collectTickRPCs","_reportHashRpc","rpc","playerResource","_PlayerResources","get","_playerResourceClass","meta","playerSlot","safe","lastReportedHash","data","hash","lastReportedHashTick","atTick","verifiedTick","maxPlayers","_ECSConfig","a","pa","safeA","connected","b","pb","safeB","hashMismatchCount","_DivergenceSignal","emit","slotA","slotB","hashA","hashB","constructor"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAgBA;;;;;;;;CAQC,GACD,OAAO,MAAeA;IAmBbC,OAAOC,IAAY,EAAQ;QAChC,MAAMC,OAAO,IAAI,CAACC,cAAc,CAACC,eAAe,CAACH,MAAM,IAAI,CAACI,cAAc;QAE1E,KAAK,MAAMC,OAAOJ,KAAM;YACtB,MAAMK,iBAAiB,IAAI,CAACC,gBAAgB,CAACC,GAAG,CAAC,IAAI,CAACC,oBAAoB,EAAEJ,IAAIK,IAAI,CAACC,UAAU;YAC/F,MAAMC,OAAON,eAAeM,IAAI;YAChCA,KAAKC,gBAAgB,GAAGR,IAAIS,IAAI,CAACC,IAAI;YACrCH,KAAKI,oBAAoB,GAAGX,IAAIS,IAAI,CAACG,MAAM;QAC7C;QAEA,wEAAwE;QACxE,oEAAoE;QACpE,MAAMC,eAAe,IAAI,CAAChB,cAAc,CAACgB,YAAY;QAErD,MAAMC,aAAa,IAAI,CAACC,UAAU,CAACD,UAAU;QAC7C,IAAK,IAAIE,IAAI,GAAGA,IAAIF,YAAYE,IAAK;YACnC,MAAMC,KAAK,IAAI,CAACf,gBAAgB,CAACC,GAAG,CAAC,IAAI,CAACC,oBAAoB,EAAEY;YAChE,MAAME,QAAQD,GAAGV,IAAI;YACrB,IAAIW,MAAMC,SAAS,KAAK,KAAKD,MAAMP,oBAAoB,KAAK,GAAG;YAC/D,IAAIO,MAAMP,oBAAoB,GAAGE,cAAc;YAE/C,IAAK,IAAIO,IAAIJ,IAAI,GAAGI,IAAIN,YAAYM,IAAK;gBACvC,MAAMC,KAAK,IAAI,CAACnB,gBAAgB,CAACC,GAAG,CAAC,IAAI,CAACC,oBAAoB,EAAEgB;gBAChE,MAAME,QAAQD,GAAGd,IAAI;gBACrB,IAAIe,MAAMH,SAAS,KAAK,KAAKG,MAAMX,oBAAoB,KAAK,GAAG;gBAC/D,IAAIW,MAAMX,oBAAoB,GAAGE,cAAc;gBAE/C,IAAIK,MAAMP,oBAAoB,KAAKW,MAAMX,oBAAoB,IAC3DO,MAAMV,gBAAgB,KAAKc,MAAMd,gBAAgB,EAAE;oBACnDU,MAAMK,iBAAiB;oBACvBD,MAAMC,iBAAiB;oBAEvB,IAAI,CAACC,iBAAiB,CAACC,IAAI,CAAC9B,MAAM;wBAChC+B,OAAOV;wBACPW,OAAOP;wBACPQ,OAAOV,MAAMV,gBAAgB;wBAC7BqB,OAAOP,MAAMd,gBAAgB;wBAC7BI,QAAQM,MAAMP,oBAAoB;oBACpC;gBACF;YACF;QACF;IACF;IA5DAmB,YACE,AAAmBf,UAAqB,EACxC,AAAmBlB,cAAqC,EACxD,AAAmBK,gBAAiC,EACpD,AAAmBsB,iBAAmC,CACtD;aAJmBT,aAAAA;aACAlB,iBAAAA;aACAK,mBAAAA;aACAsB,oBAAAA;IAClB;AAwDL"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ECSRunner } from '../ecs-runner.js';
|
|
2
|
+
import type { IAbstractInputConstructor, InputData } from '../types/index.js';
|
|
3
|
+
type AddRPCFn = <TInputCtor extends IAbstractInputConstructor>(InputCtor: TInputCtor, data: InputData<InstanceType<TInputCtor>>) => void;
|
|
4
|
+
export interface HashReporterConfig {
|
|
5
|
+
reportInterval: number;
|
|
6
|
+
reportHashRpc: IAbstractInputConstructor;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Creates a hash reporter function for use in `drainInputs`.
|
|
10
|
+
*
|
|
11
|
+
* The returned function should be called from your `drainInputs` callback.
|
|
12
|
+
* It periodically reports the simulation state hash via the ReportHash RPC.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createHashReporter(runner: ECSRunner, config: HashReporterConfig): (addRPC: AddRPCFn) => void;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=create-hash-reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-hash-reporter.d.ts","sourceRoot":"","sources":["../../../src/lib/hash-verification/create-hash-reporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,yBAAyB,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9E,KAAK,QAAQ,GAAG,CAAC,UAAU,SAAS,yBAAyB,EAC3D,SAAS,EAAE,UAAU,EACrB,IAAI,EAAE,SAAS,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,KACtC,IAAI,CAAC;AAEV,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,yBAAyB,CAAC;CAC1C;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,kBAAkB,GAAG,CAAC,MAAM,EAAE,QAAQ,KAAK,IAAI,CAc5G"}
|
|
@@ -6,14 +6,17 @@
|
|
|
6
6
|
*/ export function createHashReporter(runner, config) {
|
|
7
7
|
let lastReportedTick = -1;
|
|
8
8
|
return (addRPC)=>{
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const hash = runner.Simulation.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
const verifiedTick = runner.Simulation.inputProvider.verifiedTick;
|
|
10
|
+
const latestReportTick = Math.floor(verifiedTick / config.reportInterval) * config.reportInterval;
|
|
11
|
+
if (latestReportTick > lastReportedTick && latestReportTick > 0) {
|
|
12
|
+
const hash = runner.Simulation.getHashAtTick(latestReportTick);
|
|
13
|
+
if (hash !== undefined) {
|
|
14
|
+
lastReportedTick = latestReportTick;
|
|
15
|
+
addRPC(config.reportHashRpc, {
|
|
16
|
+
hash,
|
|
17
|
+
atTick: latestReportTick
|
|
18
|
+
});
|
|
19
|
+
}
|
|
17
20
|
}
|
|
18
21
|
};
|
|
19
22
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/hash-verification/create-hash-reporter.ts"],"sourcesContent":["import type { ECSRunner } from '../ecs-runner.js';\nimport type { IAbstractInputConstructor, InputData } from '../types/index.js';\n\ntype AddRPCFn = <TInputCtor extends IAbstractInputConstructor>(\n InputCtor: TInputCtor,\n data: InputData<InstanceType<TInputCtor>>,\n) => void;\n\nexport interface HashReporterConfig {\n reportInterval: number;\n reportHashRpc: IAbstractInputConstructor;\n}\n\n/**\n * Creates a hash reporter function for use in `drainInputs`.\n *\n * The returned function should be called from your `drainInputs` callback.\n * It periodically reports the simulation state hash via the ReportHash RPC.\n */\nexport function createHashReporter(runner: ECSRunner, config: HashReporterConfig): (addRPC: AddRPCFn) => void {\n let lastReportedTick = -1;\n\n return (addRPC: AddRPCFn) => {\n const
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/hash-verification/create-hash-reporter.ts"],"sourcesContent":["import type { ECSRunner } from '../ecs-runner.js';\nimport type { IAbstractInputConstructor, InputData } from '../types/index.js';\n\ntype AddRPCFn = <TInputCtor extends IAbstractInputConstructor>(\n InputCtor: TInputCtor,\n data: InputData<InstanceType<TInputCtor>>,\n) => void;\n\nexport interface HashReporterConfig {\n reportInterval: number;\n reportHashRpc: IAbstractInputConstructor;\n}\n\n/**\n * Creates a hash reporter function for use in `drainInputs`.\n *\n * The returned function should be called from your `drainInputs` callback.\n * It periodically reports the simulation state hash via the ReportHash RPC.\n */\nexport function createHashReporter(runner: ECSRunner, config: HashReporterConfig): (addRPC: AddRPCFn) => void {\n let lastReportedTick = -1;\n\n return (addRPC: AddRPCFn) => {\n const verifiedTick = runner.Simulation.inputProvider.verifiedTick;\n const latestReportTick = Math.floor(verifiedTick / config.reportInterval) * config.reportInterval;\n if (latestReportTick > lastReportedTick && latestReportTick > 0) {\n const hash = runner.Simulation.getHashAtTick(latestReportTick);\n if (hash !== undefined) {\n lastReportedTick = latestReportTick;\n addRPC(config.reportHashRpc, { hash, atTick: latestReportTick });\n }\n }\n };\n}\n"],"names":["createHashReporter","runner","config","lastReportedTick","addRPC","verifiedTick","Simulation","inputProvider","latestReportTick","Math","floor","reportInterval","hash","getHashAtTick","undefined","reportHashRpc","atTick"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;","mappings":"AAaA;;;;;CAKC,GACD,OAAO,SAASA,mBAAmBC,MAAiB,EAAEC,MAA0B;IAC9E,IAAIC,mBAAmB,CAAC;IAExB,OAAO,CAACC;QACN,MAAMC,eAAeJ,OAAOK,UAAU,CAACC,aAAa,CAACF,YAAY;QACjE,MAAMG,mBAAmBC,KAAKC,KAAK,CAACL,eAAeH,OAAOS,cAAc,IAAIT,OAAOS,cAAc;QACjG,IAAIH,mBAAmBL,oBAAoBK,mBAAmB,GAAG;YAC/D,MAAMI,OAAOX,OAAOK,UAAU,CAACO,aAAa,CAACL;YAC7C,IAAII,SAASE,WAAW;gBACtBX,mBAAmBK;gBACnBJ,OAAOF,OAAOa,aAAa,EAAE;oBAAEH;oBAAMI,QAAQR;gBAAiB;YAChE;QACF;IACF;AACF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Signal } from '../signals/signal.js';
|
|
2
|
+
export interface DivergenceData {
|
|
3
|
+
slotA: number;
|
|
4
|
+
slotB: number;
|
|
5
|
+
hashA: number;
|
|
6
|
+
hashB: number;
|
|
7
|
+
atTick: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class DivergenceSignal extends Signal<DivergenceData> {
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=divergence.signal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"divergence.signal.d.ts","sourceRoot":"","sources":["../../../src/lib/hash-verification/divergence.signal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,gBAAiB,SAAQ,MAAM,CAAC,cAAc,CAAC;CAAG"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { DivergenceSignal, type DivergenceData } from './divergence.signal.js';
|
|
2
|
+
export { AbstractHashVerificationSystem } from './abstract-hash-verification.system.js';
|
|
3
|
+
export { createHashReporter, type HashReporterConfig } from './create-hash-reporter.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|