@akashic/headless-driver 2.13.9 → 2.14.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.
@@ -0,0 +1,7 @@
1
+ export declare class TimeKeeper {
2
+ _currentTime: number;
3
+ now: () => number;
4
+ advance(ms: number): void;
5
+ rewind(): void;
6
+ set(time: number): void;
7
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TimeKeeper = void 0;
4
+ class TimeKeeper {
5
+ constructor() {
6
+ this._currentTime = 0;
7
+ // NOTE: this を束縛するためアロー関数で定義
8
+ this.now = () => {
9
+ return this._currentTime;
10
+ };
11
+ }
12
+ advance(ms) {
13
+ this._currentTime += ms;
14
+ }
15
+ rewind() {
16
+ this._currentTime = 0;
17
+ }
18
+ set(time) {
19
+ this._currentTime = time;
20
+ }
21
+ }
22
+ exports.TimeKeeper = TimeKeeper;
@@ -20,6 +20,7 @@ export declare abstract class Platform {
20
20
  protected errorHandler: (err: any) => void;
21
21
  protected loadFileHandler: RunnerLoadFileHandler;
22
22
  constructor(param: PlatformParameters);
23
+ abstract advanceLoopers(ms: number): void;
23
24
  sendToExternal(_playId: string, data: any): void;
24
25
  loadGameConfiguration: (url: string, callback: (err: Error | null, data?: string | Uint8Array) => void) => void;
25
26
  }
@@ -1,7 +1,9 @@
1
+ /// <reference types="node" />
1
2
  import type { AMFlow } from "@akashic/amflow";
2
3
  import { Trigger } from "@akashic/trigger";
4
+ import { TimeKeeper } from "../TimeKeeper";
3
5
  import type { Platform } from "./Platform";
4
- import type { RunnerAdvanceConditionFunc, RunnerExecutionMode, RunnerLoadFileHandler, RunnerPlayer, RunnerPointEvent, RunnerRenderingMode } from "./types";
6
+ import type { RunnerAdvanceConditionFunc, RunnerExecutionMode, RunnerLoadFileHandler, RunnerLoopMode, RunnerPlayer, RunnerPointEvent, RunnerRenderingMode } from "./types";
5
7
  export interface RunnerParameters {
6
8
  contentUrl: string;
7
9
  assetBaseUrl: string;
@@ -12,6 +14,7 @@ export interface RunnerParameters {
12
14
  runnerId: string;
13
15
  amflow: AMFlow;
14
16
  executionMode: RunnerExecutionMode;
17
+ loopMode?: RunnerLoopMode;
15
18
  trusted?: boolean;
16
19
  renderingMode?: RunnerRenderingMode;
17
20
  loadFileHandler: RunnerLoadFileHandler;
@@ -38,6 +41,7 @@ export declare abstract class Runner {
38
41
  */
39
42
  abstract readonly g: any;
40
43
  abstract platform: Platform | null;
44
+ abstract fps: number | null;
41
45
  errorTrigger: Trigger<Error>;
42
46
  sendToExternalTrigger: Trigger<any>;
43
47
  private params;
@@ -50,6 +54,7 @@ export declare abstract class Runner {
50
54
  get configurationBaseUrl(): string | undefined;
51
55
  get amflow(): AMFlow;
52
56
  get executionMode(): RunnerExecutionMode;
57
+ get loopMode(): RunnerLoopMode;
53
58
  get trusted(): boolean;
54
59
  get renderingMode(): RunnerRenderingMode;
55
60
  get loadFileHandler(): RunnerLoadFileHandler;
@@ -61,6 +66,9 @@ export declare abstract class Runner {
61
66
  get externalValue(): {
62
67
  [key: string]: any;
63
68
  } | undefined;
69
+ protected timekeeper: TimeKeeper;
70
+ protected timekeeperTimerId: NodeJS.Timer | null;
71
+ protected timekeeperPrevTime: number;
64
72
  constructor(params: RunnerParameters);
65
73
  /**
66
74
  * Runner を開始する。
@@ -84,11 +92,11 @@ export declare abstract class Runner {
84
92
  * Runner を指定ミリ秒だけ進行する。
85
93
  * @param ms 進行するミリ秒。
86
94
  */
87
- abstract advance(ms: number): void;
95
+ abstract advance(ms: number): Promise<void>;
88
96
  /**
89
97
  * Runner を一フレーム進行する。
90
98
  */
91
- abstract step(): void;
99
+ abstract step(): Promise<void>;
92
100
  /**
93
101
  * Runner に対して任意のポイントイベントを発火させる。
94
102
  * @param event 発火させるポイントイベント
@@ -105,6 +113,12 @@ export declare abstract class Runner {
105
113
  * @param timeout タイムアウトまでのミリ秒数。省略時は `5000` 。ゲーム内時間ではなく実時間である点に注意。
106
114
  */
107
115
  advanceUntil(condition: RunnerAdvanceConditionFunc, timeout?: number): Promise<void>;
116
+ /**
117
+ * 次フレームを飛ばさない程度に時間を進める。
118
+ */
108
119
  protected abstract _stepMinimal(): void;
120
+ protected advancePlatform(ms: number): void;
121
+ protected startTimekeeper(): void;
122
+ protected stopTimekeeper(): void;
109
123
  protected onError(error: Error): void;
110
124
  }
@@ -11,6 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.Runner = void 0;
13
13
  const trigger_1 = require("@akashic/trigger");
14
+ const TimeKeeper_1 = require("../TimeKeeper");
14
15
  class Runner {
15
16
  get runnerId() {
16
17
  return this.params.runnerId;
@@ -39,6 +40,10 @@ class Runner {
39
40
  get executionMode() {
40
41
  return this.params.executionMode;
41
42
  }
43
+ get loopMode() {
44
+ var _a;
45
+ return (_a = this.params.loopMode) !== null && _a !== void 0 ? _a : "realtime";
46
+ }
42
47
  get trusted() {
43
48
  return !!this.params.trusted;
44
49
  }
@@ -64,6 +69,9 @@ class Runner {
64
69
  constructor(params) {
65
70
  this.errorTrigger = new trigger_1.Trigger();
66
71
  this.sendToExternalTrigger = new trigger_1.Trigger();
72
+ this.timekeeper = new TimeKeeper_1.TimeKeeper();
73
+ this.timekeeperTimerId = null;
74
+ this.timekeeperPrevTime = 0;
67
75
  this.params = params;
68
76
  }
69
77
  /**
@@ -74,9 +82,9 @@ class Runner {
74
82
  advanceUntil(condition, timeout = 5000) {
75
83
  return __awaiter(this, void 0, void 0, function* () {
76
84
  return new Promise((resolve, reject) => {
77
- const limit = Date.now() + timeout;
85
+ const limit = performance.now() + timeout;
78
86
  const handler = () => {
79
- if (limit < Date.now()) {
87
+ if (limit < performance.now()) {
80
88
  return void reject(new Error("Runner#advanceUntil(): processing timeout"));
81
89
  }
82
90
  try {
@@ -93,6 +101,46 @@ class Runner {
93
101
  });
94
102
  });
95
103
  }
104
+ advancePlatform(ms) {
105
+ // 以下のメソッドプロパティが存在することは呼び出し側で保証する
106
+ const fps = this.fps;
107
+ const platform = this.platform;
108
+ const timekeeper = this.timekeeper;
109
+ const frame = 1000 / fps;
110
+ const frameWithGrace = frame * 1.2; // 1 フレームよりも少し大きめの値
111
+ const steps = Math.floor(ms / frame);
112
+ let progress = 0;
113
+ for (let i = 0; i < steps; i++) {
114
+ const now = timekeeper.now();
115
+ // NOTE: 浮動小数による誤差を考慮し、 1 フレームよりも少し大きめに目標時刻を進め、その後 Looper を確実に進めた後に再度目標時刻を正常値に戻す。
116
+ timekeeper.advance(frameWithGrace);
117
+ // NOTE: game-driver の内部実装により Looper 経由で一度に進める時間に制限がある。
118
+ // そのため一度に進める時間を fps に応じて分割する。
119
+ platform.advanceLoopers(frame);
120
+ timekeeper.set(now + frame);
121
+ progress += frame;
122
+ }
123
+ timekeeper.advance(ms - progress);
124
+ platform.advanceLoopers(ms - progress);
125
+ }
126
+ startTimekeeper() {
127
+ this.stopTimekeeper();
128
+ const duration = 1000 / this.fps / 2; // this.fps != null の条件でのみしか呼ばれないため non-null assertion を利用
129
+ this.timekeeperPrevTime = performance.now();
130
+ this.timekeeperTimerId = setInterval(() => {
131
+ const now = performance.now();
132
+ const delta = now - this.timekeeperPrevTime;
133
+ this.timekeeper.advance(delta);
134
+ this.timekeeperPrevTime = now;
135
+ }, duration);
136
+ }
137
+ stopTimekeeper() {
138
+ if (this.timekeeperTimerId == null) {
139
+ return;
140
+ }
141
+ clearInterval(this.timekeeperTimerId);
142
+ this.timekeeperTimerId = null;
143
+ }
96
144
  onError(error) {
97
145
  this.stop();
98
146
  this.errorTrigger.fire(error);
@@ -2,7 +2,7 @@ import type { AMFlowClient } from "../play/amflow/AMFlowClient";
2
2
  import type { PlayManager } from "../play/PlayManager";
3
3
  import type { EncodingType } from "../utils";
4
4
  import type { RunnerStartParameters } from "./Runner";
5
- import type { RunnerExecutionMode, RunnerPlayer, RunnerRenderingMode } from "./types";
5
+ import type { RunnerExecutionMode, RunnerLoopMode, RunnerPlayer, RunnerRenderingMode } from "./types";
6
6
  import type { RunnerV1Game } from "./v1";
7
7
  import { RunnerV1 } from "./v1";
8
8
  import type { RunnerV2Game } from "./v2";
@@ -14,6 +14,7 @@ export interface CreateRunnerParameters {
14
14
  amflow: AMFlowClient;
15
15
  playToken: string;
16
16
  executionMode: RunnerExecutionMode;
17
+ loopMode?: RunnerLoopMode;
17
18
  gameArgs?: any;
18
19
  player?: RunnerPlayer;
19
20
  /**
@@ -105,6 +105,7 @@ class RunnerManager {
105
105
  playToken: params.playToken,
106
106
  amflow,
107
107
  executionMode: params.executionMode,
108
+ loopMode: params.loopMode,
108
109
  trusted: params.trusted,
109
110
  renderingMode: params.renderingMode,
110
111
  loadFileHandler: this.createLoadFileHandler(params.allowedUrls),
@@ -1,5 +1,6 @@
1
1
  import type { EncodingType } from "../utils";
2
2
  export type RunnerExecutionMode = "active" | "passive";
3
+ export type RunnerLoopMode = "realtime" | "replay";
3
4
  export type RunnerRenderingMode = "none" | "canvas";
4
5
  export type RunnerAdvanceConditionFunc = () => boolean;
5
6
  export interface RunnerPointEvent {
@@ -15,7 +15,7 @@ export declare class RunnerV1 extends Runner {
15
15
  stop(): void;
16
16
  pause(): void;
17
17
  resume(): void;
18
- step(): void;
18
+ step(): Promise<void>;
19
19
  protected _stepMinimal(): void;
20
20
  advance(ms: number): Promise<void>;
21
21
  changeGameDriverState(param: gdr.GameDriverInitializeParameterObject): Promise<void>;
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.RunnerV1 = void 0;
13
+ const promises_1 = require("node:timers/promises");
13
14
  const engine_files_v1_1 = require("engine-files-v1");
14
15
  const Runner_1 = require("../Runner");
15
16
  const PlatformV1_1 = require("./platform/PlatformV1");
@@ -44,6 +45,7 @@ class RunnerV1 extends Runner_1.Runner {
44
45
  this.driver.stopGame();
45
46
  this.driver = null;
46
47
  }
48
+ this.stopTimekeeper();
47
49
  this.running = false;
48
50
  }
49
51
  pause() {
@@ -52,6 +54,7 @@ class RunnerV1 extends Runner_1.Runner {
52
54
  return;
53
55
  }
54
56
  this.platform.pauseLoopers();
57
+ this.stopTimekeeper();
55
58
  this.running = false;
56
59
  }
57
60
  resume() {
@@ -60,26 +63,31 @@ class RunnerV1 extends Runner_1.Runner {
60
63
  return;
61
64
  }
62
65
  this.platform.resumeLoopers();
66
+ this.startTimekeeper();
63
67
  this.running = true;
64
68
  }
65
69
  step() {
66
- if (this.fps == null || this.platform == null) {
67
- this.errorTrigger.fire(new Error("Cannot call Runner#step() before initialized"));
68
- return;
69
- }
70
- if (this.running) {
71
- this.errorTrigger.fire(new Error("Cannot call Runner#step() in running"));
72
- return;
73
- }
74
- this.platform.advanceLoopers(Math.ceil(1000 / this.fps));
70
+ return __awaiter(this, void 0, void 0, function* () {
71
+ if (this.fps == null || this.platform == null) {
72
+ this.errorTrigger.fire(new Error("Cannot call Runner#step() before initialized"));
73
+ return;
74
+ }
75
+ if (this.running) {
76
+ this.errorTrigger.fire(new Error("Cannot call Runner#step() in running"));
77
+ return;
78
+ }
79
+ this.timekeeper.advance(1000 / this.fps);
80
+ this.platform.advanceLoopers(Math.ceil(1000 / this.fps));
81
+ yield (0, promises_1.setImmediate)();
82
+ });
75
83
  }
76
84
  _stepMinimal() {
77
85
  if (this.fps == null || this.platform == null) {
78
- this.errorTrigger.fire(new Error("RunnerV1#_stepHalf(): Cannot call Runner#step() before initialized"));
86
+ this.errorTrigger.fire(new Error("RunnerV1#_stepMinimal(): Cannot call Runner#step() before initialized"));
79
87
  return;
80
88
  }
81
89
  if (this.running) {
82
- this.errorTrigger.fire(new Error("RunnerV1#_stepHalf(): Cannot call Runner#step() in running"));
90
+ this.errorTrigger.fire(new Error("RunnerV1#_stepMinimal(): Cannot call Runner#step() in running"));
83
91
  return;
84
92
  }
85
93
  // NOTE: 現状 PDI の API 仕様により this.step() では厳密なフレーム更新ができない。そこで、一フレームの 1/2 の時間で進行することでフレームが飛んでしまうことを防止する。
@@ -103,14 +111,7 @@ class RunnerV1 extends Runner_1.Runner {
103
111
  skipThreshold: Math.ceil(ms / this.fps) + 1
104
112
  }
105
113
  });
106
- const delta = Math.ceil(1000 / this.fps);
107
- let progress = 0;
108
- while (progress <= ms) {
109
- // NOTE: game-driver の内部実装により Looper 経由で一度に進める時間に制限がある。
110
- // そのため一度に進める時間を fps に応じて分割する。
111
- this.platform.advanceLoopers(delta);
112
- progress += delta;
113
- }
114
+ this.advancePlatform(ms);
114
115
  yield this.changeGameDriverState({
115
116
  loopConfiguration: {
116
117
  loopMode,
@@ -4,9 +4,9 @@ exports.NullAudioAsset = void 0;
4
4
  const engine_files_v1_1 = require("engine-files-v1");
5
5
  class NullAudioAsset extends engine_files_v1_1.akashicEngine.AudioAsset {
6
6
  _load(loader) {
7
- setTimeout(() => {
7
+ setImmediate(() => {
8
8
  loader._onAssetLoad(this);
9
- }, 0);
9
+ });
10
10
  }
11
11
  }
12
12
  exports.NullAudioAsset = NullAudioAsset;
@@ -9,9 +9,9 @@ class NullImageAsset extends engine_files_v1_1.akashicEngine.ImageAsset {
9
9
  this._surface = null;
10
10
  }
11
11
  _load(loader) {
12
- setTimeout(() => {
12
+ setImmediate(() => {
13
13
  loader._onAssetLoad(this);
14
- }, 0);
14
+ });
15
15
  }
16
16
  asSurface() {
17
17
  return this._surface || (this._surface = new NullSurface_1.NullSurface(this.width, this.height, null));
@@ -10,9 +10,9 @@ class NullVideoAsset extends engine_files_v1_1.akashicEngine.VideoAsset {
10
10
  this._player = new engine_files_v1_1.akashicEngine.VideoPlayer();
11
11
  }
12
12
  _load(loader) {
13
- setTimeout(() => {
13
+ setImmediate(() => {
14
14
  loader._onAssetLoad(this);
15
- }, 0);
15
+ });
16
16
  }
17
17
  asSurface() {
18
18
  return this._surface || (this._surface = new NullSurface_1.NullSurface(this.width, this.height, null));
@@ -15,7 +15,7 @@ export declare class RunnerV2 extends Runner {
15
15
  stop(): void;
16
16
  pause(): void;
17
17
  resume(): void;
18
- step(): void;
18
+ step(): Promise<void>;
19
19
  advance(ms: number): Promise<void>;
20
20
  changeGameDriverState(param: gdr.GameDriverInitializeParameterObject): Promise<void>;
21
21
  firePointEvent(event: RunnerPointEvent): void;
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.RunnerV2 = void 0;
13
+ const promises_1 = require("node:timers/promises");
13
14
  const engine_files_v2_1 = require("engine-files-v2");
14
15
  const Runner_1 = require("../Runner");
15
16
  const PlatformV2_1 = require("./platform/PlatformV2");
@@ -44,6 +45,7 @@ class RunnerV2 extends Runner_1.Runner {
44
45
  this.driver.stopGame();
45
46
  this.driver = null;
46
47
  }
48
+ this.stopTimekeeper();
47
49
  this.running = false;
48
50
  }
49
51
  pause() {
@@ -52,6 +54,7 @@ class RunnerV2 extends Runner_1.Runner {
52
54
  return;
53
55
  }
54
56
  this.platform.pauseLoopers();
57
+ this.stopTimekeeper();
55
58
  this.running = false;
56
59
  }
57
60
  resume() {
@@ -60,18 +63,23 @@ class RunnerV2 extends Runner_1.Runner {
60
63
  return;
61
64
  }
62
65
  this.platform.resumeLoopers();
66
+ this.startTimekeeper();
63
67
  this.running = true;
64
68
  }
65
69
  step() {
66
- if (this.fps == null || this.platform == null) {
67
- this.errorTrigger.fire(new Error("Cannot call Runner#step() before initialized"));
68
- return;
69
- }
70
- if (this.running) {
71
- this.errorTrigger.fire(new Error("Cannot call Runner#step() in running"));
72
- return;
73
- }
74
- this.platform.advanceLoopers(Math.ceil(1000 / this.fps));
70
+ return __awaiter(this, void 0, void 0, function* () {
71
+ if (this.fps == null || this.platform == null) {
72
+ this.errorTrigger.fire(new Error("Cannot call Runner#step() before initialized"));
73
+ return;
74
+ }
75
+ if (this.running) {
76
+ this.errorTrigger.fire(new Error("Cannot call Runner#step() in running"));
77
+ return;
78
+ }
79
+ this.timekeeper.advance(1000 / this.fps);
80
+ this.platform.advanceLoopers(Math.ceil(1000 / this.fps));
81
+ yield (0, promises_1.setImmediate)();
82
+ });
75
83
  }
76
84
  advance(ms) {
77
85
  return __awaiter(this, void 0, void 0, function* () {
@@ -91,14 +99,7 @@ class RunnerV2 extends Runner_1.Runner {
91
99
  skipThreshold: Math.ceil(ms / this.fps) + 1
92
100
  }
93
101
  });
94
- const delta = Math.ceil(1000 / this.fps);
95
- let progress = 0;
96
- while (progress <= ms) {
97
- // NOTE: game-driver の内部実装により Looper 経由で一度に進める時間に制限がある。
98
- // そのため一度に進める時間を fps に応じて分割する。
99
- this.platform.advanceLoopers(delta);
100
- progress += delta;
101
- }
102
+ this.advancePlatform(ms);
102
103
  yield this.changeGameDriverState({
103
104
  loopConfiguration: {
104
105
  loopMode,
@@ -146,11 +147,11 @@ class RunnerV2 extends Runner_1.Runner {
146
147
  }
147
148
  _stepMinimal() {
148
149
  if (this.fps == null || this.platform == null) {
149
- this.errorTrigger.fire(new Error("RunnerV2#_stepHalf(): Cannot call Runner#step() before initialized"));
150
+ this.errorTrigger.fire(new Error("RunnerV2#_stepMinimal(): Cannot call Runner#step() before initialized"));
150
151
  return;
151
152
  }
152
153
  if (this.running) {
153
- this.errorTrigger.fire(new Error("RunnerV2#_stepHalf(): Cannot call Runner#step() in running"));
154
+ this.errorTrigger.fire(new Error("RunnerV2#_stepMinimal(): Cannot call Runner#step() in running"));
154
155
  return;
155
156
  }
156
157
  // NOTE: 現状 PDI の API 仕様により this.step() では厳密なフレーム更新ができない。そこで、一フレームの 1/2 の時間で進行することでフレームが飛んでしまうことを防止する。
@@ -4,9 +4,9 @@ exports.NullAudioAsset = void 0;
4
4
  const engine_files_v2_1 = require("engine-files-v2");
5
5
  class NullAudioAsset extends engine_files_v2_1.akashicEngine.AudioAsset {
6
6
  _load(loader) {
7
- setTimeout(() => {
7
+ setImmediate(() => {
8
8
  loader._onAssetLoad(this);
9
- }, 0);
9
+ });
10
10
  }
11
11
  }
12
12
  exports.NullAudioAsset = NullAudioAsset;
@@ -9,9 +9,9 @@ class NullImageAsset extends engine_files_v2_1.akashicEngine.ImageAsset {
9
9
  this._surface = null;
10
10
  }
11
11
  _load(loader) {
12
- setTimeout(() => {
12
+ setImmediate(() => {
13
13
  loader._onAssetLoad(this);
14
- }, 0);
14
+ });
15
15
  }
16
16
  asSurface() {
17
17
  return this._surface || (this._surface = new NullSurface_1.NullSurface(this.width, this.height, null));
@@ -10,9 +10,9 @@ class NullVideoAsset extends engine_files_v2_1.akashicEngine.VideoAsset {
10
10
  this._player = new engine_files_v2_1.akashicEngine.VideoPlayer();
11
11
  }
12
12
  _load(loader) {
13
- setTimeout(() => {
13
+ setImmediate(() => {
14
14
  loader._onAssetLoad(this);
15
- }, 0);
15
+ });
16
16
  }
17
17
  asSurface() {
18
18
  return this._surface || (this._surface = new NullSurface_1.NullSurface(this.width, this.height, null));
@@ -19,7 +19,7 @@ export declare class RunnerV3 extends Runner {
19
19
  stop(): void;
20
20
  pause(): void;
21
21
  resume(): void;
22
- step(): void;
22
+ step(): Promise<void>;
23
23
  advance(ms: number): Promise<void>;
24
24
  changeGameDriverState(param: gdr.GameDriverInitializeParameterObject): Promise<void>;
25
25
  firePointEvent(event: RunnerPointEvent): void;
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.RunnerV3 = void 0;
13
+ const promises_1 = require("node:timers/promises");
13
14
  const Runner_1 = require("../Runner");
14
15
  const engineFiles_1 = require("./engineFiles");
15
16
  const PlatformV3_1 = require("./platform/PlatformV3");
@@ -44,6 +45,7 @@ class RunnerV3 extends Runner_1.Runner {
44
45
  this.driver.stopGame();
45
46
  this.driver = null;
46
47
  }
48
+ this.stopTimekeeper();
47
49
  this.running = false;
48
50
  }
49
51
  pause() {
@@ -52,6 +54,7 @@ class RunnerV3 extends Runner_1.Runner {
52
54
  return;
53
55
  }
54
56
  this.platform.pauseLoopers();
57
+ this.stopTimekeeper();
55
58
  this.running = false;
56
59
  }
57
60
  resume() {
@@ -60,18 +63,27 @@ class RunnerV3 extends Runner_1.Runner {
60
63
  return;
61
64
  }
62
65
  this.platform.resumeLoopers();
66
+ this.startTimekeeper();
63
67
  this.running = true;
64
68
  }
65
69
  step() {
66
- if (this.fps == null || this.platform == null) {
67
- this.errorTrigger.fire(new Error("Cannot call Runner#step() before initialized"));
68
- return;
69
- }
70
- if (this.running) {
71
- this.errorTrigger.fire(new Error("Cannot call Runner#step() in running"));
72
- return;
73
- }
74
- this.platform.stepLoopers();
70
+ return __awaiter(this, void 0, void 0, function* () {
71
+ if (this.fps == null || this.platform == null) {
72
+ this.errorTrigger.fire(new Error("Cannot call Runner#step() before initialized"));
73
+ return;
74
+ }
75
+ if (this.running) {
76
+ this.errorTrigger.fire(new Error("Cannot call Runner#step() in running"));
77
+ return;
78
+ }
79
+ this.timekeeper.advance(1000 / this.fps);
80
+ this.platform.stepLoopers();
81
+ // Looper を進行させるとアセット読み込み・AMFlow のコールバックなどの setImmediate() が実行されるが、
82
+ // それらを Runner#step() の完了タイミングで確実に処理するため、Runner#step() の完了を setImmediate() で遅延させている。
83
+ // ここを単純な Promise で返しても microtask queue に積まれるため、macrotask queue に積まれる setImmediate() の処理の完了を待つことはできない。
84
+ // @see https://nodejs.org/en/learn/asynchronous-work/understanding-setimmediate
85
+ yield (0, promises_1.setImmediate)();
86
+ });
75
87
  }
76
88
  advance(ms) {
77
89
  return __awaiter(this, void 0, void 0, function* () {
@@ -96,14 +108,7 @@ class RunnerV3 extends Runner_1.Runner {
96
108
  skipThreshold: Math.ceil(ms / this.fps) + 1
97
109
  }
98
110
  });
99
- const delta = Math.ceil(1000 / this.fps);
100
- let progress = 0;
101
- while (progress <= ms) {
102
- // NOTE: game-driver の内部実装により Looper 経由で一度に進める時間に制限がある。
103
- // そのため一度に進める時間を fps に応じて分割する。
104
- this.platform.advanceLoopers(delta);
105
- progress += delta;
106
- }
111
+ this.advancePlatform(ms);
107
112
  yield this.changeGameDriverState({
108
113
  loopConfiguration: {
109
114
  loopMode,
@@ -170,7 +175,16 @@ class RunnerV3 extends Runner_1.Runner {
170
175
  return this.getPrimarySurface()._drawable;
171
176
  }
172
177
  _stepMinimal() {
173
- this.step();
178
+ if (this.fps == null || this.platform == null) {
179
+ this.errorTrigger.fire(new Error("RunnerV3#_stepMinimal(): Cannot call Runner#step() before initialized"));
180
+ return;
181
+ }
182
+ if (this.running) {
183
+ this.errorTrigger.fire(new Error("RunnerV3#_stepMinimal(): Cannot call Runner#step() in running"));
184
+ return;
185
+ }
186
+ // NOTE: 現状 PDI の API 仕様により this.step() では厳密なフレーム更新ができない。そこで、一フレームの 1/2 の時間で進行することでフレームが飛んでしまうことを防止する。
187
+ this.platform.advanceLoopers(1000 / this.fps / 2);
174
188
  }
175
189
  initGameDriver() {
176
190
  return new Promise((resolve, reject) => {
@@ -183,6 +197,7 @@ class RunnerV3 extends Runner_1.Runner {
183
197
  name: this.player ? this.player.name : undefined
184
198
  };
185
199
  const executionMode = this.executionMode === "active" ? engineFiles_1.gameDriver.ExecutionMode.Active : engineFiles_1.gameDriver.ExecutionMode.Passive;
200
+ const loopMode = this.loopMode === "replay" ? engineFiles_1.gameDriver.LoopMode.Replay : engineFiles_1.gameDriver.LoopMode.Realtime;
186
201
  this.platform = new PlatformV3_1.PlatformV3({
187
202
  configurationBaseUrl: this.configurationBaseUrl,
188
203
  assetBaseUrl: this.assetBaseUrl,
@@ -212,7 +227,8 @@ class RunnerV3 extends Runner_1.Runner {
212
227
  executionMode
213
228
  },
214
229
  loopConfiguration: {
215
- loopMode: engineFiles_1.gameDriver.LoopMode.Realtime
230
+ loopMode,
231
+ targetTimeFunc: this.timekeeper.now
216
232
  },
217
233
  gameArgs: this.gameArgs
218
234
  }, (e) => {
@@ -14,9 +14,9 @@ class NullAudioAsset extends Asset_1.Asset {
14
14
  this.data = undefined;
15
15
  }
16
16
  _load(loader) {
17
- setTimeout(() => {
17
+ setImmediate(() => {
18
18
  loader._onAssetLoad(this);
19
- }, 0);
19
+ });
20
20
  }
21
21
  play() {
22
22
  return this._system.createPlayer();
@@ -12,9 +12,9 @@ class NullImageAsset extends Asset_1.Asset {
12
12
  this.height = height;
13
13
  }
14
14
  _load(loader) {
15
- setTimeout(() => {
15
+ setImmediate(() => {
16
16
  loader._onAssetLoad(this);
17
- }, 0);
17
+ });
18
18
  }
19
19
  asSurface() {
20
20
  return this._surface || (this._surface = new NullSurface_1.NullSurface(this.width, this.height));
@@ -19,9 +19,9 @@ class NullVideoAsset extends Asset_1.Asset {
19
19
  this._player = new NullVideoPlayer_1.NullVideoPlayer();
20
20
  }
21
21
  _load(loader) {
22
- setTimeout(() => {
22
+ setImmediate(() => {
23
23
  loader._onAssetLoad(this);
24
- }, 0);
24
+ });
25
25
  }
26
26
  asSurface() {
27
27
  return this._surface || (this._surface = new NullSurface_1.NullSurface(this.width, this.height));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akashic/headless-driver",
3
- "version": "2.13.9",
3
+ "version": "2.14.0",
4
4
  "description": "A library to execute contents using Akashic Engine headlessly",
5
5
  "main": "lib/index.js",
6
6
  "author": "DWANGO Co., Ltd.",