@thi.ng/timestep 0.1.1 → 0.3.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/CHANGELOG.md +18 -1
- package/README.md +4 -4
- package/api.d.ts +15 -1
- package/api.js +10 -1
- package/package.json +2 -2
- package/state.d.ts +9 -2
- package/state.js +13 -0
- package/timestep.d.ts +37 -11
- package/timestep.js +56 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2023-
|
|
3
|
+
- **Last updated**: 2023-05-05T21:29:28Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -9,6 +9,23 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
9
9
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
10
10
|
and/or version bumps of transitive dependencies.
|
|
11
11
|
|
|
12
|
+
## [0.3.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/timestep@0.3.0) (2023-05-05)
|
|
13
|
+
|
|
14
|
+
#### 🚀 Features
|
|
15
|
+
|
|
16
|
+
- add AState.reset() & impls ([a2c4946](https://github.com/thi-ng/umbrella/commit/a2c4946))
|
|
17
|
+
|
|
18
|
+
#### ♻️ Refactoring
|
|
19
|
+
|
|
20
|
+
- rename IUpdatable => ITimeStep ([2cc3951](https://github.com/thi-ng/umbrella/commit/2cc3951))
|
|
21
|
+
|
|
22
|
+
## [0.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/timestep@0.2.0) (2023-04-28)
|
|
23
|
+
|
|
24
|
+
#### 🚀 Features
|
|
25
|
+
|
|
26
|
+
- add INotify support, add fixed time field ([b68c0c6](https://github.com/thi-ng/umbrella/commit/b68c0c6))
|
|
27
|
+
- add integrateAll/interpolateAll() helpers ([71087cf](https://github.com/thi-ng/umbrella/commit/71087cf))
|
|
28
|
+
|
|
12
29
|
### [0.1.1](https://github.com/thi-ng/umbrella/tree/@thi.ng/timestep@0.1.1) (2023-04-25)
|
|
13
30
|
|
|
14
31
|
#### ♻️ Refactoring
|
package/README.md
CHANGED
|
@@ -31,8 +31,8 @@ upon which this implementation is principally based on.
|
|
|
31
31
|
The package provides a configurable [`TimeStep`
|
|
32
32
|
class](https://docs.thi.ng/umbrella/timestep/classes/TimeStep.html) to manage
|
|
33
33
|
the update logic. To participate in these managed updates, the user must provide
|
|
34
|
-
state values/wrappers implementing the [`
|
|
35
|
-
interface](https://docs.thi.ng/umbrella/timestep/interfaces/
|
|
34
|
+
state values/wrappers implementing the [`ITimeStep`
|
|
35
|
+
interface](https://docs.thi.ng/umbrella/timestep/interfaces/ITimeStep.html) for
|
|
36
36
|
the two main phases of the frame update:
|
|
37
37
|
|
|
38
38
|
- `integrate(dt: number, ctx: ReadonlyTimeStep): void`: Depending on actual
|
|
@@ -75,7 +75,7 @@ const b = defVector([0, 0], (x, dt) => [x[0] - 10 * dt, x[1] + 20 * dt]);
|
|
|
75
75
|
// the simulated render frame rate here is only 25 fps...
|
|
76
76
|
setInterval(() => {
|
|
77
77
|
// provide current time and an array of state values to update
|
|
78
|
-
// (any
|
|
78
|
+
// (any ITimeStep impl can be given here, incl. custom types)
|
|
79
79
|
sim.update(Date.now(), [a, b]);
|
|
80
80
|
// show current frame, num updates, time (relative to start) and interpolated state values
|
|
81
81
|
console.log(sim.frame, sim.updates, sim.current, a.value, b.value);
|
|
@@ -148,7 +148,7 @@ For Node.js REPL:
|
|
|
148
148
|
const timestep = await import("@thi.ng/timestep");
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
-
Package sizes (brotli'd, pre-treeshake): ESM:
|
|
151
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 758 bytes
|
|
152
152
|
|
|
153
153
|
## Dependencies
|
|
154
154
|
|
package/api.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Interface for participating in the {@link TimeStep.update} logic.
|
|
3
3
|
*/
|
|
4
|
-
export interface
|
|
4
|
+
export interface ITimeStep {
|
|
5
5
|
/**
|
|
6
6
|
* 1st phase of the update cycle and main update function for this
|
|
7
7
|
* simulation state. Receives timestep (in seconds) and a `ctx` object.
|
|
@@ -36,6 +36,10 @@ export interface IUpdatable {
|
|
|
36
36
|
interpolate(alpha: number, ctx: ReadonlyTimeStep): void;
|
|
37
37
|
}
|
|
38
38
|
export interface ReadonlyTimeStep {
|
|
39
|
+
/**
|
|
40
|
+
* Simulation time since start (resolution {@link ReadonlyTimeStep.dt}).
|
|
41
|
+
*/
|
|
42
|
+
readonly t: number;
|
|
39
43
|
/**
|
|
40
44
|
* Configured timestep (in seconds)
|
|
41
45
|
*/
|
|
@@ -88,4 +92,14 @@ export interface TimeStepOpts {
|
|
|
88
92
|
}
|
|
89
93
|
export type StateUpdate<T> = (curr: T, dt: number, ctx: ReadonlyTimeStep) => T;
|
|
90
94
|
export type StateInterpolation<T> = (prev: T, curr: T, alpha: number, ctx: ReadonlyTimeStep) => T;
|
|
95
|
+
/**
|
|
96
|
+
* Event ID for {@link TimeStep.addListener} to be notified of individual
|
|
97
|
+
* {@link ITimeStep.integrate} iterations.
|
|
98
|
+
*/
|
|
99
|
+
export declare const EVENT_INTEGRATE = "integrate";
|
|
100
|
+
/**
|
|
101
|
+
* Event ID for {@link TimeStep.addListener} to be notified of calls to
|
|
102
|
+
* {@link TimeStep.update} (triggered at the very end of a frame update).
|
|
103
|
+
*/
|
|
104
|
+
export declare const EVENT_FRAME = "frame";
|
|
91
105
|
//# sourceMappingURL=api.d.ts.map
|
package/api.js
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Event ID for {@link TimeStep.addListener} to be notified of individual
|
|
3
|
+
* {@link ITimeStep.integrate} iterations.
|
|
4
|
+
*/
|
|
5
|
+
export const EVENT_INTEGRATE = "integrate";
|
|
6
|
+
/**
|
|
7
|
+
* Event ID for {@link TimeStep.addListener} to be notified of calls to
|
|
8
|
+
* {@link TimeStep.update} (triggered at the very end of a frame update).
|
|
9
|
+
*/
|
|
10
|
+
export const EVENT_FRAME = "frame";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/timestep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Deterministic fixed timestep simulation updates with state interpolation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -88,5 +88,5 @@
|
|
|
88
88
|
"status": "alpha",
|
|
89
89
|
"year": 2023
|
|
90
90
|
},
|
|
91
|
-
"gitHead": "
|
|
91
|
+
"gitHead": "81dcbba93e7230c2f400bee87878145d902e975b\n"
|
|
92
92
|
}
|
package/state.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { IDeref } from "@thi.ng/api";
|
|
2
2
|
import type { ReadonlyVec } from "@thi.ng/vectors";
|
|
3
|
-
import type {
|
|
3
|
+
import type { ITimeStep, ReadonlyTimeStep, StateInterpolation, StateUpdate } from "./api.js";
|
|
4
4
|
/**
|
|
5
5
|
* Abstract base class for simulation state wrappers.
|
|
6
6
|
*
|
|
7
7
|
* @remarks
|
|
8
8
|
* See {@link defNumeric} and {@link defVector}.
|
|
9
9
|
*/
|
|
10
|
-
export declare abstract class AState<T> implements IDeref<T>,
|
|
10
|
+
export declare abstract class AState<T> implements IDeref<T>, ITimeStep {
|
|
11
11
|
value: T;
|
|
12
12
|
update: StateUpdate<T>;
|
|
13
13
|
mix: StateInterpolation<T>;
|
|
@@ -17,12 +17,15 @@ export declare abstract class AState<T> implements IDeref<T>, IUpdatable {
|
|
|
17
17
|
deref(): T;
|
|
18
18
|
integrate(dt: number, ctx: ReadonlyTimeStep): void;
|
|
19
19
|
interpolate(alpha: number, ctx: ReadonlyTimeStep): void;
|
|
20
|
+
abstract reset(value: T): void;
|
|
20
21
|
}
|
|
21
22
|
export declare class NumericState extends AState<number> {
|
|
22
23
|
constructor(x: number, update: StateUpdate<number>);
|
|
24
|
+
reset(value: number): void;
|
|
23
25
|
}
|
|
24
26
|
export declare class VectorState extends AState<ReadonlyVec> {
|
|
25
27
|
constructor(x: ReadonlyVec, update: StateUpdate<ReadonlyVec>);
|
|
28
|
+
reset(value: ReadonlyVec): void;
|
|
26
29
|
}
|
|
27
30
|
/**
|
|
28
31
|
* Returns a new {@link NumericState} wrapper for given value `x` and its update
|
|
@@ -36,6 +39,10 @@ export declare const defNumeric: (x: number, update: StateUpdate<number>) => Num
|
|
|
36
39
|
* Returns a new {@link VectorState} wrapper for given vector `v` and its update
|
|
37
40
|
* function for use with {@link TimeStep.update}.
|
|
38
41
|
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* **IMPORTANT:** The `update` function MUST return a new vector and not mutate
|
|
44
|
+
* the existing one!
|
|
45
|
+
*
|
|
39
46
|
* @param x
|
|
40
47
|
* @param update
|
|
41
48
|
*/
|
package/state.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mix } from "@thi.ng/math/mix";
|
|
2
2
|
import { mixN } from "@thi.ng/vectors/mixn";
|
|
3
|
+
import { set } from "@thi.ng/vectors/set";
|
|
3
4
|
/**
|
|
4
5
|
* Abstract base class for simulation state wrappers.
|
|
5
6
|
*
|
|
@@ -28,11 +29,19 @@ export class NumericState extends AState {
|
|
|
28
29
|
constructor(x, update) {
|
|
29
30
|
super(x, update, mix);
|
|
30
31
|
}
|
|
32
|
+
reset(value) {
|
|
33
|
+
this.prev = this.curr = this.value = value;
|
|
34
|
+
}
|
|
31
35
|
}
|
|
32
36
|
export class VectorState extends AState {
|
|
33
37
|
constructor(x, update) {
|
|
34
38
|
super(x, update, (a, b, t) => mixN([], a, b, t));
|
|
35
39
|
}
|
|
40
|
+
reset(value) {
|
|
41
|
+
set(this.prev, value);
|
|
42
|
+
set(this.curr, value);
|
|
43
|
+
set(this.value, value);
|
|
44
|
+
}
|
|
36
45
|
}
|
|
37
46
|
/**
|
|
38
47
|
* Returns a new {@link NumericState} wrapper for given value `x` and its update
|
|
@@ -46,6 +55,10 @@ export const defNumeric = (x, update) => new NumericState(x, update);
|
|
|
46
55
|
* Returns a new {@link VectorState} wrapper for given vector `v` and its update
|
|
47
56
|
* function for use with {@link TimeStep.update}.
|
|
48
57
|
*
|
|
58
|
+
* @remarks
|
|
59
|
+
* **IMPORTANT:** The `update` function MUST return a new vector and not mutate
|
|
60
|
+
* the existing one!
|
|
61
|
+
*
|
|
49
62
|
* @param x
|
|
50
63
|
* @param update
|
|
51
64
|
*/
|
package/timestep.d.ts
CHANGED
|
@@ -1,30 +1,56 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { type Event, type INotify, type Listener } from "@thi.ng/api";
|
|
2
|
+
import { type ITimeStep, type ReadonlyTimeStep, type TimeStepOpts } from "./api.js";
|
|
3
|
+
export declare class TimeStep implements INotify {
|
|
4
4
|
start: number;
|
|
5
5
|
dt: number;
|
|
6
6
|
maxFrameTime: number;
|
|
7
7
|
scale: number;
|
|
8
|
+
t: number;
|
|
9
|
+
current: number;
|
|
8
10
|
accumulator: number;
|
|
9
11
|
frame: number;
|
|
10
12
|
updates: number;
|
|
13
|
+
protected __eventIntegrate: Event;
|
|
14
|
+
protected __eventFrame: Event;
|
|
11
15
|
constructor(opts?: Partial<TimeStepOpts>);
|
|
16
|
+
addListener(id: string, fn: Listener, scope?: any): boolean;
|
|
17
|
+
removeListener(id: string, fn: Listener, scope?: any): boolean;
|
|
18
|
+
notify(event: Event): boolean;
|
|
12
19
|
/**
|
|
13
|
-
* Updates internal time to given new time `now` (
|
|
14
|
-
* the required number of fixed
|
|
15
|
-
* given `state` values.
|
|
20
|
+
* Updates internal time to given new time `now` (given value will be scaled
|
|
21
|
+
* via {@link TimeStepOpts.scale}) and performs the required number of fixed
|
|
22
|
+
* timesteps to integrate and interpolate the given `state` values.
|
|
16
23
|
*
|
|
17
24
|
* @remarks
|
|
18
|
-
* If
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
25
|
+
* If the scaled time difference since the last step is greater than
|
|
26
|
+
* {@link TimeStepOpts.maxFrameTime}, it will be limited to the latter.
|
|
27
|
+
*
|
|
28
|
+
* If `interpolate` is false, the {@link ITimeStep.interpolate} phase of the
|
|
29
|
+
* update cycle is skipped. This is useful when using this setup to simulate
|
|
30
|
+
* sub-steps (e.g. in XPBD) and only requiring the interpolation stage for
|
|
31
|
+
* the last step.
|
|
22
32
|
*
|
|
23
33
|
* @param now
|
|
24
34
|
* @param items
|
|
25
35
|
* @param interpolate
|
|
26
36
|
*/
|
|
27
|
-
update(now: number, items:
|
|
37
|
+
update(now: number, items: ITimeStep[], interpolate?: boolean): void;
|
|
28
38
|
}
|
|
29
39
|
export declare const defTimeStep: (opts?: Partial<TimeStepOpts>) => TimeStep;
|
|
40
|
+
/**
|
|
41
|
+
* Calls {@link ITimeStep.integrate} for all given items (in given order).
|
|
42
|
+
*
|
|
43
|
+
* @param dt
|
|
44
|
+
* @param ctx
|
|
45
|
+
* @param items
|
|
46
|
+
*/
|
|
47
|
+
export declare const integrateAll: (dt: number, ctx: ReadonlyTimeStep, ...items: ITimeStep[]) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Calls {@link ITimeStep.interpolate} for all given items (in given order).
|
|
50
|
+
*
|
|
51
|
+
* @param dt
|
|
52
|
+
* @param ctx
|
|
53
|
+
* @param items
|
|
54
|
+
*/
|
|
55
|
+
export declare const interpolateAll: (alpha: number, ctx: ReadonlyTimeStep, ...items: ITimeStep[]) => void;
|
|
30
56
|
//# sourceMappingURL=timestep.d.ts.map
|
package/timestep.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { INotifyMixin, } from "@thi.ng/api";
|
|
3
|
+
import { EVENT_FRAME, EVENT_INTEGRATE, } from "./api.js";
|
|
4
|
+
let TimeStep = class TimeStep {
|
|
2
5
|
constructor(opts) {
|
|
6
|
+
this.t = 0;
|
|
3
7
|
this.current = 0;
|
|
4
8
|
this.accumulator = 0;
|
|
5
9
|
this.frame = 0;
|
|
@@ -15,17 +19,31 @@ export class TimeStep {
|
|
|
15
19
|
this.maxFrameTime = $opts.maxFrameTime;
|
|
16
20
|
this.scale = $opts.scale;
|
|
17
21
|
this.start = $opts.startTime * this.scale;
|
|
22
|
+
this.__eventIntegrate = Object.freeze({
|
|
23
|
+
id: EVENT_INTEGRATE,
|
|
24
|
+
target: this,
|
|
25
|
+
});
|
|
26
|
+
this.__eventFrame = Object.freeze({ id: EVENT_FRAME, target: this });
|
|
18
27
|
}
|
|
28
|
+
// @ts-ignore mixin
|
|
29
|
+
addListener(id, fn, scope) { }
|
|
30
|
+
// @ts-ignore mixin
|
|
31
|
+
removeListener(id, fn, scope) { }
|
|
32
|
+
// @ts-ignore mixin
|
|
33
|
+
notify(event) { }
|
|
19
34
|
/**
|
|
20
|
-
* Updates internal time to given new time `now` (
|
|
21
|
-
* the required number of fixed
|
|
22
|
-
* given `state` values.
|
|
35
|
+
* Updates internal time to given new time `now` (given value will be scaled
|
|
36
|
+
* via {@link TimeStepOpts.scale}) and performs the required number of fixed
|
|
37
|
+
* timesteps to integrate and interpolate the given `state` values.
|
|
23
38
|
*
|
|
24
39
|
* @remarks
|
|
25
|
-
* If
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
40
|
+
* If the scaled time difference since the last step is greater than
|
|
41
|
+
* {@link TimeStepOpts.maxFrameTime}, it will be limited to the latter.
|
|
42
|
+
*
|
|
43
|
+
* If `interpolate` is false, the {@link ITimeStep.interpolate} phase of the
|
|
44
|
+
* update cycle is skipped. This is useful when using this setup to simulate
|
|
45
|
+
* sub-steps (e.g. in XPBD) and only requiring the interpolation stage for
|
|
46
|
+
* the last step.
|
|
29
47
|
*
|
|
30
48
|
* @param now
|
|
31
49
|
* @param items
|
|
@@ -33,8 +51,6 @@ export class TimeStep {
|
|
|
33
51
|
*/
|
|
34
52
|
update(now, items, interpolate = true) {
|
|
35
53
|
now = now * this.scale - this.start;
|
|
36
|
-
if (this.current < 0)
|
|
37
|
-
this.current = now;
|
|
38
54
|
this.accumulator += Math.min(now - this.current, this.maxFrameTime);
|
|
39
55
|
this.current = now;
|
|
40
56
|
const n = items.length;
|
|
@@ -42,8 +58,10 @@ export class TimeStep {
|
|
|
42
58
|
while (this.accumulator >= dt) {
|
|
43
59
|
for (let i = 0; i < n; i++)
|
|
44
60
|
items[i].integrate(dt, this);
|
|
61
|
+
this.t += dt;
|
|
45
62
|
this.accumulator -= dt;
|
|
46
63
|
this.updates++;
|
|
64
|
+
this.notify(this.__eventIntegrate);
|
|
47
65
|
}
|
|
48
66
|
if (interpolate) {
|
|
49
67
|
const alpha = this.accumulator / dt;
|
|
@@ -51,6 +69,33 @@ export class TimeStep {
|
|
|
51
69
|
items[i].interpolate(alpha, this);
|
|
52
70
|
}
|
|
53
71
|
this.frame++;
|
|
72
|
+
this.notify(this.__eventFrame);
|
|
54
73
|
}
|
|
55
|
-
}
|
|
74
|
+
};
|
|
75
|
+
TimeStep = __decorate([
|
|
76
|
+
INotifyMixin
|
|
77
|
+
], TimeStep);
|
|
78
|
+
export { TimeStep };
|
|
56
79
|
export const defTimeStep = (opts) => new TimeStep(opts);
|
|
80
|
+
/**
|
|
81
|
+
* Calls {@link ITimeStep.integrate} for all given items (in given order).
|
|
82
|
+
*
|
|
83
|
+
* @param dt
|
|
84
|
+
* @param ctx
|
|
85
|
+
* @param items
|
|
86
|
+
*/
|
|
87
|
+
export const integrateAll = (dt, ctx, ...items) => {
|
|
88
|
+
for (let i = 0, n = items.length; i < n; i++)
|
|
89
|
+
items[i].integrate(dt, ctx);
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Calls {@link ITimeStep.interpolate} for all given items (in given order).
|
|
93
|
+
*
|
|
94
|
+
* @param dt
|
|
95
|
+
* @param ctx
|
|
96
|
+
* @param items
|
|
97
|
+
*/
|
|
98
|
+
export const interpolateAll = (alpha, ctx, ...items) => {
|
|
99
|
+
for (let i = 0, n = items.length; i < n; i++)
|
|
100
|
+
items[i].interpolate(alpha, ctx);
|
|
101
|
+
};
|