@crowdedkingdoms/crowdyjs 8.22.0 → 10.0.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/MIGRATION.md +66 -0
- package/README.md +111 -7
- package/dist/crowdy-client.d.ts +3 -0
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +2 -0
- package/dist/domains/playerCodeProjects.d.ts +25 -0
- package/dist/domains/playerCodeProjects.d.ts.map +1 -0
- package/dist/domains/playerCodeProjects.js +299 -0
- package/dist/domains/playerCompute.d.ts +1 -1
- package/dist/domains/playerCompute.js +1 -1
- package/dist/generated/graphql.d.ts +792 -7
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +57 -0
- package/dist/index.d.ts +3 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -5
- package/dist/kit/npcs.d.ts.map +1 -1
- package/dist/kit/social.d.ts.map +1 -1
- package/dist/live-coding/assets/browser-authoring-index.json +1 -0
- package/dist/live-coding/assets/manifest.json +19 -0
- package/dist/live-coding/assets/tree-sitter-rust.wasm +0 -0
- package/dist/live-coding/assets/web-tree-sitter.wasm +0 -0
- package/dist/live-coding/browser-authoring-index.generated.d.ts +2 -0
- package/dist/live-coding/browser-authoring-index.generated.d.ts.map +1 -0
- package/dist/live-coding/browser-authoring-index.generated.js +5127 -0
- package/dist/live-coding/lsp-protocol.d.ts +98 -0
- package/dist/live-coding/lsp-protocol.d.ts.map +1 -0
- package/dist/live-coding/lsp-protocol.js +70 -0
- package/dist/live-coding/monaco-services.d.ts +22 -0
- package/dist/live-coding/monaco-services.d.ts.map +1 -0
- package/dist/live-coding/monaco-services.js +69 -0
- package/dist/live-coding/platform-index.d.ts +29 -0
- package/dist/live-coding/platform-index.d.ts.map +1 -0
- package/dist/live-coding/platform-index.js +281 -0
- package/dist/live-coding/rust-analysis.d.ts +24 -0
- package/dist/live-coding/rust-analysis.d.ts.map +1 -0
- package/dist/live-coding/rust-analysis.js +375 -0
- package/dist/live-coding/rust-lsp-server.d.ts +43 -0
- package/dist/live-coding/rust-lsp-server.d.ts.map +1 -0
- package/dist/live-coding/rust-lsp-server.js +455 -0
- package/dist/live-coding/rust-lsp.worker.d.ts +2 -0
- package/dist/live-coding/rust-lsp.worker.d.ts.map +1 -0
- package/dist/live-coding/rust-lsp.worker.js +26 -0
- package/dist/live-coding/vfs.d.ts +41 -0
- package/dist/live-coding/vfs.d.ts.map +1 -0
- package/dist/live-coding/vfs.js +174 -0
- package/dist/live-coding/worker-transport.d.ts +63 -0
- package/dist/live-coding/worker-transport.d.ts.map +1 -0
- package/dist/live-coding/worker-transport.js +217 -0
- package/dist/mod-studio/controller.d.ts +188 -0
- package/dist/mod-studio/controller.d.ts.map +1 -0
- package/dist/mod-studio/controller.js +915 -0
- package/dist/mod-studio/diagnostics.d.ts +22 -0
- package/dist/mod-studio/diagnostics.d.ts.map +1 -0
- package/dist/mod-studio/diagnostics.js +141 -0
- package/dist/mod-studio/dom-shell.d.ts +55 -0
- package/dist/mod-studio/dom-shell.d.ts.map +1 -0
- package/dist/mod-studio/dom-shell.js +527 -0
- package/dist/mod-studio/editor.d.ts +17 -0
- package/dist/mod-studio/editor.d.ts.map +1 -0
- package/dist/mod-studio/editor.js +1 -0
- package/dist/mod-studio/index.d.ts +10 -0
- package/dist/mod-studio/index.d.ts.map +1 -0
- package/dist/mod-studio/index.js +9 -0
- package/dist/mod-studio/models.d.ts +149 -0
- package/dist/mod-studio/models.d.ts.map +1 -0
- package/dist/mod-studio/models.js +62 -0
- package/dist/mod-studio/monaco-editor.d.ts +16 -0
- package/dist/mod-studio/monaco-editor.d.ts.map +1 -0
- package/dist/mod-studio/monaco-editor.js +548 -0
- package/dist/mod-studio/mount.d.ts +17 -0
- package/dist/mod-studio/mount.d.ts.map +1 -0
- package/dist/mod-studio/mount.js +86 -0
- package/dist/mod-studio/starter-projects.d.ts +11 -0
- package/dist/mod-studio/starter-projects.d.ts.map +1 -0
- package/dist/mod-studio/starter-projects.js +102 -0
- package/dist/mod-studio/styles.d.ts +2 -0
- package/dist/mod-studio/styles.d.ts.map +1 -0
- package/dist/mod-studio/styles.js +14 -0
- package/dist/mod-studio/textarea-editor.d.ts +8 -0
- package/dist/mod-studio/textarea-editor.d.ts.map +1 -0
- package/dist/mod-studio/textarea-editor.js +71 -0
- package/package.json +18 -6
- package/dist/live-coding/ide.d.ts +0 -16
- package/dist/live-coding/ide.d.ts.map +0 -1
- package/dist/live-coding/ide.js +0 -399
- package/dist/live-coding/live-coding-controller.d.ts +0 -75
- package/dist/live-coding/live-coding-controller.d.ts.map +0 -1
- package/dist/live-coding/live-coding-controller.js +0 -141
- package/dist/live-coding/mount.d.ts +0 -26
- package/dist/live-coding/mount.d.ts.map +0 -1
- package/dist/live-coding/mount.js +0 -108
- package/dist/live-coding/templates.d.ts +0 -17
- package/dist/live-coding/templates.d.ts.map +0 -1
- package/dist/live-coding/templates.js +0 -59
package/dist/live-coding/ide.js
DELETED
|
@@ -1,399 +0,0 @@
|
|
|
1
|
-
import { LiveCodingController, } from './live-coding-controller.js';
|
|
2
|
-
import { mountLiveCoding, } from './mount.js';
|
|
3
|
-
import { PLAYER_CODE_TEMPLATES, } from './templates.js';
|
|
4
|
-
let vscodeServicesPromise = null;
|
|
5
|
-
/**
|
|
6
|
-
* Lazy Monaco live-coding IDE. Monaco and the language-client stack stay out
|
|
7
|
-
* of the importing game's hot bundle until this async mount is called.
|
|
8
|
-
* Missing LSP configuration degrades to the dependency-free textarea panel.
|
|
9
|
-
*/
|
|
10
|
-
export async function mountLiveCodingIDE(el, options) {
|
|
11
|
-
const token = resolveToken(options.appToken);
|
|
12
|
-
if (!options.languageServiceUrl || !token) {
|
|
13
|
-
return mountLiveCoding(el, options);
|
|
14
|
-
}
|
|
15
|
-
if (options.editorWorkerFactory) {
|
|
16
|
-
globalThis.MonacoEnvironment = { getWorker: options.editorWorkerFactory };
|
|
17
|
-
}
|
|
18
|
-
try {
|
|
19
|
-
const [monaco, languageClientModule, wsJsonRpc, vscodeWrapperModule, workerFactoryModule,] = await Promise.all([
|
|
20
|
-
import('@codingame/monaco-vscode-editor-api'),
|
|
21
|
-
import('monaco-languageclient'),
|
|
22
|
-
import('vscode-ws-jsonrpc'),
|
|
23
|
-
import('monaco-languageclient/vscodeApiWrapper'),
|
|
24
|
-
import('monaco-languageclient/workerFactory'),
|
|
25
|
-
]);
|
|
26
|
-
if (!vscodeServicesPromise) {
|
|
27
|
-
const wrapper = new vscodeWrapperModule.MonacoVscodeApiWrapper({
|
|
28
|
-
$type: 'classic',
|
|
29
|
-
viewsConfig: { $type: 'EditorService' },
|
|
30
|
-
userConfiguration: {
|
|
31
|
-
json: JSON.stringify({
|
|
32
|
-
'workbench.colorTheme': 'Default Dark Modern',
|
|
33
|
-
'editor.wordBasedSuggestions': 'off',
|
|
34
|
-
}),
|
|
35
|
-
},
|
|
36
|
-
monacoWorkerFactory: workerFactoryModule.configureDefaultWorkerFactory,
|
|
37
|
-
});
|
|
38
|
-
vscodeServicesPromise = wrapper.start();
|
|
39
|
-
}
|
|
40
|
-
await vscodeServicesPromise;
|
|
41
|
-
monaco.languages.register({ id: 'rust', extensions: ['.rs'] });
|
|
42
|
-
installRustSyntaxHighlighting(monaco);
|
|
43
|
-
monaco.languages.register({ id: 'toml', extensions: ['.toml'] });
|
|
44
|
-
const templates = options.templates ?? PLAYER_CODE_TEMPLATES;
|
|
45
|
-
const root = document.createElement('div');
|
|
46
|
-
root.className = 'ck-live-coding ck-live-coding-ide';
|
|
47
|
-
root.style.cssText =
|
|
48
|
-
'display:grid;grid-template-rows:auto auto minmax(320px,1fr) auto auto;' +
|
|
49
|
-
'gap:6px;min-height:520px;';
|
|
50
|
-
const controls = document.createElement('div');
|
|
51
|
-
controls.className = 'ck-live-coding-controls';
|
|
52
|
-
const target = select(['server', 'client']);
|
|
53
|
-
const template = document.createElement('select');
|
|
54
|
-
const deploy = button('Deploy');
|
|
55
|
-
const draft = button('Deploy draft');
|
|
56
|
-
const stop = button('Stop');
|
|
57
|
-
controls.append(target, template, deploy, draft, stop);
|
|
58
|
-
const tabs = document.createElement('div');
|
|
59
|
-
tabs.className = 'ck-live-coding-tabs';
|
|
60
|
-
tabs.style.cssText = 'display:flex;gap:4px;flex-wrap:wrap;';
|
|
61
|
-
const editorHost = document.createElement('div');
|
|
62
|
-
editorHost.className = 'ck-live-coding-monaco';
|
|
63
|
-
editorHost.style.cssText = 'min-height:320px;border:1px solid #333;';
|
|
64
|
-
const problems = document.createElement('div');
|
|
65
|
-
problems.className = 'ck-live-coding-problems';
|
|
66
|
-
const status = document.createElement('pre');
|
|
67
|
-
status.className = 'ck-live-coding-status';
|
|
68
|
-
const meter = document.createElement('div');
|
|
69
|
-
meter.className = 'ck-live-coding-meter';
|
|
70
|
-
root.append(controls, tabs, editorHost, problems, status, meter);
|
|
71
|
-
el.appendChild(root);
|
|
72
|
-
const controller = new LiveCodingController({
|
|
73
|
-
...options,
|
|
74
|
-
onStatus: (value) => renderStatus(status, meter, value),
|
|
75
|
-
});
|
|
76
|
-
const editor = monaco.editor.create(editorHost, {
|
|
77
|
-
automaticLayout: true,
|
|
78
|
-
minimap: { enabled: false },
|
|
79
|
-
theme: 'vs-dark',
|
|
80
|
-
fontSize: 14,
|
|
81
|
-
tabSize: 2,
|
|
82
|
-
});
|
|
83
|
-
let models = [];
|
|
84
|
-
let languageClient = null;
|
|
85
|
-
let socket = null;
|
|
86
|
-
let modelRootUri = 'file:///player-mod';
|
|
87
|
-
const sourceJson = () => JSON.stringify(Object.fromEntries(models.map(({ path, model }) => [path, model.getValue()])));
|
|
88
|
-
const showModel = (path) => {
|
|
89
|
-
const found = models.find((entry) => entry.path === path);
|
|
90
|
-
if (found)
|
|
91
|
-
editor.setModel(found.model);
|
|
92
|
-
for (const child of Array.from(tabs.children)) {
|
|
93
|
-
child.dataset.active =
|
|
94
|
-
child.dataset.path === path ? 'true' : 'false';
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
const setFiles = (sourceFilesJson) => {
|
|
98
|
-
for (const entry of models)
|
|
99
|
-
entry.model.dispose();
|
|
100
|
-
models = [];
|
|
101
|
-
tabs.replaceChildren();
|
|
102
|
-
const files = parseFiles(sourceFilesJson);
|
|
103
|
-
for (const [path, content] of Object.entries(files)) {
|
|
104
|
-
const uri = monaco.Uri.parse(`${modelRootUri}/${path}`);
|
|
105
|
-
const model = monaco.editor.createModel(content, path.endsWith('.rs') ? 'rust' : path.endsWith('.toml') ? 'toml' : 'plaintext', uri);
|
|
106
|
-
models.push({ path, model });
|
|
107
|
-
const tab = button(path);
|
|
108
|
-
tab.dataset.path = path;
|
|
109
|
-
tab.addEventListener('click', () => showModel(path));
|
|
110
|
-
tabs.appendChild(tab);
|
|
111
|
-
}
|
|
112
|
-
const preferred = models.find((entry) => entry.path === 'src/lib.rs') ?? models[0];
|
|
113
|
-
if (preferred)
|
|
114
|
-
showModel(preferred.path);
|
|
115
|
-
};
|
|
116
|
-
const refreshTemplates = () => {
|
|
117
|
-
template.replaceChildren();
|
|
118
|
-
const matching = templates.filter((item) => item.target === target.value);
|
|
119
|
-
for (const item of matching) {
|
|
120
|
-
template.append(new Option(item.title, item.id));
|
|
121
|
-
}
|
|
122
|
-
if (matching[0])
|
|
123
|
-
setFiles(matching[0].sourceFilesJson);
|
|
124
|
-
};
|
|
125
|
-
target.addEventListener('change', refreshTemplates);
|
|
126
|
-
template.addEventListener('change', () => {
|
|
127
|
-
const selected = templates.find((item) => item.id === template.value);
|
|
128
|
-
if (selected)
|
|
129
|
-
setFiles(selected.sourceFilesJson);
|
|
130
|
-
});
|
|
131
|
-
const doDeploy = (draftMode) => {
|
|
132
|
-
void controller
|
|
133
|
-
.deploy({
|
|
134
|
-
name: options.moduleName ?? 'scratch-mod',
|
|
135
|
-
target: target.value,
|
|
136
|
-
sourceFilesJson: sourceJson(),
|
|
137
|
-
draft: draftMode || options.draftByDefault,
|
|
138
|
-
})
|
|
139
|
-
.catch((error) => {
|
|
140
|
-
status.textContent = `error: ${error.message}`;
|
|
141
|
-
});
|
|
142
|
-
};
|
|
143
|
-
deploy.addEventListener('click', () => doDeploy(false));
|
|
144
|
-
draft.addEventListener('click', () => doDeploy(true));
|
|
145
|
-
stop.addEventListener('click', () => controller.stop());
|
|
146
|
-
const markerSubscription = monaco.editor.onDidChangeMarkers(() => {
|
|
147
|
-
const markers = monaco.editor.getModelMarkers({});
|
|
148
|
-
const errors = markers.filter((marker) => marker.severity === monaco.MarkerSeverity.Error).length;
|
|
149
|
-
const warnings = markers.filter((marker) => marker.severity === monaco.MarkerSeverity.Warning).length;
|
|
150
|
-
problems.textContent = `${errors} error(s) · ${warnings} warning(s)`;
|
|
151
|
-
});
|
|
152
|
-
refreshTemplates();
|
|
153
|
-
const authenticated = await authenticatedSocket(options.languageServiceUrl, token, String(options.appId), sourceJson());
|
|
154
|
-
socket = authenticated.socket;
|
|
155
|
-
const initialSource = sourceJson();
|
|
156
|
-
modelRootUri = authenticated.workspaceUri.replace(/\/$/, '');
|
|
157
|
-
setFiles(initialSource);
|
|
158
|
-
const adapter = websocketAdapter(socket);
|
|
159
|
-
const reader = new wsJsonRpc.WebSocketMessageReader(adapter);
|
|
160
|
-
const writer = new wsJsonRpc.WebSocketMessageWriter(adapter);
|
|
161
|
-
languageClient = new languageClientModule.MonacoLanguageClient({
|
|
162
|
-
name: 'Crowdy Rust',
|
|
163
|
-
clientOptions: {
|
|
164
|
-
documentSelector: [{ language: 'rust' }, { language: 'toml' }],
|
|
165
|
-
workspaceFolder: {
|
|
166
|
-
uri: monaco.Uri.parse(modelRootUri),
|
|
167
|
-
name: 'player-mod',
|
|
168
|
-
index: 0,
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
messageTransports: { reader, writer },
|
|
172
|
-
});
|
|
173
|
-
await languageClient.start();
|
|
174
|
-
void controller.refreshUsage().catch(() => { });
|
|
175
|
-
return {
|
|
176
|
-
controller,
|
|
177
|
-
destroy: () => {
|
|
178
|
-
markerSubscription.dispose();
|
|
179
|
-
controller.stop();
|
|
180
|
-
void languageClient?.stop().catch(() => { });
|
|
181
|
-
socket?.close();
|
|
182
|
-
editor.dispose();
|
|
183
|
-
for (const entry of models)
|
|
184
|
-
entry.model.dispose();
|
|
185
|
-
root.remove();
|
|
186
|
-
},
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
catch (error) {
|
|
190
|
-
console.warn('Monaco live-coding IDE unavailable; using textarea fallback', error);
|
|
191
|
-
el.replaceChildren();
|
|
192
|
-
return mountLiveCoding(el, options);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
function installRustSyntaxHighlighting(monaco) {
|
|
196
|
-
const keywords = new Set([
|
|
197
|
-
'as',
|
|
198
|
-
'async',
|
|
199
|
-
'await',
|
|
200
|
-
'break',
|
|
201
|
-
'const',
|
|
202
|
-
'continue',
|
|
203
|
-
'crate',
|
|
204
|
-
'dyn',
|
|
205
|
-
'else',
|
|
206
|
-
'enum',
|
|
207
|
-
'extern',
|
|
208
|
-
'false',
|
|
209
|
-
'fn',
|
|
210
|
-
'for',
|
|
211
|
-
'if',
|
|
212
|
-
'impl',
|
|
213
|
-
'in',
|
|
214
|
-
'let',
|
|
215
|
-
'loop',
|
|
216
|
-
'match',
|
|
217
|
-
'mod',
|
|
218
|
-
'move',
|
|
219
|
-
'mut',
|
|
220
|
-
'pub',
|
|
221
|
-
'ref',
|
|
222
|
-
'return',
|
|
223
|
-
'self',
|
|
224
|
-
'static',
|
|
225
|
-
'struct',
|
|
226
|
-
'super',
|
|
227
|
-
'trait',
|
|
228
|
-
'true',
|
|
229
|
-
'type',
|
|
230
|
-
'unsafe',
|
|
231
|
-
'use',
|
|
232
|
-
'where',
|
|
233
|
-
'while',
|
|
234
|
-
]);
|
|
235
|
-
const types = new Set([
|
|
236
|
-
'Self',
|
|
237
|
-
'String',
|
|
238
|
-
'Vec',
|
|
239
|
-
'Option',
|
|
240
|
-
'Result',
|
|
241
|
-
'bool',
|
|
242
|
-
'char',
|
|
243
|
-
'str',
|
|
244
|
-
'usize',
|
|
245
|
-
'isize',
|
|
246
|
-
'u8',
|
|
247
|
-
'u16',
|
|
248
|
-
'u32',
|
|
249
|
-
'u64',
|
|
250
|
-
'u128',
|
|
251
|
-
'i8',
|
|
252
|
-
'i16',
|
|
253
|
-
'i32',
|
|
254
|
-
'i64',
|
|
255
|
-
'i128',
|
|
256
|
-
'f32',
|
|
257
|
-
'f64',
|
|
258
|
-
]);
|
|
259
|
-
const state = {
|
|
260
|
-
clone() {
|
|
261
|
-
return this;
|
|
262
|
-
},
|
|
263
|
-
equals(other) {
|
|
264
|
-
return other === this;
|
|
265
|
-
},
|
|
266
|
-
};
|
|
267
|
-
monaco.languages.setTokensProvider('rust', {
|
|
268
|
-
getInitialState: () => state,
|
|
269
|
-
tokenize: (line, currentState) => {
|
|
270
|
-
const tokens = [];
|
|
271
|
-
let index = 0;
|
|
272
|
-
while (index < line.length) {
|
|
273
|
-
const rest = line.slice(index);
|
|
274
|
-
let match;
|
|
275
|
-
if ((match = rest.match(/^\s+/))) {
|
|
276
|
-
tokens.push({ startIndex: index, scopes: '' });
|
|
277
|
-
}
|
|
278
|
-
else if ((match = rest.match(/^\/\/.*$/))) {
|
|
279
|
-
tokens.push({ startIndex: index, scopes: 'comment.rust' });
|
|
280
|
-
}
|
|
281
|
-
else if ((match = rest.match(/^\/\*.*?(?:\*\/|$)/))) {
|
|
282
|
-
tokens.push({ startIndex: index, scopes: 'comment.rust' });
|
|
283
|
-
}
|
|
284
|
-
else if ((match = rest.match(/^"(?:\\.|[^"\\])*"?/))) {
|
|
285
|
-
tokens.push({ startIndex: index, scopes: 'string.rust' });
|
|
286
|
-
}
|
|
287
|
-
else if ((match = rest.match(/^(?:0x[\da-fA-F_]+|\d[\d_]*)/))) {
|
|
288
|
-
tokens.push({ startIndex: index, scopes: 'number.rust' });
|
|
289
|
-
}
|
|
290
|
-
else if ((match = rest.match(/^[A-Za-z_]\w*!/))) {
|
|
291
|
-
tokens.push({ startIndex: index, scopes: 'macro.rust' });
|
|
292
|
-
}
|
|
293
|
-
else if ((match = rest.match(/^[A-Za-z_]\w*/))) {
|
|
294
|
-
const word = match[0];
|
|
295
|
-
tokens.push({
|
|
296
|
-
startIndex: index,
|
|
297
|
-
scopes: keywords.has(word)
|
|
298
|
-
? 'keyword.rust'
|
|
299
|
-
: types.has(word)
|
|
300
|
-
? 'type.rust'
|
|
301
|
-
: 'identifier.rust',
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
else {
|
|
305
|
-
match = rest.match(/^./);
|
|
306
|
-
tokens.push({ startIndex: index, scopes: 'delimiter.rust' });
|
|
307
|
-
}
|
|
308
|
-
index += match?.[0].length ?? 1;
|
|
309
|
-
}
|
|
310
|
-
return { tokens, endState: currentState };
|
|
311
|
-
},
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
function resolveToken(token) {
|
|
315
|
-
const value = typeof token === 'function' ? token() : token;
|
|
316
|
-
return value && value.length > 0 ? value : null;
|
|
317
|
-
}
|
|
318
|
-
function parseFiles(sourceFilesJson) {
|
|
319
|
-
const parsed = JSON.parse(sourceFilesJson);
|
|
320
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
321
|
-
throw new Error('sourceFilesJson must be an object');
|
|
322
|
-
}
|
|
323
|
-
return Object.fromEntries(Object.entries(parsed).map(([path, value]) => {
|
|
324
|
-
if (typeof value !== 'string')
|
|
325
|
-
throw new Error(`Invalid source file ${path}`);
|
|
326
|
-
return [path, value];
|
|
327
|
-
}));
|
|
328
|
-
}
|
|
329
|
-
async function authenticatedSocket(url, token, appId, sourceFilesJson) {
|
|
330
|
-
const socket = new WebSocket(url);
|
|
331
|
-
let workspaceUri = 'file:///player-mod';
|
|
332
|
-
await new Promise((resolve, reject) => {
|
|
333
|
-
const timeout = setTimeout(() => reject(new Error('language service timeout')), 10000);
|
|
334
|
-
socket.addEventListener('open', () => {
|
|
335
|
-
socket.send(JSON.stringify({
|
|
336
|
-
type: 'authenticate',
|
|
337
|
-
token,
|
|
338
|
-
appId,
|
|
339
|
-
sourceFilesJson,
|
|
340
|
-
}));
|
|
341
|
-
}, { once: true });
|
|
342
|
-
socket.addEventListener('message', (event) => {
|
|
343
|
-
try {
|
|
344
|
-
const message = JSON.parse(String(event.data));
|
|
345
|
-
if (message.type !== 'ready' || !message.workspaceUri) {
|
|
346
|
-
throw new Error('language service refused session');
|
|
347
|
-
}
|
|
348
|
-
workspaceUri = message.workspaceUri;
|
|
349
|
-
clearTimeout(timeout);
|
|
350
|
-
resolve();
|
|
351
|
-
}
|
|
352
|
-
catch (error) {
|
|
353
|
-
clearTimeout(timeout);
|
|
354
|
-
reject(error);
|
|
355
|
-
}
|
|
356
|
-
}, { once: true });
|
|
357
|
-
socket.addEventListener('close', (event) => {
|
|
358
|
-
clearTimeout(timeout);
|
|
359
|
-
reject(new Error(event.reason || 'language service closed'));
|
|
360
|
-
}, { once: true });
|
|
361
|
-
});
|
|
362
|
-
return { socket, workspaceUri };
|
|
363
|
-
}
|
|
364
|
-
function websocketAdapter(socket) {
|
|
365
|
-
return {
|
|
366
|
-
send: (content) => socket.send(content),
|
|
367
|
-
onMessage: (callback) => socket.addEventListener('message', (event) => callback(event.data)),
|
|
368
|
-
onError: (callback) => socket.addEventListener('error', (event) => callback(event)),
|
|
369
|
-
onClose: (callback) => socket.addEventListener('close', (event) => callback(event.code, event.reason)),
|
|
370
|
-
dispose: () => socket.close(),
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
function select(values) {
|
|
374
|
-
const element = document.createElement('select');
|
|
375
|
-
for (const value of values)
|
|
376
|
-
element.append(new Option(value, value));
|
|
377
|
-
return element;
|
|
378
|
-
}
|
|
379
|
-
function button(label) {
|
|
380
|
-
const element = document.createElement('button');
|
|
381
|
-
element.type = 'button';
|
|
382
|
-
element.textContent = label;
|
|
383
|
-
return element;
|
|
384
|
-
}
|
|
385
|
-
function renderStatus(status, meter, value) {
|
|
386
|
-
status.textContent = [
|
|
387
|
-
`[${value.target}] ${value.phase}`,
|
|
388
|
-
value.message,
|
|
389
|
-
value.compileLog,
|
|
390
|
-
]
|
|
391
|
-
.filter(Boolean)
|
|
392
|
-
.join('\n');
|
|
393
|
-
if (value.usage) {
|
|
394
|
-
meter.textContent =
|
|
395
|
-
`units ${value.usage.hourUnitsUsed}/${value.usage.unitsPerHour ?? '∞'} · ` +
|
|
396
|
-
`compiles ${value.usage.compilesThisHour}/${value.usage.maxCompilesPerHour} · ` +
|
|
397
|
-
`gate ${value.usage.gateStatus}${value.usage.gateReason ? ` (${value.usage.gateReason})` : ''}`;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import type { PlayerComputeAPI } from '../domains/playerCompute.js';
|
|
2
|
-
import type { PlayerWalletAPI } from '../domains/playerWallet.js';
|
|
3
|
-
import { PlayerCodeBroker, type PlayerCodeBrokerOptions, type PlayerCodeGridBounds } from '../player-runtime/player-code-broker.js';
|
|
4
|
-
export type LiveCodingTarget = 'server' | 'client';
|
|
5
|
-
export interface LiveCodingStatus {
|
|
6
|
-
phase: 'idle' | 'deploying' | 'compiling' | 'compile_failed' | 'enabling' | 'running' | 'error';
|
|
7
|
-
target: LiveCodingTarget;
|
|
8
|
-
message?: string;
|
|
9
|
-
compileLog?: string | null;
|
|
10
|
-
/** Remaining-budget snapshot from playerComputeUsage, when loaded. */
|
|
11
|
-
usage?: {
|
|
12
|
-
hourUnitsUsed: string;
|
|
13
|
-
unitsPerHour: string | null;
|
|
14
|
-
compilesThisHour: number;
|
|
15
|
-
maxCompilesPerHour: number;
|
|
16
|
-
gateStatus: string;
|
|
17
|
-
gateReason: string | null;
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
export interface LiveCodingControllerOptions {
|
|
21
|
-
playerCompute: Pick<PlayerComputeAPI, 'deploy' | 'setEnabled' | 'usage' | 'runs' | 'logs' | 'versions' | 'artifactBytes'>;
|
|
22
|
-
playerWallet?: Pick<PlayerWalletAPI, 'balance'>;
|
|
23
|
-
/** BigInt scalars are strings on the wire (codegen). */
|
|
24
|
-
appId: string;
|
|
25
|
-
gridId: string;
|
|
26
|
-
grid: PlayerCodeGridBounds;
|
|
27
|
-
/** The platform glue worker asset URL for client deploys. */
|
|
28
|
-
workerUrl: string | URL;
|
|
29
|
-
/** Page-side host-call router for the broker (World Stores reads + owner effects). */
|
|
30
|
-
onHostCall: PlayerCodeBrokerOptions['onHostCall'];
|
|
31
|
-
onPresentation?: PlayerCodeBrokerOptions['onPresentation'];
|
|
32
|
-
/** Client tick cadence (ms); the worker self-drives `tick` for HUD-style mods. */
|
|
33
|
-
clientTickIntervalMs?: number;
|
|
34
|
-
onStatus?: (status: LiveCodingStatus) => void;
|
|
35
|
-
/** Poll interval for compile status (ms). */
|
|
36
|
-
pollMs?: number;
|
|
37
|
-
/** Injectable clock/sleep + broker factory for tests. */
|
|
38
|
-
sleep?: (ms: number) => Promise<void>;
|
|
39
|
-
brokerFactory?: (options: PlayerCodeBrokerOptions) => PlayerCodeBroker;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Headless driver for the live-coding deploy loop (08), shared by the DOM
|
|
43
|
-
* panel and any custom host UI. It owns the edit -> deploy -> compile-poll ->
|
|
44
|
-
* (server) enable / (client) fetch+respawn state machine, surfaces compile
|
|
45
|
-
* feedback and the quota/wallet meters, and manages the client broker
|
|
46
|
-
* lifecycle. All IO is injected so it is unit-testable without a browser.
|
|
47
|
-
*/
|
|
48
|
-
export declare class LiveCodingController {
|
|
49
|
-
private readonly options;
|
|
50
|
-
private broker;
|
|
51
|
-
private status;
|
|
52
|
-
constructor(options: LiveCodingControllerOptions);
|
|
53
|
-
getStatus(): LiveCodingStatus;
|
|
54
|
-
private set;
|
|
55
|
-
private sleep;
|
|
56
|
-
/** Refresh the quota/wallet meter for the panel. */
|
|
57
|
-
refreshUsage(): Promise<void>;
|
|
58
|
-
/**
|
|
59
|
-
* Deploy source and run it. For server targets: deploy -> poll compile ->
|
|
60
|
-
* enable. For client targets: deploy -> poll compile -> fetch bytes ->
|
|
61
|
-
* (re)spawn the broker worker. Draft mode suppresses server egress to other
|
|
62
|
-
* sessions while iterating.
|
|
63
|
-
*/
|
|
64
|
-
deploy(input: {
|
|
65
|
-
name: string;
|
|
66
|
-
target: LiveCodingTarget;
|
|
67
|
-
sourceFilesJson: string;
|
|
68
|
-
tickHz?: number;
|
|
69
|
-
draft?: boolean;
|
|
70
|
-
}): Promise<void>;
|
|
71
|
-
private pollCompile;
|
|
72
|
-
private runClient;
|
|
73
|
-
stop(): void;
|
|
74
|
-
}
|
|
75
|
-
//# sourceMappingURL=live-coding-controller.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"live-coding-controller.d.ts","sourceRoot":"","sources":["../../src/live-coding/live-coding-controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EAC1B,MAAM,yCAAyC,CAAC;AAEjD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EACD,MAAM,GACN,WAAW,GACX,WAAW,GACX,gBAAgB,GAChB,UAAU,GACV,SAAS,GACT,OAAO,CAAC;IACZ,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,sEAAsE;IACtE,KAAK,CAAC,EAAE;QACN,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,gBAAgB,EAAE,MAAM,CAAC;QACzB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;CACH;AAED,MAAM,WAAW,2BAA2B;IAC1C,aAAa,EAAE,IAAI,CACjB,gBAAgB,EAChB,QAAQ,GAAG,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,eAAe,CACnF,CAAC;IACF,YAAY,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC;IAChD,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,oBAAoB,CAAC;IAC3B,6DAA6D;IAC7D,SAAS,EAAE,MAAM,GAAG,GAAG,CAAC;IACxB,sFAAsF;IACtF,UAAU,EAAE,uBAAuB,CAAC,YAAY,CAAC,CAAC;IAClD,cAAc,CAAC,EAAE,uBAAuB,CAAC,gBAAgB,CAAC,CAAC;IAC3D,kFAAkF;IAClF,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC9C,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,gBAAgB,CAAC;CACxE;AAED;;;;;;GAMG;AACH,qBAAa,oBAAoB;IAInB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,MAAM,CAAyD;gBAE1C,OAAO,EAAE,2BAA2B;IAEjE,SAAS,IAAI,gBAAgB;IAI7B,OAAO,CAAC,GAAG;IAKX,OAAO,CAAC,KAAK;IAMb,oDAAoD;IAC9C,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBnC;;;;;OAKG;IACG,MAAM,CAAC,KAAK,EAAE;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,gBAAgB,CAAC;QACzB,eAAe,EAAE,MAAM,CAAC;QACxB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,GAAG,OAAO,CAAC,IAAI,CAAC;YA6CH,WAAW;YAoBX,SAAS;IA4BvB,IAAI,IAAI,IAAI;CAKb"}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { PlayerCodeBroker, } from '../player-runtime/player-code-broker.js';
|
|
2
|
-
/**
|
|
3
|
-
* Headless driver for the live-coding deploy loop (08), shared by the DOM
|
|
4
|
-
* panel and any custom host UI. It owns the edit -> deploy -> compile-poll ->
|
|
5
|
-
* (server) enable / (client) fetch+respawn state machine, surfaces compile
|
|
6
|
-
* feedback and the quota/wallet meters, and manages the client broker
|
|
7
|
-
* lifecycle. All IO is injected so it is unit-testable without a browser.
|
|
8
|
-
*/
|
|
9
|
-
export class LiveCodingController {
|
|
10
|
-
constructor(options) {
|
|
11
|
-
this.options = options;
|
|
12
|
-
this.broker = null;
|
|
13
|
-
this.status = { phase: 'idle', target: 'server' };
|
|
14
|
-
}
|
|
15
|
-
getStatus() {
|
|
16
|
-
return this.status;
|
|
17
|
-
}
|
|
18
|
-
set(next) {
|
|
19
|
-
this.status = { ...this.status, ...next };
|
|
20
|
-
this.options.onStatus?.(this.status);
|
|
21
|
-
}
|
|
22
|
-
sleep(ms) {
|
|
23
|
-
return (this.options.sleep ?? ((d) => new Promise((r) => setTimeout(r, d))))(ms);
|
|
24
|
-
}
|
|
25
|
-
/** Refresh the quota/wallet meter for the panel. */
|
|
26
|
-
async refreshUsage() {
|
|
27
|
-
const usage = await this.options.playerCompute.usage({
|
|
28
|
-
appId: this.options.appId,
|
|
29
|
-
});
|
|
30
|
-
this.set({
|
|
31
|
-
usage: {
|
|
32
|
-
hourUnitsUsed: String(usage.hourUnitsUsed),
|
|
33
|
-
unitsPerHour: usage.unitsPerHour != null ? String(usage.unitsPerHour) : null,
|
|
34
|
-
compilesThisHour: usage.compilesThisHour,
|
|
35
|
-
maxCompilesPerHour: usage.maxCompilesPerHour,
|
|
36
|
-
gateStatus: usage.gateStatus,
|
|
37
|
-
gateReason: usage.gateReason ?? null,
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Deploy source and run it. For server targets: deploy -> poll compile ->
|
|
43
|
-
* enable. For client targets: deploy -> poll compile -> fetch bytes ->
|
|
44
|
-
* (re)spawn the broker worker. Draft mode suppresses server egress to other
|
|
45
|
-
* sessions while iterating.
|
|
46
|
-
*/
|
|
47
|
-
async deploy(input) {
|
|
48
|
-
const { name, target } = input;
|
|
49
|
-
this.set({ phase: 'deploying', target, message: undefined, compileLog: null });
|
|
50
|
-
let version;
|
|
51
|
-
try {
|
|
52
|
-
version = await this.options.playerCompute.deploy({
|
|
53
|
-
appId: this.options.appId,
|
|
54
|
-
gridId: this.options.gridId,
|
|
55
|
-
name,
|
|
56
|
-
target: target === 'server' ? 'SERVER' : 'CLIENT',
|
|
57
|
-
sourceFilesJson: input.sourceFilesJson,
|
|
58
|
-
tickHz: input.tickHz,
|
|
59
|
-
draft: input.draft,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
// Compile-quota refusals and cap breaches surface here (P2 governor).
|
|
64
|
-
this.set({ phase: 'error', message: err.message });
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
const compiled = await this.pollCompile(name, version.versionId);
|
|
68
|
-
if (!compiled.ok) {
|
|
69
|
-
this.set({
|
|
70
|
-
phase: 'compile_failed',
|
|
71
|
-
message: 'compile failed',
|
|
72
|
-
compileLog: compiled.log,
|
|
73
|
-
});
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
if (target === 'server') {
|
|
77
|
-
this.set({ phase: 'enabling' });
|
|
78
|
-
await this.options.playerCompute.setEnabled({
|
|
79
|
-
appId: this.options.appId,
|
|
80
|
-
gridId: this.options.gridId,
|
|
81
|
-
name,
|
|
82
|
-
enabled: true,
|
|
83
|
-
});
|
|
84
|
-
this.set({ phase: 'running', message: 'server module live' });
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
await this.runClient(name);
|
|
88
|
-
}
|
|
89
|
-
await this.refreshUsage().catch(() => { });
|
|
90
|
-
}
|
|
91
|
-
async pollCompile(name, versionId) {
|
|
92
|
-
this.set({ phase: 'compiling' });
|
|
93
|
-
const pollMs = this.options.pollMs ?? 1500;
|
|
94
|
-
for (let i = 0; i < 60; i++) {
|
|
95
|
-
const versions = await this.options.playerCompute.versions({
|
|
96
|
-
appId: this.options.appId,
|
|
97
|
-
gridId: this.options.gridId,
|
|
98
|
-
name,
|
|
99
|
-
});
|
|
100
|
-
const v = versions.find((row) => row.versionId === versionId);
|
|
101
|
-
if (v?.compileStatus === 'succeeded')
|
|
102
|
-
return { ok: true, log: v.compileLog ?? null };
|
|
103
|
-
if (v?.compileStatus === 'failed')
|
|
104
|
-
return { ok: false, log: v.compileLog ?? null };
|
|
105
|
-
await this.sleep(pollMs);
|
|
106
|
-
}
|
|
107
|
-
return { ok: false, log: 'compile timed out' };
|
|
108
|
-
}
|
|
109
|
-
async runClient(name) {
|
|
110
|
-
const artifact = await this.options.playerCompute.artifactBytes({
|
|
111
|
-
appId: this.options.appId,
|
|
112
|
-
gridId: this.options.gridId,
|
|
113
|
-
name,
|
|
114
|
-
});
|
|
115
|
-
const options = {
|
|
116
|
-
workerUrl: this.options.workerUrl,
|
|
117
|
-
grid: this.options.grid,
|
|
118
|
-
artifactHash: artifact.artifactHash,
|
|
119
|
-
fuelPerDispatch: artifact.fuelPerDispatch,
|
|
120
|
-
onHostCall: this.options.onHostCall,
|
|
121
|
-
onPresentation: this.options.onPresentation,
|
|
122
|
-
tickIntervalMs: this.options.clientTickIntervalMs ?? 1000,
|
|
123
|
-
};
|
|
124
|
-
const broker = this.options.brokerFactory?.(options) ?? new PlayerCodeBroker(options);
|
|
125
|
-
if (this.broker) {
|
|
126
|
-
await broker.start(artifact.bytes);
|
|
127
|
-
this.broker.stop();
|
|
128
|
-
this.broker = broker;
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
await broker.start(artifact.bytes);
|
|
132
|
-
this.broker = broker;
|
|
133
|
-
}
|
|
134
|
-
this.set({ phase: 'running', message: 'client module live in the sandbox' });
|
|
135
|
-
}
|
|
136
|
-
stop() {
|
|
137
|
-
this.broker?.stop();
|
|
138
|
-
this.broker = null;
|
|
139
|
-
this.set({ phase: 'idle' });
|
|
140
|
-
}
|
|
141
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { LiveCodingController, type LiveCodingControllerOptions } from './live-coding-controller.js';
|
|
2
|
-
import { type PlayerCodeTemplate } from './templates.js';
|
|
3
|
-
export interface MountLiveCodingOptions extends Omit<LiveCodingControllerOptions, 'onStatus'> {
|
|
4
|
-
/** Module name to deploy under; defaults to a per-grid scratch name. */
|
|
5
|
-
moduleName?: string;
|
|
6
|
-
/** Templates offered in the picker; defaults to the built-in starter set. */
|
|
7
|
-
templates?: PlayerCodeTemplate[];
|
|
8
|
-
/** Deploy in draft mode by default (server egress suppressed while iterating). */
|
|
9
|
-
draftByDefault?: boolean;
|
|
10
|
-
}
|
|
11
|
-
export interface LiveCodingHandle {
|
|
12
|
-
controller: LiveCodingController;
|
|
13
|
-
destroy: () => void;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Mount the live-coding panel into a host element (08 option A). Deliberately
|
|
17
|
-
* dependency-free DOM: a textarea editor, target + template pickers, deploy /
|
|
18
|
-
* draft-deploy / stop buttons, a status+console line, and the quota/wallet
|
|
19
|
-
* meter. Games skin it via the returned nodes or their own CSS; a game that
|
|
20
|
-
* wants a custom UI can drive LiveCodingController directly instead.
|
|
21
|
-
*
|
|
22
|
-
* Safe to import in non-DOM contexts; mounting throws only if called without
|
|
23
|
-
* a document.
|
|
24
|
-
*/
|
|
25
|
-
export declare function mountLiveCoding(el: HTMLElement, options: MountLiveCodingOptions): LiveCodingHandle;
|
|
26
|
-
//# sourceMappingURL=mount.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../src/live-coding/mount.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,KAAK,2BAA2B,EAEjC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,sBACf,SAAQ,IAAI,CAAC,2BAA2B,EAAE,UAAU,CAAC;IACrD,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACjC,kFAAkF;IAClF,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,oBAAoB,CAAC;IACjC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,WAAW,EACf,OAAO,EAAE,sBAAsB,GAC9B,gBAAgB,CAmFlB"}
|