@pix3/vite-plugin 2.0.0-alpha.0 → 2.0.0-alpha.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/LICENSE +201 -201
- package/README.md +40 -38
- package/dist/build/player-modules.d.ts +24 -0
- package/dist/build/player-modules.js +50 -0
- package/dist/editor-page.js +23 -23
- package/dist/index.js +13 -1
- package/dist/module-paths.js +1 -1
- package/dist/player/debug/context.d.ts +9 -0
- package/dist/player/debug/context.js +1 -0
- package/dist/player/debug/device-info.d.ts +15 -0
- package/dist/player/debug/device-info.js +36 -0
- package/dist/player/debug/frame-stats.d.ts +79 -0
- package/dist/player/debug/frame-stats.js +159 -0
- package/dist/player/debug/index.d.ts +10 -0
- package/dist/player/debug/index.js +20 -0
- package/dist/player/debug/long-frames.d.ts +46 -0
- package/dist/player/debug/long-frames.js +79 -0
- package/dist/player/debug/overlay.d.ts +25 -0
- package/dist/player/debug/overlay.js +320 -0
- package/dist/player/debug/session.d.ts +92 -0
- package/dist/player/debug/session.js +156 -0
- package/dist/player/dev/editor-link.d.ts +16 -0
- package/dist/player/dev/editor-link.js +125 -0
- package/dist/player/index.d.ts +7 -0
- package/dist/player/index.js +18 -1
- package/dist/vite-config.js +6 -3
- package/package.json +60 -60
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "Pix3 editor" link over the game in dev (`virtual:pix3/dev`; not on `?clean`, not in a
|
|
3
|
+
* frame, not in a build). A second editor tab would open read-only (the writer lock), so the link
|
|
4
|
+
* first asks over a `BroadcastChannel` whether an editor of this dev server is open: if one
|
|
5
|
+
* answers, the page says so and tries to bring its opener forward; if none does, this tab becomes
|
|
6
|
+
* the editor. The editor answers in `packages/editor-core/src/host/editor-presence.ts`.
|
|
7
|
+
*/
|
|
8
|
+
/** Keep in step with `editor-presence.ts` in editor-core. */
|
|
9
|
+
export const EDITOR_PRESENCE_CHANNEL = 'pix3-editor-presence';
|
|
10
|
+
const ANSWER_MS = 300;
|
|
11
|
+
const HIDDEN_KEY = 'pix3:editor-link:hidden';
|
|
12
|
+
/** True when an editor tab of this origin answers within {@link ANSWER_MS}. */
|
|
13
|
+
export const isEditorOpen = (timeoutMs = ANSWER_MS) => new Promise(resolve => {
|
|
14
|
+
if (typeof BroadcastChannel === 'undefined') {
|
|
15
|
+
resolve(false);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const channel = new BroadcastChannel(EDITOR_PRESENCE_CHANNEL);
|
|
19
|
+
const id = Math.random().toString(36).slice(2);
|
|
20
|
+
const done = (open) => {
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
channel.close();
|
|
23
|
+
resolve(open);
|
|
24
|
+
};
|
|
25
|
+
const timer = setTimeout(() => done(false), timeoutMs);
|
|
26
|
+
channel.onmessage = event => {
|
|
27
|
+
const data = event.data;
|
|
28
|
+
if (data?.type === 'pong' && data.id === id)
|
|
29
|
+
done(true);
|
|
30
|
+
};
|
|
31
|
+
channel.postMessage({ type: 'ping', id });
|
|
32
|
+
});
|
|
33
|
+
/** Go to the editor: this tab becomes it, unless one is already open. */
|
|
34
|
+
export const openEditor = async (editorUrl) => {
|
|
35
|
+
if (await isEditorOpen()) {
|
|
36
|
+
try {
|
|
37
|
+
// The editor opened this page (Play in Browser Tab / with Profiler): it is the opener.
|
|
38
|
+
window.opener?.focus();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// A cross-origin or severed opener: nothing to focus.
|
|
42
|
+
}
|
|
43
|
+
return 'already-open';
|
|
44
|
+
}
|
|
45
|
+
window.location.href = editorUrl;
|
|
46
|
+
return 'navigated';
|
|
47
|
+
};
|
|
48
|
+
const STYLE = `
|
|
49
|
+
:host { all: initial; }
|
|
50
|
+
.link {
|
|
51
|
+
position: fixed; left: 8px; bottom: 8px; z-index: 2147483646;
|
|
52
|
+
display: flex; align-items: center; gap: 2px;
|
|
53
|
+
font: 600 11px/1 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
54
|
+
color: #e8e8e8; background: rgba(18, 18, 20, 0.72); border: 1px solid rgba(255,255,255,0.14);
|
|
55
|
+
border-radius: 999px; padding: 2px; opacity: 0.55; transition: opacity 120ms ease;
|
|
56
|
+
}
|
|
57
|
+
.link:hover, .link:focus-within { opacity: 1; }
|
|
58
|
+
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer;
|
|
59
|
+
border-radius: 999px; display: inline-flex; align-items: center; gap: 5px; padding: 4px 8px; }
|
|
60
|
+
button:hover { background: rgba(255,255,255,0.1); }
|
|
61
|
+
button:focus-visible { outline: 2px solid rgba(255,207,51,0.6); }
|
|
62
|
+
.close { padding: 4px; }
|
|
63
|
+
svg { display: block; width: 12px; height: 12px; }
|
|
64
|
+
.note { padding: 4px 8px 4px 4px; color: #ffcf33; font-weight: 500; }
|
|
65
|
+
`;
|
|
66
|
+
// Feather `edit-3` and `x`, inline: the player has no IconService.
|
|
67
|
+
const EDIT_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>';
|
|
68
|
+
const CLOSE_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
|
|
69
|
+
const isHidden = () => {
|
|
70
|
+
try {
|
|
71
|
+
return sessionStorage.getItem(HIDDEN_KEY) === '1';
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
/** Mount the link; returns the teardown. Hidden for the rest of the tab's session by its close button. */
|
|
78
|
+
export function mountEditorLink(editorUrl) {
|
|
79
|
+
if (isHidden())
|
|
80
|
+
return () => undefined;
|
|
81
|
+
const host = document.createElement('div');
|
|
82
|
+
host.className = 'pix3-editor-link';
|
|
83
|
+
const root = host.attachShadow({ mode: 'open' });
|
|
84
|
+
root.innerHTML = `<style>${STYLE}</style>
|
|
85
|
+
<div class="link" part="link">
|
|
86
|
+
<button type="button" data-action="open" title="Open this project in the Pix3 editor">${EDIT_ICON}<span>Pix3 editor</span></button>
|
|
87
|
+
<span class="note" hidden>Already open in another tab</span>
|
|
88
|
+
<button type="button" class="close" data-action="hide" title="Hide" aria-label="Hide the editor link">${CLOSE_ICON}</button>
|
|
89
|
+
</div>`;
|
|
90
|
+
const link = root.querySelector('.link');
|
|
91
|
+
const note = root.querySelector('.note');
|
|
92
|
+
// Keep the game's own listeners out of the link's clicks.
|
|
93
|
+
for (const type of ['pointerdown', 'pointerup', 'touchstart', 'touchend', 'keydown']) {
|
|
94
|
+
link.addEventListener(type, event => event.stopPropagation());
|
|
95
|
+
}
|
|
96
|
+
let noteTimer;
|
|
97
|
+
root.addEventListener('click', event => {
|
|
98
|
+
const action = event.target
|
|
99
|
+
?.closest('[data-action]')
|
|
100
|
+
?.getAttribute('data-action');
|
|
101
|
+
if (action === 'hide') {
|
|
102
|
+
try {
|
|
103
|
+
sessionStorage.setItem(HIDDEN_KEY, '1');
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Hidden for this page only.
|
|
107
|
+
}
|
|
108
|
+
host.remove();
|
|
109
|
+
}
|
|
110
|
+
else if (action === 'open') {
|
|
111
|
+
void openEditor(editorUrl).then(outcome => {
|
|
112
|
+
if (outcome !== 'already-open')
|
|
113
|
+
return;
|
|
114
|
+
note.hidden = false;
|
|
115
|
+
clearTimeout(noteTimer);
|
|
116
|
+
noteTimer = setTimeout(() => (note.hidden = true), 3000);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
document.body.appendChild(host);
|
|
121
|
+
return () => {
|
|
122
|
+
clearTimeout(noteTimer);
|
|
123
|
+
host.remove();
|
|
124
|
+
};
|
|
125
|
+
}
|
package/dist/player/index.d.ts
CHANGED
|
@@ -8,6 +8,9 @@ import 'virtual:pix3/postprocessing';
|
|
|
8
8
|
* `index.html` (zip). Port of the 1.x `runtime/src/main.ts` + `register-project-scripts.ts`.
|
|
9
9
|
*
|
|
10
10
|
* `window.__PIX3_PLAYER__` is the gate's witness: `status`, `frames` (loop iterations), `errors`.
|
|
11
|
+
* In dev (`virtual:pix3/dev`), `/?debug` adds the debug overlay (`player/debug/`) and
|
|
12
|
+
* `__PIX3_PLAYER__.debug.snapshot()`, a plain page gets the "Pix3 editor" link
|
|
13
|
+
* (`player/dev/`), and `?scene=<res path>` boots that scene.
|
|
11
14
|
*/
|
|
12
15
|
export interface Pix3PlayerState {
|
|
13
16
|
status: 'booting' | 'running' | 'failed';
|
|
@@ -17,6 +20,10 @@ export interface Pix3PlayerState {
|
|
|
17
20
|
/** The scene the player booted into. */
|
|
18
21
|
scene: string | null;
|
|
19
22
|
runner: SceneRunner | null;
|
|
23
|
+
/** Debug mode only (`/?debug` in dev): the overlay's numbers, for an agent over CDP. */
|
|
24
|
+
debug?: {
|
|
25
|
+
snapshot(): unknown;
|
|
26
|
+
};
|
|
20
27
|
}
|
|
21
28
|
declare global {
|
|
22
29
|
interface Window {
|
package/dist/player/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AssetLoader, AudioService, registerBuiltInScripts, registerProjectScriptExports, ResourceManager, RuntimeRenderer, SceneLoader, SceneManager, SceneRunner, ScriptRegistry, } from '@pix3/runtime';
|
|
2
|
+
import { devSceneOverride, installDevTools } from 'virtual:pix3/dev';
|
|
2
3
|
import { embeddedAssets } from 'virtual:pix3/embedded-assets';
|
|
3
4
|
import { installNetworkService } from 'virtual:pix3/network';
|
|
4
5
|
import { modules as projectScriptModules } from 'virtual:pix3/project-scripts';
|
|
@@ -30,6 +31,19 @@ const installState = () => {
|
|
|
30
31
|
});
|
|
31
32
|
return state;
|
|
32
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* Dev only: the `?scene=` the editor's Play in Browser Tab / with Profiler asks for, when it is one
|
|
36
|
+
* of the project's scenes (a prefab or a typo falls back to the entry scene, with a warning).
|
|
37
|
+
*/
|
|
38
|
+
const requestedScene = () => {
|
|
39
|
+
const scene = devSceneOverride();
|
|
40
|
+
if (!scene)
|
|
41
|
+
return null;
|
|
42
|
+
if (scenePaths.includes(scene))
|
|
43
|
+
return scene;
|
|
44
|
+
console.warn(`[pix3] ?scene=${scene} is not one of the project's scenes; starting the entry scene.`);
|
|
45
|
+
return null;
|
|
46
|
+
};
|
|
33
47
|
/** Boot the game into `target` (a selector or an element; `#app` by default). */
|
|
34
48
|
export async function startGame(target = '#app', options = {}) {
|
|
35
49
|
const state = installState();
|
|
@@ -49,7 +63,7 @@ export async function startGame(target = '#app', options = {}) {
|
|
|
49
63
|
// No SceneSaver: a player never writes scenes back out, and one would pin every node class
|
|
50
64
|
// and `yaml.stringify` into the bundle (CLAUDE.md «Playable export size»).
|
|
51
65
|
const sceneManager = new SceneManager(sceneLoader);
|
|
52
|
-
const scenePath = options.scene || activeScenePath || scenePaths[0];
|
|
66
|
+
const scenePath = options.scene || requestedScene() || activeScenePath || scenePaths[0];
|
|
53
67
|
if (!scenePath)
|
|
54
68
|
throw new Error('No scenes found in this project.');
|
|
55
69
|
state.scene = scenePath;
|
|
@@ -75,6 +89,9 @@ export async function startGame(target = '#app', options = {}) {
|
|
|
75
89
|
// Multiplayer (D5): owned by the runner, inert until a script connects; the generated module
|
|
76
90
|
// is a no-op when nothing in the project mentions the network.
|
|
77
91
|
installNetworkService(runner);
|
|
92
|
+
// Debug mode (`/?debug`, dev only): the overlay subscribes before the scene loads, so a
|
|
93
|
+
// failed boot still shows its errors there.
|
|
94
|
+
installDevTools({ runner, renderer, audio: audioService, state });
|
|
78
95
|
runner.setBatching2DEnabled(true);
|
|
79
96
|
runner.setProjectFonts(runtimeFonts.length > 0 ? runtimeFonts : null);
|
|
80
97
|
if (runtimeLocalization) {
|
package/dist/vite-config.js
CHANGED
|
@@ -19,8 +19,9 @@ export const pix3ViteConfig = (options) => {
|
|
|
19
19
|
const { user, format } = options;
|
|
20
20
|
const projectRoot = resolve(user.root ?? process.cwd());
|
|
21
21
|
const linkedEditorCore = findPackageDir(projectRoot, '@pix3/editor-core');
|
|
22
|
-
// Vite checks `fs.allow` against real paths
|
|
23
|
-
|
|
22
|
+
// Vite checks `fs.allow` against real paths (`.native`: long names on Windows, as Vite resolves
|
|
23
|
+
// them); a workspace or pnpm link points elsewhere.
|
|
24
|
+
const editorCoreDir = linkedEditorCore ? realpathSync.native(linkedEditorCore) : null;
|
|
24
25
|
// An explicit `fs.allow` switches off Vite's workspace-root default; keep it when the user
|
|
25
26
|
// set none, and add this package (the page client and the player are served from it by
|
|
26
27
|
// `/@fs/`) and the prebuilt editor (served from wherever npm put it: hoisted, pnpm, a
|
|
@@ -37,7 +38,9 @@ export const pix3ViteConfig = (options) => {
|
|
|
37
38
|
// One runtime and one three for the game and the editor (plan §B.2, S1), plus the bare
|
|
38
39
|
// subpaths the prebuilt editor imports (`dist/optimize-deps.json`).
|
|
39
40
|
include: ['@pix3/runtime', 'three', ...editorOptimizeDeps(editorCoreDir)],
|
|
40
|
-
|
|
41
|
+
// The installed player imports virtual modules resolved by this plugin's Vite hooks;
|
|
42
|
+
// the dependency optimizer cannot resolve them while prebundling node_modules.
|
|
43
|
+
exclude: ['@pix3/editor-core', '@pix3/vite-plugin/player'],
|
|
41
44
|
},
|
|
42
45
|
server: { fs: { allow } },
|
|
43
46
|
...(format
|
package/package.json
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@pix3/vite-plugin",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
4
|
-
"description": "Pix3 editor, file API, sync barrier and build as a Vite plugin.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/pix3dev/pix3-core.git",
|
|
9
|
-
"directory": "packages/vite-plugin"
|
|
10
|
-
},
|
|
11
|
-
"homepage": "https://github.com/pix3dev/pix3-core/tree/main/packages/vite-plugin#readme",
|
|
12
|
-
"bugs": {
|
|
13
|
-
"url": "https://github.com/pix3dev/pix3-core/issues"
|
|
14
|
-
},
|
|
15
|
-
"engines": {
|
|
16
|
-
"node": ">=20.19"
|
|
17
|
-
},
|
|
18
|
-
"main": "./dist/index.js",
|
|
19
|
-
"types": "./dist/index.d.ts",
|
|
20
|
-
"exports": {
|
|
21
|
-
".": {
|
|
22
|
-
"types": "./dist/index.d.ts",
|
|
23
|
-
"import": "./dist/index.js"
|
|
24
|
-
},
|
|
25
|
-
"./player": {
|
|
26
|
-
"types": "./dist/player/index.d.ts",
|
|
27
|
-
"import": "./dist/player/index.js"
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
"files": [
|
|
31
|
-
"dist",
|
|
32
|
-
"README.md"
|
|
33
|
-
],
|
|
34
|
-
"scripts": {
|
|
35
|
-
"build": "tsc -p tsconfig.build.json && tsc -p player/tsconfig.build.json",
|
|
36
|
-
"prepack": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && npm run build",
|
|
37
|
-
"type-check": "tsc --noEmit && tsc --noEmit -p tsconfig.build.json && tsc --noEmit -p player/tsconfig.json"
|
|
38
|
-
},
|
|
39
|
-
"dependencies": {
|
|
40
|
-
"fflate": "^0.8.3",
|
|
41
|
-
"vite-plugin-singlefile": "^2.3.3",
|
|
42
|
-
"ws": "^8.18.0",
|
|
43
|
-
"yaml": "^2.6.0"
|
|
44
|
-
},
|
|
45
|
-
"peerDependencies": {
|
|
46
|
-
"@pix3/runtime": "2.0.0-alpha.
|
|
47
|
-
"vite": "^7 || ^8"
|
|
48
|
-
},
|
|
49
|
-
"devDependencies": {
|
|
50
|
-
"@types/node": "^24.5.2",
|
|
51
|
-
"@types/ws": "^8.18.1",
|
|
52
|
-
"typescript": "~5.8.3",
|
|
53
|
-
"vite": "^8.3.3"
|
|
54
|
-
},
|
|
55
|
-
"license": "Apache-2.0",
|
|
56
|
-
"publishConfig": {
|
|
57
|
-
"access": "public",
|
|
58
|
-
"tag": "next"
|
|
59
|
-
}
|
|
60
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@pix3/vite-plugin",
|
|
3
|
+
"version": "2.0.0-alpha.1",
|
|
4
|
+
"description": "Pix3 editor, file API, sync barrier and build as a Vite plugin.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/pix3dev/pix3-core.git",
|
|
9
|
+
"directory": "packages/vite-plugin"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/pix3dev/pix3-core/tree/main/packages/vite-plugin#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/pix3dev/pix3-core/issues"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20.19"
|
|
17
|
+
},
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./player": {
|
|
26
|
+
"types": "./dist/player/index.d.ts",
|
|
27
|
+
"import": "./dist/player/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc -p tsconfig.build.json && tsc -p player/tsconfig.build.json",
|
|
36
|
+
"prepack": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && npm run build",
|
|
37
|
+
"type-check": "tsc --noEmit && tsc --noEmit -p tsconfig.build.json && tsc --noEmit -p player/tsconfig.json"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"fflate": "^0.8.3",
|
|
41
|
+
"vite-plugin-singlefile": "^2.3.3",
|
|
42
|
+
"ws": "^8.18.0",
|
|
43
|
+
"yaml": "^2.6.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@pix3/runtime": "2.0.0-alpha.1",
|
|
47
|
+
"vite": "^7 || ^8"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^24.5.2",
|
|
51
|
+
"@types/ws": "^8.18.1",
|
|
52
|
+
"typescript": "~5.8.3",
|
|
53
|
+
"vite": "^8.3.3"
|
|
54
|
+
},
|
|
55
|
+
"license": "Apache-2.0",
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public",
|
|
58
|
+
"tag": "next"
|
|
59
|
+
}
|
|
60
|
+
}
|