@chrrxs/robloxstudio-mcp 3.0.0 → 3.0.2
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/dist/index.js +9401 -9047
- package/package.json +2 -2
- package/studio-plugin/MCPPlugin.rbxmx +400 -97
- package/studio-plugin/.Carbon.rbxm.lock +0 -0
- package/studio-plugin/Carbon.rbxm +0 -0
- package/studio-plugin/INSTALLATION.md +0 -170
- package/studio-plugin/MCPInspectorPlugin.rbxmx +0 -169759
- package/studio-plugin/default.project.json +0 -19
- package/studio-plugin/dev.project.json +0 -23
- package/studio-plugin/include/LibMP.lua +0 -156378
- package/studio-plugin/inspector-icon.png +0 -0
- package/studio-plugin/package-lock.json +0 -706
- package/studio-plugin/package.json +0 -19
- package/studio-plugin/plugin.json +0 -10
- package/studio-plugin/src/modules/AssetSanitizationPolicy.ts +0 -127
- package/studio-plugin/src/modules/ClientBroker.ts +0 -450
- package/studio-plugin/src/modules/Communication.ts +0 -601
- package/studio-plugin/src/modules/EvalBridges.ts +0 -255
- package/studio-plugin/src/modules/HttpDiagnostics.ts +0 -50
- package/studio-plugin/src/modules/LuauExec.ts +0 -403
- package/studio-plugin/src/modules/Recording.ts +0 -28
- package/studio-plugin/src/modules/RenderMonitor.ts +0 -60
- package/studio-plugin/src/modules/RuntimeLogBuffer.ts +0 -210
- package/studio-plugin/src/modules/ServerUrlSettings.ts +0 -117
- package/studio-plugin/src/modules/State.ts +0 -39
- package/studio-plugin/src/modules/StopPlayMonitor.ts +0 -267
- package/studio-plugin/src/modules/UI.ts +0 -597
- package/studio-plugin/src/modules/Utils.ts +0 -527
- package/studio-plugin/src/modules/handlers/AssetHandlers.ts +0 -391
- package/studio-plugin/src/modules/handlers/BreakpointHandlers.ts +0 -460
- package/studio-plugin/src/modules/handlers/CaptureHandlers.ts +0 -170
- package/studio-plugin/src/modules/handlers/EvalRuntimeHandlers.ts +0 -149
- package/studio-plugin/src/modules/handlers/GenerateModelHandlers.ts +0 -168
- package/studio-plugin/src/modules/handlers/InputHandlers.ts +0 -163
- package/studio-plugin/src/modules/handlers/LogHandlers.ts +0 -14
- package/studio-plugin/src/modules/handlers/MemoryHandlers.ts +0 -44
- package/studio-plugin/src/modules/handlers/MetadataHandlers.ts +0 -96
- package/studio-plugin/src/modules/handlers/MicroProfilerHandlers.ts +0 -1263
- package/studio-plugin/src/modules/handlers/PropertyHandlers.ts +0 -62
- package/studio-plugin/src/modules/handlers/QueryHandlers.ts +0 -716
- package/studio-plugin/src/modules/handlers/SceneAnalysisHandlers.ts +0 -216
- package/studio-plugin/src/modules/handlers/ScriptHandlers.ts +0 -531
- package/studio-plugin/src/modules/handlers/ScriptProfilerHandlers.ts +0 -386
- package/studio-plugin/src/modules/handlers/SerializationHandlers.ts +0 -172
- package/studio-plugin/src/modules/handlers/TestHandlers.ts +0 -350
- package/studio-plugin/src/server/index.server.ts +0 -135
- package/studio-plugin/src/types/index.d.ts +0 -57
- package/studio-plugin/tsconfig.json +0 -20
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { HttpService, ServerStorage } from "@rbxts/services";
|
|
2
|
-
|
|
3
|
-
const LEGACY_SETTING_KEY_PREFIX = "MCP_SERVER_URL_";
|
|
4
|
-
const SETTING_KEY_PREFIX = "MCP_LAST_SUCCESSFUL_SERVER_URL_";
|
|
5
|
-
const GLOBAL_SETTING_KEY = "MCP_LAST_SUCCESSFUL_SERVER_URL_GLOBAL_V1";
|
|
6
|
-
|
|
7
|
-
let pluginRef: Plugin | undefined;
|
|
8
|
-
|
|
9
|
-
function init(p: Plugin): void {
|
|
10
|
-
pluginRef = p;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function normalizeServerUrl(serverUrl: string | undefined): string {
|
|
14
|
-
let normalized = (serverUrl ?? "").gsub("^%s+", "")[0].gsub("%s+$", "")[0];
|
|
15
|
-
if (normalized === "") return "";
|
|
16
|
-
|
|
17
|
-
if (normalized.match("^%a[%w+.-]*://")[0] === undefined) {
|
|
18
|
-
normalized = `http://${normalized}`;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
while (
|
|
22
|
-
normalized.size() > 0 &&
|
|
23
|
-
normalized.sub(-1) === "/" &&
|
|
24
|
-
normalized.match("^%a[%w+.-]*://$")[0] === undefined
|
|
25
|
-
) {
|
|
26
|
-
normalized = normalized.sub(1, -2);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return normalized;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function extractPort(serverUrl: string): number | undefined {
|
|
33
|
-
const [portStr] = serverUrl.match(":(%d+)$");
|
|
34
|
-
if (portStr === undefined) return undefined;
|
|
35
|
-
return tonumber(portStr);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function addUnique(values: string[], value: string): void {
|
|
39
|
-
if (!values.includes(value)) {
|
|
40
|
-
values.push(value);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function computeInstanceIds(options?: { createAnonymous?: boolean }): string[] {
|
|
45
|
-
const ids: string[] = [];
|
|
46
|
-
if (game.PlaceId !== 0) {
|
|
47
|
-
addUnique(ids, `place:${tostring(game.PlaceId)}`);
|
|
48
|
-
}
|
|
49
|
-
const existing = ServerStorage.GetAttribute("__MCPPlaceId");
|
|
50
|
-
if (typeIs(existing, "string") && existing !== "") {
|
|
51
|
-
addUnique(ids, `anon:${existing as string}`);
|
|
52
|
-
} else if (game.PlaceId === 0 && options?.createAnonymous === true) {
|
|
53
|
-
const fresh = HttpService.GenerateGUID(false);
|
|
54
|
-
pcall(() => ServerStorage.SetAttribute("__MCPPlaceId", fresh));
|
|
55
|
-
addUnique(ids, `anon:${fresh}`);
|
|
56
|
-
}
|
|
57
|
-
return ids;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function settingKey(instanceId: string): string {
|
|
61
|
-
return SETTING_KEY_PREFIX + instanceId;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function legacySettingKey(instanceId: string): string {
|
|
65
|
-
return LEGACY_SETTING_KEY_PREFIX + instanceId;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function readSettingString(key: string): string | undefined {
|
|
69
|
-
if (!pluginRef) return undefined;
|
|
70
|
-
const [ok, value] = pcall(() => pluginRef!.GetSetting(key));
|
|
71
|
-
if (!ok || !typeIs(value, "string")) return undefined;
|
|
72
|
-
|
|
73
|
-
const normalized = normalizeServerUrl(value as string);
|
|
74
|
-
return normalized !== "" ? normalized : undefined;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function writeSettingString(key: string, serverUrl: string): void {
|
|
78
|
-
if (!pluginRef) return;
|
|
79
|
-
pcall(() => pluginRef!.SetSetting(key, serverUrl));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function rememberServerUrl(serverUrl: string): void {
|
|
83
|
-
const normalized = normalizeServerUrl(serverUrl);
|
|
84
|
-
if (!pluginRef || normalized === "") return;
|
|
85
|
-
writeSettingString(GLOBAL_SETTING_KEY, normalized);
|
|
86
|
-
for (const instanceId of computeInstanceIds({ createAnonymous: true })) {
|
|
87
|
-
writeSettingString(settingKey(instanceId), normalized);
|
|
88
|
-
writeSettingString(legacySettingKey(instanceId), normalized);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function readServerUrl(): string | undefined {
|
|
93
|
-
if (!pluginRef) return undefined;
|
|
94
|
-
// Reading settings should not mint a place identity. Client play DMs have
|
|
95
|
-
// their own ServerStorage; creating an id there makes a misleading anon id
|
|
96
|
-
// that never matches the edit/server bridge identity.
|
|
97
|
-
for (const instanceId of computeInstanceIds()) {
|
|
98
|
-
const remembered = readSettingString(settingKey(instanceId));
|
|
99
|
-
if (remembered !== undefined) return remembered;
|
|
100
|
-
}
|
|
101
|
-
const globalRemembered = readSettingString(GLOBAL_SETTING_KEY);
|
|
102
|
-
if (globalRemembered !== undefined) return globalRemembered;
|
|
103
|
-
for (const instanceId of computeInstanceIds()) {
|
|
104
|
-
const legacyRemembered = readSettingString(legacySettingKey(instanceId));
|
|
105
|
-
if (legacyRemembered !== undefined) return legacyRemembered;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return undefined;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export = {
|
|
112
|
-
init,
|
|
113
|
-
normalizeServerUrl,
|
|
114
|
-
extractPort,
|
|
115
|
-
rememberServerUrl,
|
|
116
|
-
readServerUrl,
|
|
117
|
-
};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { Connection } from "../types";
|
|
2
|
-
|
|
3
|
-
const CURRENT_VERSION = "__VERSION__";
|
|
4
|
-
const PLUGIN_VARIANT = "__PLUGIN_VARIANT__";
|
|
5
|
-
const BASE_PORT = 58741;
|
|
6
|
-
|
|
7
|
-
function createConnection(port: number): Connection {
|
|
8
|
-
return {
|
|
9
|
-
port,
|
|
10
|
-
serverUrl: `http://localhost:${port}`,
|
|
11
|
-
isActive: false,
|
|
12
|
-
pollInterval: 0.5,
|
|
13
|
-
lastPoll: 0,
|
|
14
|
-
consecutiveFailures: 0,
|
|
15
|
-
maxFailuresBeforeError: 50,
|
|
16
|
-
lastSuccessfulConnection: 0,
|
|
17
|
-
currentRetryDelay: 0.5,
|
|
18
|
-
maxRetryDelay: 5,
|
|
19
|
-
retryBackoffMultiplier: 1.2,
|
|
20
|
-
lastHttpOk: false,
|
|
21
|
-
lastMcpOk: false,
|
|
22
|
-
mcpWaitStartTime: undefined,
|
|
23
|
-
isPolling: false,
|
|
24
|
-
heartbeatConnection: undefined,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const connection = createConnection(BASE_PORT);
|
|
29
|
-
|
|
30
|
-
function getActiveConnection(): Connection {
|
|
31
|
-
return connection;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export = {
|
|
35
|
-
CURRENT_VERSION,
|
|
36
|
-
PLUGIN_VARIANT,
|
|
37
|
-
BASE_PORT,
|
|
38
|
-
getActiveConnection,
|
|
39
|
-
};
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
// Cross-DM stop_playtest signaling via plugin:SetSetting, scoped by
|
|
2
|
-
// per-instance setting key so the same Studio process can host playtests
|
|
3
|
-
// for multiple places without one place's stop_playtest yanking another's.
|
|
4
|
-
// During publish-after-connect, both "anon:<uuid>" and "place:<PlaceId>"
|
|
5
|
-
// can refer to the same Studio place, so stop requests are mirrored across
|
|
6
|
-
// both keys while the monitor waits for a matching result on either key.
|
|
7
|
-
//
|
|
8
|
-
// `plugin:SetSetting` / `plugin:GetSetting` is a per-plugin persistent store
|
|
9
|
-
// shared across every DataModel the plugin runs in (edit DMs, play-server
|
|
10
|
-
// DMs, play-client DMs). For each connected place we use a dedicated key
|
|
11
|
-
// "MCP_STOP_PLAY_<instanceId>" as a tiny request/result mailbox:
|
|
12
|
-
//
|
|
13
|
-
// * The edit DM's handler writes a tokenized stop request into its own key
|
|
14
|
-
// (computed from its placeId / ServerStorage anon UUID).
|
|
15
|
-
// * Each play-server DM's monitor loop polls the key matching its own
|
|
16
|
-
// instanceId at 1Hz. On a fresh token, it calls StudioTestService:EndTest
|
|
17
|
-
// and writes a matching result token. Play-server DMs for other places
|
|
18
|
-
// never touch this key.
|
|
19
|
-
// * The edit DM waits up to ~8s for its result token, confirming a matching
|
|
20
|
-
// play-server actually consumed the request.
|
|
21
|
-
//
|
|
22
|
-
// Earlier versions used a single shared boolean flag, which let any
|
|
23
|
-
// play-server DM in the same Studio process consume any place's stop
|
|
24
|
-
// request — silently yanking teammates' playtests. The per-key scoping
|
|
25
|
-
// below is the fix.
|
|
26
|
-
|
|
27
|
-
import { HttpService, RunService, ServerStorage } from "@rbxts/services";
|
|
28
|
-
|
|
29
|
-
const StudioTestService = game.GetService("StudioTestService");
|
|
30
|
-
|
|
31
|
-
const SETTING_KEY_PREFIX = "MCP_STOP_PLAY_";
|
|
32
|
-
// Keep this conservative. plugin:GetSetting is backed by Studio's plugin
|
|
33
|
-
// settings store, and this monitor runs during every play session, including
|
|
34
|
-
// manually-started Play. The official reference implementation polls at 1s.
|
|
35
|
-
const POLL_INTERVAL_SEC = 1;
|
|
36
|
-
// Total time we wait for the matching play-server DM to consume the
|
|
37
|
-
// signal. Must cover: monitor detection (<= POLL_INTERVAL_SEC) +
|
|
38
|
-
// StudioTestService:EndTest teardown (several seconds on heavier places).
|
|
39
|
-
// 8s is intentionally shorter than the MCP request timeout but long enough
|
|
40
|
-
// for the 1s monitor cadence plus ordinary Studio teardown latency.
|
|
41
|
-
const WAIT_FOR_CONSUMPTION_TIMEOUT_SEC = 8.0;
|
|
42
|
-
const WAIT_POLL_SEC = 0.1;
|
|
43
|
-
const REQUEST_TTL_SEC = 12.0;
|
|
44
|
-
|
|
45
|
-
let pluginRef: Plugin | undefined;
|
|
46
|
-
let endTestIssued = false;
|
|
47
|
-
|
|
48
|
-
interface StopPayload {
|
|
49
|
-
kind?: string;
|
|
50
|
-
id?: string;
|
|
51
|
-
requestedAt?: number;
|
|
52
|
-
consumedAt?: number;
|
|
53
|
-
ok?: boolean;
|
|
54
|
-
error?: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface StopRequestResult {
|
|
58
|
-
ok: boolean;
|
|
59
|
-
requestId?: string;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
interface StopConsumptionResult {
|
|
63
|
-
ok: boolean;
|
|
64
|
-
consumed: boolean;
|
|
65
|
-
error?: string;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function init(p: Plugin): void {
|
|
69
|
-
pluginRef = p;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Mirror of Communication.computeInstanceId(). Duplicated here because
|
|
73
|
-
// StopPlayMonitor runs in both edit and play-server DMs, and both must
|
|
74
|
-
// agree on the place identifier (published places: placeId; unpublished:
|
|
75
|
-
// UUID on ServerStorage's __MCPPlaceId attribute, travels with the .rbxl
|
|
76
|
-
// into the play DM).
|
|
77
|
-
function addUnique(values: string[], value: string): void {
|
|
78
|
-
if (!values.includes(value)) {
|
|
79
|
-
values.push(value);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function computeInstanceIds(): string[] {
|
|
84
|
-
const ids: string[] = [];
|
|
85
|
-
if (game.PlaceId !== 0) {
|
|
86
|
-
addUnique(ids, `place:${tostring(game.PlaceId)}`);
|
|
87
|
-
}
|
|
88
|
-
const existing = ServerStorage.GetAttribute("__MCPPlaceId");
|
|
89
|
-
if (typeIs(existing, "string") && existing !== "") {
|
|
90
|
-
addUnique(ids, `anon:${existing as string}`);
|
|
91
|
-
} else if (game.PlaceId === 0) {
|
|
92
|
-
const fresh = HttpService.GenerateGUID(false);
|
|
93
|
-
pcall(() => ServerStorage.SetAttribute("__MCPPlaceId", fresh));
|
|
94
|
-
addUnique(ids, `anon:${fresh}`);
|
|
95
|
-
}
|
|
96
|
-
return ids;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function settingKey(instanceId: string): string {
|
|
100
|
-
return SETTING_KEY_PREFIX + instanceId;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function settingKeys(): string[] {
|
|
104
|
-
return computeInstanceIds().map((instanceId) => settingKey(instanceId));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function readSetting(key: string): unknown {
|
|
108
|
-
if (!pluginRef) return undefined;
|
|
109
|
-
const [ok, value] = pcall(() => pluginRef!.GetSetting(key));
|
|
110
|
-
return ok ? value : undefined;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function writeSetting(key: string, value: unknown): boolean {
|
|
114
|
-
if (!pluginRef) return false;
|
|
115
|
-
const [ok] = pcall(() => pluginRef!.SetSetting(key, value));
|
|
116
|
-
return ok;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function decodePayload(value: unknown): StopPayload | undefined {
|
|
120
|
-
let decoded = value;
|
|
121
|
-
if (typeIs(value, "string")) {
|
|
122
|
-
const [ok, result] = pcall(() => HttpService.JSONDecode(value as string));
|
|
123
|
-
if (!ok) return undefined;
|
|
124
|
-
decoded = result;
|
|
125
|
-
}
|
|
126
|
-
if (!typeIs(decoded, "table")) return undefined;
|
|
127
|
-
const payload = decoded as StopPayload;
|
|
128
|
-
if (!typeIs(payload.kind, "string") || !typeIs(payload.id, "string")) {
|
|
129
|
-
return undefined;
|
|
130
|
-
}
|
|
131
|
-
return payload;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function writePayload(key: string, payload: StopPayload): boolean {
|
|
135
|
-
const [encodedOk, encoded] = pcall(() => HttpService.JSONEncode(payload));
|
|
136
|
-
if (!encodedOk || !typeIs(encoded, "string")) return false;
|
|
137
|
-
return writeSetting(key, encoded);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function writeResult(key: string, request: StopPayload, ok: boolean, errText?: string): void {
|
|
141
|
-
writePayload(key, {
|
|
142
|
-
kind: "result",
|
|
143
|
-
id: request.id,
|
|
144
|
-
requestedAt: request.requestedAt,
|
|
145
|
-
consumedAt: tick(),
|
|
146
|
-
ok,
|
|
147
|
-
error: errText,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function handleStopRequest(key: string, request: StopPayload): void {
|
|
152
|
-
if (request.kind !== "request" || !typeIs(request.id, "string")) return;
|
|
153
|
-
if (!typeIs(request.requestedAt, "number")) {
|
|
154
|
-
writeSetting(key, false);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const age = tick() - request.requestedAt;
|
|
159
|
-
if (age < -5 || age > REQUEST_TTL_SEC) {
|
|
160
|
-
writeSetting(key, false);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (endTestIssued) {
|
|
165
|
-
writeResult(
|
|
166
|
-
key,
|
|
167
|
-
request,
|
|
168
|
-
false,
|
|
169
|
-
"StudioTestService:EndTest was already issued for this play session, but the runtime DataModel is still alive.",
|
|
170
|
-
);
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (!RunService.IsRunning() || !RunService.IsServer()) {
|
|
175
|
-
writeResult(key, request, false, "StopPlayMonitor is not running in the server DataModel.");
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
endTestIssued = true;
|
|
180
|
-
const [endOk, endErr] = pcall(() => StudioTestService.EndTest("stopped_by_mcp"));
|
|
181
|
-
writeResult(key, request, endOk, endOk ? undefined : tostring(endErr));
|
|
182
|
-
if (!endOk) {
|
|
183
|
-
endTestIssued = false;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function startMonitor(): void {
|
|
188
|
-
if (!pluginRef) {
|
|
189
|
-
warn("[robloxstudio-mcp] StopPlayMonitor.startMonitor called before init; skipping");
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
task.spawn(() => {
|
|
193
|
-
while (true) {
|
|
194
|
-
for (const myKey of settingKeys()) {
|
|
195
|
-
const value = readSetting(myKey);
|
|
196
|
-
if (value === true) {
|
|
197
|
-
// Legacy boolean requests are ambiguous and may be stale from
|
|
198
|
-
// a prior crashed session. New stop requests use token payloads.
|
|
199
|
-
writeSetting(myKey, false);
|
|
200
|
-
} else {
|
|
201
|
-
const payload = decodePayload(value);
|
|
202
|
-
if (payload) {
|
|
203
|
-
handleStopRequest(myKey, payload);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
task.wait(POLL_INTERVAL_SEC);
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function requestStop(): StopRequestResult {
|
|
213
|
-
if (!pluginRef) return { ok: false };
|
|
214
|
-
const requestId = HttpService.GenerateGUID(false);
|
|
215
|
-
const payload: StopPayload = {
|
|
216
|
-
kind: "request",
|
|
217
|
-
id: requestId,
|
|
218
|
-
requestedAt: tick(),
|
|
219
|
-
};
|
|
220
|
-
let ok = false;
|
|
221
|
-
for (const myKey of settingKeys()) {
|
|
222
|
-
ok = writePayload(myKey, payload) || ok;
|
|
223
|
-
}
|
|
224
|
-
return { ok, requestId: ok ? requestId : undefined };
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function waitForConsumption(requestId: string): StopConsumptionResult {
|
|
228
|
-
if (!pluginRef) return { ok: false, consumed: false, error: "Plugin reference is not initialized." };
|
|
229
|
-
const start = tick();
|
|
230
|
-
while (tick() - start < WAIT_FOR_CONSUMPTION_TIMEOUT_SEC) {
|
|
231
|
-
for (const myKey of settingKeys()) {
|
|
232
|
-
const payload = decodePayload(readSetting(myKey));
|
|
233
|
-
if (payload && payload.kind === "result" && payload.id === requestId) {
|
|
234
|
-
return {
|
|
235
|
-
ok: payload.ok === true,
|
|
236
|
-
consumed: true,
|
|
237
|
-
error: payload.error,
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
task.wait(WAIT_POLL_SEC);
|
|
242
|
-
}
|
|
243
|
-
return {
|
|
244
|
-
ok: false,
|
|
245
|
-
consumed: false,
|
|
246
|
-
error: "Timed out waiting for the play-server DataModel to acknowledge stop_playtest.",
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function clearPending(requestId?: string): void {
|
|
251
|
-
if (!pluginRef) return;
|
|
252
|
-
for (const myKey of settingKeys()) {
|
|
253
|
-
if (requestId !== undefined) {
|
|
254
|
-
const payload = decodePayload(readSetting(myKey));
|
|
255
|
-
if (payload && payload.id !== requestId) continue;
|
|
256
|
-
}
|
|
257
|
-
writeSetting(myKey, false);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
export = {
|
|
262
|
-
init,
|
|
263
|
-
startMonitor,
|
|
264
|
-
requestStop,
|
|
265
|
-
waitForConsumption,
|
|
266
|
-
clearPending,
|
|
267
|
-
};
|