@al8b/runtime 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -0
- package/dist/assets/constructors.d.mts +6 -0
- package/dist/assets/constructors.d.ts +6 -0
- package/dist/assets/constructors.js +40 -0
- package/dist/assets/constructors.js.map +1 -0
- package/dist/assets/constructors.mjs +12 -0
- package/dist/assets/constructors.mjs.map +1 -0
- package/dist/assets/index.d.mts +11 -0
- package/dist/assets/index.d.ts +11 -0
- package/dist/assets/index.js +276 -0
- package/dist/assets/index.js.map +1 -0
- package/dist/assets/index.mjs +247 -0
- package/dist/assets/index.mjs.map +1 -0
- package/dist/assets/loader.d.mts +83 -0
- package/dist/assets/loader.d.ts +83 -0
- package/dist/assets/loader.js +260 -0
- package/dist/assets/loader.js.map +1 -0
- package/dist/assets/loader.mjs +237 -0
- package/dist/assets/loader.mjs.map +1 -0
- package/dist/browser/index.js +16599 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/index.min.js +171 -0
- package/dist/constants.d.mts +16 -0
- package/dist/constants.d.ts +16 -0
- package/dist/constants.js +49 -0
- package/dist/constants.js.map +1 -0
- package/dist/constants.mjs +18 -0
- package/dist/constants.mjs.map +1 -0
- package/dist/core/api-factory.d.mts +63 -0
- package/dist/core/api-factory.d.ts +63 -0
- package/dist/core/api-factory.js +239 -0
- package/dist/core/api-factory.js.map +1 -0
- package/dist/core/api-factory.mjs +214 -0
- package/dist/core/api-factory.mjs.map +1 -0
- package/dist/core/assets-registry.d.mts +14 -0
- package/dist/core/assets-registry.d.ts +14 -0
- package/dist/core/assets-registry.js +64 -0
- package/dist/core/assets-registry.js.map +1 -0
- package/dist/core/assets-registry.mjs +41 -0
- package/dist/core/assets-registry.mjs.map +1 -0
- package/dist/core/controller.d.mts +109 -0
- package/dist/core/controller.d.ts +109 -0
- package/dist/core/controller.js +1782 -0
- package/dist/core/controller.js.map +1 -0
- package/dist/core/controller.mjs +1758 -0
- package/dist/core/controller.mjs.map +1 -0
- package/dist/core/debug-logger.d.mts +35 -0
- package/dist/core/debug-logger.d.ts +35 -0
- package/dist/core/debug-logger.js +177 -0
- package/dist/core/debug-logger.js.map +1 -0
- package/dist/core/debug-logger.mjs +154 -0
- package/dist/core/debug-logger.mjs.map +1 -0
- package/dist/core/error-handler.d.mts +25 -0
- package/dist/core/error-handler.d.ts +25 -0
- package/dist/core/error-handler.js +106 -0
- package/dist/core/error-handler.js.map +1 -0
- package/dist/core/error-handler.mjs +81 -0
- package/dist/core/error-handler.mjs.map +1 -0
- package/dist/core/index.d.mts +14 -0
- package/dist/core/index.d.ts +14 -0
- package/dist/core/index.js +1782 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/index.mjs +1757 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/hot-reload/index.d.mts +7 -0
- package/dist/hot-reload/index.d.ts +7 -0
- package/dist/hot-reload/index.js +103 -0
- package/dist/hot-reload/index.js.map +1 -0
- package/dist/hot-reload/index.mjs +78 -0
- package/dist/hot-reload/index.mjs.map +1 -0
- package/dist/hot-reload/updater.d.mts +33 -0
- package/dist/hot-reload/updater.d.ts +33 -0
- package/dist/hot-reload/updater.js +101 -0
- package/dist/hot-reload/updater.js.map +1 -0
- package/dist/hot-reload/updater.mjs +78 -0
- package/dist/hot-reload/updater.mjs.map +1 -0
- package/dist/index.d.mts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +1859 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1817 -0
- package/dist/index.mjs.map +1 -0
- package/dist/input/index.d.mts +2 -0
- package/dist/input/index.d.ts +2 -0
- package/dist/input/index.js +79 -0
- package/dist/input/index.js.map +1 -0
- package/dist/input/index.mjs +54 -0
- package/dist/input/index.mjs.map +1 -0
- package/dist/input/manager.d.mts +37 -0
- package/dist/input/manager.d.ts +37 -0
- package/dist/input/manager.js +77 -0
- package/dist/input/manager.js.map +1 -0
- package/dist/input/manager.mjs +54 -0
- package/dist/input/manager.mjs.map +1 -0
- package/dist/loop/game-loop.d.mts +63 -0
- package/dist/loop/game-loop.d.ts +63 -0
- package/dist/loop/game-loop.js +156 -0
- package/dist/loop/game-loop.js.map +1 -0
- package/dist/loop/game-loop.mjs +131 -0
- package/dist/loop/game-loop.mjs.map +1 -0
- package/dist/loop/index.d.mts +1 -0
- package/dist/loop/index.d.ts +1 -0
- package/dist/loop/index.js +156 -0
- package/dist/loop/index.js.map +1 -0
- package/dist/loop/index.mjs +131 -0
- package/dist/loop/index.mjs.map +1 -0
- package/dist/storage/index.d.mts +1 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +31 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/index.mjs +6 -0
- package/dist/storage/index.mjs.map +1 -0
- package/dist/system/api.d.mts +28 -0
- package/dist/system/api.d.ts +28 -0
- package/dist/system/api.js +126 -0
- package/dist/system/api.js.map +1 -0
- package/dist/system/api.mjs +101 -0
- package/dist/system/api.mjs.map +1 -0
- package/dist/system/index.d.mts +2 -0
- package/dist/system/index.d.ts +2 -0
- package/dist/system/index.js +126 -0
- package/dist/system/index.js.map +1 -0
- package/dist/system/index.mjs +101 -0
- package/dist/system/index.mjs.map +1 -0
- package/dist/types/assets.d.mts +43 -0
- package/dist/types/assets.d.ts +43 -0
- package/dist/types/assets.js +19 -0
- package/dist/types/assets.js.map +1 -0
- package/dist/types/assets.mjs +1 -0
- package/dist/types/assets.mjs.map +1 -0
- package/dist/types/bridge.d.mts +66 -0
- package/dist/types/bridge.d.ts +66 -0
- package/dist/types/bridge.js +19 -0
- package/dist/types/bridge.js.map +1 -0
- package/dist/types/bridge.mjs +1 -0
- package/dist/types/bridge.mjs.map +1 -0
- package/dist/types/index.d.mts +6 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.js +19 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/index.mjs +1 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/types/runtime.d.mts +71 -0
- package/dist/types/runtime.d.ts +71 -0
- package/dist/types/runtime.js +19 -0
- package/dist/types/runtime.js.map +1 -0
- package/dist/types/runtime.mjs +1 -0
- package/dist/types/runtime.mjs.map +1 -0
- package/dist/types/vm.d.mts +1 -0
- package/dist/types/vm.d.ts +1 -0
- package/dist/types/vm.js +19 -0
- package/dist/types/vm.js.map +1 -0
- package/dist/types/vm.mjs +1 -0
- package/dist/types/vm.mjs.map +1 -0
- package/dist/utils/deep-clone.d.mts +14 -0
- package/dist/utils/deep-clone.d.ts +14 -0
- package/dist/utils/deep-clone.js +42 -0
- package/dist/utils/deep-clone.js.map +1 -0
- package/dist/utils/deep-clone.mjs +19 -0
- package/dist/utils/deep-clone.mjs.map +1 -0
- package/dist/utils/index.d.mts +3 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +156 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/index.mjs +129 -0
- package/dist/utils/index.mjs.map +1 -0
- package/dist/utils/object-pool.d.mts +66 -0
- package/dist/utils/object-pool.d.ts +66 -0
- package/dist/utils/object-pool.js +113 -0
- package/dist/utils/object-pool.js.map +1 -0
- package/dist/utils/object-pool.mjs +90 -0
- package/dist/utils/object-pool.mjs.map +1 -0
- package/dist/utils/shallow-equal.d.mts +15 -0
- package/dist/utils/shallow-equal.d.ts +15 -0
- package/dist/utils/shallow-equal.js +53 -0
- package/dist/utils/shallow-equal.js.map +1 -0
- package/dist/utils/shallow-equal.mjs +30 -0
- package/dist/utils/shallow-equal.mjs.map +1 -0
- package/dist/vm/index.d.mts +1 -0
- package/dist/vm/index.d.ts +1 -0
- package/dist/vm/index.js +37 -0
- package/dist/vm/index.js.map +1 -0
- package/dist/vm/index.mjs +9 -0
- package/dist/vm/index.mjs.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/input/manager.ts
|
|
5
|
+
import { GamepadInput, KeyboardInput, MouseInput, TouchInput } from "@al8b/input";
|
|
6
|
+
var InputManager = class {
|
|
7
|
+
static {
|
|
8
|
+
__name(this, "InputManager");
|
|
9
|
+
}
|
|
10
|
+
keyboard;
|
|
11
|
+
mouse;
|
|
12
|
+
touch;
|
|
13
|
+
gamepad;
|
|
14
|
+
constructor(canvas) {
|
|
15
|
+
this.keyboard = new KeyboardInput();
|
|
16
|
+
this.mouse = new MouseInput();
|
|
17
|
+
this.touch = new TouchInput(this.mouse);
|
|
18
|
+
this.gamepad = new GamepadInput();
|
|
19
|
+
if (canvas) {
|
|
20
|
+
this.attachCanvas(canvas);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Attach input systems to canvas
|
|
25
|
+
*/
|
|
26
|
+
attachCanvas(canvas) {
|
|
27
|
+
this.mouse.setCanvas(canvas);
|
|
28
|
+
this.touch.setCanvas(canvas);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Update all input systems (call each frame)
|
|
32
|
+
*/
|
|
33
|
+
update() {
|
|
34
|
+
this.keyboard.update();
|
|
35
|
+
this.mouse.update();
|
|
36
|
+
this.touch.update();
|
|
37
|
+
this.gamepad.update();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get input states for game code
|
|
41
|
+
*/
|
|
42
|
+
getStates() {
|
|
43
|
+
return {
|
|
44
|
+
keyboard: this.keyboard.state,
|
|
45
|
+
mouse: this.mouse.state,
|
|
46
|
+
touch: this.touch.state,
|
|
47
|
+
gamepad: this.gamepad.status
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
export {
|
|
52
|
+
InputManager
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=manager.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/input/manager.ts"],"sourcesContent":["/**\n * InputManager - Manages all input systems\n *\n * Responsibilities:\n * - Initialize input systems\n * - Update input state each frame\n * - Provide unified input interface\n */\n\nimport { GamepadInput, KeyboardInput, MouseInput, TouchInput } from \"@al8b/input\";\n\nexport class InputManager {\n\tpublic keyboard: KeyboardInput;\n\tpublic mouse: MouseInput;\n\tpublic touch: TouchInput;\n\tpublic gamepad: GamepadInput;\n\n\tconstructor(canvas?: HTMLCanvasElement) {\n\t\t// Initialize all input subsystems\n\t\t// Each system handles its own event listeners and state management\n\t\tthis.keyboard = new KeyboardInput();\n\t\tthis.mouse = new MouseInput();\n\t\tthis.touch = new TouchInput(this.mouse);\n\t\tthis.gamepad = new GamepadInput();\n\n\t\t// Attach event listeners to canvas if provided\n\t\t// Canvas is required for mouse and touch input\n\t\tif (canvas) {\n\t\t\tthis.attachCanvas(canvas);\n\t\t}\n\t}\n\n\t/**\n\t * Attach input systems to canvas\n\t */\n\tattachCanvas(canvas: HTMLCanvasElement): void {\n\t\tthis.mouse.setCanvas(canvas);\n\t\tthis.touch.setCanvas(canvas);\n\t}\n\n\t/**\n\t * Update all input systems (call each frame)\n\t */\n\tupdate(): void {\n\t\tthis.keyboard.update();\n\t\tthis.mouse.update();\n\t\tthis.touch.update();\n\t\tthis.gamepad.update();\n\t}\n\n\t/**\n\t * Get input states for game code\n\t */\n\tgetStates(): {\n\t\tkeyboard: any;\n\t\tmouse: any;\n\t\ttouch: any;\n\t\tgamepad: any;\n\t} {\n\t\treturn {\n\t\t\tkeyboard: this.keyboard.state,\n\t\t\tmouse: this.mouse.state,\n\t\t\ttouch: this.touch.state,\n\t\t\tgamepad: this.gamepad.status,\n\t\t};\n\t}\n}\n"],"mappings":";;;;AASA,SAASA,cAAcC,eAAeC,YAAYC,kBAAkB;AAE7D,IAAMC,eAAN,MAAMA;EAXb,OAWaA;;;EACLC;EACAC;EACAC;EACAC;EAEP,YAAYC,QAA4B;AAGvC,SAAKJ,WAAW,IAAIK,cAAAA;AACpB,SAAKJ,QAAQ,IAAIK,WAAAA;AACjB,SAAKJ,QAAQ,IAAIK,WAAW,KAAKN,KAAK;AACtC,SAAKE,UAAU,IAAIK,aAAAA;AAInB,QAAIJ,QAAQ;AACX,WAAKK,aAAaL,MAAAA;IACnB;EACD;;;;EAKAK,aAAaL,QAAiC;AAC7C,SAAKH,MAAMS,UAAUN,MAAAA;AACrB,SAAKF,MAAMQ,UAAUN,MAAAA;EACtB;;;;EAKAO,SAAe;AACd,SAAKX,SAASW,OAAM;AACpB,SAAKV,MAAMU,OAAM;AACjB,SAAKT,MAAMS,OAAM;AACjB,SAAKR,QAAQQ,OAAM;EACpB;;;;EAKAC,YAKE;AACD,WAAO;MACNZ,UAAU,KAAKA,SAASa;MACxBZ,OAAO,KAAKA,MAAMY;MAClBX,OAAO,KAAKA,MAAMW;MAClBV,SAAS,KAAKA,QAAQW;IACvB;EACD;AACD;","names":["GamepadInput","KeyboardInput","MouseInput","TouchInput","InputManager","keyboard","mouse","touch","gamepad","canvas","KeyboardInput","MouseInput","TouchInput","GamepadInput","attachCanvas","setCanvas","update","getStates","state","status"]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GameLoop - Manages the game update/draw cycle
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - requestAnimationFrame loop
|
|
6
|
+
* - Delta time calculation
|
|
7
|
+
* - FPS calculation
|
|
8
|
+
* - Update rate management
|
|
9
|
+
* - Frame skipping for catch-up
|
|
10
|
+
*/
|
|
11
|
+
interface GameLoopCallbacks {
|
|
12
|
+
onUpdate: () => void;
|
|
13
|
+
onDraw: () => void;
|
|
14
|
+
onTick?: () => void;
|
|
15
|
+
onWatchStep?: () => void;
|
|
16
|
+
getUpdateRate?: () => number | undefined;
|
|
17
|
+
setFPS?: (fps: number) => void;
|
|
18
|
+
}
|
|
19
|
+
interface GameLoopState {
|
|
20
|
+
currentFrame: number;
|
|
21
|
+
floatingFrame: number;
|
|
22
|
+
dt: number;
|
|
23
|
+
lastTime: number;
|
|
24
|
+
fps: number;
|
|
25
|
+
updateRate: number;
|
|
26
|
+
}
|
|
27
|
+
declare class GameLoop {
|
|
28
|
+
private callbacks;
|
|
29
|
+
private state;
|
|
30
|
+
private stopped;
|
|
31
|
+
private animationFrameId;
|
|
32
|
+
constructor(callbacks: GameLoopCallbacks);
|
|
33
|
+
/**
|
|
34
|
+
* Start the game loop
|
|
35
|
+
*/
|
|
36
|
+
start(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Stop the game loop
|
|
39
|
+
*/
|
|
40
|
+
stop(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Resume the game loop
|
|
43
|
+
*/
|
|
44
|
+
resume(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Main game loop
|
|
47
|
+
*/
|
|
48
|
+
private loop;
|
|
49
|
+
/**
|
|
50
|
+
* Get current state
|
|
51
|
+
*/
|
|
52
|
+
getState(): GameLoopState;
|
|
53
|
+
/**
|
|
54
|
+
* Set update rate
|
|
55
|
+
*/
|
|
56
|
+
setUpdateRate(rate: number): void;
|
|
57
|
+
/**
|
|
58
|
+
* Get FPS
|
|
59
|
+
*/
|
|
60
|
+
getFPS(): number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { GameLoop, type GameLoopCallbacks, type GameLoopState };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GameLoop - Manages the game update/draw cycle
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - requestAnimationFrame loop
|
|
6
|
+
* - Delta time calculation
|
|
7
|
+
* - FPS calculation
|
|
8
|
+
* - Update rate management
|
|
9
|
+
* - Frame skipping for catch-up
|
|
10
|
+
*/
|
|
11
|
+
interface GameLoopCallbacks {
|
|
12
|
+
onUpdate: () => void;
|
|
13
|
+
onDraw: () => void;
|
|
14
|
+
onTick?: () => void;
|
|
15
|
+
onWatchStep?: () => void;
|
|
16
|
+
getUpdateRate?: () => number | undefined;
|
|
17
|
+
setFPS?: (fps: number) => void;
|
|
18
|
+
}
|
|
19
|
+
interface GameLoopState {
|
|
20
|
+
currentFrame: number;
|
|
21
|
+
floatingFrame: number;
|
|
22
|
+
dt: number;
|
|
23
|
+
lastTime: number;
|
|
24
|
+
fps: number;
|
|
25
|
+
updateRate: number;
|
|
26
|
+
}
|
|
27
|
+
declare class GameLoop {
|
|
28
|
+
private callbacks;
|
|
29
|
+
private state;
|
|
30
|
+
private stopped;
|
|
31
|
+
private animationFrameId;
|
|
32
|
+
constructor(callbacks: GameLoopCallbacks);
|
|
33
|
+
/**
|
|
34
|
+
* Start the game loop
|
|
35
|
+
*/
|
|
36
|
+
start(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Stop the game loop
|
|
39
|
+
*/
|
|
40
|
+
stop(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Resume the game loop
|
|
43
|
+
*/
|
|
44
|
+
resume(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Main game loop
|
|
47
|
+
*/
|
|
48
|
+
private loop;
|
|
49
|
+
/**
|
|
50
|
+
* Get current state
|
|
51
|
+
*/
|
|
52
|
+
getState(): GameLoopState;
|
|
53
|
+
/**
|
|
54
|
+
* Set update rate
|
|
55
|
+
*/
|
|
56
|
+
setUpdateRate(rate: number): void;
|
|
57
|
+
/**
|
|
58
|
+
* Get FPS
|
|
59
|
+
*/
|
|
60
|
+
getFPS(): number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { GameLoop, type GameLoopCallbacks, type GameLoopState };
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/loop/game-loop.ts
|
|
22
|
+
var game_loop_exports = {};
|
|
23
|
+
__export(game_loop_exports, {
|
|
24
|
+
GameLoop: () => GameLoop
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(game_loop_exports);
|
|
27
|
+
|
|
28
|
+
// src/constants.ts
|
|
29
|
+
var DEFAULT_FPS = 60;
|
|
30
|
+
var DEFAULT_UPDATE_RATE = 60;
|
|
31
|
+
var FRAME_TIME_MS = 1e3 / DEFAULT_FPS;
|
|
32
|
+
var PAUSE_THRESHOLD_MS = 160;
|
|
33
|
+
var LOADING_BAR_THROTTLE_MS = 16;
|
|
34
|
+
|
|
35
|
+
// src/loop/game-loop.ts
|
|
36
|
+
var GameLoop = class {
|
|
37
|
+
static {
|
|
38
|
+
__name(this, "GameLoop");
|
|
39
|
+
}
|
|
40
|
+
callbacks;
|
|
41
|
+
state;
|
|
42
|
+
stopped = false;
|
|
43
|
+
animationFrameId = null;
|
|
44
|
+
constructor(callbacks) {
|
|
45
|
+
this.callbacks = callbacks;
|
|
46
|
+
this.state = {
|
|
47
|
+
currentFrame: 0,
|
|
48
|
+
floatingFrame: 0,
|
|
49
|
+
dt: FRAME_TIME_MS,
|
|
50
|
+
lastTime: performance.now(),
|
|
51
|
+
fps: DEFAULT_FPS,
|
|
52
|
+
updateRate: DEFAULT_UPDATE_RATE
|
|
53
|
+
};
|
|
54
|
+
this.loop = this.loop.bind(this);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Start the game loop
|
|
58
|
+
*/
|
|
59
|
+
start() {
|
|
60
|
+
this.stopped = false;
|
|
61
|
+
this.state.lastTime = performance.now();
|
|
62
|
+
this.state.currentFrame = 0;
|
|
63
|
+
this.state.floatingFrame = 0;
|
|
64
|
+
this.loop();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Stop the game loop
|
|
68
|
+
*/
|
|
69
|
+
stop() {
|
|
70
|
+
this.stopped = true;
|
|
71
|
+
if (this.animationFrameId !== null) {
|
|
72
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
73
|
+
this.animationFrameId = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Resume the game loop
|
|
78
|
+
*/
|
|
79
|
+
resume() {
|
|
80
|
+
if (!this.stopped) return;
|
|
81
|
+
this.stopped = false;
|
|
82
|
+
this.state.lastTime = performance.now();
|
|
83
|
+
this.loop();
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Main game loop
|
|
87
|
+
*/
|
|
88
|
+
loop() {
|
|
89
|
+
if (this.stopped) return;
|
|
90
|
+
this.animationFrameId = requestAnimationFrame(this.loop);
|
|
91
|
+
const time = performance.now();
|
|
92
|
+
if (Math.abs(time - this.state.lastTime) > PAUSE_THRESHOLD_MS) {
|
|
93
|
+
this.state.lastTime = time - LOADING_BAR_THROTTLE_MS;
|
|
94
|
+
}
|
|
95
|
+
const dt = time - this.state.lastTime;
|
|
96
|
+
this.state.dt = this.state.dt * 0.9 + dt * 0.1;
|
|
97
|
+
this.state.lastTime = time;
|
|
98
|
+
const fps = Math.round(1e3 / this.state.dt);
|
|
99
|
+
this.state.fps = fps;
|
|
100
|
+
if (this.callbacks.setFPS) {
|
|
101
|
+
this.callbacks.setFPS(fps);
|
|
102
|
+
}
|
|
103
|
+
let updateRate = this.state.updateRate;
|
|
104
|
+
if (this.callbacks.getUpdateRate) {
|
|
105
|
+
const rate = this.callbacks.getUpdateRate();
|
|
106
|
+
if (rate != null && rate > 0 && Number.isFinite(rate)) {
|
|
107
|
+
updateRate = rate;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
this.state.floatingFrame += this.state.dt * updateRate / 1e3;
|
|
111
|
+
let ds = Math.min(10, Math.round(this.state.floatingFrame - this.state.currentFrame));
|
|
112
|
+
if ((ds === 0 || ds === 2) && updateRate === DEFAULT_UPDATE_RATE && Math.abs(fps - DEFAULT_FPS) < 2) {
|
|
113
|
+
ds = 1;
|
|
114
|
+
this.state.floatingFrame = this.state.currentFrame + 1;
|
|
115
|
+
}
|
|
116
|
+
for (let i = 1; i <= ds; i++) {
|
|
117
|
+
this.callbacks.onUpdate();
|
|
118
|
+
if (i < ds && this.callbacks.onTick) {
|
|
119
|
+
this.callbacks.onTick();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
this.state.currentFrame += ds;
|
|
123
|
+
this.callbacks.onDraw();
|
|
124
|
+
if (this.callbacks.onTick) {
|
|
125
|
+
this.callbacks.onTick();
|
|
126
|
+
}
|
|
127
|
+
if (ds > 0 && this.callbacks.onWatchStep) {
|
|
128
|
+
this.callbacks.onWatchStep();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get current state
|
|
133
|
+
*/
|
|
134
|
+
getState() {
|
|
135
|
+
return this.state;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Set update rate
|
|
139
|
+
*/
|
|
140
|
+
setUpdateRate(rate) {
|
|
141
|
+
if (rate > 0 && Number.isFinite(rate)) {
|
|
142
|
+
this.state.updateRate = rate;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get FPS
|
|
147
|
+
*/
|
|
148
|
+
getFPS() {
|
|
149
|
+
return this.state.fps;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
153
|
+
0 && (module.exports = {
|
|
154
|
+
GameLoop
|
|
155
|
+
});
|
|
156
|
+
//# sourceMappingURL=game-loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/loop/game-loop.ts","../../src/constants.ts"],"sourcesContent":["import { DEFAULT_FPS, DEFAULT_UPDATE_RATE, FRAME_TIME_MS, LOADING_BAR_THROTTLE_MS, PAUSE_THRESHOLD_MS } from \"../constants\";\n\n/**\n * GameLoop - Manages the game update/draw cycle\n *\n * Responsibilities:\n * - requestAnimationFrame loop\n * - Delta time calculation\n * - FPS calculation\n * - Update rate management\n * - Frame skipping for catch-up\n */\n\nexport interface GameLoopCallbacks {\n\tonUpdate: () => void;\n\tonDraw: () => void;\n\tonTick?: () => void;\n\tonWatchStep?: () => void;\n\tgetUpdateRate?: () => number | undefined;\n\tsetFPS?: (fps: number) => void;\n}\n\nexport interface GameLoopState {\n\tcurrentFrame: number;\n\tfloatingFrame: number;\n\tdt: number;\n\tlastTime: number;\n\tfps: number;\n\tupdateRate: number;\n}\n\nexport class GameLoop {\n\tprivate callbacks: GameLoopCallbacks;\n\tprivate state: GameLoopState;\n\tprivate stopped = false;\n\tprivate animationFrameId: number | null = null;\n\n\tconstructor(callbacks: GameLoopCallbacks) {\n\t\tthis.callbacks = callbacks;\n\t\tthis.state = {\n\t\t\tcurrentFrame: 0,\n\t\t\tfloatingFrame: 0,\n\t\t\tdt: FRAME_TIME_MS,\n\t\t\tlastTime: performance.now(),\n\t\t\tfps: DEFAULT_FPS,\n\t\t\tupdateRate: DEFAULT_UPDATE_RATE,\n\t\t};\n\t\t// Bind loop once\n\t\tthis.loop = this.loop.bind(this);\n\t}\n\n\t/**\n\t * Start the game loop\n\t */\n\tstart(): void {\n\t\tthis.stopped = false;\n\t\tthis.state.lastTime = performance.now();\n\t\tthis.state.currentFrame = 0;\n\t\tthis.state.floatingFrame = 0;\n\t\tthis.loop();\n\t}\n\n\t/**\n\t * Stop the game loop\n\t */\n\tstop(): void {\n\t\tthis.stopped = true;\n\t\tif (this.animationFrameId !== null) {\n\t\t\tcancelAnimationFrame(this.animationFrameId);\n\t\t\tthis.animationFrameId = null;\n\t\t}\n\t}\n\n\t/**\n\t * Resume the game loop\n\t */\n\tresume(): void {\n\t\tif (!this.stopped) return;\n\t\tthis.stopped = false;\n\t\tthis.state.lastTime = performance.now();\n\t\tthis.loop();\n\t}\n\n\t/**\n\t * Main game loop\n\t */\n\tprivate loop(): void {\n\t\tif (this.stopped) return;\n\n\t\t// Schedule next frame\n\t\tthis.animationFrameId = requestAnimationFrame(this.loop);\n\n\t\tconst time = performance.now();\n\n\t\t// Recover from long pause (tab switch, etc)\n\t\tif (Math.abs(time - this.state.lastTime) > PAUSE_THRESHOLD_MS) {\n\t\t\tthis.state.lastTime = time - LOADING_BAR_THROTTLE_MS;\n\t\t}\n\n\t\t// Calculate delta time\n\t\tconst dt = time - this.state.lastTime;\n\t\tthis.state.dt = this.state.dt * 0.9 + dt * 0.1; // Smooth with exponential moving average\n\t\tthis.state.lastTime = time;\n\n\t\t// Calculate FPS and update in global context\n\t\tconst fps = Math.round(1000 / this.state.dt);\n\t\tthis.state.fps = fps;\n\t\tif (this.callbacks.setFPS) {\n\t\t\tthis.callbacks.setFPS(fps);\n\t\t}\n\n\t\t// Read update_rate from global context each frame\n\t\tlet updateRate = this.state.updateRate; // Default\n\t\tif (this.callbacks.getUpdateRate) {\n\t\t\tconst rate = this.callbacks.getUpdateRate();\n\t\t\tif (rate != null && rate > 0 && Number.isFinite(rate)) {\n\t\t\t\tupdateRate = rate;\n\t\t\t}\n\t\t}\n\n\t\t// Calculate how many update steps needed\n\t\tthis.state.floatingFrame += (this.state.dt * updateRate) / 1000;\n\t\tlet ds = Math.min(10, Math.round(this.state.floatingFrame - this.state.currentFrame));\n\n\t\t// Correction for 60fps (reduce jitter)\n\t\tif ((ds === 0 || ds === 2) && updateRate === DEFAULT_UPDATE_RATE && Math.abs(fps - DEFAULT_FPS) < 2) {\n\t\t\tds = 1;\n\t\t\tthis.state.floatingFrame = this.state.currentFrame + 1;\n\t\t}\n\n\t\t// Call update() multiple times if needed (catch up)\n\t\t// Loop from 1 to ds (inclusive), not 0 to steps-1\n\t\tfor (let i = 1; i <= ds; i++) {\n\t\t\tthis.callbacks.onUpdate();\n\n\t\t\t// Tick between updates (for threads/coroutines)\n\t\t\tif (i < ds && this.callbacks.onTick) {\n\t\t\t\tthis.callbacks.onTick();\n\t\t\t}\n\t\t}\n\n\t\t// Update current frame\n\t\tthis.state.currentFrame += ds;\n\n\t\t// Call draw() once per frame\n\t\tthis.callbacks.onDraw();\n\n\t\t// Tick after draw\n\t\tif (this.callbacks.onTick) {\n\t\t\tthis.callbacks.onTick();\n\t\t}\n\n\t\t// Watch step after draw if ds > 0\n\t\tif (ds > 0 && this.callbacks.onWatchStep) {\n\t\t\tthis.callbacks.onWatchStep();\n\t\t}\n\t}\n\n\t/**\n\t * Get current state\n\t */\n\tgetState(): GameLoopState {\n\t\treturn this.state;\n\t}\n\n\t/**\n\t * Set update rate\n\t */\n\tsetUpdateRate(rate: number): void {\n\t\tif (rate > 0 && Number.isFinite(rate)) {\n\t\t\tthis.state.updateRate = rate;\n\t\t}\n\t}\n\n\t/**\n\t * Get FPS\n\t */\n\tgetFPS(): number {\n\t\treturn this.state.fps;\n\t}\n}\n","/** Default frames per second */\nexport const DEFAULT_FPS = 60;\n\n/** Default update rate (updates per second) */\nexport const DEFAULT_UPDATE_RATE = 60;\n\n/** Frame time in milliseconds at 60 FPS */\nexport const FRAME_TIME_MS = 1000 / DEFAULT_FPS;\n\n/** Threshold in ms to detect long pauses (tab switch, etc.) */\nexport const PAUSE_THRESHOLD_MS = 160;\n\n/** Default tile/block size in pixels */\nexport const DEFAULT_BLOCK_SIZE = 16;\n\n/** Minimum interval between loading bar redraws in ms (~60fps) */\nexport const LOADING_BAR_THROTTLE_MS = 16;\n\n/** Timeout in ms for individual asset loads (sprite/map HTTP requests) */\nexport const ASSET_LOAD_TIMEOUT_MS = 30_000;\n"],"mappings":"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","names":["DEFAULT_FPS","DEFAULT_UPDATE_RATE","FRAME_TIME_MS","PAUSE_THRESHOLD_MS","LOADING_BAR_THROTTLE_MS","GameLoop","callbacks","state","stopped","animationFrameId","currentFrame","floatingFrame","dt","FRAME_TIME_MS","lastTime","performance","now","fps","DEFAULT_FPS","updateRate","DEFAULT_UPDATE_RATE","loop","bind","start","stop","cancelAnimationFrame","resume","requestAnimationFrame","time","Math","abs","PAUSE_THRESHOLD_MS","LOADING_BAR_THROTTLE_MS","round","setFPS","getUpdateRate","rate","Number","isFinite","ds","min","i","onUpdate","onTick","onDraw","onWatchStep","getState","setUpdateRate","getFPS"]}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/constants.ts
|
|
5
|
+
var DEFAULT_FPS = 60;
|
|
6
|
+
var DEFAULT_UPDATE_RATE = 60;
|
|
7
|
+
var FRAME_TIME_MS = 1e3 / DEFAULT_FPS;
|
|
8
|
+
var PAUSE_THRESHOLD_MS = 160;
|
|
9
|
+
var LOADING_BAR_THROTTLE_MS = 16;
|
|
10
|
+
|
|
11
|
+
// src/loop/game-loop.ts
|
|
12
|
+
var GameLoop = class {
|
|
13
|
+
static {
|
|
14
|
+
__name(this, "GameLoop");
|
|
15
|
+
}
|
|
16
|
+
callbacks;
|
|
17
|
+
state;
|
|
18
|
+
stopped = false;
|
|
19
|
+
animationFrameId = null;
|
|
20
|
+
constructor(callbacks) {
|
|
21
|
+
this.callbacks = callbacks;
|
|
22
|
+
this.state = {
|
|
23
|
+
currentFrame: 0,
|
|
24
|
+
floatingFrame: 0,
|
|
25
|
+
dt: FRAME_TIME_MS,
|
|
26
|
+
lastTime: performance.now(),
|
|
27
|
+
fps: DEFAULT_FPS,
|
|
28
|
+
updateRate: DEFAULT_UPDATE_RATE
|
|
29
|
+
};
|
|
30
|
+
this.loop = this.loop.bind(this);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Start the game loop
|
|
34
|
+
*/
|
|
35
|
+
start() {
|
|
36
|
+
this.stopped = false;
|
|
37
|
+
this.state.lastTime = performance.now();
|
|
38
|
+
this.state.currentFrame = 0;
|
|
39
|
+
this.state.floatingFrame = 0;
|
|
40
|
+
this.loop();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Stop the game loop
|
|
44
|
+
*/
|
|
45
|
+
stop() {
|
|
46
|
+
this.stopped = true;
|
|
47
|
+
if (this.animationFrameId !== null) {
|
|
48
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
49
|
+
this.animationFrameId = null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resume the game loop
|
|
54
|
+
*/
|
|
55
|
+
resume() {
|
|
56
|
+
if (!this.stopped) return;
|
|
57
|
+
this.stopped = false;
|
|
58
|
+
this.state.lastTime = performance.now();
|
|
59
|
+
this.loop();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Main game loop
|
|
63
|
+
*/
|
|
64
|
+
loop() {
|
|
65
|
+
if (this.stopped) return;
|
|
66
|
+
this.animationFrameId = requestAnimationFrame(this.loop);
|
|
67
|
+
const time = performance.now();
|
|
68
|
+
if (Math.abs(time - this.state.lastTime) > PAUSE_THRESHOLD_MS) {
|
|
69
|
+
this.state.lastTime = time - LOADING_BAR_THROTTLE_MS;
|
|
70
|
+
}
|
|
71
|
+
const dt = time - this.state.lastTime;
|
|
72
|
+
this.state.dt = this.state.dt * 0.9 + dt * 0.1;
|
|
73
|
+
this.state.lastTime = time;
|
|
74
|
+
const fps = Math.round(1e3 / this.state.dt);
|
|
75
|
+
this.state.fps = fps;
|
|
76
|
+
if (this.callbacks.setFPS) {
|
|
77
|
+
this.callbacks.setFPS(fps);
|
|
78
|
+
}
|
|
79
|
+
let updateRate = this.state.updateRate;
|
|
80
|
+
if (this.callbacks.getUpdateRate) {
|
|
81
|
+
const rate = this.callbacks.getUpdateRate();
|
|
82
|
+
if (rate != null && rate > 0 && Number.isFinite(rate)) {
|
|
83
|
+
updateRate = rate;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
this.state.floatingFrame += this.state.dt * updateRate / 1e3;
|
|
87
|
+
let ds = Math.min(10, Math.round(this.state.floatingFrame - this.state.currentFrame));
|
|
88
|
+
if ((ds === 0 || ds === 2) && updateRate === DEFAULT_UPDATE_RATE && Math.abs(fps - DEFAULT_FPS) < 2) {
|
|
89
|
+
ds = 1;
|
|
90
|
+
this.state.floatingFrame = this.state.currentFrame + 1;
|
|
91
|
+
}
|
|
92
|
+
for (let i = 1; i <= ds; i++) {
|
|
93
|
+
this.callbacks.onUpdate();
|
|
94
|
+
if (i < ds && this.callbacks.onTick) {
|
|
95
|
+
this.callbacks.onTick();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
this.state.currentFrame += ds;
|
|
99
|
+
this.callbacks.onDraw();
|
|
100
|
+
if (this.callbacks.onTick) {
|
|
101
|
+
this.callbacks.onTick();
|
|
102
|
+
}
|
|
103
|
+
if (ds > 0 && this.callbacks.onWatchStep) {
|
|
104
|
+
this.callbacks.onWatchStep();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get current state
|
|
109
|
+
*/
|
|
110
|
+
getState() {
|
|
111
|
+
return this.state;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Set update rate
|
|
115
|
+
*/
|
|
116
|
+
setUpdateRate(rate) {
|
|
117
|
+
if (rate > 0 && Number.isFinite(rate)) {
|
|
118
|
+
this.state.updateRate = rate;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get FPS
|
|
123
|
+
*/
|
|
124
|
+
getFPS() {
|
|
125
|
+
return this.state.fps;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
export {
|
|
129
|
+
GameLoop
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=game-loop.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/constants.ts","../../src/loop/game-loop.ts"],"sourcesContent":["/** Default frames per second */\nexport const DEFAULT_FPS = 60;\n\n/** Default update rate (updates per second) */\nexport const DEFAULT_UPDATE_RATE = 60;\n\n/** Frame time in milliseconds at 60 FPS */\nexport const FRAME_TIME_MS = 1000 / DEFAULT_FPS;\n\n/** Threshold in ms to detect long pauses (tab switch, etc.) */\nexport const PAUSE_THRESHOLD_MS = 160;\n\n/** Default tile/block size in pixels */\nexport const DEFAULT_BLOCK_SIZE = 16;\n\n/** Minimum interval between loading bar redraws in ms (~60fps) */\nexport const LOADING_BAR_THROTTLE_MS = 16;\n\n/** Timeout in ms for individual asset loads (sprite/map HTTP requests) */\nexport const ASSET_LOAD_TIMEOUT_MS = 30_000;\n","import { DEFAULT_FPS, DEFAULT_UPDATE_RATE, FRAME_TIME_MS, LOADING_BAR_THROTTLE_MS, PAUSE_THRESHOLD_MS } from \"../constants\";\n\n/**\n * GameLoop - Manages the game update/draw cycle\n *\n * Responsibilities:\n * - requestAnimationFrame loop\n * - Delta time calculation\n * - FPS calculation\n * - Update rate management\n * - Frame skipping for catch-up\n */\n\nexport interface GameLoopCallbacks {\n\tonUpdate: () => void;\n\tonDraw: () => void;\n\tonTick?: () => void;\n\tonWatchStep?: () => void;\n\tgetUpdateRate?: () => number | undefined;\n\tsetFPS?: (fps: number) => void;\n}\n\nexport interface GameLoopState {\n\tcurrentFrame: number;\n\tfloatingFrame: number;\n\tdt: number;\n\tlastTime: number;\n\tfps: number;\n\tupdateRate: number;\n}\n\nexport class GameLoop {\n\tprivate callbacks: GameLoopCallbacks;\n\tprivate state: GameLoopState;\n\tprivate stopped = false;\n\tprivate animationFrameId: number | null = null;\n\n\tconstructor(callbacks: GameLoopCallbacks) {\n\t\tthis.callbacks = callbacks;\n\t\tthis.state = {\n\t\t\tcurrentFrame: 0,\n\t\t\tfloatingFrame: 0,\n\t\t\tdt: FRAME_TIME_MS,\n\t\t\tlastTime: performance.now(),\n\t\t\tfps: DEFAULT_FPS,\n\t\t\tupdateRate: DEFAULT_UPDATE_RATE,\n\t\t};\n\t\t// Bind loop once\n\t\tthis.loop = this.loop.bind(this);\n\t}\n\n\t/**\n\t * Start the game loop\n\t */\n\tstart(): void {\n\t\tthis.stopped = false;\n\t\tthis.state.lastTime = performance.now();\n\t\tthis.state.currentFrame = 0;\n\t\tthis.state.floatingFrame = 0;\n\t\tthis.loop();\n\t}\n\n\t/**\n\t * Stop the game loop\n\t */\n\tstop(): void {\n\t\tthis.stopped = true;\n\t\tif (this.animationFrameId !== null) {\n\t\t\tcancelAnimationFrame(this.animationFrameId);\n\t\t\tthis.animationFrameId = null;\n\t\t}\n\t}\n\n\t/**\n\t * Resume the game loop\n\t */\n\tresume(): void {\n\t\tif (!this.stopped) return;\n\t\tthis.stopped = false;\n\t\tthis.state.lastTime = performance.now();\n\t\tthis.loop();\n\t}\n\n\t/**\n\t * Main game loop\n\t */\n\tprivate loop(): void {\n\t\tif (this.stopped) return;\n\n\t\t// Schedule next frame\n\t\tthis.animationFrameId = requestAnimationFrame(this.loop);\n\n\t\tconst time = performance.now();\n\n\t\t// Recover from long pause (tab switch, etc)\n\t\tif (Math.abs(time - this.state.lastTime) > PAUSE_THRESHOLD_MS) {\n\t\t\tthis.state.lastTime = time - LOADING_BAR_THROTTLE_MS;\n\t\t}\n\n\t\t// Calculate delta time\n\t\tconst dt = time - this.state.lastTime;\n\t\tthis.state.dt = this.state.dt * 0.9 + dt * 0.1; // Smooth with exponential moving average\n\t\tthis.state.lastTime = time;\n\n\t\t// Calculate FPS and update in global context\n\t\tconst fps = Math.round(1000 / this.state.dt);\n\t\tthis.state.fps = fps;\n\t\tif (this.callbacks.setFPS) {\n\t\t\tthis.callbacks.setFPS(fps);\n\t\t}\n\n\t\t// Read update_rate from global context each frame\n\t\tlet updateRate = this.state.updateRate; // Default\n\t\tif (this.callbacks.getUpdateRate) {\n\t\t\tconst rate = this.callbacks.getUpdateRate();\n\t\t\tif (rate != null && rate > 0 && Number.isFinite(rate)) {\n\t\t\t\tupdateRate = rate;\n\t\t\t}\n\t\t}\n\n\t\t// Calculate how many update steps needed\n\t\tthis.state.floatingFrame += (this.state.dt * updateRate) / 1000;\n\t\tlet ds = Math.min(10, Math.round(this.state.floatingFrame - this.state.currentFrame));\n\n\t\t// Correction for 60fps (reduce jitter)\n\t\tif ((ds === 0 || ds === 2) && updateRate === DEFAULT_UPDATE_RATE && Math.abs(fps - DEFAULT_FPS) < 2) {\n\t\t\tds = 1;\n\t\t\tthis.state.floatingFrame = this.state.currentFrame + 1;\n\t\t}\n\n\t\t// Call update() multiple times if needed (catch up)\n\t\t// Loop from 1 to ds (inclusive), not 0 to steps-1\n\t\tfor (let i = 1; i <= ds; i++) {\n\t\t\tthis.callbacks.onUpdate();\n\n\t\t\t// Tick between updates (for threads/coroutines)\n\t\t\tif (i < ds && this.callbacks.onTick) {\n\t\t\t\tthis.callbacks.onTick();\n\t\t\t}\n\t\t}\n\n\t\t// Update current frame\n\t\tthis.state.currentFrame += ds;\n\n\t\t// Call draw() once per frame\n\t\tthis.callbacks.onDraw();\n\n\t\t// Tick after draw\n\t\tif (this.callbacks.onTick) {\n\t\t\tthis.callbacks.onTick();\n\t\t}\n\n\t\t// Watch step after draw if ds > 0\n\t\tif (ds > 0 && this.callbacks.onWatchStep) {\n\t\t\tthis.callbacks.onWatchStep();\n\t\t}\n\t}\n\n\t/**\n\t * Get current state\n\t */\n\tgetState(): GameLoopState {\n\t\treturn this.state;\n\t}\n\n\t/**\n\t * Set update rate\n\t */\n\tsetUpdateRate(rate: number): void {\n\t\tif (rate > 0 && Number.isFinite(rate)) {\n\t\t\tthis.state.updateRate = rate;\n\t\t}\n\t}\n\n\t/**\n\t * Get FPS\n\t */\n\tgetFPS(): number {\n\t\treturn this.state.fps;\n\t}\n}\n"],"mappings":"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","names":["DEFAULT_FPS","DEFAULT_UPDATE_RATE","FRAME_TIME_MS","PAUSE_THRESHOLD_MS","LOADING_BAR_THROTTLE_MS","GameLoop","callbacks","state","stopped","animationFrameId","currentFrame","floatingFrame","dt","FRAME_TIME_MS","lastTime","performance","now","fps","DEFAULT_FPS","updateRate","DEFAULT_UPDATE_RATE","loop","bind","start","stop","cancelAnimationFrame","resume","requestAnimationFrame","time","Math","abs","PAUSE_THRESHOLD_MS","LOADING_BAR_THROTTLE_MS","round","setFPS","getUpdateRate","rate","Number","isFinite","ds","min","i","onUpdate","onTick","onDraw","onWatchStep","getState","setUpdateRate","getFPS"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { GameLoop, GameLoopCallbacks, GameLoopState } from './game-loop.mjs';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { GameLoop, GameLoopCallbacks, GameLoopState } from './game-loop.js';
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/loop/index.ts
|
|
22
|
+
var loop_exports = {};
|
|
23
|
+
__export(loop_exports, {
|
|
24
|
+
GameLoop: () => GameLoop
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(loop_exports);
|
|
27
|
+
|
|
28
|
+
// src/constants.ts
|
|
29
|
+
var DEFAULT_FPS = 60;
|
|
30
|
+
var DEFAULT_UPDATE_RATE = 60;
|
|
31
|
+
var FRAME_TIME_MS = 1e3 / DEFAULT_FPS;
|
|
32
|
+
var PAUSE_THRESHOLD_MS = 160;
|
|
33
|
+
var LOADING_BAR_THROTTLE_MS = 16;
|
|
34
|
+
|
|
35
|
+
// src/loop/game-loop.ts
|
|
36
|
+
var GameLoop = class {
|
|
37
|
+
static {
|
|
38
|
+
__name(this, "GameLoop");
|
|
39
|
+
}
|
|
40
|
+
callbacks;
|
|
41
|
+
state;
|
|
42
|
+
stopped = false;
|
|
43
|
+
animationFrameId = null;
|
|
44
|
+
constructor(callbacks) {
|
|
45
|
+
this.callbacks = callbacks;
|
|
46
|
+
this.state = {
|
|
47
|
+
currentFrame: 0,
|
|
48
|
+
floatingFrame: 0,
|
|
49
|
+
dt: FRAME_TIME_MS,
|
|
50
|
+
lastTime: performance.now(),
|
|
51
|
+
fps: DEFAULT_FPS,
|
|
52
|
+
updateRate: DEFAULT_UPDATE_RATE
|
|
53
|
+
};
|
|
54
|
+
this.loop = this.loop.bind(this);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Start the game loop
|
|
58
|
+
*/
|
|
59
|
+
start() {
|
|
60
|
+
this.stopped = false;
|
|
61
|
+
this.state.lastTime = performance.now();
|
|
62
|
+
this.state.currentFrame = 0;
|
|
63
|
+
this.state.floatingFrame = 0;
|
|
64
|
+
this.loop();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Stop the game loop
|
|
68
|
+
*/
|
|
69
|
+
stop() {
|
|
70
|
+
this.stopped = true;
|
|
71
|
+
if (this.animationFrameId !== null) {
|
|
72
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
73
|
+
this.animationFrameId = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Resume the game loop
|
|
78
|
+
*/
|
|
79
|
+
resume() {
|
|
80
|
+
if (!this.stopped) return;
|
|
81
|
+
this.stopped = false;
|
|
82
|
+
this.state.lastTime = performance.now();
|
|
83
|
+
this.loop();
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Main game loop
|
|
87
|
+
*/
|
|
88
|
+
loop() {
|
|
89
|
+
if (this.stopped) return;
|
|
90
|
+
this.animationFrameId = requestAnimationFrame(this.loop);
|
|
91
|
+
const time = performance.now();
|
|
92
|
+
if (Math.abs(time - this.state.lastTime) > PAUSE_THRESHOLD_MS) {
|
|
93
|
+
this.state.lastTime = time - LOADING_BAR_THROTTLE_MS;
|
|
94
|
+
}
|
|
95
|
+
const dt = time - this.state.lastTime;
|
|
96
|
+
this.state.dt = this.state.dt * 0.9 + dt * 0.1;
|
|
97
|
+
this.state.lastTime = time;
|
|
98
|
+
const fps = Math.round(1e3 / this.state.dt);
|
|
99
|
+
this.state.fps = fps;
|
|
100
|
+
if (this.callbacks.setFPS) {
|
|
101
|
+
this.callbacks.setFPS(fps);
|
|
102
|
+
}
|
|
103
|
+
let updateRate = this.state.updateRate;
|
|
104
|
+
if (this.callbacks.getUpdateRate) {
|
|
105
|
+
const rate = this.callbacks.getUpdateRate();
|
|
106
|
+
if (rate != null && rate > 0 && Number.isFinite(rate)) {
|
|
107
|
+
updateRate = rate;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
this.state.floatingFrame += this.state.dt * updateRate / 1e3;
|
|
111
|
+
let ds = Math.min(10, Math.round(this.state.floatingFrame - this.state.currentFrame));
|
|
112
|
+
if ((ds === 0 || ds === 2) && updateRate === DEFAULT_UPDATE_RATE && Math.abs(fps - DEFAULT_FPS) < 2) {
|
|
113
|
+
ds = 1;
|
|
114
|
+
this.state.floatingFrame = this.state.currentFrame + 1;
|
|
115
|
+
}
|
|
116
|
+
for (let i = 1; i <= ds; i++) {
|
|
117
|
+
this.callbacks.onUpdate();
|
|
118
|
+
if (i < ds && this.callbacks.onTick) {
|
|
119
|
+
this.callbacks.onTick();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
this.state.currentFrame += ds;
|
|
123
|
+
this.callbacks.onDraw();
|
|
124
|
+
if (this.callbacks.onTick) {
|
|
125
|
+
this.callbacks.onTick();
|
|
126
|
+
}
|
|
127
|
+
if (ds > 0 && this.callbacks.onWatchStep) {
|
|
128
|
+
this.callbacks.onWatchStep();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get current state
|
|
133
|
+
*/
|
|
134
|
+
getState() {
|
|
135
|
+
return this.state;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Set update rate
|
|
139
|
+
*/
|
|
140
|
+
setUpdateRate(rate) {
|
|
141
|
+
if (rate > 0 && Number.isFinite(rate)) {
|
|
142
|
+
this.state.updateRate = rate;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get FPS
|
|
147
|
+
*/
|
|
148
|
+
getFPS() {
|
|
149
|
+
return this.state.fps;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
153
|
+
0 && (module.exports = {
|
|
154
|
+
GameLoop
|
|
155
|
+
});
|
|
156
|
+
//# sourceMappingURL=index.js.map
|