@rydr/game-sdk 1.12.0 → 1.13.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/README.md CHANGED
@@ -14,11 +14,11 @@ This package is the **public contract** between platform and game:
14
14
 
15
15
  ## Install
16
16
 
17
- Public git dependency (no registry, no token):
17
+ Public npm package (no token needed — the source repo is private, the package is public):
18
18
 
19
19
  ```jsonc
20
20
  // package.json
21
- "dependencies": { "@rydr/game-sdk": "github:bdefrenne/rydr-game-sdk#semver:^1.0.0" }
21
+ "dependencies": { "@rydr/game-sdk": "^1.0.0" }
22
22
  ```
23
23
 
24
24
  **Starting a new game?** Don't wire this by hand — scaffold from
@@ -8,5 +8,5 @@
8
8
  */
9
9
  export declare const RYDR_PROTOCOL_VERSION: 5;
10
10
  /** Semver of this SDK build. Sent in the handshake for telemetry/debugging. */
11
- export declare const RYDR_SDK_VERSION = "1.12.0";
11
+ export declare const RYDR_SDK_VERSION = "1.13.0";
12
12
  //# sourceMappingURL=version.d.ts.map
@@ -10,5 +10,5 @@
10
10
  // shells omit it, the client falls back to its default).
11
11
  export const RYDR_PROTOCOL_VERSION = 5;
12
12
  /** Semver of this SDK build. Sent in the handshake for telemetry/debugging. */
13
- export const RYDR_SDK_VERSION = "1.12.0";
13
+ export const RYDR_SDK_VERSION = "1.13.0";
14
14
  //# sourceMappingURL=version.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `@rydr/game-sdk/three` — optional three.js helpers, kept out of the renderer-agnostic core.
3
+ *
4
+ * Importing this entry point requires `three` (declared as a peer dependency). Games that render a
5
+ * platform world should load it through {@link loadWorldGroup} here instead of hand-rolling a
6
+ * `GLTFLoader` + DRACO + caching per game.
7
+ */
8
+ export * from "./world-loader.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/three/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `@rydr/game-sdk/three` — optional three.js helpers, kept out of the renderer-agnostic core.
3
+ *
4
+ * Importing this entry point requires `three` (declared as a peer dependency). Games that render a
5
+ * platform world should load it through {@link loadWorldGroup} here instead of hand-rolling a
6
+ * `GLTFLoader` + DRACO + caching per game.
7
+ */
8
+ export * from "./world-loader.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/three/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * world-loader — opinionated three.js helper for loading a platform {@link CoreWorld} into a scene.
3
+ *
4
+ * The SDK core ({@link applyWorld}) is deliberately renderer-agnostic: it walks a `CoreWorld` and calls
5
+ * back a `loadGlb(url)` you provide, never touching `three`. This module is the **three-aware layer on
6
+ * top** — it owns a `GLTFLoader` + DRACO decoder and the caching every game was otherwise copy-pasting,
7
+ * so it lives behind the optional `@rydr/game-sdk/three` entry point (with `three` as a peer
8
+ * dependency) and never burdens games that don't render 3D.
9
+ *
10
+ * Two caches make repeated loads cheap:
11
+ * - **Per-URL GLB dedup** ({@link createGlbLoader}): each glb is fetched + DRACO-decoded once per
12
+ * assembly; repeated placements of the same prop get a `.clone()` (shared geometry/material). This
13
+ * also keeps `applyWorld`'s "add each object to the group" correct — the same Object3D instance
14
+ * can't be added to a parent twice.
15
+ * - **Per-world assembled-group cache** ({@link loadWorldGroup}, keyed by {@link CoreWorld.id}): a
16
+ * platform world is a static environment, so once built it is reused across game/runtime instances
17
+ * (replays, level restarts, missions sharing a world) instead of being re-fetched, re-decoded,
18
+ * re-uploaded to the GPU and re-shader-compiled every time. Callers detach the group from their
19
+ * scene on teardown but must **not** dispose its geometry/materials — the cache owns them. Use
20
+ * {@link clearWorldCache} to release them when cycling through many large worlds.
21
+ */
22
+ import * as THREE from "three";
23
+ import type { CoreWorld } from "../protocol/worlds.js";
24
+ /** DRACO decoder — pinned to the same version the platform world editor uses. */
25
+ export declare const DRACO_DECODER_PATH = "https://www.gstatic.com/draco/versioned/decoders/1.5.6/";
26
+ export interface GlbLoaderOptions {
27
+ /** Override the DRACO decoder directory (defaults to {@link DRACO_DECODER_PATH}). */
28
+ dracoDecoderPath?: string;
29
+ }
30
+ /**
31
+ * A `loadGlb` callback for {@link applyWorld}: fetches a glb by URL and resolves to its root scene.
32
+ *
33
+ * The returned closure shares one `GLTFLoader` + DRACO decoder across all URLs and fetches +
34
+ * DRACO-decodes each URL **once** — repeated URLs resolve to a lightweight `.clone()` (shared
35
+ * geometry/material) rather than re-downloading and re-decoding. Cloning is also required for
36
+ * correctness: `applyWorld` adds every returned object to the group, and a single Object3D can't be
37
+ * parented twice.
38
+ */
39
+ export declare function createGlbLoader(opts?: GlbLoaderOptions): (url: string) => Promise<THREE.Object3D>;
40
+ /**
41
+ * Load a platform world's meshes into a group at identity, cached by world id. The caller adds the
42
+ * returned group to its scene and detaches it on teardown **without disposing it** (the cache owns the
43
+ * geometry/materials, so the next load reuses them instantly). Throws if any glb fails to load.
44
+ */
45
+ export declare function loadWorldGroup(coreWorld: CoreWorld, opts?: GlbLoaderOptions): Promise<THREE.Group>;
46
+ /**
47
+ * Release cached assembled worlds and dispose their geometry + materials (textures are left alone —
48
+ * they may be shared elsewhere). Pass an `id` to drop a single world, or omit it to clear everything.
49
+ * Call this when a game cycles through many large worlds and the retain-forever default would grow
50
+ * memory unbounded. Safe to call while a group is still in a scene, but detach it from the scene first.
51
+ */
52
+ export declare function clearWorldCache(id?: string): void;
53
+ //# sourceMappingURL=world-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"world-loader.d.ts","sourceRoot":"","sources":["../../src/three/world-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,iFAAiF;AACjF,eAAO,MAAM,kBAAkB,4DAA4D,CAAC;AAE5F,MAAM,WAAW,gBAAgB;IAC/B,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,gBAAqB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAcrG;AAKD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAgBtG;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAWjD"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * world-loader — opinionated three.js helper for loading a platform {@link CoreWorld} into a scene.
3
+ *
4
+ * The SDK core ({@link applyWorld}) is deliberately renderer-agnostic: it walks a `CoreWorld` and calls
5
+ * back a `loadGlb(url)` you provide, never touching `three`. This module is the **three-aware layer on
6
+ * top** — it owns a `GLTFLoader` + DRACO decoder and the caching every game was otherwise copy-pasting,
7
+ * so it lives behind the optional `@rydr/game-sdk/three` entry point (with `three` as a peer
8
+ * dependency) and never burdens games that don't render 3D.
9
+ *
10
+ * Two caches make repeated loads cheap:
11
+ * - **Per-URL GLB dedup** ({@link createGlbLoader}): each glb is fetched + DRACO-decoded once per
12
+ * assembly; repeated placements of the same prop get a `.clone()` (shared geometry/material). This
13
+ * also keeps `applyWorld`'s "add each object to the group" correct — the same Object3D instance
14
+ * can't be added to a parent twice.
15
+ * - **Per-world assembled-group cache** ({@link loadWorldGroup}, keyed by {@link CoreWorld.id}): a
16
+ * platform world is a static environment, so once built it is reused across game/runtime instances
17
+ * (replays, level restarts, missions sharing a world) instead of being re-fetched, re-decoded,
18
+ * re-uploaded to the GPU and re-shader-compiled every time. Callers detach the group from their
19
+ * scene on teardown but must **not** dispose its geometry/materials — the cache owns them. Use
20
+ * {@link clearWorldCache} to release them when cycling through many large worlds.
21
+ */
22
+ import * as THREE from "three";
23
+ import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
24
+ import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js";
25
+ import { applyWorld } from "../world-runtime.js";
26
+ /** DRACO decoder — pinned to the same version the platform world editor uses. */
27
+ export const DRACO_DECODER_PATH = "https://www.gstatic.com/draco/versioned/decoders/1.5.6/";
28
+ /**
29
+ * A `loadGlb` callback for {@link applyWorld}: fetches a glb by URL and resolves to its root scene.
30
+ *
31
+ * The returned closure shares one `GLTFLoader` + DRACO decoder across all URLs and fetches +
32
+ * DRACO-decodes each URL **once** — repeated URLs resolve to a lightweight `.clone()` (shared
33
+ * geometry/material) rather than re-downloading and re-decoding. Cloning is also required for
34
+ * correctness: `applyWorld` adds every returned object to the group, and a single Object3D can't be
35
+ * parented twice.
36
+ */
37
+ export function createGlbLoader(opts = {}) {
38
+ const loader = new GLTFLoader();
39
+ const draco = new DRACOLoader();
40
+ draco.setDecoderPath(opts.dracoDecoderPath ?? DRACO_DECODER_PATH);
41
+ loader.setDRACOLoader(draco);
42
+ const decoded = new Map();
43
+ return (url) => {
44
+ let p = decoded.get(url);
45
+ if (!p) {
46
+ p = loader.loadAsync(url).then((g) => g.scene);
47
+ decoded.set(url, p);
48
+ }
49
+ return p.then((scene) => scene.clone());
50
+ };
51
+ }
52
+ /** Assembled worlds, keyed by {@link CoreWorld.id}. See the module doc for the lifecycle contract. */
53
+ const worldGroupCache = new Map();
54
+ /**
55
+ * Load a platform world's meshes into a group at identity, cached by world id. The caller adds the
56
+ * returned group to its scene and detaches it on teardown **without disposing it** (the cache owns the
57
+ * geometry/materials, so the next load reuses them instantly). Throws if any glb fails to load.
58
+ */
59
+ export function loadWorldGroup(coreWorld, opts = {}) {
60
+ let p = worldGroupCache.get(coreWorld.id);
61
+ if (!p) {
62
+ p = (async () => {
63
+ const group = new THREE.Group();
64
+ group.name = "platform-world";
65
+ await applyWorld(group, coreWorld, { loadGlb: createGlbLoader(opts) });
66
+ return group;
67
+ })();
68
+ // Don't cache a rejected build — let the next attempt retry the fetch.
69
+ p.catch(() => {
70
+ if (worldGroupCache.get(coreWorld.id) === p)
71
+ worldGroupCache.delete(coreWorld.id);
72
+ });
73
+ worldGroupCache.set(coreWorld.id, p);
74
+ }
75
+ return p;
76
+ }
77
+ /**
78
+ * Release cached assembled worlds and dispose their geometry + materials (textures are left alone —
79
+ * they may be shared elsewhere). Pass an `id` to drop a single world, or omit it to clear everything.
80
+ * Call this when a game cycles through many large worlds and the retain-forever default would grow
81
+ * memory unbounded. Safe to call while a group is still in a scene, but detach it from the scene first.
82
+ */
83
+ export function clearWorldCache(id) {
84
+ const drop = (key, promise) => {
85
+ worldGroupCache.delete(key);
86
+ void promise.then(disposeGroup).catch(() => { });
87
+ };
88
+ if (id !== undefined) {
89
+ const p = worldGroupCache.get(id);
90
+ if (p)
91
+ drop(id, p);
92
+ return;
93
+ }
94
+ for (const [key, p] of worldGroupCache)
95
+ drop(key, p);
96
+ }
97
+ /** Dispose a group's geometry + materials (not textures). */
98
+ function disposeGroup(group) {
99
+ group.parent?.remove(group);
100
+ group.traverse((o) => {
101
+ const mesh = o;
102
+ if (!mesh.isMesh)
103
+ return;
104
+ mesh.geometry?.dispose();
105
+ for (const m of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
106
+ m?.dispose();
107
+ }
108
+ });
109
+ }
110
+ //# sourceMappingURL=world-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"world-loader.js","sourceRoot":"","sources":["../../src/three/world-loader.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rydr/game-sdk",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/bdefrenne/rydr-game-sdk.git"
@@ -15,6 +15,10 @@
15
15
  ".": {
16
16
  "types": "./dist/index.d.ts",
17
17
  "import": "./dist/index.js"
18
+ },
19
+ "./three": {
20
+ "types": "./dist/three/index.d.ts",
21
+ "import": "./dist/three/index.js"
18
22
  }
19
23
  },
20
24
  "main": "./dist/index.js",
@@ -32,7 +36,17 @@
32
36
  "version": "node scripts/sync-version.mjs && node scripts/release-changelog.mjs && git add src/protocol/version.ts CHANGELOG.md",
33
37
  "postversion": "git push --follow-tags"
34
38
  },
39
+ "peerDependencies": {
40
+ "three": ">=0.160.0"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "three": {
44
+ "optional": true
45
+ }
46
+ },
35
47
  "devDependencies": {
48
+ "@types/three": "^0.184.0",
49
+ "three": "^0.184.0",
36
50
  "typescript": "^5.5.0"
37
51
  }
38
52
  }