@crowdedkingdoms/crowdyjs 17.12.0-dev.1 → 17.14.0-dev.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/MIGRATION.md +127 -0
- package/README.md +99 -4
- package/dist/crowdy-dsh/bridge.d.ts.map +1 -1
- package/dist/crowdy-dsh/bridge.js +2 -0
- package/dist/crowdy-studio/controller.d.ts +64 -8
- package/dist/crowdy-studio/controller.d.ts.map +1 -1
- package/dist/crowdy-studio/controller.js +345 -46
- package/dist/crowdy-studio/dom-shell.d.ts.map +1 -1
- package/dist/crowdy-studio/dom-shell.js +16 -9
- package/dist/crowdy-studio/embed/hud-layer.d.ts +2 -1
- package/dist/crowdy-studio/embed/hud-layer.d.ts.map +1 -1
- package/dist/crowdy-studio/embed/panel.d.ts +9 -6
- package/dist/crowdy-studio/embed/panel.d.ts.map +1 -1
- package/dist/crowdy-studio/embed/panel.js +4 -2
- package/dist/crowdy-studio/index.d.ts +2 -2
- package/dist/crowdy-studio/index.d.ts.map +1 -1
- package/dist/crowdy-studio/models.d.ts +1 -1
- package/dist/crowdy-studio/starter-projects.d.ts +22 -0
- package/dist/crowdy-studio/starter-projects.d.ts.map +1 -1
- package/dist/crowdy-studio/starter-projects.js +129 -5
- package/dist/domains/exec.d.ts +222 -20
- package/dist/domains/exec.d.ts.map +1 -1
- package/dist/domains/exec.js +215 -21
- package/dist/domains/marketplace.d.ts +15 -3
- package/dist/domains/marketplace.d.ts.map +1 -1
- package/dist/domains/marketplace.js +17 -3
- package/dist/errors.d.ts +3 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/generated/graphql.d.ts +422 -0
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +23 -11
- package/dist/grid-mods/exec-client-halves.d.ts +140 -0
- package/dist/grid-mods/exec-client-halves.d.ts.map +1 -0
- package/dist/grid-mods/exec-client-halves.js +341 -0
- package/dist/grid-mods/grid-host-calls.d.ts +3 -1
- package/dist/grid-mods/grid-host-calls.d.ts.map +1 -1
- package/dist/grid-mods/grid-host-calls.js +3 -1
- package/dist/grid-mods/index.d.ts +1 -0
- package/dist/grid-mods/index.d.ts.map +1 -1
- package/dist/grid-mods/index.js +1 -0
- package/dist/grid-mods/mod-runtime.d.ts +10 -2
- package/dist/grid-mods/mod-runtime.d.ts.map +1 -1
- package/dist/grid-mods/mod-runtime.js +2 -0
- package/dist/grid-scope.d.ts +1 -0
- package/dist/grid-scope.d.ts.map +1 -1
- package/dist/grid-scope.js +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/kit/npcs.d.ts.map +1 -1
- package/dist/kit/social.d.ts.map +1 -1
- package/dist/live-coding/rust-analysis.js +13 -1
- package/dist/player-runtime/glue-runtime.d.ts +19 -6
- package/dist/player-runtime/glue-runtime.d.ts.map +1 -1
- package/dist/player-runtime/glue-runtime.js +40 -16
- package/dist/player-runtime/player-code-broker.d.ts +29 -0
- package/dist/player-runtime/player-code-broker.d.ts.map +1 -1
- package/dist/player-runtime/player-code-broker.js +41 -8
- package/dist/player-runtime/player-glue-worker.d.ts +2 -1
- package/dist/player-runtime/player-glue-worker.d.ts.map +1 -1
- package/dist/player-runtime/player-glue-worker.js +3 -1
- package/dist/stores/actors.d.ts +12 -2
- package/dist/stores/actors.d.ts.map +1 -1
- package/dist/stores/actors.js +43 -18
- package/dist/stores/chunks.d.ts +12 -0
- package/dist/stores/chunks.d.ts.map +1 -1
- package/dist/stores/chunks.js +112 -39
- package/dist/stores/durable.d.ts.map +1 -1
- package/dist/stores/durable.js +2 -1
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PlayerCodeBroker, } from '../player-runtime/player-code-broker.js';
|
|
2
|
+
import { CrowdyError, CrowdyGraphQLError } from '../errors.js';
|
|
2
3
|
import { digestCanonicalJson, sha256Digest, } from '../player-host/json-schema.js';
|
|
3
4
|
import { parseRustcDiagnostics } from './diagnostics.js';
|
|
4
5
|
import { githubNewRepositoryUrl, githubRepositorySlug } from './github/new-repo.js';
|
|
@@ -7,6 +8,9 @@ import { parseClientTickIntervalMs } from './client-tick-interval.js';
|
|
|
7
8
|
import { createCrowdyStudioStarterProject, } from './starter-projects.js';
|
|
8
9
|
/** As ck-exec's mod names. */
|
|
9
10
|
const MOD_NAME = /^[a-z0-9_-]{1,48}$/;
|
|
11
|
+
/** As a ck-exec build's crate names. */
|
|
12
|
+
const CRATE_NAME = /^[a-z][a-z0-9_-]{0,63}$/;
|
|
13
|
+
const LOG_LEVELS = ['error', 'warn', 'info', 'debug'];
|
|
10
14
|
class OperationCancelledError extends Error {
|
|
11
15
|
}
|
|
12
16
|
/**
|
|
@@ -18,6 +22,7 @@ export class CrowdyStudioController {
|
|
|
18
22
|
constructor(options) {
|
|
19
23
|
this.options = options;
|
|
20
24
|
this.state = {
|
|
25
|
+
serverEngine: 'player-compute',
|
|
21
26
|
projects: [],
|
|
22
27
|
project: null,
|
|
23
28
|
personalLibraryFiles: [],
|
|
@@ -55,8 +60,15 @@ export class CrowdyStudioController {
|
|
|
55
60
|
this.surfaceTimers = new Map();
|
|
56
61
|
this.pageVisible = true;
|
|
57
62
|
this.destroyed = false;
|
|
63
|
+
this.modConnection = null;
|
|
58
64
|
// ----- GitHub repository card ------------------------------------------
|
|
59
65
|
this.githubStatusGeneration = 0;
|
|
66
|
+
const serverEngine = options.serverEngine ?? (options.mods ? 'ck-exec' : 'player-compute');
|
|
67
|
+
if (serverEngine === 'ck-exec' && !options.mods) {
|
|
68
|
+
throw new Error("serverEngine 'ck-exec' needs the mods option (client.exec)");
|
|
69
|
+
}
|
|
70
|
+
this.mods = serverEngine === 'ck-exec' ? options.mods : null;
|
|
71
|
+
this.state = { ...this.state, serverEngine };
|
|
60
72
|
if (options.onStateChange)
|
|
61
73
|
this.listeners.add(options.onStateChange);
|
|
62
74
|
}
|
|
@@ -186,9 +198,14 @@ export class CrowdyStudioController {
|
|
|
186
198
|
if (this.state.project && !(await this.saveNow())) {
|
|
187
199
|
throw new Error('Resolve or retry the current project save before creating another');
|
|
188
200
|
}
|
|
201
|
+
const modStarter = this.mods && projectTargets(options.kind).includes('SERVER')
|
|
202
|
+
? await this.mods.modStarter(this.options.appId)
|
|
203
|
+
: undefined;
|
|
189
204
|
const input = createCrowdyStudioStarterProject({
|
|
190
205
|
...options,
|
|
191
206
|
...this.scope(),
|
|
207
|
+
...(modStarter ? { modStarter } : {}),
|
|
208
|
+
engine: this.state.serverEngine,
|
|
192
209
|
});
|
|
193
210
|
const project = await this.options.projectProvider.createProject(input);
|
|
194
211
|
this.installProject(project);
|
|
@@ -254,6 +271,7 @@ export class CrowdyStudioController {
|
|
|
254
271
|
this.stopSurfacePolling();
|
|
255
272
|
this.broker?.stop();
|
|
256
273
|
this.broker = null;
|
|
274
|
+
this.closeModConnection();
|
|
257
275
|
this.clearSaveTimers();
|
|
258
276
|
this.editGeneration = 0;
|
|
259
277
|
this.persistedGeneration = 0;
|
|
@@ -890,7 +908,7 @@ export class CrowdyStudioController {
|
|
|
890
908
|
}
|
|
891
909
|
this.checkOperation(operation);
|
|
892
910
|
// A mod has no client pairing: its players call it by name.
|
|
893
|
-
if (!this.
|
|
911
|
+
if (!this.mods) {
|
|
894
912
|
const requiredClientName = project.metadata.pairingPreference === 'REQUIRED'
|
|
895
913
|
? client.name
|
|
896
914
|
: null;
|
|
@@ -966,8 +984,10 @@ export class CrowdyStudioController {
|
|
|
966
984
|
message: `Submitting ${name}`,
|
|
967
985
|
},
|
|
968
986
|
});
|
|
969
|
-
if (
|
|
970
|
-
return
|
|
987
|
+
if (this.mods) {
|
|
988
|
+
return target === 'SERVER'
|
|
989
|
+
? this.buildMod(name, files, operation)
|
|
990
|
+
: this.buildClientHalf(name, files, operation);
|
|
971
991
|
}
|
|
972
992
|
// The server resolves the source from the project itself: the saved
|
|
973
993
|
// files at the project revision, or — for a GITHUB project — the rust at
|
|
@@ -1019,37 +1039,75 @@ export class CrowdyStudioController {
|
|
|
1019
1039
|
return null;
|
|
1020
1040
|
}
|
|
1021
1041
|
/**
|
|
1022
|
-
* The SERVER target as a ck-exec mod: the project's server files
|
|
1023
|
-
*
|
|
1042
|
+
* The SERVER target as a ck-exec mod: the project's server crate files, built on the
|
|
1043
|
+
* platform, then deployed to this grid (switched off until {@link enableServer}). Other
|
|
1044
|
+
* project files, such as grid program assets, are not part of the crate.
|
|
1024
1045
|
*/
|
|
1025
1046
|
async buildMod(name, files, operation) {
|
|
1026
|
-
const mods = this.
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1047
|
+
const mods = this.mods;
|
|
1048
|
+
const problem = modNameProblem(this.requireProject());
|
|
1049
|
+
if (problem)
|
|
1050
|
+
throw new Error(problem);
|
|
1030
1051
|
const { appId, gridId } = this.scope();
|
|
1031
1052
|
const queued = await mods.modBuild(appId, {
|
|
1032
|
-
name,
|
|
1033
|
-
files: files
|
|
1053
|
+
name: crateName(name),
|
|
1054
|
+
files: crateFiles(files),
|
|
1055
|
+
});
|
|
1056
|
+
this.checkOperation(operation);
|
|
1057
|
+
const built = await this.awaitModBuild('SERVER', name, queued.buildId, operation);
|
|
1058
|
+
if (!built)
|
|
1059
|
+
return null;
|
|
1060
|
+
const mod = await mods.modDeploy(appId, gridId, name, built.buildId);
|
|
1061
|
+
this.checkOperation(operation);
|
|
1062
|
+
return { target: 'SERVER', name, versionId: String(mod.version) };
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* The CLIENT target as a ck-exec mod's CLIENT half: the project's `crowdy-client-sdk` crate,
|
|
1066
|
+
* built on the platform (`modClientBuild`, a build of `kind` `client`). It is attached to the
|
|
1067
|
+
* project's mod when the project runs ({@link attachClientHalf}).
|
|
1068
|
+
*/
|
|
1069
|
+
async buildClientHalf(name, files, operation) {
|
|
1070
|
+
const problem = modNameProblem(this.requireProject());
|
|
1071
|
+
if (problem)
|
|
1072
|
+
throw new Error(problem);
|
|
1073
|
+
const cargo = files.find((file) => file.path === 'Cargo.toml')?.content ?? '';
|
|
1074
|
+
if (/^\s*crowdy-compute-sdk\s*=/m.test(cargo)) {
|
|
1075
|
+
this.recordBuild('CLIENT', LEGACY_CLIENT_CRATE);
|
|
1076
|
+
this.update({
|
|
1077
|
+
runtime: {
|
|
1078
|
+
phase: 'COMPILE_FAILED',
|
|
1079
|
+
target: 'CLIENT',
|
|
1080
|
+
message: `${name} is a legacy player compute crate; a CLIENT half builds on crowdy-client-sdk`,
|
|
1081
|
+
},
|
|
1082
|
+
});
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
const queued = await this.mods.modClientBuild(this.options.appId, {
|
|
1086
|
+
name: crateName(name),
|
|
1087
|
+
files: crateFiles(files),
|
|
1034
1088
|
});
|
|
1035
1089
|
this.checkOperation(operation);
|
|
1090
|
+
const built = await this.awaitModBuild('CLIENT', name, queued.buildId, operation);
|
|
1091
|
+
return built ? { target: 'CLIENT', name, versionId: built.buildId } : null;
|
|
1092
|
+
}
|
|
1093
|
+
/** Polls a mod build, a mod's or a CLIENT half's, recording its log; null when it did not succeed. */
|
|
1094
|
+
async awaitModBuild(target, name, buildId, operation) {
|
|
1095
|
+
const mods = this.mods;
|
|
1036
1096
|
const limit = this.options.compilePollLimit ?? 60;
|
|
1037
1097
|
const pollMs = this.options.compilePollMs ?? 1500;
|
|
1038
1098
|
for (let attempt = 0; attempt < limit; attempt++) {
|
|
1039
|
-
const b = await mods.modBuildStatus(appId,
|
|
1099
|
+
const b = await mods.modBuildStatus(this.options.appId, buildId);
|
|
1040
1100
|
this.checkOperation(operation);
|
|
1041
1101
|
if (b.status === 'succeeded') {
|
|
1042
|
-
this.recordBuild(
|
|
1043
|
-
|
|
1044
|
-
this.checkOperation(operation);
|
|
1045
|
-
return { target: 'SERVER', name, versionId: String(mod.version) };
|
|
1102
|
+
this.recordBuild(target, b.log ?? '');
|
|
1103
|
+
return b;
|
|
1046
1104
|
}
|
|
1047
1105
|
if (b.status === 'failed') {
|
|
1048
|
-
this.recordBuild(
|
|
1106
|
+
this.recordBuild(target, b.log ?? 'Compilation failed without output');
|
|
1049
1107
|
this.update({
|
|
1050
1108
|
runtime: {
|
|
1051
1109
|
phase: 'COMPILE_FAILED',
|
|
1052
|
-
target
|
|
1110
|
+
target,
|
|
1053
1111
|
message: `${name} failed to compile`,
|
|
1054
1112
|
},
|
|
1055
1113
|
});
|
|
@@ -1059,12 +1117,106 @@ export class CrowdyStudioController {
|
|
|
1059
1117
|
this.checkOperation(operation);
|
|
1060
1118
|
}
|
|
1061
1119
|
const timeout = `Compilation timed out after ${limit} polls`;
|
|
1062
|
-
this.recordBuild(
|
|
1120
|
+
this.recordBuild(target, timeout);
|
|
1063
1121
|
this.update({
|
|
1064
|
-
runtime: { phase: 'COMPILE_FAILED', target
|
|
1122
|
+
runtime: { phase: 'COMPILE_FAILED', target, message: timeout },
|
|
1065
1123
|
});
|
|
1066
1124
|
return null;
|
|
1067
1125
|
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Attaches a built CLIENT half to the project's mod, consents to it as its author (the API
|
|
1128
|
+
* serves a CLIENT half only to a player who consented or trusts its author, the author too) and
|
|
1129
|
+
* fetches the served module for this browser's preview. A CLIENT-only project first makes sure
|
|
1130
|
+
* the mod it rides is the player's and switched on.
|
|
1131
|
+
*/
|
|
1132
|
+
async attachClientHalf(compiled, operation) {
|
|
1133
|
+
const mods = this.mods;
|
|
1134
|
+
const { appId, gridId } = this.scope();
|
|
1135
|
+
const project = this.requireProject();
|
|
1136
|
+
const modName = projectModName(project);
|
|
1137
|
+
if (!projectTargets(project.kind).includes('SERVER')) {
|
|
1138
|
+
await this.ensureClientOnlyMod(modName, operation);
|
|
1139
|
+
}
|
|
1140
|
+
this.update({
|
|
1141
|
+
runtime: { phase: 'ENABLING', target: 'CLIENT', message: `Attaching ${compiled.name} to ${modName}` },
|
|
1142
|
+
});
|
|
1143
|
+
const attached = await mods.modClientDeploy(appId, gridId, modName, compiled.versionId);
|
|
1144
|
+
this.checkOperation(operation);
|
|
1145
|
+
await mods.consentClientMod(appId, attached.modId, attached.capabilityHash);
|
|
1146
|
+
this.checkOperation(operation);
|
|
1147
|
+
this.recordNote('CLIENT', `Attached to mod '${modName}' as CLIENT version ${attached.clientVersion} (capabilities ` +
|
|
1148
|
+
`${attached.capabilityHash}; ticks every ${attached.tickIntervalMs} ms). Visitors of grid ` +
|
|
1149
|
+
`${gridId} run it once they consent to it or trust you; you consented to it as its author.`);
|
|
1150
|
+
let artifact;
|
|
1151
|
+
try {
|
|
1152
|
+
artifact = await mods.modClientArtifactBytes(appId, attached.modId);
|
|
1153
|
+
}
|
|
1154
|
+
catch (error) {
|
|
1155
|
+
if (error instanceof CrowdyGraphQLError && error.code === 'NOT_FOUND') {
|
|
1156
|
+
throw new CrowdyError({
|
|
1157
|
+
message: `${compiled.name} is attached to mod '${modName}', but its preview did not load: the ` +
|
|
1158
|
+
'API serves a CLIENT half only while its mod is switched on and not held, to a player ' +
|
|
1159
|
+
`with run_client_code who stands in grid ${gridId}`,
|
|
1160
|
+
cause: error,
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
if (error instanceof CrowdyGraphQLError && error.code === 'RATE_LIMITED') {
|
|
1164
|
+
throw new CrowdyError({
|
|
1165
|
+
message: `${compiled.name} is attached to mod '${modName}', but its preview was fetched too ` +
|
|
1166
|
+
'often (12 a minute); deploy again in a minute',
|
|
1167
|
+
cause: error,
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
throw error;
|
|
1171
|
+
}
|
|
1172
|
+
this.checkOperation(operation);
|
|
1173
|
+
if (artifact.digest !== attached.digest || artifact.clientVersion !== attached.clientVersion) {
|
|
1174
|
+
throw new Error('The served CLIENT half is not the one just attached; deploy again');
|
|
1175
|
+
}
|
|
1176
|
+
return artifact;
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* A CLIENT half rides a mod: without a mod of this name running as the player on the grid,
|
|
1180
|
+
* the mod starter's server half is deployed under it first, and the log says so. The mod is
|
|
1181
|
+
* switched on, since only a running mod's CLIENT half is served.
|
|
1182
|
+
*/
|
|
1183
|
+
async ensureClientOnlyMod(name, operation) {
|
|
1184
|
+
const mods = this.mods;
|
|
1185
|
+
const { appId, gridId } = this.scope();
|
|
1186
|
+
const mine = (await mods.myMods(appId)).find((mod) => String(mod.gridId) === gridId && mod.name === name);
|
|
1187
|
+
this.checkOperation(operation);
|
|
1188
|
+
if (!mine) {
|
|
1189
|
+
if (!this.canTarget('SERVER', 'write') || !this.canTarget('SERVER', 'run')) {
|
|
1190
|
+
throw new Error(`A CLIENT half rides a mod, and grid ${gridId} has no mod '${name}' of yours; deploying one needs SERVER write and run permissions here`);
|
|
1191
|
+
}
|
|
1192
|
+
this.update({
|
|
1193
|
+
runtime: { phase: 'COMPILING', target: 'SERVER', message: `Deploying the mod starter as ${name}` },
|
|
1194
|
+
});
|
|
1195
|
+
const starter = await mods.modStarter(appId);
|
|
1196
|
+
this.checkOperation(operation);
|
|
1197
|
+
const queued = await mods.modBuild(appId, { name: crateName(name), files: starter.files });
|
|
1198
|
+
this.checkOperation(operation);
|
|
1199
|
+
const built = await this.awaitModBuild('SERVER', name, queued.buildId, operation);
|
|
1200
|
+
if (!built) {
|
|
1201
|
+
throw new Error(`The mod starter did not build, so the CLIENT half has no mod '${name}' to ride`);
|
|
1202
|
+
}
|
|
1203
|
+
await mods.modDeploy(appId, gridId, name, built.buildId);
|
|
1204
|
+
this.checkOperation(operation);
|
|
1205
|
+
this.recordNote('SERVER', `Grid ${gridId} had no mod '${name}' of yours, and a CLIENT half rides a mod: deployed the ` +
|
|
1206
|
+
`ck-exec mod starter (${starter.crate}) as '${name}', its server half.`);
|
|
1207
|
+
}
|
|
1208
|
+
if (!mine?.enabled) {
|
|
1209
|
+
await mods.modSetEnabled(appId, gridId, name, true);
|
|
1210
|
+
this.checkOperation(operation);
|
|
1211
|
+
if (mine)
|
|
1212
|
+
this.recordNote('SERVER', `Switched mod '${name}' on, so its CLIENT half is served.`);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
recordNote(target, note) {
|
|
1216
|
+
this.update({
|
|
1217
|
+
buildOutput: [this.state.buildOutput, `## ${target}\n${note}`].filter(Boolean).join('\n\n'),
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1068
1220
|
recordBuild(target, log) {
|
|
1069
1221
|
const section = `## ${target}\n${log || 'Compiled successfully.'}`;
|
|
1070
1222
|
const authoritativeDiagnostics = [
|
|
@@ -1087,9 +1239,9 @@ export class CrowdyStudioController {
|
|
|
1087
1239
|
this.checkOperation(operation);
|
|
1088
1240
|
}
|
|
1089
1241
|
async setServerEnabled(name, enabled) {
|
|
1090
|
-
if (this.
|
|
1242
|
+
if (this.mods) {
|
|
1091
1243
|
const { appId, gridId } = this.scope();
|
|
1092
|
-
await this.
|
|
1244
|
+
await this.mods.modSetEnabled(appId, gridId, name, enabled);
|
|
1093
1245
|
return;
|
|
1094
1246
|
}
|
|
1095
1247
|
await this.options.playerCompute.setEnabled({
|
|
@@ -1103,28 +1255,47 @@ export class CrowdyStudioController {
|
|
|
1103
1255
|
throw new Error(`${compiled.name} compiled successfully, but run_client_code is unavailable on this grid`);
|
|
1104
1256
|
}
|
|
1105
1257
|
const runtime = this.clientRuntimeOptions();
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1258
|
+
let brokerOptions;
|
|
1259
|
+
let bytes;
|
|
1260
|
+
if (this.mods) {
|
|
1261
|
+
const half = await this.attachClientHalf(compiled, operation);
|
|
1262
|
+
bytes = half.bytes;
|
|
1263
|
+
brokerOptions = {
|
|
1264
|
+
...runtime,
|
|
1265
|
+
engine: 'ck-exec',
|
|
1266
|
+
moduleName: half.name,
|
|
1267
|
+
artifactHash: half.digest,
|
|
1268
|
+
fuelPerDispatch: half.fuelPerDispatch,
|
|
1269
|
+
consentedHostCalls: half.capabilitySummary.hostFunctions,
|
|
1270
|
+
onPresentation: this.options.onPresentation,
|
|
1271
|
+
tickIntervalMs: this.options.clientTickIntervalMs ?? half.tickIntervalMs,
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
else {
|
|
1275
|
+
const artifact = await this.options.playerCompute.artifactBytes({
|
|
1276
|
+
...this.scope(),
|
|
1277
|
+
name: compiled.name,
|
|
1278
|
+
versionId: compiled.versionId,
|
|
1279
|
+
});
|
|
1280
|
+
this.checkOperation(operation);
|
|
1281
|
+
if (artifact.versionId !== compiled.versionId ||
|
|
1282
|
+
!artifact.artifactHash ||
|
|
1283
|
+
artifact.bytes.byteLength === 0) {
|
|
1284
|
+
throw new Error('Client artifact did not match the compiled project version');
|
|
1285
|
+
}
|
|
1286
|
+
bytes = artifact.bytes;
|
|
1287
|
+
brokerOptions = {
|
|
1288
|
+
...runtime,
|
|
1289
|
+
artifactHash: artifact.artifactHash,
|
|
1290
|
+
fuelPerDispatch: artifact.fuelPerDispatch,
|
|
1291
|
+
onPresentation: this.options.onPresentation,
|
|
1292
|
+
tickIntervalMs: this.options.clientTickIntervalMs ??
|
|
1293
|
+
parseClientTickIntervalMs(this.requireProject().files.find((file) => file.target === 'CLIENT' && file.path === 'Cargo.toml')?.content),
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1125
1296
|
const broker = this.options.brokerFactory?.(brokerOptions) ??
|
|
1126
1297
|
new PlayerCodeBroker(brokerOptions);
|
|
1127
|
-
await broker.start(
|
|
1298
|
+
await broker.start(bytes);
|
|
1128
1299
|
this.checkOperation(operation);
|
|
1129
1300
|
const previous = this.broker;
|
|
1130
1301
|
this.broker = broker;
|
|
@@ -1151,10 +1322,12 @@ export class CrowdyStudioController {
|
|
|
1151
1322
|
this.broker = null;
|
|
1152
1323
|
}
|
|
1153
1324
|
}
|
|
1154
|
-
|
|
1325
|
+
// On ck-exec a CLIENT-only project's CLIENT half rides a mod too: switched off, it is no
|
|
1326
|
+
// longer served to visitors.
|
|
1327
|
+
if (projectTargets(project.kind).includes('SERVER') || this.mods) {
|
|
1155
1328
|
serverStopped = false;
|
|
1156
1329
|
try {
|
|
1157
|
-
await this.setServerEnabled(moduleNameFor(project, 'SERVER'), false);
|
|
1330
|
+
await this.setServerEnabled(this.mods ? projectModName(project) : moduleNameFor(project, 'SERVER'), false);
|
|
1158
1331
|
serverStopped = true;
|
|
1159
1332
|
}
|
|
1160
1333
|
catch (error) {
|
|
@@ -1182,6 +1355,12 @@ export class CrowdyStudioController {
|
|
|
1182
1355
|
if (!projectTargets(project.kind).includes('SERVER')) {
|
|
1183
1356
|
throw new Error('Invoke requires a SERVER target');
|
|
1184
1357
|
}
|
|
1358
|
+
if (this.mods) {
|
|
1359
|
+
const result = await this.callMod(moduleNameFor(project, 'SERVER'), exportName.trim() || 'state', paramsJson);
|
|
1360
|
+
this.checkAgentOperation(agentOperation);
|
|
1361
|
+
this.update({ invokeResult: result });
|
|
1362
|
+
return result;
|
|
1363
|
+
}
|
|
1185
1364
|
const result = await this.options.playerCompute.invoke({
|
|
1186
1365
|
...this.scope(),
|
|
1187
1366
|
moduleName: moduleNameFor(project, 'SERVER'),
|
|
@@ -1214,10 +1393,65 @@ export class CrowdyStudioController {
|
|
|
1214
1393
|
else
|
|
1215
1394
|
this.stopSurfacePolling();
|
|
1216
1395
|
}
|
|
1396
|
+
/**
|
|
1397
|
+
* The mod's endpoint `method` with `paramsJson` as its arguments, through one exec
|
|
1398
|
+
* connection to the mod (`mod:<name>`, keyed by the grid) that later calls reuse.
|
|
1399
|
+
*/
|
|
1400
|
+
async callMod(name, method, paramsJson) {
|
|
1401
|
+
const text = paramsJson?.trim();
|
|
1402
|
+
let args = null;
|
|
1403
|
+
if (text) {
|
|
1404
|
+
try {
|
|
1405
|
+
args = JSON.parse(text);
|
|
1406
|
+
}
|
|
1407
|
+
catch {
|
|
1408
|
+
throw new Error('The call arguments must be JSON');
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
if (this.modConnection?.name !== name) {
|
|
1412
|
+
this.closeModConnection();
|
|
1413
|
+
const connection = this.mods.connect(this.options.appId, {
|
|
1414
|
+
nodeType: modNodeType(name),
|
|
1415
|
+
key: this.options.gridId,
|
|
1416
|
+
});
|
|
1417
|
+
connection.catch(() => {
|
|
1418
|
+
if (this.modConnection?.connection === connection)
|
|
1419
|
+
this.modConnection = null;
|
|
1420
|
+
});
|
|
1421
|
+
this.modConnection = { name, connection };
|
|
1422
|
+
}
|
|
1423
|
+
const connection = await this.modConnection.connection;
|
|
1424
|
+
const started = Date.now();
|
|
1425
|
+
const value = await connection.call(modNodeType(name), this.options.gridId, method, args);
|
|
1426
|
+
return {
|
|
1427
|
+
resultJson: JSON.stringify(value ?? null, (_key, v) => typeof v === 'bigint' ? v.toString() : v),
|
|
1428
|
+
durationUs: (Date.now() - started) * 1000,
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
closeModConnection() {
|
|
1432
|
+
const open = this.modConnection;
|
|
1433
|
+
this.modConnection = null;
|
|
1434
|
+
void open?.connection.then((connection) => connection.close(), () => { });
|
|
1435
|
+
}
|
|
1217
1436
|
async refreshSurface(surface) {
|
|
1218
1437
|
if (!this.state.project)
|
|
1219
1438
|
return;
|
|
1220
1439
|
const serverName = this.state.project.metadata.serverModuleName;
|
|
1440
|
+
if (this.mods && surface !== 'usage') {
|
|
1441
|
+
// ck-exec records no runs; a mod's log is its own `ctx.log` lines.
|
|
1442
|
+
if (surface === 'runs') {
|
|
1443
|
+
this.update({ runs: [] });
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
const modName = projectModNameOrNull(this.state.project);
|
|
1447
|
+
const lines = modName
|
|
1448
|
+
? await this.mods.modLogs(this.options.appId, this.options.gridId, modName, {
|
|
1449
|
+
limit: 50,
|
|
1450
|
+
})
|
|
1451
|
+
: [];
|
|
1452
|
+
this.update({ logs: lines.map((line) => modLogRow(modName, line)) });
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1221
1455
|
if (surface === 'runs') {
|
|
1222
1456
|
this.update({
|
|
1223
1457
|
runs: await this.options.playerCompute.runs({
|
|
@@ -1239,11 +1473,14 @@ export class CrowdyStudioController {
|
|
|
1239
1473
|
});
|
|
1240
1474
|
return;
|
|
1241
1475
|
}
|
|
1476
|
+
// Player compute's units and compile quota, which nothing spends on ck-exec.
|
|
1242
1477
|
const [usage, wallet] = await Promise.all([
|
|
1243
|
-
this.
|
|
1478
|
+
!this.mods
|
|
1479
|
+
? this.options.playerCompute.usage({ appId: this.options.appId })
|
|
1480
|
+
: Promise.resolve(null),
|
|
1244
1481
|
this.options.playerWallet?.balance() ?? Promise.resolve(null),
|
|
1245
1482
|
]);
|
|
1246
|
-
this.update({ usage: normalizeUsage(usage), wallet });
|
|
1483
|
+
this.update({ usage: usage ? normalizeUsage(usage) : null, wallet });
|
|
1247
1484
|
}
|
|
1248
1485
|
destroy() {
|
|
1249
1486
|
if (this.destroyed)
|
|
@@ -1254,6 +1491,7 @@ export class CrowdyStudioController {
|
|
|
1254
1491
|
this.stopSurfacePolling();
|
|
1255
1492
|
this.broker?.stop();
|
|
1256
1493
|
this.broker = null;
|
|
1494
|
+
this.closeModConnection();
|
|
1257
1495
|
this.listeners.clear();
|
|
1258
1496
|
this.humanEditListeners.clear();
|
|
1259
1497
|
}
|
|
@@ -1499,6 +1737,67 @@ function sameFileRef(a, b) {
|
|
|
1499
1737
|
function compareProjectFile(a, b) {
|
|
1500
1738
|
return crowdyStudioFileKey(a.target, a.path).localeCompare(crowdyStudioFileKey(b.target, b.path));
|
|
1501
1739
|
}
|
|
1740
|
+
/** The node type players call a mod by, as `execModType`. */
|
|
1741
|
+
function modNodeType(name) {
|
|
1742
|
+
return `mod:${name}`;
|
|
1743
|
+
}
|
|
1744
|
+
/** What a mod build takes: `Cargo.toml`, `README.md` and Rust under `src/`. */
|
|
1745
|
+
function isModCrateFile(path) {
|
|
1746
|
+
return (path === 'Cargo.toml' ||
|
|
1747
|
+
path === 'README.md' ||
|
|
1748
|
+
(path.startsWith('src/') && path.endsWith('.rs')));
|
|
1749
|
+
}
|
|
1750
|
+
function crateFiles(files) {
|
|
1751
|
+
return files
|
|
1752
|
+
.filter((file) => isModCrateFile(file.path))
|
|
1753
|
+
.map((file) => ({ path: file.path, content: file.content }));
|
|
1754
|
+
}
|
|
1755
|
+
/** A build's crate name for a module: its own when it is one, else `mod-<name>`. */
|
|
1756
|
+
function crateName(name) {
|
|
1757
|
+
return CRATE_NAME.test(name) ? name : `mod-${name}`;
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* The mod a ck-exec project runs as: its SERVER module's name, or for a CLIENT-only project the
|
|
1761
|
+
* CLIENT module's, which names the mod its CLIENT half rides.
|
|
1762
|
+
*/
|
|
1763
|
+
function projectModName(project) {
|
|
1764
|
+
return moduleNameFor(project, projectTargets(project.kind).includes('SERVER') ? 'SERVER' : 'CLIENT');
|
|
1765
|
+
}
|
|
1766
|
+
function projectModNameOrNull(project) {
|
|
1767
|
+
try {
|
|
1768
|
+
return projectModName(project);
|
|
1769
|
+
}
|
|
1770
|
+
catch {
|
|
1771
|
+
return null;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
/** Why a ck-exec project's mod name cannot be a mod's, or null. */
|
|
1775
|
+
function modNameProblem(project) {
|
|
1776
|
+
const name = projectModName(project);
|
|
1777
|
+
if (MOD_NAME.test(name))
|
|
1778
|
+
return null;
|
|
1779
|
+
return projectTargets(project.kind).includes('SERVER')
|
|
1780
|
+
? `The server module name '${name}' must be 1-48 lowercase letters, digits, - or _ to run as a mod`
|
|
1781
|
+
: `The CLIENT module name '${name}' names the mod its CLIENT half rides, so it must be 1-48 lowercase letters, digits, - or _`;
|
|
1782
|
+
}
|
|
1783
|
+
const LEGACY_CLIENT_CRATE = 'This CLIENT crate depends on crowdy-compute-sdk, legacy player compute. On ck-exec a CLIENT ' +
|
|
1784
|
+
'target is a mod\u2019s CLIENT half, one crowdy-client-sdk crate: in Cargo.toml replace the ' +
|
|
1785
|
+
'crowdy-compute-sdk line with crowdy-client-sdk = "0.1.0", and in src/ use crowdy_client_sdk ' +
|
|
1786
|
+
'in place of crowdy_compute_sdk. Its host calls are the same, less the Game Model and sessions.';
|
|
1787
|
+
function modLogRow(moduleName, line) {
|
|
1788
|
+
const level = LOG_LEVELS[line.level] ?? 'debug';
|
|
1789
|
+
return {
|
|
1790
|
+
runId: line.id,
|
|
1791
|
+
moduleName,
|
|
1792
|
+
triggerSource: level,
|
|
1793
|
+
startedAt: line.at,
|
|
1794
|
+
durationUs: 0,
|
|
1795
|
+
fuelUsed: '0',
|
|
1796
|
+
success: level !== 'error',
|
|
1797
|
+
errorMessage: line.text,
|
|
1798
|
+
level,
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1502
1801
|
function moduleNameFor(project, target) {
|
|
1503
1802
|
const name = target === 'SERVER'
|
|
1504
1803
|
? project.metadata.serverModuleName
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom-shell.d.ts","sourceRoot":"","sources":["../../src/crowdy-studio/dom-shell.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"dom-shell.d.ts","sourceRoot":"","sources":["../../src/crowdy-studio/dom-shell.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EACL,sBAAsB,EAIvB,MAAM,aAAa,CAAC;AAuCrB;;;;;;;GAOG;AACH;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,kDAAkD;IAClD,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC/B,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,2BAA2B;IAC1C,0EAA0E;IAC1E,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,WAAW,KAAK,cAAc,CAAC;IAClD,qEAAqE;IACrE,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,sBAAsB,KAAK,IAAI,CAAC;CAC5D;AAED,qBAAa,oBAAoB;IAkE7B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAlE/B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IACzC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAc;IAC9C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAa;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAc;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IACjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAa;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IACjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAmB;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAoB;IACrD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAmB;IACnD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IACjD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAc;IAC5C,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAmB;IACvD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAc;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAmB;IAChD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IACnD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA2C;IACxE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;IAC5D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA8C;IAC1E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+C;IACzE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAwB;IAC7C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAa;IAC/C,OAAO,CAAC,WAAW,CAAyB;IAC5C,OAAO,CAAC,YAAY,CAAkC;IACtD,OAAO,CAAC,SAAS,CAAkC;IACnD,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,QAAQ,CAAS;gBAGvB,IAAI,EAAE,WAAW,EACA,UAAU,EAAE,sBAAsB,EAClC,YAAY,GAAE,2BAAgC;IA8VjE,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;IAsCtC,OAAO,IAAI,IAAI;IAiBf,OAAO,CAAC,UAAU;IAmBlB;;;;;OAKG;IACH,OAAO,CAAC,wBAAwB;IAgBhC,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,WAAW;IAsCnB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,UAAU;IA2DlB,OAAO,CAAC,WAAW;IAgBnB,OAAO,CAAC,aAAa;IAWrB,wEAAwE;IACxE,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,cAAc;IA+BtB,OAAO,CAAC,eAAe;IAyBvB,OAAO,CAAC,cAAc;IA2BtB,OAAO,CAAC,cAAc;IA6CtB,OAAO,CAAC,cAAc;IAuDtB,OAAO,CAAC,kBAAkB;IAyD1B,OAAO,CAAC,gBAAgB;IA4DxB,OAAO,CAAC,UAAU;IA4ClB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,YAAY;IAmCpB,OAAO,CAAC,cAAc;IAkCtB,OAAO,CAAC,cAAc;IAqDtB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,iBAAiB;YAmBX,GAAG;CAQlB"}
|
|
@@ -274,12 +274,13 @@ export class CrowdyStudioDomShell {
|
|
|
274
274
|
clientModuleName: this.clientModuleName.value,
|
|
275
275
|
}));
|
|
276
276
|
this.pairing.addEventListener('change', () => this.controller.setPairingPreference(this.pairing.value));
|
|
277
|
-
|
|
278
|
-
this.invokeExport
|
|
277
|
+
const onMod = controller.getState().serverEngine === 'ck-exec';
|
|
278
|
+
this.invokeExport = input(onMod ? 'Endpoint' : 'Export name');
|
|
279
|
+
this.invokeExport.value = onMod ? 'state' : 'invoke';
|
|
279
280
|
this.invokeParams = document.createElement('textarea');
|
|
280
281
|
this.invokeParams.placeholder = '{"example": true}';
|
|
281
|
-
this.invokeParams.setAttribute('aria-label', 'Invoke JSON parameters');
|
|
282
|
-
const invokeButton = button('Invoke server export');
|
|
282
|
+
this.invokeParams.setAttribute('aria-label', onMod ? 'Call arguments (JSON)' : 'Invoke JSON parameters');
|
|
283
|
+
const invokeButton = button(onMod ? 'Call mod endpoint' : 'Invoke server export');
|
|
283
284
|
this.invokeResult = document.createElement('pre');
|
|
284
285
|
const invokeControls = element('div', 'ck-crowdy-studio-invoke');
|
|
285
286
|
invokeControls.append(this.invokeExport, this.invokeParams, invokeButton);
|
|
@@ -310,7 +311,9 @@ export class CrowdyStudioDomShell {
|
|
|
310
311
|
this.renderProblems(state);
|
|
311
312
|
this.renderBuild(state);
|
|
312
313
|
this.renderRuntimeRows(this.logsPanel, state.logs);
|
|
313
|
-
this.renderRuntimeRows(this.runsPanel, state.runs
|
|
314
|
+
this.renderRuntimeRows(this.runsPanel, state.runs, state.serverEngine === 'ck-exec'
|
|
315
|
+
? 'A mod keeps no run records; its log lines are under Logs.'
|
|
316
|
+
: undefined);
|
|
314
317
|
this.invokeResult.textContent = state.invokeResult
|
|
315
318
|
? formatInvokeResult(state.invokeResult)
|
|
316
319
|
: '';
|
|
@@ -900,7 +903,9 @@ export class CrowdyStudioDomShell {
|
|
|
900
903
|
setInputUnlessFocused(this.clientModuleName, project.metadata.clientModuleName ?? '');
|
|
901
904
|
this.serverModuleName.disabled = !projectTargets(project.kind).includes('SERVER');
|
|
902
905
|
this.clientModuleName.disabled = !projectTargets(project.kind).includes('CLIENT');
|
|
903
|
-
|
|
906
|
+
// A mod has no pairing: its CLIENT half rides it.
|
|
907
|
+
this.pairing.disabled =
|
|
908
|
+
project.kind !== 'FULL_STACK' || state.serverEngine === 'ck-exec';
|
|
904
909
|
this.pairing.value = project.metadata.pairingPreference;
|
|
905
910
|
}
|
|
906
911
|
renderProblems(state) {
|
|
@@ -959,15 +964,17 @@ export class CrowdyStudioDomShell {
|
|
|
959
964
|
output.textContent = state.buildOutput || 'No build has run.';
|
|
960
965
|
this.buildPanel.append(output);
|
|
961
966
|
}
|
|
962
|
-
renderRuntimeRows(panel, rows) {
|
|
967
|
+
renderRuntimeRows(panel, rows, emptyText = 'No runtime records.') {
|
|
963
968
|
panel.replaceChildren();
|
|
964
969
|
if (rows.length === 0) {
|
|
965
|
-
panel.append(empty(
|
|
970
|
+
panel.append(empty(emptyText));
|
|
966
971
|
return;
|
|
967
972
|
}
|
|
968
973
|
for (const row of rows) {
|
|
969
974
|
const line = document.createElement('pre');
|
|
970
|
-
line.textContent =
|
|
975
|
+
line.textContent = row.level
|
|
976
|
+
? `${row.success ? '·' : '✗'} ${row.startedAt} ${row.moduleName} ${row.level}\n${row.errorMessage ?? ''}`
|
|
977
|
+
: `${row.success ? '✓' : '✗'} ${row.startedAt} ${row.moduleName} ${row.triggerSource} · ${row.durationUs}µs · ${row.fuelUsed} fuel${row.errorMessage ? `\n${row.errorMessage}` : ''}`;
|
|
971
978
|
panel.append(line);
|
|
972
979
|
}
|
|
973
980
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A persistent, game-owned HUD layer for
|
|
2
|
+
* A persistent, game-owned HUD layer for CLIENT mods (ck-exec CLIENT halves
|
|
3
|
+
* and legacy player-compute CLIENT modules). A
|
|
3
4
|
* running client mod never touches the game DOM; it emits `hud_set` payloads
|
|
4
5
|
* that the broker forwards here. This layer lives outside the studio panel so
|
|
5
6
|
* a mod's HUD survives panel re-renders and keeps rendering after the coding
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hud-layer.d.ts","sourceRoot":"","sources":["../../../src/crowdy-studio/embed/hud-layer.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"hud-layer.d.ts","sourceRoot":"","sources":["../../../src/crowdy-studio/embed/hud-layer.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,oBAAoB;IACnC,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,IAAI,CAA+B;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2C;IACnE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAE3D,OAAO,CAAC,UAAU;IAUlB,+DAA+D;IAC/D,GAAG,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI;IAKtC,oEAAoE;IACpE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI5B,KAAK,IAAI,IAAI;IAKb;;;;OAIG;IACH,YAAY,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI;IAShE,OAAO,IAAI,IAAI;IAQf,OAAO,CAAC,MAAM;IAYd,OAAO,CAAC,aAAa;IAerB,OAAO,CAAC,SAAS;IAajB,mEAAmE;IACnE,OAAO,CAAC,QAAQ;CAQjB"}
|