Skip to content

Commit cbfadf6

Browse files
committed
refactor: better time management for engine loop
1 parent 0011e7b commit cbfadf6

File tree

5 files changed

+91
-85
lines changed

5 files changed

+91
-85
lines changed

src/core/Engine.ts

+19-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { EngineOptions } from '../options/EngineOptions'
2+
import { EngineError } from '../errors/EngineError'
23
import type { OuterNode } from './OuterNode'
34
import type { TreeNode } from './TreeNode'
45
import { Time } from './Time'
@@ -12,13 +13,9 @@ export class Engine {
1213
*/
1314
private _options: EngineOptions
1415
/**
15-
* Engine container element.
16+
* Engine DOM container element.
1617
*/
1718
private _container: HTMLElement
18-
/**
19-
* Root node of the engine's graph.
20-
*/
21-
public rootNode: TreeNode
2219
/**
2320
* Engine's attached systems.
2421
*/
@@ -27,6 +24,10 @@ export class Engine {
2724
* Engine's time utility.
2825
*/
2926
private _time: Time = new Time()
27+
/**
28+
* Root node of the engine's graph.
29+
*/
30+
public rootNode: TreeNode | undefined
3031

3132
/**
3233
* Get engine's options.
@@ -64,7 +65,7 @@ export class Engine {
6465
framerate: null,
6566
...options,
6667
}
67-
this._container = document.querySelector(this._options.container)
68+
this._container = document.querySelector(this._options.container as string) as HTMLElement
6869
this.init()
6970
}
7071

@@ -79,7 +80,7 @@ export class Engine {
7980
public run(): void {
8081
// Check if root node exists before loading.
8182
if (!this.rootNode) {
82-
throw new Error('No root node given to engine, cannot run.')
83+
throw new EngineError(this, 'ENGINE:FAILURE', 'No root node given to engine, cannot run.')
8384
}
8485
// Load systems.
8586
this.systems.forEach((system) => {
@@ -97,21 +98,26 @@ export class Engine {
9798
* to fix the timestep for fixed update loops (useful for physics and user interactions).
9899
*/
99100
protected step(now: number): void {
101+
if (!this.rootNode) {
102+
throw new EngineError(this, 'ENGINE:FAILURE', 'No root node given to engine, cannot run.')
103+
}
100104
// Update delta and last frame time
101-
this.time.updateDelta(now)
102-
this.time.updateLast()
105+
this.time.update(now)
103106
// Fix timestep of fixedStep methods
104107
this.time.fixTimestep(() => {
108+
if (!this.rootNode) {
109+
throw new EngineError(this, 'ENGINE:FAILURE', 'No root node given to engine, cannot run.')
110+
}
105111
this.systems.forEach((system) => {
106-
Promise.resolve().then(() => system.fixedStep())
112+
system.fixedStep()
107113
})
108-
Promise.resolve().then(() => this.rootNode.fixedStep())
114+
this.rootNode.fixedStep()
109115
})
110116
// Do non fixed steps
111117
this.systems.forEach((system) => {
112-
system.step()
118+
system.step(this.time.delta)
113119
})
114-
this.rootNode.step()
120+
this.rootNode.step(this.time.delta)
115121
// Request next step
116122
requestAnimationFrame(this.step.bind(this))
117123
}

src/core/InnerNode.ts

+23-19
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { EngineError } from '../errors/EngineError'
12
import type { Engine } from './Engine'
23
import { TreeNode } from './TreeNode'
34

@@ -13,14 +14,14 @@ export class InnerNode extends TreeNode {
1314
/**
1415
* Node's children nodes.
1516
*/
16-
protected children: TreeNode[]
17+
protected _children: TreeNode[]
1718

1819
/**
1920
* Setup children and calls onCreate method.
2021
*/
2122
constructor(parent: TreeNode | Engine) {
2223
super(parent)
23-
this.children = []
24+
this._children = []
2425
this.onCreate()
2526
}
2627

@@ -31,11 +32,11 @@ export class InnerNode extends TreeNode {
3132
* @sealed
3233
*/
3334
public add(node: TreeNode): number {
34-
this.children.push(node)
35+
this._children.push(node)
3536
// Load the added node if added after loading.
36-
if (this.isLoaded)
37+
if (this._isLoaded)
3738
node.load()
38-
return this.children.length
39+
return this._children.length
3940
}
4041

4142
/**
@@ -46,7 +47,7 @@ export class InnerNode extends TreeNode {
4647
*/
4748
public remove(node: TreeNode): TreeNode {
4849
let removedNode
49-
this.children = this.children.filter((n: TreeNode) => {
50+
this._children = this._children.filter((n: TreeNode) => {
5051
// Unloads and filters the given node out.
5152
if (n.id === node.id) {
5253
removedNode = n
@@ -55,6 +56,8 @@ export class InnerNode extends TreeNode {
5556
}
5657
return true
5758
})
59+
if (!removedNode)
60+
throw new EngineError(this._engine, 'NODE:FAILURE', 'The node you tried to remove is not a child of this node.')
5861
return removedNode
5962
}
6063

@@ -67,10 +70,10 @@ export class InnerNode extends TreeNode {
6770
*/
6871
public load(): void {
6972
this.onLoad()
70-
for (let i = 0, len = this.children.length; i !== len; ++i) {
71-
this.children[i].load()
73+
for (let i = 0, len = this._children.length; i !== len; ++i) {
74+
this._children[i].load()
7275
}
73-
this.isLoaded = true
76+
this._isLoaded = true
7477
}
7578

7679
/**
@@ -80,10 +83,10 @@ export class InnerNode extends TreeNode {
8083
* Calls the `onStep` method and children's `step` methods.
8184
* @sealed
8285
*/
83-
public step(): void {
84-
this.onStep()
85-
for (let i = 0, len = this.children.length; i !== len; ++i) {
86-
this.children[i].step()
86+
public step(delta: number): void {
87+
this.onStep(delta)
88+
for (let i = 0, len = this._children.length; i !== len; ++i) {
89+
this._children[i].step(delta)
8790
}
8891
}
8992

@@ -96,8 +99,8 @@ export class InnerNode extends TreeNode {
9699
*/
97100
public fixedStep(): void {
98101
this.onFixedStep()
99-
for (let i = 0, len = this.children.length; i !== len; ++i) {
100-
this.children[i].fixedStep()
102+
for (let i = 0, len = this._children.length; i !== len; ++i) {
103+
this._children[i].fixedStep()
101104
}
102105
}
103106

@@ -110,10 +113,10 @@ export class InnerNode extends TreeNode {
110113
*/
111114
public unload(): void {
112115
this.onUnload()
113-
for (let i = 0, len = this.children.length; i !== len; ++i) {
114-
this.children[i].unload()
116+
for (let i = 0, len = this._children.length; i !== len; ++i) {
117+
this._children[i].unload()
115118
}
116-
this.isLoaded = false
119+
this._isLoaded = false
117120
}
118121

119122
/**
@@ -138,7 +141,8 @@ export class InnerNode extends TreeNode {
138141
* this function is to be implemented when needed.
139142
* @virtual
140143
*/
141-
protected onStep(): void { }
144+
// eslint-disable-next-line unused-imports/no-unused-vars
145+
protected onStep(delta: number): void { }
142146

143147
/**
144148
* Called by the parent node at each fixed step of the loop,

src/core/OuterNode.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { EngineError } from '../errors/EngineError'
12
import type { Engine } from './Engine'
23
import { TreeNode } from './TreeNode'
34

@@ -21,7 +22,7 @@ export class OuterNode extends TreeNode {
2122
* @sealed
2223
*/
2324
public add(): number {
24-
throw new Error('You tried to add a child to an outer node. Outer nodes cannot have children.')
25+
throw new EngineError(this._engine, 'NODE:FAILURE', 'You tried to add a child to an outer node. Outer nodes cannot have children.')
2526
}
2627

2728
/**
@@ -30,7 +31,7 @@ export class OuterNode extends TreeNode {
3031
* @sealed
3132
*/
3233
public remove(): TreeNode {
33-
throw new Error('You tried to remove a child to an outer node. Outer nodes cannot have children.')
34+
throw new EngineError(this._engine, 'NODE:FAILURE', 'You tried to remove a child to an outer node. Outer nodes cannot have children.')
3435
}
3536

3637
/**
@@ -42,7 +43,7 @@ export class OuterNode extends TreeNode {
4243
*/
4344
public load(): void {
4445
this.onLoad()
45-
this.isLoaded = true
46+
this._isLoaded = true
4647
}
4748

4849
/**
@@ -52,8 +53,8 @@ export class OuterNode extends TreeNode {
5253
* Calls the `onStep` method.
5354
* @sealed
5455
*/
55-
public step(): void {
56-
this.onStep()
56+
public step(delta: number): void {
57+
this.onStep(delta)
5758
}
5859

5960
/**
@@ -76,7 +77,7 @@ export class OuterNode extends TreeNode {
7677
*/
7778
public unload(): void {
7879
this.onUnload()
79-
this.isLoaded = false
80+
this._isLoaded = false
8081
}
8182

8283
/**
@@ -101,7 +102,8 @@ export class OuterNode extends TreeNode {
101102
* this method is to be implemented when needed.
102103
* @virtual
103104
*/
104-
protected onStep(): void { }
105+
// eslint-disable-next-line unused-imports/no-unused-vars
106+
protected onStep(delta: number): void { }
105107

106108
/**
107109
* Called by the parent node at each fixed step of the loop,

src/core/Time.ts

+28-39
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,46 @@ export class Time {
22
/**
33
* Start of the engine's step.
44
*/
5-
private now: number
5+
private _now: number = performance.now()
66
/**
7-
* Delta time between frame.
7+
* Delta time between frame in seconds.
88
*/
99
private _delta: number = 0
1010
/**
1111
* Accumulator used to store time between frames
1212
*/
13-
private accumulator: number = 0
13+
private _accumulator: number = 0
1414
/**
1515
* Time of the last engine's step.
1616
*/
17-
private last: number = performance.now()
17+
private _last: number = performance.now()
1818
/**
1919
* Slow motion scaling factor.
2020
*/
2121
private _slow: number = 1
2222
/**
2323
* Target framerate of the engine.
2424
*/
25-
private targetFramerate: number = 60
25+
private _targetFramerate: number = 60
2626
/**
2727
* Current framerate in frames per second.
2828
*/
29-
private fps: number
30-
/**
31-
* Ideal time of a step.
32-
*/
33-
private _timestep: number = (1 / this.targetFramerate) * this.slow
29+
private _fps: number = 0
3430

3531
/**
36-
* Get engine's delta time.
32+
* Get engine's delta time in seconds.
3733
*/
3834
public get delta(): number { return this._delta }
3935

4036
/**
41-
* Get engine's timestep.
37+
* Get the current engine's time for a step in seconds (this is the ideal time of a frame).
4238
*/
43-
public get timestep(): number { return this._timestep }
39+
public get timestep(): number { return (1 / this._targetFramerate) * this._slow }
4440

4541
/**
4642
* Get current framerate in frames per second.
4743
*/
48-
public get framerate(): number { return this.fps }
49-
/**
50-
* Set an ideal maximum framerate in frames per second.
51-
*/
52-
public set framerate(target: number) {
53-
this.targetFramerate = target
54-
this._timestep = (1 / this.targetFramerate) * this.slow
55-
}
44+
public get framerate(): number { return this._fps }
5645

5746
/**
5847
* Get slow scaling factor.
@@ -63,39 +52,39 @@ export class Time {
6352
*/
6453
public set slow(value: number) {
6554
this._slow = value
66-
this._timestep = (1 / this.targetFramerate) * this.slow
6755
}
6856

69-
/**
70-
* Updates the delta time, used by the engine's step loop.
71-
*/
72-
public updateDelta(now?: number) {
73-
this.now = now ?? performance.now()
74-
this._delta = (this.now - this.last) / 1000
75-
this.accumulator += this._delta
76-
this.fps = 1 / this._delta
57+
public get targetFramerate(): number { return this._targetFramerate }
58+
59+
public set targetFramerate(value: number) {
60+
this._targetFramerate = value
7761
}
7862

7963
/**
80-
* Updates the time of the last update.
64+
* Updates delta time and related variables, used by the engine's step loop.
8165
*/
82-
public updateLast() {
83-
this.last = this.now
66+
public update(now?: number) {
67+
this._now = now ?? performance.now()
68+
this._delta = (this._now - this._last) / 1000
69+
this._last = this._now
70+
this._accumulator += this._delta
71+
this._fps = 1 / this._delta
8472
}
8573

8674
/**
8775
* Fixes the timestep of a given function during the engine's step loop.
8876
* Used in engine's `step` method to make fixed steps.
8977
* @param callback
9078
*/
91-
public fixTimestep(callback: (timestep?: number) => void) {
92-
const updateStepsCount = 0
93-
while (this.accumulator >= this._timestep) {
94-
callback(this._timestep)
95-
this.accumulator -= this._timestep
79+
public fixTimestep(callback: () => void) {
80+
let updateStepsCount = 0
81+
while (this._accumulator >= this.timestep) {
82+
callback()
83+
this._accumulator -= this.timestep
84+
++updateStepsCount
9685
// To prevent spiral of death
9786
if (updateStepsCount >= 240) {
98-
this.accumulator = 0
87+
this._accumulator = 0
9988
break
10089
}
10190
}

0 commit comments

Comments
 (0)