@vpmedia/phaser 1.125.0 → 1.127.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/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +117 -14
- package/dist/index.js.map +1 -1
- package/dist/phaser/core/device_util.d.ts.map +1 -1
- package/dist/phaser/core/dom.d.ts.map +1 -1
- package/dist/phaser/core/game.d.ts.map +1 -1
- package/dist/phaser/core/input.d.ts.map +1 -1
- package/dist/phaser/core/input_handler.d.ts.map +1 -1
- package/dist/phaser/core/page_lifecycle.d.ts +49 -0
- package/dist/phaser/core/page_lifecycle.d.ts.map +1 -0
- package/dist/phaser/core/sound_manager.d.ts.map +1 -1
- package/package.json +1 -2
- package/src/index.ts +13 -0
- package/src/phaser/core/device_util.ts +4 -1
- package/src/phaser/core/dom.ts +2 -1
- package/src/phaser/core/frame_data.ts +1 -1
- package/src/phaser/core/game.test.ts +131 -0
- package/src/phaser/core/game.ts +5 -0
- package/src/phaser/core/input.ts +3 -2
- package/src/phaser/core/input_handler.ts +5 -4
- package/src/phaser/core/page_lifecycle.test.ts +178 -0
- package/src/phaser/core/page_lifecycle.ts +142 -0
- package/src/phaser/core/scale_manager.ts +1 -1
- package/src/phaser/core/sound_manager.ts +3 -8
- package/src/phaser/display/webgl/graphics.ts +2 -2
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal page lifecycle observer.
|
|
3
|
+
* @see https://developer.chrome.com/docs/web-platform/page-lifecycle-api
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** The page is visible and has focus. */
|
|
7
|
+
export const PAGE_STATE_ACTIVE = 'active';
|
|
8
|
+
/** The page is visible but does not have focus. */
|
|
9
|
+
export const PAGE_STATE_PASSIVE = 'passive';
|
|
10
|
+
/** The page is not visible. */
|
|
11
|
+
export const PAGE_STATE_HIDDEN = 'hidden';
|
|
12
|
+
/** The browser has frozen the page to reclaim its resources. */
|
|
13
|
+
export const PAGE_STATE_FROZEN = 'frozen';
|
|
14
|
+
/** The page is being unloaded. */
|
|
15
|
+
export const PAGE_STATE_TERMINATED = 'terminated';
|
|
16
|
+
|
|
17
|
+
/** One of the states a page can be in. */
|
|
18
|
+
export type PageState =
|
|
19
|
+
| typeof PAGE_STATE_ACTIVE
|
|
20
|
+
| typeof PAGE_STATE_PASSIVE
|
|
21
|
+
| typeof PAGE_STATE_HIDDEN
|
|
22
|
+
| typeof PAGE_STATE_FROZEN
|
|
23
|
+
| typeof PAGE_STATE_TERMINATED;
|
|
24
|
+
|
|
25
|
+
/** Called with the state the page has just entered and the one it left. */
|
|
26
|
+
export type PageStateListener = (state: PageState, previous: PageState) => void;
|
|
27
|
+
|
|
28
|
+
const listeners = new Set<PageStateListener>();
|
|
29
|
+
let currentState: PageState | null = null;
|
|
30
|
+
let started = false;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Reads the state the page is in right now.
|
|
34
|
+
* @returns {PageState} The current page state.
|
|
35
|
+
*/
|
|
36
|
+
export const readPageState = (): PageState => {
|
|
37
|
+
if (document.visibilityState === 'hidden') {
|
|
38
|
+
return PAGE_STATE_HIDDEN;
|
|
39
|
+
}
|
|
40
|
+
return document.hasFocus() ? PAGE_STATE_ACTIVE : PAGE_STATE_PASSIVE;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Records a new state and tells the listeners, ignoring transitions to the state already held.
|
|
45
|
+
* @param {PageState} next - The state the page has entered.
|
|
46
|
+
*/
|
|
47
|
+
const enterState = (next: PageState): void => {
|
|
48
|
+
const previous = currentState ?? readPageState();
|
|
49
|
+
if (next === previous) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
currentState = next;
|
|
53
|
+
const current = [...listeners];
|
|
54
|
+
for (const listener of current) {
|
|
55
|
+
listener(next, previous);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Starts observing the page, if it is not observed already.
|
|
61
|
+
*/
|
|
62
|
+
export const startPageLifecycle = (): void => {
|
|
63
|
+
if (started) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
started = true;
|
|
67
|
+
currentState = readPageState();
|
|
68
|
+
for (const type of ['pageshow', 'focus', 'blur', 'visibilitychange', 'resume']) {
|
|
69
|
+
globalThis.addEventListener(
|
|
70
|
+
type,
|
|
71
|
+
(event: Event): void => {
|
|
72
|
+
// blur and focus also fire for elements inside the page, which say nothing about the page
|
|
73
|
+
const fromElement = event.target !== globalThis && event.target !== document;
|
|
74
|
+
if ((event.type === 'blur' || event.type === 'focus') && fromElement) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
enterState(readPageState());
|
|
78
|
+
},
|
|
79
|
+
true
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
globalThis.addEventListener(
|
|
83
|
+
'freeze',
|
|
84
|
+
(): void => {
|
|
85
|
+
enterState(PAGE_STATE_FROZEN);
|
|
86
|
+
},
|
|
87
|
+
true
|
|
88
|
+
);
|
|
89
|
+
globalThis.addEventListener(
|
|
90
|
+
'pagehide',
|
|
91
|
+
(event: Event): void => {
|
|
92
|
+
enterState((event as PageTransitionEvent).persisted ? PAGE_STATE_FROZEN : PAGE_STATE_TERMINATED);
|
|
93
|
+
},
|
|
94
|
+
true
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Returns the state the page is in, starting the observer on first use.
|
|
100
|
+
* @returns {PageState} The current page state.
|
|
101
|
+
*/
|
|
102
|
+
export const getPageState = (): PageState => {
|
|
103
|
+
startPageLifecycle();
|
|
104
|
+
return currentState ?? readPageState();
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Registers a listener for every state change.
|
|
109
|
+
* @param {PageStateListener} listener - The listener to call on each change.
|
|
110
|
+
* @returns {() => void} A function that removes the listener again.
|
|
111
|
+
*/
|
|
112
|
+
export const onPageStateChange = (listener: PageStateListener): (() => void) => {
|
|
113
|
+
startPageLifecycle();
|
|
114
|
+
listeners.add(listener);
|
|
115
|
+
return (): void => {
|
|
116
|
+
listeners.delete(listener);
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Runs a callback the next time the page becomes active, once.
|
|
122
|
+
* @param {() => void} callback - The callback to run.
|
|
123
|
+
* @returns {() => void} A function that cancels the pending callback.
|
|
124
|
+
*/
|
|
125
|
+
export const oncePageActive = (callback: () => void): (() => void) => {
|
|
126
|
+
const stop = onPageStateChange((state: PageState): void => {
|
|
127
|
+
if (state === PAGE_STATE_ACTIVE) {
|
|
128
|
+
stop();
|
|
129
|
+
callback();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
return stop;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Drops every listener and stops tracking. Only the tests need this.
|
|
137
|
+
*/
|
|
138
|
+
export const resetPageLifecycle = (): void => {
|
|
139
|
+
listeners.clear();
|
|
140
|
+
currentState = null;
|
|
141
|
+
started = false;
|
|
142
|
+
};
|
|
@@ -453,7 +453,7 @@ export class ScaleManager {
|
|
|
453
453
|
}
|
|
454
454
|
this.bounds.setTo(this.offset.x, this.offset.y, this.width, this.height);
|
|
455
455
|
// Can be invoked in boot pre-input
|
|
456
|
-
if (this.game.input as Input | undefined) {
|
|
456
|
+
if ((this.game.input as Input | undefined)?.scale) {
|
|
457
457
|
this.game.input.scale.setTo(this.scaleFactor.x, this.scaleFactor.y);
|
|
458
458
|
}
|
|
459
459
|
}
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
import type { Game } from './game.js';
|
|
2
|
-
import {
|
|
3
|
-
addPageLifecycleCallback,
|
|
4
|
-
getPageLifecycleEventEmitter,
|
|
5
|
-
PAGE_LIFECYCLE_STATE_ACTIVE,
|
|
6
|
-
PAGE_LIFECYCLE_STATE_CHANGE_EVENT,
|
|
7
|
-
} from '@vpmedia/simplify';
|
|
8
2
|
import { ArraySet } from './array_set.js';
|
|
9
3
|
import { AUDIO_DISABLED, AUDIO_STANDARD, AUDIO_WEBKIT } from './const.js';
|
|
10
4
|
import { ENGINE_ERROR_CREATING_AUDIO_CONTEXT } from './error_code.js';
|
|
@@ -12,6 +6,7 @@ import { Signal } from './signal.js';
|
|
|
12
6
|
import { Sound } from './sound.js';
|
|
13
7
|
import { SoundSprite } from './sound_sprite.js';
|
|
14
8
|
import type { Callback } from './callback.js';
|
|
9
|
+
import { onPageStateChange, oncePageActive } from './page_lifecycle.js';
|
|
15
10
|
|
|
16
11
|
export class SoundManager {
|
|
17
12
|
public game!: Game;
|
|
@@ -115,7 +110,7 @@ export class SoundManager {
|
|
|
115
110
|
this.addUnlockHandlers();
|
|
116
111
|
}
|
|
117
112
|
this.context.addEventListener('statechange', this.onContextStateChange, false);
|
|
118
|
-
|
|
113
|
+
onPageStateChange(this.onPageLifecycleChange);
|
|
119
114
|
}
|
|
120
115
|
|
|
121
116
|
/**
|
|
@@ -293,7 +288,7 @@ export class SoundManager {
|
|
|
293
288
|
const typedError = error instanceof Error ? error : new Error(String(error));
|
|
294
289
|
this.game.logger.fatal('SoundManager', { error: typedError, tags: { 'asset.key': key } });
|
|
295
290
|
if (typedError.name === 'InvalidStateError') {
|
|
296
|
-
|
|
291
|
+
oncePageActive((): void => {
|
|
297
292
|
void this.decode(key);
|
|
298
293
|
});
|
|
299
294
|
} else if (typedError.name === 'EncodingError') {
|
|
@@ -470,8 +470,8 @@ export const buildPoly = (graphicsData: DisplayGraphicsData, webGLData: Graphics
|
|
|
470
470
|
const r = color[0] * alpha;
|
|
471
471
|
const g = color[1] * alpha;
|
|
472
472
|
const b = color[2] * alpha;
|
|
473
|
-
const triangles = triangulate(points, null, 2);
|
|
474
|
-
if (triangles
|
|
473
|
+
const triangles: number[] | null = triangulate(points, null, 2);
|
|
474
|
+
if (!triangles?.length) {
|
|
475
475
|
return false;
|
|
476
476
|
}
|
|
477
477
|
const vertPos = verts.length / 6;
|