@arcadible/sdk 1.5.0-next.4 → 1.5.1
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/README.md +24 -3
- package/dist/client.js +17 -9
- package/dist/context/client.d.ts +2 -4
- package/dist/context/client.js +9 -23
- package/dist/facts.d.ts +11 -0
- package/dist/facts.js +33 -0
- package/dist/lifecycle/pause.d.ts +1 -3
- package/dist/lifecycle/pause.js +26 -50
- package/dist/listeners.d.ts +13 -0
- package/dist/listeners.js +48 -0
- package/dist/system/client.d.ts +2 -4
- package/dist/system/client.js +20 -45
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,20 +7,26 @@
|
|
|
7
7
|
<h1 align="center">Arcadible SDK</h1>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
|
|
10
|
+
How your game works with Arcadible.
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
14
14
|
<a href="https://developer.arcadible.com/references/arcadible-sdk">Docs</a> ·
|
|
15
15
|
<a href="https://developer.arcadible.com/build/initialization">Initialization</a> ·
|
|
16
16
|
<a href="https://developer.arcadible.com/build/saves">Saves</a> ·
|
|
17
|
+
<a href="https://developer.arcadible.com/build/pausing">Pausing</a> ·
|
|
17
18
|
<a href="https://arcadible.com">Arcadible</a> ·
|
|
18
19
|
<a href="https://studio.arcadible.com">Studio</a>
|
|
19
20
|
</p>
|
|
20
21
|
|
|
21
22
|
<br>
|
|
22
23
|
|
|
23
|
-
The Arcadible SDK is optional: a game runs on [Arcadible](https://arcadible.com) without it. With it, a game gets
|
|
24
|
+
The Arcadible SDK is optional: a game runs on [Arcadible](https://arcadible.com) without it. With it, a game gets these from the play page around it:
|
|
25
|
+
|
|
26
|
+
- **[Initialization](https://developer.arcadible.com/build/initialization),** bracketed, so Arcadible's loading screen shows the game's splash and progress until its first frame is drawn.
|
|
27
|
+
- **[Saves](https://developer.arcadible.com/build/saves),** kept by the play page, so the player's progress carries over from one version to the next.
|
|
28
|
+
- **[Pausing](https://developer.arcadible.com/build/pausing):** Arcadible tells the game when to stop, go quiet, and save, and when to carry on.
|
|
29
|
+
- **[`system` and `context`](https://developer.arcadible.com/references/arcadible-sdk#system):** the store's language, and when this run launched.
|
|
24
30
|
|
|
25
31
|
## Install
|
|
26
32
|
|
|
@@ -58,11 +64,26 @@ Saves are for what your game remembers between sessions, such as progress, setti
|
|
|
58
64
|
|
|
59
65
|
Off Arcadible, such as on your dev server, the package still works, with the same rules and limits: there's no loading screen, so `initComplete()` resolves at once, saves are kept in the game's own browser storage, `arcadible.saves.where` is `"build"`, and `arcadible.build` is `null`.
|
|
60
66
|
|
|
67
|
+
## Pause and resume
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
arcadible.on("pause", async () => {
|
|
71
|
+
// … stop your game loop and timers, and silence your audio …
|
|
72
|
+
await arcadible.saves.write("progress", JSON.stringify(progress));
|
|
73
|
+
});
|
|
74
|
+
arcadible.on("resume", () => {
|
|
75
|
+
// … start your game loop and timers again, and your audio …
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Arcadible pauses your game when the page is hidden, when it covers your game, and before it closes or reloads it, when it waits a moment for your save. Stop, go quiet, and save, drawing nothing over your game; on `resume`, carry on, or show a "Ready?" first in an action game.
|
|
80
|
+
|
|
61
81
|
## Learn more
|
|
62
82
|
|
|
63
83
|
- [Initialization](https://developer.arcadible.com/build/initialization): where each call goes, progress, and what comes after.
|
|
64
84
|
- [Saves](https://developer.arcadible.com/build/saves): using saves well, across versions, channels, and tabs, and where they're kept.
|
|
65
|
-
- [
|
|
85
|
+
- [Pausing](https://developer.arcadible.com/build/pausing): what a pause asks of your game, and resuming.
|
|
86
|
+
- [Arcadible SDK](https://developer.arcadible.com/references/arcadible-sdk): including it, every call, the saves' included, `system` and `context`, and every code.
|
|
66
87
|
- [Limits](https://developer.arcadible.com/references/limits#saves): how many saves, and how large.
|
|
67
88
|
|
|
68
89
|
## License
|
package/dist/client.js
CHANGED
|
@@ -33,28 +33,36 @@ export function createClient(options = {}) {
|
|
|
33
33
|
requestTimeout: options.requestTimeout,
|
|
34
34
|
standalone: () => serve({ ...STANDALONE, backend: backend() }),
|
|
35
35
|
});
|
|
36
|
-
|
|
36
|
+
// each module's game side, made on the bus as the client starts, before
|
|
37
|
+
// anything arrives.
|
|
38
|
+
const saves = createSaves(bus, {
|
|
37
39
|
timeout: options.requestTimeout ?? REQUEST_TIMEOUT,
|
|
38
40
|
});
|
|
39
|
-
let settled = null;
|
|
40
41
|
const system = createSystemClient(bus);
|
|
41
42
|
const context = createContextClient(bus);
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const lifecycle = createLifecycle(bus, win);
|
|
44
|
+
const pause = createPause(bus);
|
|
45
|
+
// the ones `ready` waits on, or tells: lifecycle and pause do neither.
|
|
46
|
+
const parts = [saves, system, context];
|
|
47
|
+
let settled = null;
|
|
48
|
+
// ready once welcomed, and each module's first messages have arrived;
|
|
49
|
+
// then each is told, so its facts are known, or null.
|
|
50
|
+
const ready = Promise.all([
|
|
51
|
+
bus.welcomed,
|
|
52
|
+
...parts.map((part) => part.opened),
|
|
53
|
+
]).then(([welcome]) => {
|
|
54
|
+
for (const part of parts)
|
|
55
|
+
part.settle?.();
|
|
46
56
|
settled = { build: buildOf(welcome.build) };
|
|
47
57
|
return settled;
|
|
48
58
|
});
|
|
49
|
-
const lifecycle = createLifecycle(bus, win);
|
|
50
|
-
const pause = createPause(bus);
|
|
51
59
|
return {
|
|
52
60
|
version: VERSION,
|
|
53
61
|
ready,
|
|
54
62
|
get build() {
|
|
55
63
|
return settled ? settled.build : undefined;
|
|
56
64
|
},
|
|
57
|
-
saves,
|
|
65
|
+
saves: saves.saves,
|
|
58
66
|
get system() {
|
|
59
67
|
return system.value;
|
|
60
68
|
},
|
package/dist/context/client.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Bus } from "../bus/client.js";
|
|
2
|
+
import { type Fact } from "../facts.js";
|
|
2
3
|
/** How this run started, fixed for the run. */
|
|
3
4
|
export interface Context {
|
|
4
5
|
readonly launch: {
|
|
@@ -6,7 +7,4 @@ export interface Context {
|
|
|
6
7
|
readonly at: string;
|
|
7
8
|
};
|
|
8
9
|
}
|
|
9
|
-
export declare function createContextClient(bus: Bus):
|
|
10
|
-
settle(): void;
|
|
11
|
-
readonly value: Context | null | undefined;
|
|
12
|
-
};
|
|
10
|
+
export declare function createContextClient(bus: Bus): Fact<Context>;
|
package/dist/context/client.js
CHANGED
|
@@ -1,31 +1,17 @@
|
|
|
1
1
|
// The game's side of `context`: `arcadible.context`, how this run started,
|
|
2
2
|
// fixed for the run. `undefined` before `ready`, and `null` where no page
|
|
3
3
|
// says (standing alone, or an older page).
|
|
4
|
-
import {
|
|
4
|
+
import { fact } from "../facts.js";
|
|
5
5
|
import { contextOf, Names } from "./rules.js";
|
|
6
6
|
// [function]
|
|
7
7
|
export function createContextClient(bus) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (!next) {
|
|
16
|
-
log("warn", "ignored a malformed context from the play page.");
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
context = Object.freeze({ launch: Object.freeze({ ...next.launch }) });
|
|
20
|
-
});
|
|
21
|
-
return {
|
|
22
|
-
settle() {
|
|
23
|
-
settled = true;
|
|
24
|
-
},
|
|
25
|
-
get value() {
|
|
26
|
-
if (!settled)
|
|
27
|
-
return undefined;
|
|
28
|
-
return context;
|
|
8
|
+
return fact(bus, Names.State, {
|
|
9
|
+
fixed: true,
|
|
10
|
+
parse(data) {
|
|
11
|
+
const context = contextOf(data);
|
|
12
|
+
return context
|
|
13
|
+
? Object.freeze({ launch: Object.freeze({ ...context.launch }) })
|
|
14
|
+
: null;
|
|
29
15
|
},
|
|
30
|
-
};
|
|
16
|
+
});
|
|
31
17
|
}
|
package/dist/facts.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Bus } from "./bus/client.js";
|
|
2
|
+
export type Fact<T> = {
|
|
3
|
+
settle(): void;
|
|
4
|
+
readonly value: T | null | undefined;
|
|
5
|
+
};
|
|
6
|
+
export type FactOptions<T> = {
|
|
7
|
+
parse(value: unknown): T | null;
|
|
8
|
+
fixed?: boolean;
|
|
9
|
+
changed?(before: T, next: T): void;
|
|
10
|
+
};
|
|
11
|
+
export declare function fact<T>(bus: Bus, name: string, options: FactOptions<T>): Fact<T>;
|
package/dist/facts.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// A fact the page sends as its module opens, such as `system`'s state or
|
|
2
|
+
// `context`: `undefined` before `ready`, the fact from then on, or `null`
|
|
3
|
+
// where no page sent one (standing alone, or an older page). Only what
|
|
4
|
+
// `parse` keeps is kept, so the host is trusted with nothing; a fixed fact
|
|
5
|
+
// takes its first and nothing after `ready`, and a changing one tells
|
|
6
|
+
// `changed` what came after it.
|
|
7
|
+
import { isObject, log } from "./bus/protocol.js";
|
|
8
|
+
// [function]
|
|
9
|
+
export function fact(bus, name, options) {
|
|
10
|
+
let value = null;
|
|
11
|
+
let settled = false;
|
|
12
|
+
bus.on(name, isObject, (data) => {
|
|
13
|
+
if (options.fixed && (value !== null || settled))
|
|
14
|
+
return;
|
|
15
|
+
const next = options.parse(data);
|
|
16
|
+
if (next === null) {
|
|
17
|
+
log("warn", `ignored a malformed ${name} from the play page.`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const before = value;
|
|
21
|
+
value = next;
|
|
22
|
+
if (before !== null && settled)
|
|
23
|
+
options.changed?.(before, next);
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
settle() {
|
|
27
|
+
settled = true;
|
|
28
|
+
},
|
|
29
|
+
get value() {
|
|
30
|
+
return settled ? value : undefined;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { Bus } from "../bus/client.js";
|
|
2
|
-
export type PauseListener = () => void | Promise<void>;
|
|
3
|
-
export type ResumeListener = () => void;
|
|
4
2
|
export type Pause = {
|
|
5
|
-
on(name:
|
|
3
|
+
on(name: unknown, listener: unknown): () => void;
|
|
6
4
|
};
|
|
7
5
|
export declare function createPause(bus: Bus): Pause;
|
package/dist/lifecycle/pause.js
CHANGED
|
@@ -3,11 +3,9 @@
|
|
|
3
3
|
// why; this side keeps that from the moment it connects, so a game that
|
|
4
4
|
// subscribes while held still gets its pause, and tells the page once its
|
|
5
5
|
// pause listeners have settled, which the page waits on only as it closes.
|
|
6
|
-
import {
|
|
6
|
+
import { isObject, log } from "../bus/protocol.js";
|
|
7
|
+
import { listeners } from "../listeners.js";
|
|
7
8
|
import { Names } from "./rules.js";
|
|
8
|
-
// the events a game can listen for on `arcadible`: a closed list, so a typo
|
|
9
|
-
// throws rather than never firing.
|
|
10
|
-
const EVENTS = ["pause", "resume"];
|
|
11
9
|
// [function]
|
|
12
10
|
function isHeld(value) {
|
|
13
11
|
return (isObject(value) &&
|
|
@@ -22,21 +20,14 @@ export function createPause(bus) {
|
|
|
22
20
|
// whether the game heard this hold's pause: a resume goes only to a game
|
|
23
21
|
// that did, so it never resumes from a pause it wasn't told of.
|
|
24
22
|
let heard = false;
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
// runs pause listeners,
|
|
28
|
-
//
|
|
29
|
-
const pause = function (
|
|
30
|
-
|
|
23
|
+
// the events a game can listen for on `arcadible`.
|
|
24
|
+
const on = listeners("arcadible", ["pause", "resume"]);
|
|
25
|
+
// runs pause listeners, and tells the page once they've all settled, for
|
|
26
|
+
// pause `count`.
|
|
27
|
+
const pause = function (run, count) {
|
|
28
|
+
const runs = run().map((result) => Promise.resolve(result));
|
|
29
|
+
if (runs.length > 0)
|
|
31
30
|
heard = true;
|
|
32
|
-
const runs = listeners.map((listener) => {
|
|
33
|
-
try {
|
|
34
|
-
return Promise.resolve(listener());
|
|
35
|
-
}
|
|
36
|
-
catch (err) {
|
|
37
|
-
return Promise.reject(err);
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
31
|
void Promise.allSettled(runs).then((results) => {
|
|
41
32
|
for (const result of results) {
|
|
42
33
|
if (result.status === "rejected") {
|
|
@@ -46,45 +37,23 @@ export function createPause(bus) {
|
|
|
46
37
|
bus.emit(Names.Paused, { n: count });
|
|
47
38
|
});
|
|
48
39
|
};
|
|
49
|
-
const resume = function () {
|
|
50
|
-
for (const listener of [...resumes]) {
|
|
51
|
-
try {
|
|
52
|
-
listener();
|
|
53
|
-
}
|
|
54
|
-
catch (err) {
|
|
55
|
-
log("error", "a resume listener failed.", err);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
40
|
bus.on(Names.Held, isHeld, (data) => {
|
|
60
41
|
const was = held;
|
|
61
42
|
held = data.held;
|
|
62
43
|
n = data.n;
|
|
63
44
|
if (held && !was)
|
|
64
|
-
pause(
|
|
45
|
+
pause(() => on.call("pause"), n);
|
|
65
46
|
else if (!held && was) {
|
|
66
47
|
if (heard)
|
|
67
|
-
resume
|
|
48
|
+
on.call("resume");
|
|
68
49
|
heard = false;
|
|
69
50
|
}
|
|
70
51
|
});
|
|
71
52
|
return {
|
|
72
53
|
on(name, listener) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (typeof listener !== "function") {
|
|
77
|
-
throw new ArcadibleError(Codes.Invalid, `arcadible.on("${name}") takes a function.`);
|
|
78
|
-
}
|
|
79
|
-
if (name === "resume") {
|
|
80
|
-
const fn = listener;
|
|
81
|
-
resumes.add(fn);
|
|
82
|
-
return () => {
|
|
83
|
-
resumes.delete(fn);
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
const fn = listener;
|
|
87
|
-
pauses.add(fn);
|
|
54
|
+
const off = on.add(name, listener);
|
|
55
|
+
if (name !== "pause")
|
|
56
|
+
return off;
|
|
88
57
|
if (!told) {
|
|
89
58
|
told = true;
|
|
90
59
|
bus.emit(Names.Listening);
|
|
@@ -94,13 +63,20 @@ export function createPause(bus) {
|
|
|
94
63
|
if (held) {
|
|
95
64
|
const count = n;
|
|
96
65
|
queueMicrotask(() => {
|
|
97
|
-
if (held && n === count &&
|
|
98
|
-
pause(
|
|
66
|
+
if (held && n === count && on.has("pause", listener)) {
|
|
67
|
+
pause(() => {
|
|
68
|
+
try {
|
|
69
|
+
return [listener()];
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
log("error", "a pause listener failed.", err);
|
|
73
|
+
return [undefined];
|
|
74
|
+
}
|
|
75
|
+
}, count);
|
|
76
|
+
}
|
|
99
77
|
});
|
|
100
78
|
}
|
|
101
|
-
return
|
|
102
|
-
pauses.delete(fn);
|
|
103
|
-
};
|
|
79
|
+
return off;
|
|
104
80
|
},
|
|
105
81
|
};
|
|
106
82
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type Listener = (...args: never[]) => unknown;
|
|
2
|
+
export type Listeners<N extends string> = {
|
|
3
|
+
add(name: unknown, listener: unknown): () => void;
|
|
4
|
+
of(name: N): Listener[];
|
|
5
|
+
has(name: N, listener: unknown): boolean;
|
|
6
|
+
call(name: N, ...args: unknown[]): unknown[];
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* The listeners for `names` on the object a game calls as `where`
|
|
10
|
+
* (`"arcadible"`, `"arcadible.system"`).
|
|
11
|
+
*/
|
|
12
|
+
export declare function listeners<N extends string>(where: string, names: readonly N[]): Listeners<N>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// The listeners behind every `on` a game calls (`arcadible.on`,
|
|
2
|
+
// `arcadible.system.on`): a closed list of names, so a typo throws rather
|
|
3
|
+
// than never firing; a listener that must be a function; the function that
|
|
4
|
+
// unsubscribes; and each listener run on its own, so one that throws stops
|
|
5
|
+
// none of the others. One shape for each, with the same messages.
|
|
6
|
+
// [import]
|
|
7
|
+
import { ArcadibleError, Codes, log } from "./bus/protocol.js";
|
|
8
|
+
// [function]
|
|
9
|
+
/**
|
|
10
|
+
* The listeners for `names` on the object a game calls as `where`
|
|
11
|
+
* (`"arcadible"`, `"arcadible.system"`).
|
|
12
|
+
*/
|
|
13
|
+
export function listeners(where, names) {
|
|
14
|
+
const sets = new Map(names.map((name) => [name, new Set()]));
|
|
15
|
+
const list = names.map((name) => JSON.stringify(name)).join(" or ");
|
|
16
|
+
const of = (name) => [...(sets.get(name) ?? [])];
|
|
17
|
+
return {
|
|
18
|
+
add(name, listener) {
|
|
19
|
+
const set = sets.get(name);
|
|
20
|
+
if (!set) {
|
|
21
|
+
throw new ArcadibleError(Codes.Invalid, `${where}.on takes ${list}, not ${JSON.stringify(name)}.`);
|
|
22
|
+
}
|
|
23
|
+
if (typeof listener !== "function") {
|
|
24
|
+
throw new ArcadibleError(Codes.Invalid, `${where}.on(${JSON.stringify(name)}) takes a function.`);
|
|
25
|
+
}
|
|
26
|
+
const fn = listener;
|
|
27
|
+
set.add(fn);
|
|
28
|
+
return () => {
|
|
29
|
+
set.delete(fn);
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
of,
|
|
33
|
+
has(name, listener) {
|
|
34
|
+
return sets.get(name)?.has(listener) ?? false;
|
|
35
|
+
},
|
|
36
|
+
call(name, ...args) {
|
|
37
|
+
return of(name).map((fn) => {
|
|
38
|
+
try {
|
|
39
|
+
return fn(...args);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
log("error", `a ${name} listener failed.`, err);
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
package/dist/system/client.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Bus } from "../bus/client.js";
|
|
2
|
+
import { type Fact } from "../facts.js";
|
|
2
3
|
/** Arcadible's state for the player, which changes while the game runs. */
|
|
3
4
|
export interface System {
|
|
4
5
|
/**
|
|
@@ -14,7 +15,4 @@ export interface System {
|
|
|
14
15
|
*/
|
|
15
16
|
on(name: "change", listener: (system: System, changed: readonly string[]) => void): () => void;
|
|
16
17
|
}
|
|
17
|
-
export declare function createSystemClient(bus: Bus):
|
|
18
|
-
settle(): void;
|
|
19
|
-
readonly value: System | null | undefined;
|
|
20
|
-
};
|
|
18
|
+
export declare function createSystemClient(bus: Bus): Fact<System>;
|
package/dist/system/client.js
CHANGED
|
@@ -2,61 +2,36 @@
|
|
|
2
2
|
// player, read at any time, with one `change` event naming what changed.
|
|
3
3
|
// `undefined` before `ready`, and `null` where no page says (standing
|
|
4
4
|
// alone, or an older page).
|
|
5
|
-
import {
|
|
5
|
+
import { fact } from "../facts.js";
|
|
6
|
+
import { listeners } from "../listeners.js";
|
|
6
7
|
import { Names, stateOf } from "./rules.js";
|
|
7
8
|
// [function]
|
|
8
9
|
export function createSystemClient(bus) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
10
|
+
// the events a game can listen for on `arcadible.system`.
|
|
11
|
+
const on = listeners("arcadible.system", ["change"]);
|
|
12
|
+
const kept = fact(bus, Names.State, {
|
|
13
|
+
parse: stateOf,
|
|
14
|
+
changed(before, next) {
|
|
15
|
+
const changed = Object.keys(next).filter((key) => next[key] !== before[key]);
|
|
16
|
+
if (changed.length > 0)
|
|
17
|
+
on.call("change", system, changed);
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
// one object for the run: what a game holds, and what `change` passes,
|
|
21
|
+
// reads the state as it is now.
|
|
12
22
|
const system = {
|
|
13
23
|
get language() {
|
|
14
|
-
return
|
|
15
|
-
},
|
|
16
|
-
on(name, listener) {
|
|
17
|
-
if (name !== "change") {
|
|
18
|
-
throw new ArcadibleError(Codes.Invalid, `arcadible.system.on takes "change", not ${JSON.stringify(name)}.`);
|
|
19
|
-
}
|
|
20
|
-
if (typeof listener !== "function") {
|
|
21
|
-
throw new ArcadibleError(Codes.Invalid, 'arcadible.system.on("change") takes a function.');
|
|
22
|
-
}
|
|
23
|
-
const fn = listener;
|
|
24
|
-
listeners.add(fn);
|
|
25
|
-
return () => {
|
|
26
|
-
listeners.delete(fn);
|
|
27
|
-
};
|
|
24
|
+
return kept.value.language;
|
|
28
25
|
},
|
|
26
|
+
on: (name, listener) => on.add(name, listener),
|
|
29
27
|
};
|
|
30
|
-
bus.on(Names.State, isObject, (data) => {
|
|
31
|
-
const next = stateOf(data);
|
|
32
|
-
if (!next) {
|
|
33
|
-
log("warn", "ignored a malformed system state from the play page.");
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
const before = state;
|
|
37
|
-
state = next;
|
|
38
|
-
if (!before || !settled)
|
|
39
|
-
return;
|
|
40
|
-
const changed = Object.keys(next).filter((key) => next[key] !== before[key]);
|
|
41
|
-
if (changed.length === 0)
|
|
42
|
-
return;
|
|
43
|
-
for (const listener of [...listeners]) {
|
|
44
|
-
try {
|
|
45
|
-
listener(system, changed);
|
|
46
|
-
}
|
|
47
|
-
catch (err) {
|
|
48
|
-
log("error", "a system change listener failed.", err);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
28
|
return {
|
|
53
|
-
settle()
|
|
54
|
-
settled = true;
|
|
55
|
-
},
|
|
29
|
+
settle: () => kept.settle(),
|
|
56
30
|
get value() {
|
|
57
|
-
|
|
31
|
+
const value = kept.value;
|
|
32
|
+
if (value === undefined)
|
|
58
33
|
return undefined;
|
|
59
|
-
return
|
|
34
|
+
return value === null ? null : system;
|
|
60
35
|
},
|
|
61
36
|
};
|
|
62
37
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "1.5.
|
|
1
|
+
export declare const VERSION = "1.5.1";
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "Offst, Inc.",
|
|
3
|
-
"description": "The Arcadible SDK:
|
|
3
|
+
"description": "The Arcadible SDK: how your game works with Arcadible.",
|
|
4
4
|
"devDependencies": {
|
|
5
5
|
"@playwright/test": "1.63.0",
|
|
6
6
|
"@types/node": "^25.0.3",
|
|
@@ -43,5 +43,5 @@
|
|
|
43
43
|
],
|
|
44
44
|
"type": "module",
|
|
45
45
|
"types": "./dist/index.d.ts",
|
|
46
|
-
"version": "1.5.
|
|
46
|
+
"version": "1.5.1"
|
|
47
47
|
}
|