@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2023-04-25T15:38:18Z
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 [`IUpdatable`
35
- interface](https://docs.thi.ng/umbrella/timestep/interfaces/IUpdatable.html) for
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 IUpdatable impl can be given here, incl. custom types)
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: 518 bytes
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 IUpdatable {
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
- export {};
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.1.1",
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": "ca22f02a137c0a4e3a38ef81e82e2bc7e3c43849\n"
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 { IUpdatable, ReadonlyTimeStep, StateInterpolation, StateUpdate } from "./api.js";
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>, IUpdatable {
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 { IUpdatable, TimeStepOpts } from "./api.js";
2
- export declare class TimeStep {
3
- current: number;
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` (in seconds) and performs
14
- * the required number of fixed timesteps to integrate and interpolate the
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 `interpolate` is false, the {@link IUpdatable.interpolate} phase of
19
- * the update cycle is skipped. This is useful when using this setup to
20
- * simulate sub-steps (e.g. in XPBD) and only requiring the interpolation
21
- * stage for the last step.
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: IUpdatable[], interpolate?: boolean): void;
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
- export class TimeStep {
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` (in seconds) and performs
21
- * the required number of fixed timesteps to integrate and interpolate the
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 `interpolate` is false, the {@link IUpdatable.interpolate} phase of
26
- * the update cycle is skipped. This is useful when using this setup to
27
- * simulate sub-steps (e.g. in XPBD) and only requiring the interpolation
28
- * stage for the last step.
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
+ };