@chrrxs/robloxstudio-mcp-inspector 2.16.2 → 2.16.3
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 +38 -1
- package/package.json +1 -1
- package/studio-plugin/MCPInspectorPlugin.rbxmx +45 -22
- package/studio-plugin/MCPPlugin.rbxmx +45 -22
- package/studio-plugin/src/modules/ClientBroker.ts +23 -10
- package/studio-plugin/src/modules/Communication.ts +8 -0
- package/studio-plugin/src/modules/StopPlayMonitor.ts +6 -1
package/dist/index.js
CHANGED
|
@@ -3221,6 +3221,10 @@ var init_tools = __esm({
|
|
|
3221
3221
|
_clientRolesForInstance(instanceId) {
|
|
3222
3222
|
return this._rolesForInstance(instanceId).filter((role) => /^client-\d+$/.test(role)).sort((a, b) => Number(a.slice("client-".length)) - Number(b.slice("client-".length)));
|
|
3223
3223
|
}
|
|
3224
|
+
_runtimeTargetsForEquivalentInstances(instanceId) {
|
|
3225
|
+
const instanceIds = new Set(this.bridge.getEquivalentInstanceIds(instanceId));
|
|
3226
|
+
return this.bridge.getInstances().filter((i) => instanceIds.has(i.instanceId) && (i.role === "server" || /^client-\d+$/.test(i.role))).map((i) => ({ instanceId: i.instanceId, role: i.role }));
|
|
3227
|
+
}
|
|
3224
3228
|
_resolveDeviceSimulatorSingleTarget(target, instance_id, toolName) {
|
|
3225
3229
|
const selectedTarget = target ?? "edit";
|
|
3226
3230
|
if (selectedTarget === "server" || selectedTarget === "all" || selectedTarget === "all-clients" || selectedTarget === "edit-proxy") {
|
|
@@ -4487,10 +4491,27 @@ ${code}`
|
|
|
4487
4491
|
}
|
|
4488
4492
|
async stopPlaytest(instance_id) {
|
|
4489
4493
|
const { instanceId } = this._resolveSingleTarget("edit", instance_id);
|
|
4490
|
-
|
|
4494
|
+
let response;
|
|
4495
|
+
let stopRequestError;
|
|
4496
|
+
try {
|
|
4497
|
+
response = await this.client.request("/api/stop-playtest", {}, instanceId, "edit");
|
|
4498
|
+
} catch (error) {
|
|
4499
|
+
stopRequestError = errorMessage(error);
|
|
4500
|
+
response = {
|
|
4501
|
+
success: false,
|
|
4502
|
+
error: "Edit stop request failed.",
|
|
4503
|
+
detail: stopRequestError
|
|
4504
|
+
};
|
|
4505
|
+
}
|
|
4491
4506
|
let wait;
|
|
4492
4507
|
if (response?.success === true) {
|
|
4493
4508
|
wait = await this._waitForRuntimeRoles(instanceId, { noRuntime: true }, 15, true);
|
|
4509
|
+
} else if (this._runtimeTargetsForEquivalentInstances(instanceId).length > 0) {
|
|
4510
|
+
wait = {
|
|
4511
|
+
ok: false,
|
|
4512
|
+
roles: this._rolesForEquivalentInstances(instanceId),
|
|
4513
|
+
timedOut: false
|
|
4514
|
+
};
|
|
4494
4515
|
}
|
|
4495
4516
|
const body = wait ? {
|
|
4496
4517
|
...response,
|
|
@@ -4498,6 +4519,22 @@ ${code}`
|
|
|
4498
4519
|
timedOut: wait.timedOut,
|
|
4499
4520
|
roles: wait.roles
|
|
4500
4521
|
} : response;
|
|
4522
|
+
if (wait && !wait.ok) {
|
|
4523
|
+
const runtimeRoles = wait.roles.filter((role) => role === "server" || /^client-\d+$/.test(role));
|
|
4524
|
+
const failureBody = {
|
|
4525
|
+
...body,
|
|
4526
|
+
success: false,
|
|
4527
|
+
error: "Playtest teardown did not complete.",
|
|
4528
|
+
message: response?.success === true ? wait.timedOut ? "Stop signal was accepted, but runtime peers did not disconnect before timeout." : "Stop signal was accepted, but runtime peers are still connected." : "Edit stop request failed, and runtime peers are still connected.",
|
|
4529
|
+
stopSignalAccepted: response?.success === true,
|
|
4530
|
+
stopRequestError,
|
|
4531
|
+
runtimeRoles,
|
|
4532
|
+
possibleCause: "A game shutdown hook such as BindToClose may be blocking Studio teardown. No runtime hard-stop or synthetic keyboard fallback was attempted."
|
|
4533
|
+
};
|
|
4534
|
+
return {
|
|
4535
|
+
content: [{ type: "text", text: JSON.stringify(failureBody) }]
|
|
4536
|
+
};
|
|
4537
|
+
}
|
|
4501
4538
|
return {
|
|
4502
4539
|
content: [{ type: "text", text: JSON.stringify(body) }]
|
|
4503
4540
|
};
|
package/package.json
CHANGED
|
@@ -432,6 +432,31 @@ end
|
|
|
432
432
|
local proxyByPlayer = {}
|
|
433
433
|
local proxyRegisterFailuresByPlayer = {}
|
|
434
434
|
local serverBrokerStarted = false
|
|
435
|
+
local function unregisterProxy(player, entry)
|
|
436
|
+
local _condition = entry
|
|
437
|
+
if _condition == nil then
|
|
438
|
+
local _player = player
|
|
439
|
+
_condition = proxyByPlayer[_player]
|
|
440
|
+
end
|
|
441
|
+
local proxy = _condition
|
|
442
|
+
if not proxy then
|
|
443
|
+
return nil
|
|
444
|
+
end
|
|
445
|
+
local _player = player
|
|
446
|
+
proxyByPlayer[_player] = nil
|
|
447
|
+
local _player_1 = player
|
|
448
|
+
proxyRegisterFailuresByPlayer[_player_1] = nil
|
|
449
|
+
postJson("/disconnect", {
|
|
450
|
+
pluginSessionId = proxy.pluginSessionId,
|
|
451
|
+
})
|
|
452
|
+
end
|
|
453
|
+
local function disconnectAllProxies()
|
|
454
|
+
for player, entry in proxyByPlayer do
|
|
455
|
+
unregisterProxy(player, entry)
|
|
456
|
+
end
|
|
457
|
+
table.clear(proxyByPlayer)
|
|
458
|
+
table.clear(proxyRegisterFailuresByPlayer)
|
|
459
|
+
end
|
|
435
460
|
local function pollProxy(proxyId, player, rf)
|
|
436
461
|
while true do
|
|
437
462
|
local _condition = player.Parent ~= nil
|
|
@@ -442,6 +467,10 @@ local function pollProxy(proxyId, player, rf)
|
|
|
442
467
|
if not _condition then
|
|
443
468
|
break
|
|
444
469
|
end
|
|
470
|
+
if not RunService:IsRunning() then
|
|
471
|
+
unregisterProxy(player)
|
|
472
|
+
break
|
|
473
|
+
end
|
|
445
474
|
local ok, res = pcall(function()
|
|
446
475
|
return HttpService:RequestAsync({
|
|
447
476
|
Url = `{mcpUrl}/poll?pluginSessionId={proxyId}`,
|
|
@@ -575,25 +604,10 @@ local function setupServerBroker()
|
|
|
575
604
|
task.spawn(registerProxy, p, broker)
|
|
576
605
|
end
|
|
577
606
|
Players.PlayerRemoving:Connect(function(p)
|
|
578
|
-
|
|
579
|
-
local entry = proxyByPlayer[_p]
|
|
580
|
-
if entry then
|
|
581
|
-
local _p_1 = p
|
|
582
|
-
proxyByPlayer[_p_1] = nil
|
|
583
|
-
local _p_2 = p
|
|
584
|
-
proxyRegisterFailuresByPlayer[_p_2] = nil
|
|
585
|
-
postJson("/disconnect", {
|
|
586
|
-
pluginSessionId = entry.pluginSessionId,
|
|
587
|
-
})
|
|
588
|
-
end
|
|
607
|
+
unregisterProxy(p)
|
|
589
608
|
end)
|
|
590
609
|
game:BindToClose(function()
|
|
591
|
-
|
|
592
|
-
postJson("/disconnect", {
|
|
593
|
-
pluginSessionId = entry.pluginSessionId,
|
|
594
|
-
})
|
|
595
|
-
end
|
|
596
|
-
table.clear(proxyByPlayer)
|
|
610
|
+
disconnectAllProxies()
|
|
597
611
|
end)
|
|
598
612
|
end
|
|
599
613
|
return {
|
|
@@ -601,6 +615,7 @@ return {
|
|
|
601
615
|
DEFAULT_MCP_URL = DEFAULT_MCP_URL,
|
|
602
616
|
getServerUrl = getServerUrl,
|
|
603
617
|
setServerUrl = setServerUrl,
|
|
618
|
+
disconnectAllProxies = disconnectAllProxies,
|
|
604
619
|
forkRole = forkRole,
|
|
605
620
|
setupClientBroker = setupClientBroker,
|
|
606
621
|
setupServerBroker = setupServerBroker,
|
|
@@ -636,6 +651,7 @@ local SerializationHandlers = TS.import(script, script.Parent, "handlers", "Seri
|
|
|
636
651
|
local MemoryHandlers = TS.import(script, script.Parent, "handlers", "MemoryHandlers")
|
|
637
652
|
local SceneAnalysisHandlers = TS.import(script, script.Parent, "handlers", "SceneAnalysisHandlers")
|
|
638
653
|
local EvalRuntimeHandlers = TS.import(script, script.Parent, "handlers", "EvalRuntimeHandlers")
|
|
654
|
+
local ClientBroker = TS.import(script, script.Parent, "ClientBroker")
|
|
639
655
|
local ServerUrlSettings = TS.import(script, script.Parent, "ServerUrlSettings")
|
|
640
656
|
local HttpDiagnostics = TS.import(script, script.Parent, "HttpDiagnostics")
|
|
641
657
|
-- Per-plugin-load random GUID. Used as the /poll URL param so the server
|
|
@@ -708,6 +724,7 @@ local function detectRole()
|
|
|
708
724
|
end
|
|
709
725
|
return "client"
|
|
710
726
|
end
|
|
727
|
+
local initialRole = detectRole()
|
|
711
728
|
local routeMap = {
|
|
712
729
|
["/api/file-tree"] = QueryHandlers.getFileTree,
|
|
713
730
|
["/api/search-files"] = QueryHandlers.searchFiles,
|
|
@@ -1125,6 +1142,7 @@ local function pollForRequests(connIndex)
|
|
|
1125
1142
|
end
|
|
1126
1143
|
end
|
|
1127
1144
|
end
|
|
1145
|
+
local deactivatePlugin
|
|
1128
1146
|
local function activatePlugin(connIndex)
|
|
1129
1147
|
local _condition = connIndex
|
|
1130
1148
|
if _condition == nil then
|
|
@@ -1157,6 +1175,11 @@ local function activatePlugin(connIndex)
|
|
|
1157
1175
|
if not conn.heartbeatConnection then
|
|
1158
1176
|
conn.heartbeatConnection = RunService.Heartbeat:Connect(function()
|
|
1159
1177
|
local now = tick()
|
|
1178
|
+
if initialRole == "server" and not RunService:IsRunning() then
|
|
1179
|
+
ClientBroker.disconnectAllProxies()
|
|
1180
|
+
deactivatePlugin(idx)
|
|
1181
|
+
return nil
|
|
1182
|
+
end
|
|
1160
1183
|
local currentInstanceId = computeInstanceId()
|
|
1161
1184
|
if lastReadyInstanceId ~= nil and currentInstanceId ~= lastReadyInstanceId then
|
|
1162
1185
|
cachedPlaceName = nil
|
|
@@ -1181,7 +1204,7 @@ local function activatePlugin(connIndex)
|
|
|
1181
1204
|
-- Watch identity fields so stale name or anon instance ids are refreshed.
|
|
1182
1205
|
ensureIdentityWatcher(conn)
|
|
1183
1206
|
end
|
|
1184
|
-
|
|
1207
|
+
function deactivatePlugin(connIndex)
|
|
1185
1208
|
local _condition = connIndex
|
|
1186
1209
|
if _condition == nil then
|
|
1187
1210
|
_condition = State.getActiveTabIndex()
|
|
@@ -1388,9 +1411,9 @@ local function computeBridgeStamp()
|
|
|
1388
1411
|
for i = 1, #combined do
|
|
1389
1412
|
h = (h * 33 + (string.byte(combined, i))) % 2147483647
|
|
1390
1413
|
end
|
|
1391
|
-
-- "2.16.
|
|
1414
|
+
-- "2.16.3" is replaced with the package version at package time
|
|
1392
1415
|
-- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
|
|
1393
|
-
return `{tostring(h)}-2.16.
|
|
1416
|
+
return `{tostring(h)}-2.16.3`
|
|
1394
1417
|
end
|
|
1395
1418
|
local BRIDGE_STAMP = computeBridgeStamp()
|
|
1396
1419
|
local function setSource(scriptInst, source)
|
|
@@ -8018,7 +8041,7 @@ return {
|
|
|
8018
8041
|
<Properties>
|
|
8019
8042
|
<string name="Name">State</string>
|
|
8020
8043
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
8021
|
-
local CURRENT_VERSION = "2.16.
|
|
8044
|
+
local CURRENT_VERSION = "2.16.3"
|
|
8022
8045
|
local PLUGIN_VARIANT = "inspector"
|
|
8023
8046
|
local MAX_CONNECTIONS = 5
|
|
8024
8047
|
local BASE_PORT = 58741
|
|
@@ -8297,7 +8320,7 @@ local function handleStopRequest(key, request)
|
|
|
8297
8320
|
return nil
|
|
8298
8321
|
end
|
|
8299
8322
|
if endTestIssued then
|
|
8300
|
-
writeResult(key, request,
|
|
8323
|
+
writeResult(key, request, false, "StudioTestService:EndTest was already issued for this play session, but the runtime DataModel is still alive.")
|
|
8301
8324
|
return nil
|
|
8302
8325
|
end
|
|
8303
8326
|
if not RunService:IsRunning() or not RunService:IsServer() then
|
|
@@ -432,6 +432,31 @@ end
|
|
|
432
432
|
local proxyByPlayer = {}
|
|
433
433
|
local proxyRegisterFailuresByPlayer = {}
|
|
434
434
|
local serverBrokerStarted = false
|
|
435
|
+
local function unregisterProxy(player, entry)
|
|
436
|
+
local _condition = entry
|
|
437
|
+
if _condition == nil then
|
|
438
|
+
local _player = player
|
|
439
|
+
_condition = proxyByPlayer[_player]
|
|
440
|
+
end
|
|
441
|
+
local proxy = _condition
|
|
442
|
+
if not proxy then
|
|
443
|
+
return nil
|
|
444
|
+
end
|
|
445
|
+
local _player = player
|
|
446
|
+
proxyByPlayer[_player] = nil
|
|
447
|
+
local _player_1 = player
|
|
448
|
+
proxyRegisterFailuresByPlayer[_player_1] = nil
|
|
449
|
+
postJson("/disconnect", {
|
|
450
|
+
pluginSessionId = proxy.pluginSessionId,
|
|
451
|
+
})
|
|
452
|
+
end
|
|
453
|
+
local function disconnectAllProxies()
|
|
454
|
+
for player, entry in proxyByPlayer do
|
|
455
|
+
unregisterProxy(player, entry)
|
|
456
|
+
end
|
|
457
|
+
table.clear(proxyByPlayer)
|
|
458
|
+
table.clear(proxyRegisterFailuresByPlayer)
|
|
459
|
+
end
|
|
435
460
|
local function pollProxy(proxyId, player, rf)
|
|
436
461
|
while true do
|
|
437
462
|
local _condition = player.Parent ~= nil
|
|
@@ -442,6 +467,10 @@ local function pollProxy(proxyId, player, rf)
|
|
|
442
467
|
if not _condition then
|
|
443
468
|
break
|
|
444
469
|
end
|
|
470
|
+
if not RunService:IsRunning() then
|
|
471
|
+
unregisterProxy(player)
|
|
472
|
+
break
|
|
473
|
+
end
|
|
445
474
|
local ok, res = pcall(function()
|
|
446
475
|
return HttpService:RequestAsync({
|
|
447
476
|
Url = `{mcpUrl}/poll?pluginSessionId={proxyId}`,
|
|
@@ -575,25 +604,10 @@ local function setupServerBroker()
|
|
|
575
604
|
task.spawn(registerProxy, p, broker)
|
|
576
605
|
end
|
|
577
606
|
Players.PlayerRemoving:Connect(function(p)
|
|
578
|
-
|
|
579
|
-
local entry = proxyByPlayer[_p]
|
|
580
|
-
if entry then
|
|
581
|
-
local _p_1 = p
|
|
582
|
-
proxyByPlayer[_p_1] = nil
|
|
583
|
-
local _p_2 = p
|
|
584
|
-
proxyRegisterFailuresByPlayer[_p_2] = nil
|
|
585
|
-
postJson("/disconnect", {
|
|
586
|
-
pluginSessionId = entry.pluginSessionId,
|
|
587
|
-
})
|
|
588
|
-
end
|
|
607
|
+
unregisterProxy(p)
|
|
589
608
|
end)
|
|
590
609
|
game:BindToClose(function()
|
|
591
|
-
|
|
592
|
-
postJson("/disconnect", {
|
|
593
|
-
pluginSessionId = entry.pluginSessionId,
|
|
594
|
-
})
|
|
595
|
-
end
|
|
596
|
-
table.clear(proxyByPlayer)
|
|
610
|
+
disconnectAllProxies()
|
|
597
611
|
end)
|
|
598
612
|
end
|
|
599
613
|
return {
|
|
@@ -601,6 +615,7 @@ return {
|
|
|
601
615
|
DEFAULT_MCP_URL = DEFAULT_MCP_URL,
|
|
602
616
|
getServerUrl = getServerUrl,
|
|
603
617
|
setServerUrl = setServerUrl,
|
|
618
|
+
disconnectAllProxies = disconnectAllProxies,
|
|
604
619
|
forkRole = forkRole,
|
|
605
620
|
setupClientBroker = setupClientBroker,
|
|
606
621
|
setupServerBroker = setupServerBroker,
|
|
@@ -636,6 +651,7 @@ local SerializationHandlers = TS.import(script, script.Parent, "handlers", "Seri
|
|
|
636
651
|
local MemoryHandlers = TS.import(script, script.Parent, "handlers", "MemoryHandlers")
|
|
637
652
|
local SceneAnalysisHandlers = TS.import(script, script.Parent, "handlers", "SceneAnalysisHandlers")
|
|
638
653
|
local EvalRuntimeHandlers = TS.import(script, script.Parent, "handlers", "EvalRuntimeHandlers")
|
|
654
|
+
local ClientBroker = TS.import(script, script.Parent, "ClientBroker")
|
|
639
655
|
local ServerUrlSettings = TS.import(script, script.Parent, "ServerUrlSettings")
|
|
640
656
|
local HttpDiagnostics = TS.import(script, script.Parent, "HttpDiagnostics")
|
|
641
657
|
-- Per-plugin-load random GUID. Used as the /poll URL param so the server
|
|
@@ -708,6 +724,7 @@ local function detectRole()
|
|
|
708
724
|
end
|
|
709
725
|
return "client"
|
|
710
726
|
end
|
|
727
|
+
local initialRole = detectRole()
|
|
711
728
|
local routeMap = {
|
|
712
729
|
["/api/file-tree"] = QueryHandlers.getFileTree,
|
|
713
730
|
["/api/search-files"] = QueryHandlers.searchFiles,
|
|
@@ -1125,6 +1142,7 @@ local function pollForRequests(connIndex)
|
|
|
1125
1142
|
end
|
|
1126
1143
|
end
|
|
1127
1144
|
end
|
|
1145
|
+
local deactivatePlugin
|
|
1128
1146
|
local function activatePlugin(connIndex)
|
|
1129
1147
|
local _condition = connIndex
|
|
1130
1148
|
if _condition == nil then
|
|
@@ -1157,6 +1175,11 @@ local function activatePlugin(connIndex)
|
|
|
1157
1175
|
if not conn.heartbeatConnection then
|
|
1158
1176
|
conn.heartbeatConnection = RunService.Heartbeat:Connect(function()
|
|
1159
1177
|
local now = tick()
|
|
1178
|
+
if initialRole == "server" and not RunService:IsRunning() then
|
|
1179
|
+
ClientBroker.disconnectAllProxies()
|
|
1180
|
+
deactivatePlugin(idx)
|
|
1181
|
+
return nil
|
|
1182
|
+
end
|
|
1160
1183
|
local currentInstanceId = computeInstanceId()
|
|
1161
1184
|
if lastReadyInstanceId ~= nil and currentInstanceId ~= lastReadyInstanceId then
|
|
1162
1185
|
cachedPlaceName = nil
|
|
@@ -1181,7 +1204,7 @@ local function activatePlugin(connIndex)
|
|
|
1181
1204
|
-- Watch identity fields so stale name or anon instance ids are refreshed.
|
|
1182
1205
|
ensureIdentityWatcher(conn)
|
|
1183
1206
|
end
|
|
1184
|
-
|
|
1207
|
+
function deactivatePlugin(connIndex)
|
|
1185
1208
|
local _condition = connIndex
|
|
1186
1209
|
if _condition == nil then
|
|
1187
1210
|
_condition = State.getActiveTabIndex()
|
|
@@ -1388,9 +1411,9 @@ local function computeBridgeStamp()
|
|
|
1388
1411
|
for i = 1, #combined do
|
|
1389
1412
|
h = (h * 33 + (string.byte(combined, i))) % 2147483647
|
|
1390
1413
|
end
|
|
1391
|
-
-- "2.16.
|
|
1414
|
+
-- "2.16.3" is replaced with the package version at package time
|
|
1392
1415
|
-- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
|
|
1393
|
-
return `{tostring(h)}-2.16.
|
|
1416
|
+
return `{tostring(h)}-2.16.3`
|
|
1394
1417
|
end
|
|
1395
1418
|
local BRIDGE_STAMP = computeBridgeStamp()
|
|
1396
1419
|
local function setSource(scriptInst, source)
|
|
@@ -8018,7 +8041,7 @@ return {
|
|
|
8018
8041
|
<Properties>
|
|
8019
8042
|
<string name="Name">State</string>
|
|
8020
8043
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
8021
|
-
local CURRENT_VERSION = "2.16.
|
|
8044
|
+
local CURRENT_VERSION = "2.16.3"
|
|
8022
8045
|
local PLUGIN_VARIANT = "main"
|
|
8023
8046
|
local MAX_CONNECTIONS = 5
|
|
8024
8047
|
local BASE_PORT = 58741
|
|
@@ -8297,7 +8320,7 @@ local function handleStopRequest(key, request)
|
|
|
8297
8320
|
return nil
|
|
8298
8321
|
end
|
|
8299
8322
|
if endTestIssued then
|
|
8300
|
-
writeResult(key, request,
|
|
8323
|
+
writeResult(key, request, false, "StudioTestService:EndTest was already issued for this play session, but the runtime DataModel is still alive.")
|
|
8301
8324
|
return nil
|
|
8302
8325
|
end
|
|
8303
8326
|
if not RunService:IsRunning() or not RunService:IsServer() then
|
|
@@ -296,8 +296,28 @@ const proxyByPlayer = new Map<Player, ProxyEntry>();
|
|
|
296
296
|
const proxyRegisterFailuresByPlayer = new Set<Player>();
|
|
297
297
|
let serverBrokerStarted = false;
|
|
298
298
|
|
|
299
|
+
function unregisterProxy(player: Player, entry?: ProxyEntry): void {
|
|
300
|
+
const proxy = entry ?? proxyByPlayer.get(player);
|
|
301
|
+
if (!proxy) return;
|
|
302
|
+
proxyByPlayer.delete(player);
|
|
303
|
+
proxyRegisterFailuresByPlayer.delete(player);
|
|
304
|
+
postJson("/disconnect", { pluginSessionId: proxy.pluginSessionId });
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function disconnectAllProxies(): void {
|
|
308
|
+
for (const [player, entry] of proxyByPlayer) {
|
|
309
|
+
unregisterProxy(player, entry);
|
|
310
|
+
}
|
|
311
|
+
proxyByPlayer.clear();
|
|
312
|
+
proxyRegisterFailuresByPlayer.clear();
|
|
313
|
+
}
|
|
314
|
+
|
|
299
315
|
function pollProxy(proxyId: string, player: Player, rf: RemoteFunction) {
|
|
300
316
|
while (player.Parent !== undefined && proxyByPlayer.has(player)) {
|
|
317
|
+
if (!RunService.IsRunning()) {
|
|
318
|
+
unregisterProxy(player);
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
301
321
|
const [ok, res] = pcall(() =>
|
|
302
322
|
HttpService.RequestAsync({
|
|
303
323
|
Url: `${mcpUrl}/poll?pluginSessionId=${proxyId}`,
|
|
@@ -396,18 +416,10 @@ function setupServerBroker() {
|
|
|
396
416
|
task.spawn(registerProxy, p, broker);
|
|
397
417
|
}
|
|
398
418
|
Players.PlayerRemoving.Connect((p) => {
|
|
399
|
-
|
|
400
|
-
if (entry) {
|
|
401
|
-
proxyByPlayer.delete(p);
|
|
402
|
-
proxyRegisterFailuresByPlayer.delete(p);
|
|
403
|
-
postJson("/disconnect", { pluginSessionId: entry.pluginSessionId });
|
|
404
|
-
}
|
|
419
|
+
unregisterProxy(p);
|
|
405
420
|
});
|
|
406
421
|
game.BindToClose(() => {
|
|
407
|
-
|
|
408
|
-
postJson("/disconnect", { pluginSessionId: entry.pluginSessionId });
|
|
409
|
-
}
|
|
410
|
-
proxyByPlayer.clear();
|
|
422
|
+
disconnectAllProxies();
|
|
411
423
|
});
|
|
412
424
|
}
|
|
413
425
|
|
|
@@ -416,6 +428,7 @@ export = {
|
|
|
416
428
|
DEFAULT_MCP_URL,
|
|
417
429
|
getServerUrl,
|
|
418
430
|
setServerUrl,
|
|
431
|
+
disconnectAllProxies,
|
|
419
432
|
forkRole,
|
|
420
433
|
setupClientBroker,
|
|
421
434
|
setupServerBroker,
|
|
@@ -18,6 +18,7 @@ import SerializationHandlers from "./handlers/SerializationHandlers";
|
|
|
18
18
|
import MemoryHandlers from "./handlers/MemoryHandlers";
|
|
19
19
|
import SceneAnalysisHandlers from "./handlers/SceneAnalysisHandlers";
|
|
20
20
|
import EvalRuntimeHandlers from "./handlers/EvalRuntimeHandlers";
|
|
21
|
+
import ClientBroker from "./ClientBroker";
|
|
21
22
|
import ServerUrlSettings from "./ServerUrlSettings";
|
|
22
23
|
import HttpDiagnostics from "./HttpDiagnostics";
|
|
23
24
|
import { Connection, RequestPayload, PollResponse, ReadyResponse } from "../types";
|
|
@@ -89,6 +90,8 @@ function detectRole(): string {
|
|
|
89
90
|
return "client";
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
const initialRole = detectRole();
|
|
94
|
+
|
|
92
95
|
type Handler = (data: Record<string, unknown>) => unknown;
|
|
93
96
|
|
|
94
97
|
const routeMap: Record<string, Handler> = {
|
|
@@ -510,6 +513,11 @@ function activatePlugin(connIndex?: number) {
|
|
|
510
513
|
if (!conn.heartbeatConnection) {
|
|
511
514
|
conn.heartbeatConnection = RunService.Heartbeat.Connect(() => {
|
|
512
515
|
const now = tick();
|
|
516
|
+
if (initialRole === "server" && !RunService.IsRunning()) {
|
|
517
|
+
ClientBroker.disconnectAllProxies();
|
|
518
|
+
deactivatePlugin(idx);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
513
521
|
const currentInstanceId = computeInstanceId();
|
|
514
522
|
if (lastReadyInstanceId !== undefined && currentInstanceId !== lastReadyInstanceId) {
|
|
515
523
|
cachedPlaceName = undefined;
|
|
@@ -162,7 +162,12 @@ function handleStopRequest(key: string, request: StopPayload): void {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
if (endTestIssued) {
|
|
165
|
-
writeResult(
|
|
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
|
+
);
|
|
166
171
|
return;
|
|
167
172
|
}
|
|
168
173
|
|