@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.
Files changed (185) hide show
  1. package/README.md +87 -0
  2. package/dist/assets/constructors.d.mts +6 -0
  3. package/dist/assets/constructors.d.ts +6 -0
  4. package/dist/assets/constructors.js +40 -0
  5. package/dist/assets/constructors.js.map +1 -0
  6. package/dist/assets/constructors.mjs +12 -0
  7. package/dist/assets/constructors.mjs.map +1 -0
  8. package/dist/assets/index.d.mts +11 -0
  9. package/dist/assets/index.d.ts +11 -0
  10. package/dist/assets/index.js +276 -0
  11. package/dist/assets/index.js.map +1 -0
  12. package/dist/assets/index.mjs +247 -0
  13. package/dist/assets/index.mjs.map +1 -0
  14. package/dist/assets/loader.d.mts +83 -0
  15. package/dist/assets/loader.d.ts +83 -0
  16. package/dist/assets/loader.js +260 -0
  17. package/dist/assets/loader.js.map +1 -0
  18. package/dist/assets/loader.mjs +237 -0
  19. package/dist/assets/loader.mjs.map +1 -0
  20. package/dist/browser/index.js +16599 -0
  21. package/dist/browser/index.js.map +1 -0
  22. package/dist/browser/index.min.js +171 -0
  23. package/dist/constants.d.mts +16 -0
  24. package/dist/constants.d.ts +16 -0
  25. package/dist/constants.js +49 -0
  26. package/dist/constants.js.map +1 -0
  27. package/dist/constants.mjs +18 -0
  28. package/dist/constants.mjs.map +1 -0
  29. package/dist/core/api-factory.d.mts +63 -0
  30. package/dist/core/api-factory.d.ts +63 -0
  31. package/dist/core/api-factory.js +239 -0
  32. package/dist/core/api-factory.js.map +1 -0
  33. package/dist/core/api-factory.mjs +214 -0
  34. package/dist/core/api-factory.mjs.map +1 -0
  35. package/dist/core/assets-registry.d.mts +14 -0
  36. package/dist/core/assets-registry.d.ts +14 -0
  37. package/dist/core/assets-registry.js +64 -0
  38. package/dist/core/assets-registry.js.map +1 -0
  39. package/dist/core/assets-registry.mjs +41 -0
  40. package/dist/core/assets-registry.mjs.map +1 -0
  41. package/dist/core/controller.d.mts +109 -0
  42. package/dist/core/controller.d.ts +109 -0
  43. package/dist/core/controller.js +1782 -0
  44. package/dist/core/controller.js.map +1 -0
  45. package/dist/core/controller.mjs +1758 -0
  46. package/dist/core/controller.mjs.map +1 -0
  47. package/dist/core/debug-logger.d.mts +35 -0
  48. package/dist/core/debug-logger.d.ts +35 -0
  49. package/dist/core/debug-logger.js +177 -0
  50. package/dist/core/debug-logger.js.map +1 -0
  51. package/dist/core/debug-logger.mjs +154 -0
  52. package/dist/core/debug-logger.mjs.map +1 -0
  53. package/dist/core/error-handler.d.mts +25 -0
  54. package/dist/core/error-handler.d.ts +25 -0
  55. package/dist/core/error-handler.js +106 -0
  56. package/dist/core/error-handler.js.map +1 -0
  57. package/dist/core/error-handler.mjs +81 -0
  58. package/dist/core/error-handler.mjs.map +1 -0
  59. package/dist/core/index.d.mts +14 -0
  60. package/dist/core/index.d.ts +14 -0
  61. package/dist/core/index.js +1782 -0
  62. package/dist/core/index.js.map +1 -0
  63. package/dist/core/index.mjs +1757 -0
  64. package/dist/core/index.mjs.map +1 -0
  65. package/dist/hot-reload/index.d.mts +7 -0
  66. package/dist/hot-reload/index.d.ts +7 -0
  67. package/dist/hot-reload/index.js +103 -0
  68. package/dist/hot-reload/index.js.map +1 -0
  69. package/dist/hot-reload/index.mjs +78 -0
  70. package/dist/hot-reload/index.mjs.map +1 -0
  71. package/dist/hot-reload/updater.d.mts +33 -0
  72. package/dist/hot-reload/updater.d.ts +33 -0
  73. package/dist/hot-reload/updater.js +101 -0
  74. package/dist/hot-reload/updater.js.map +1 -0
  75. package/dist/hot-reload/updater.mjs +78 -0
  76. package/dist/hot-reload/updater.mjs.map +1 -0
  77. package/dist/index.d.mts +24 -0
  78. package/dist/index.d.ts +24 -0
  79. package/dist/index.js +1859 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/index.mjs +1817 -0
  82. package/dist/index.mjs.map +1 -0
  83. package/dist/input/index.d.mts +2 -0
  84. package/dist/input/index.d.ts +2 -0
  85. package/dist/input/index.js +79 -0
  86. package/dist/input/index.js.map +1 -0
  87. package/dist/input/index.mjs +54 -0
  88. package/dist/input/index.mjs.map +1 -0
  89. package/dist/input/manager.d.mts +37 -0
  90. package/dist/input/manager.d.ts +37 -0
  91. package/dist/input/manager.js +77 -0
  92. package/dist/input/manager.js.map +1 -0
  93. package/dist/input/manager.mjs +54 -0
  94. package/dist/input/manager.mjs.map +1 -0
  95. package/dist/loop/game-loop.d.mts +63 -0
  96. package/dist/loop/game-loop.d.ts +63 -0
  97. package/dist/loop/game-loop.js +156 -0
  98. package/dist/loop/game-loop.js.map +1 -0
  99. package/dist/loop/game-loop.mjs +131 -0
  100. package/dist/loop/game-loop.mjs.map +1 -0
  101. package/dist/loop/index.d.mts +1 -0
  102. package/dist/loop/index.d.ts +1 -0
  103. package/dist/loop/index.js +156 -0
  104. package/dist/loop/index.js.map +1 -0
  105. package/dist/loop/index.mjs +131 -0
  106. package/dist/loop/index.mjs.map +1 -0
  107. package/dist/storage/index.d.mts +1 -0
  108. package/dist/storage/index.d.ts +1 -0
  109. package/dist/storage/index.js +31 -0
  110. package/dist/storage/index.js.map +1 -0
  111. package/dist/storage/index.mjs +6 -0
  112. package/dist/storage/index.mjs.map +1 -0
  113. package/dist/system/api.d.mts +28 -0
  114. package/dist/system/api.d.ts +28 -0
  115. package/dist/system/api.js +126 -0
  116. package/dist/system/api.js.map +1 -0
  117. package/dist/system/api.mjs +101 -0
  118. package/dist/system/api.mjs.map +1 -0
  119. package/dist/system/index.d.mts +2 -0
  120. package/dist/system/index.d.ts +2 -0
  121. package/dist/system/index.js +126 -0
  122. package/dist/system/index.js.map +1 -0
  123. package/dist/system/index.mjs +101 -0
  124. package/dist/system/index.mjs.map +1 -0
  125. package/dist/types/assets.d.mts +43 -0
  126. package/dist/types/assets.d.ts +43 -0
  127. package/dist/types/assets.js +19 -0
  128. package/dist/types/assets.js.map +1 -0
  129. package/dist/types/assets.mjs +1 -0
  130. package/dist/types/assets.mjs.map +1 -0
  131. package/dist/types/bridge.d.mts +66 -0
  132. package/dist/types/bridge.d.ts +66 -0
  133. package/dist/types/bridge.js +19 -0
  134. package/dist/types/bridge.js.map +1 -0
  135. package/dist/types/bridge.mjs +1 -0
  136. package/dist/types/bridge.mjs.map +1 -0
  137. package/dist/types/index.d.mts +6 -0
  138. package/dist/types/index.d.ts +6 -0
  139. package/dist/types/index.js +19 -0
  140. package/dist/types/index.js.map +1 -0
  141. package/dist/types/index.mjs +1 -0
  142. package/dist/types/index.mjs.map +1 -0
  143. package/dist/types/runtime.d.mts +71 -0
  144. package/dist/types/runtime.d.ts +71 -0
  145. package/dist/types/runtime.js +19 -0
  146. package/dist/types/runtime.js.map +1 -0
  147. package/dist/types/runtime.mjs +1 -0
  148. package/dist/types/runtime.mjs.map +1 -0
  149. package/dist/types/vm.d.mts +1 -0
  150. package/dist/types/vm.d.ts +1 -0
  151. package/dist/types/vm.js +19 -0
  152. package/dist/types/vm.js.map +1 -0
  153. package/dist/types/vm.mjs +1 -0
  154. package/dist/types/vm.mjs.map +1 -0
  155. package/dist/utils/deep-clone.d.mts +14 -0
  156. package/dist/utils/deep-clone.d.ts +14 -0
  157. package/dist/utils/deep-clone.js +42 -0
  158. package/dist/utils/deep-clone.js.map +1 -0
  159. package/dist/utils/deep-clone.mjs +19 -0
  160. package/dist/utils/deep-clone.mjs.map +1 -0
  161. package/dist/utils/index.d.mts +3 -0
  162. package/dist/utils/index.d.ts +3 -0
  163. package/dist/utils/index.js +156 -0
  164. package/dist/utils/index.js.map +1 -0
  165. package/dist/utils/index.mjs +129 -0
  166. package/dist/utils/index.mjs.map +1 -0
  167. package/dist/utils/object-pool.d.mts +66 -0
  168. package/dist/utils/object-pool.d.ts +66 -0
  169. package/dist/utils/object-pool.js +113 -0
  170. package/dist/utils/object-pool.js.map +1 -0
  171. package/dist/utils/object-pool.mjs +90 -0
  172. package/dist/utils/object-pool.mjs.map +1 -0
  173. package/dist/utils/shallow-equal.d.mts +15 -0
  174. package/dist/utils/shallow-equal.d.ts +15 -0
  175. package/dist/utils/shallow-equal.js +53 -0
  176. package/dist/utils/shallow-equal.js.map +1 -0
  177. package/dist/utils/shallow-equal.mjs +30 -0
  178. package/dist/utils/shallow-equal.mjs.map +1 -0
  179. package/dist/vm/index.d.mts +1 -0
  180. package/dist/vm/index.d.ts +1 -0
  181. package/dist/vm/index.js +37 -0
  182. package/dist/vm/index.js.map +1 -0
  183. package/dist/vm/index.mjs +9 -0
  184. package/dist/vm/index.mjs.map +1 -0
  185. 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