@chrrxs/robloxstudio-mcp 3.0.0 → 3.0.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/dist/index.js +217 -51
- package/package.json +2 -2
- package/studio-plugin/MCPPlugin.rbxmx +332 -40
- 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
|
@@ -271,6 +271,7 @@ local MemoryHandlers = TS.import(script, script.Parent, "handlers", "MemoryHandl
|
|
|
271
271
|
local SceneAnalysisHandlers = TS.import(script, script.Parent, "handlers", "SceneAnalysisHandlers")
|
|
272
272
|
local CaptureHandlers = TS.import(script, script.Parent, "handlers", "CaptureHandlers")
|
|
273
273
|
local InputHandlers = TS.import(script, script.Parent, "handlers", "InputHandlers")
|
|
274
|
+
local MetadataHandlers = TS.import(script, script.Parent, "handlers", "MetadataHandlers")
|
|
274
275
|
local EvalRuntimeHandlers = TS.import(script, script.Parent, "handlers", "EvalRuntimeHandlers")
|
|
275
276
|
local BreakpointHandlers = TS.import(script, script.Parent, "handlers", "BreakpointHandlers")
|
|
276
277
|
local ScriptProfilerHandlers = TS.import(script, script.Parent, "handlers", "ScriptProfilerHandlers")
|
|
@@ -351,6 +352,7 @@ local CLIENT_BROKER_ALLOWED_ENDPOINTS = {
|
|
|
351
352
|
["/api/capture-begin"] = true,
|
|
352
353
|
["/api/simulate-mouse-input"] = true,
|
|
353
354
|
["/api/simulate-keyboard-input"] = true,
|
|
355
|
+
["/api/focus-viewport"] = true,
|
|
354
356
|
}
|
|
355
357
|
-- Throttle re-ready calls per proxyId so a brief window of unknownInstance
|
|
356
358
|
-- polls doesn't cause a re-register stampede.
|
|
@@ -559,6 +561,9 @@ local function setupClientBroker()
|
|
|
559
561
|
if payload and payload.endpoint == "/api/simulate-keyboard-input" then
|
|
560
562
|
return InputHandlers.simulateKeyboardInput(payload.data or {})
|
|
561
563
|
end
|
|
564
|
+
if payload and payload.endpoint == "/api/focus-viewport" then
|
|
565
|
+
return MetadataHandlers.focusViewport(payload.data or {})
|
|
566
|
+
end
|
|
562
567
|
if payload and payload.endpoint == "/api/execute-luau" then
|
|
563
568
|
return handleExecuteLuau(payload.data)
|
|
564
569
|
end
|
|
@@ -885,6 +890,8 @@ local routeMap = {
|
|
|
885
890
|
["/api/delete-script-lines"] = ScriptHandlers.deleteScriptLines,
|
|
886
891
|
["/api/get-attributes"] = MetadataHandlers.getAttributes,
|
|
887
892
|
["/api/get-selection"] = MetadataHandlers.getSelection,
|
|
893
|
+
["/api/set-selection"] = MetadataHandlers.setSelection,
|
|
894
|
+
["/api/focus-viewport"] = MetadataHandlers.focusViewport,
|
|
888
895
|
["/api/execute-luau"] = MetadataHandlers.executeLuau,
|
|
889
896
|
["/api/eval-runtime"] = EvalRuntimeHandlers.evalRuntime,
|
|
890
897
|
["/api/start-playtest"] = TestHandlers.startPlaytest,
|
|
@@ -1521,9 +1528,9 @@ local function computeBridgeStamp()
|
|
|
1521
1528
|
for i = 1, #combined do
|
|
1522
1529
|
h = (h * 33 + (string.byte(combined, i))) % 2147483647
|
|
1523
1530
|
end
|
|
1524
|
-
-- "3.0.
|
|
1531
|
+
-- "3.0.1" is replaced with the package version at package time
|
|
1525
1532
|
-- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
|
|
1526
|
-
return `{tostring(h)}-3.0.
|
|
1533
|
+
return `{tostring(h)}-3.0.1`
|
|
1527
1534
|
end
|
|
1528
1535
|
local BRIDGE_STAMP = computeBridgeStamp()
|
|
1529
1536
|
local function setSource(scriptInst, source)
|
|
@@ -3638,6 +3645,180 @@ local function getSelection(_requestData)
|
|
|
3638
3645
|
message = `{#selection} object(s) selected`,
|
|
3639
3646
|
}
|
|
3640
3647
|
end
|
|
3648
|
+
-- The counterpart to getSelection: this sets it. Selecting what the agent just
|
|
3649
|
+
-- built shows the user the result and puts the instance under Studio's own
|
|
3650
|
+
-- move/scale handles, which is why it is a tool and not left to execute_luau.
|
|
3651
|
+
local function setSelection(requestData)
|
|
3652
|
+
local rawPaths = requestData.paths
|
|
3653
|
+
if not (type(rawPaths) == "table") then
|
|
3654
|
+
return {
|
|
3655
|
+
error = "paths is required (an empty array clears in set mode)",
|
|
3656
|
+
}
|
|
3657
|
+
end
|
|
3658
|
+
local _condition = (requestData.mode)
|
|
3659
|
+
if _condition == nil then
|
|
3660
|
+
_condition = "set"
|
|
3661
|
+
end
|
|
3662
|
+
local mode = _condition
|
|
3663
|
+
if mode ~= "set" and mode ~= "add" and mode ~= "remove" then
|
|
3664
|
+
return {
|
|
3665
|
+
error = `mode must be "set", "add" or "remove" (got: {mode})`,
|
|
3666
|
+
}
|
|
3667
|
+
end
|
|
3668
|
+
local paths = rawPaths
|
|
3669
|
+
if #paths == 0 and mode ~= "set" then
|
|
3670
|
+
return {
|
|
3671
|
+
error = `paths cannot be empty in {mode} mode`,
|
|
3672
|
+
}
|
|
3673
|
+
end
|
|
3674
|
+
local targets = {}
|
|
3675
|
+
local missing = {}
|
|
3676
|
+
for _, entry in paths do
|
|
3677
|
+
if not (type(entry) == "string") or entry == "" then
|
|
3678
|
+
return {
|
|
3679
|
+
error = "paths must contain non-empty instance path strings",
|
|
3680
|
+
}
|
|
3681
|
+
end
|
|
3682
|
+
local instance = getInstanceByPath(entry)
|
|
3683
|
+
if instance then
|
|
3684
|
+
table.insert(targets, instance)
|
|
3685
|
+
else
|
|
3686
|
+
table.insert(missing, entry)
|
|
3687
|
+
end
|
|
3688
|
+
end
|
|
3689
|
+
local clearsSelection = mode == "set" and #paths == 0
|
|
3690
|
+
if #targets == 0 and not clearsSelection then
|
|
3691
|
+
return {
|
|
3692
|
+
error = "No matching instances found for any path",
|
|
3693
|
+
missingPaths = missing,
|
|
3694
|
+
}
|
|
3695
|
+
end
|
|
3696
|
+
local ok, err = pcall(function()
|
|
3697
|
+
if mode == "add" then
|
|
3698
|
+
Selection:Add(targets)
|
|
3699
|
+
elseif mode == "remove" then
|
|
3700
|
+
Selection:Remove(targets)
|
|
3701
|
+
else
|
|
3702
|
+
Selection:Set(targets)
|
|
3703
|
+
end
|
|
3704
|
+
end)
|
|
3705
|
+
if not ok then
|
|
3706
|
+
return {
|
|
3707
|
+
error = `Selection.{mode} failed: {tostring(err)}`,
|
|
3708
|
+
}
|
|
3709
|
+
end
|
|
3710
|
+
return {
|
|
3711
|
+
success = true,
|
|
3712
|
+
mode = mode,
|
|
3713
|
+
selected = #targets,
|
|
3714
|
+
missingPaths = missing,
|
|
3715
|
+
message = if clearsSelection then "Selection cleared" elseif mode == "remove" then `Deselected {#targets} object(s)` else `{#targets} object(s) {if mode == "add" then "added to" else "in"} the selection`,
|
|
3716
|
+
}
|
|
3717
|
+
end
|
|
3718
|
+
-- Aims the Studio camera at an instance and frames it from a sensible angle.
|
|
3719
|
+
-- This completes the screenshot loop capture_screenshot documents: build,
|
|
3720
|
+
-- focus, screenshot. Framing distance comes from the bounding box and the
|
|
3721
|
+
-- camera's own field of view so any subject fills a similar share of frame.
|
|
3722
|
+
local function focusViewport(requestData)
|
|
3723
|
+
local instancePath = requestData.path
|
|
3724
|
+
if not (instancePath ~= "" and instancePath) then
|
|
3725
|
+
return {
|
|
3726
|
+
error = "path is required (the instance to frame)",
|
|
3727
|
+
}
|
|
3728
|
+
end
|
|
3729
|
+
local instance = getInstanceByPath(instancePath)
|
|
3730
|
+
if not instance then
|
|
3731
|
+
return {
|
|
3732
|
+
error = `Instance not found: {instancePath}`,
|
|
3733
|
+
}
|
|
3734
|
+
end
|
|
3735
|
+
local workspace = game:GetService("Workspace")
|
|
3736
|
+
local camera = workspace.CurrentCamera
|
|
3737
|
+
if not camera then
|
|
3738
|
+
return {
|
|
3739
|
+
error = "Workspace.CurrentCamera is unavailable right now",
|
|
3740
|
+
}
|
|
3741
|
+
end
|
|
3742
|
+
local _condition = tonumber(requestData.padding)
|
|
3743
|
+
if _condition == nil then
|
|
3744
|
+
_condition = 1
|
|
3745
|
+
end
|
|
3746
|
+
local padding = _condition
|
|
3747
|
+
if padding <= 0 or padding > 10 then
|
|
3748
|
+
return {
|
|
3749
|
+
error = "padding must be greater than 0 and at most 10",
|
|
3750
|
+
}
|
|
3751
|
+
end
|
|
3752
|
+
local compassOverride = if requestData.from == nil then nil else tonumber(requestData.from)
|
|
3753
|
+
if requestData.from ~= nil and compassOverride == nil then
|
|
3754
|
+
return {
|
|
3755
|
+
error = "from must be a compass angle in degrees",
|
|
3756
|
+
}
|
|
3757
|
+
end
|
|
3758
|
+
local elevationOverride = if requestData.angleY == nil then nil else tonumber(requestData.angleY)
|
|
3759
|
+
if requestData.angleY ~= nil and (elevationOverride == nil or elevationOverride < -89 or elevationOverride > 89) then
|
|
3760
|
+
return {
|
|
3761
|
+
error = "angleY must be between -89 and 89",
|
|
3762
|
+
}
|
|
3763
|
+
end
|
|
3764
|
+
local ok, err = pcall(function()
|
|
3765
|
+
local boundsCF
|
|
3766
|
+
local boundsSize
|
|
3767
|
+
if instance:IsA("Model") then
|
|
3768
|
+
boundsCF, boundsSize = instance:GetBoundingBox()
|
|
3769
|
+
elseif instance:IsA("BasePart") then
|
|
3770
|
+
boundsCF = instance.CFrame
|
|
3771
|
+
boundsSize = instance.Size
|
|
3772
|
+
else
|
|
3773
|
+
return error(`Cannot frame a {instance.ClassName}: it has no 3D bounding box. Frame a part or model instead.`)
|
|
3774
|
+
end
|
|
3775
|
+
local center = boundsCF.Position
|
|
3776
|
+
local direction = camera.CFrame.LookVector * (-1)
|
|
3777
|
+
local currentHorizontal = Vector3.new(direction.X, 0, direction.Z)
|
|
3778
|
+
local horizontalDirection = if currentHorizontal.Magnitude < 0.001 then Vector3.new(1, 0, 0) else currentHorizontal.Unit
|
|
3779
|
+
if compassOverride ~= nil then
|
|
3780
|
+
local yaw = math.rad(compassOverride)
|
|
3781
|
+
horizontalDirection = Vector3.new(math.cos(yaw), 0, math.sin(yaw))
|
|
3782
|
+
end
|
|
3783
|
+
local vertical = direction.Y
|
|
3784
|
+
local horizontalMagnitude = math.sqrt(math.max(0, 1 - vertical * vertical))
|
|
3785
|
+
if elevationOverride ~= nil then
|
|
3786
|
+
local pitch = math.rad(elevationOverride)
|
|
3787
|
+
vertical = math.sin(pitch)
|
|
3788
|
+
horizontalMagnitude = math.cos(pitch)
|
|
3789
|
+
end
|
|
3790
|
+
local _horizontalDirection = horizontalDirection
|
|
3791
|
+
local _horizontalMagnitude = horizontalMagnitude
|
|
3792
|
+
local _vector3 = Vector3.new(0, vertical, 0)
|
|
3793
|
+
direction = (_horizontalDirection * _horizontalMagnitude + _vector3).Unit
|
|
3794
|
+
camera.CameraType = Enum.CameraType.Scriptable
|
|
3795
|
+
local _direction = direction
|
|
3796
|
+
local _arg0 = math.max(boundsSize.Magnitude, 1)
|
|
3797
|
+
camera.CFrame = CFrame.lookAt(center + (_direction * _arg0), center)
|
|
3798
|
+
camera.Focus = CFrame.new(center)
|
|
3799
|
+
camera:ZoomToExtents(boundsCF, boundsSize)
|
|
3800
|
+
if padding ~= 1 then
|
|
3801
|
+
local fittedOffset = camera.CFrame.Position - center
|
|
3802
|
+
local _arg0_1 = fittedOffset * padding
|
|
3803
|
+
camera.CFrame = CFrame.lookAt(center + _arg0_1, center)
|
|
3804
|
+
end
|
|
3805
|
+
camera.Focus = CFrame.new(center)
|
|
3806
|
+
end)
|
|
3807
|
+
if not ok then
|
|
3808
|
+
return {
|
|
3809
|
+
error = `focus failed: {tostring(err)}`,
|
|
3810
|
+
}
|
|
3811
|
+
end
|
|
3812
|
+
return {
|
|
3813
|
+
success = true,
|
|
3814
|
+
path = getInstancePath(instance),
|
|
3815
|
+
cameraPosition = {
|
|
3816
|
+
X = camera.CFrame.Position.X,
|
|
3817
|
+
Y = camera.CFrame.Position.Y,
|
|
3818
|
+
Z = camera.CFrame.Position.Z,
|
|
3819
|
+
},
|
|
3820
|
+
}
|
|
3821
|
+
end
|
|
3641
3822
|
local function executeLuau(requestData)
|
|
3642
3823
|
local code = requestData.code
|
|
3643
3824
|
if not (code ~= "" and code) or code == "" then
|
|
@@ -3654,6 +3835,8 @@ end
|
|
|
3654
3835
|
return {
|
|
3655
3836
|
getAttributes = getAttributes,
|
|
3656
3837
|
getSelection = getSelection,
|
|
3838
|
+
setSelection = setSelection,
|
|
3839
|
+
focusViewport = focusViewport,
|
|
3657
3840
|
executeLuau = executeLuau,
|
|
3658
3841
|
}
|
|
3659
3842
|
]]></string>
|
|
@@ -3674,6 +3857,8 @@ local DEFAULT_MAX_TIMERS_PER_GROUP = 5
|
|
|
3674
3857
|
local DEFAULT_MAX_RELATED_TIMERS = 3
|
|
3675
3858
|
local DEFAULT_MAX_EVENTS = 250000
|
|
3676
3859
|
local MAX_EVENTS = 1000000
|
|
3860
|
+
local COOPERATIVE_CLOCK_CHECK_INTERVAL = 128
|
|
3861
|
+
local MAX_COOPERATIVE_SLICE_SECONDS = 0.008
|
|
3677
3862
|
local DEFAULT_FRAME_WINDOW = 240
|
|
3678
3863
|
local BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
|
3679
3864
|
local PAD_BYTE = (string.byte("="))
|
|
@@ -3690,6 +3875,87 @@ local FOCUS_GROUP_MASKS = {
|
|
|
3690
3875
|
jobs = { "Jobs" },
|
|
3691
3876
|
}
|
|
3692
3877
|
local cachedLibMP
|
|
3878
|
+
-- Sampling the clock avoids a timing call for every event while still bounding
|
|
3879
|
+
-- each VM resumption. Sorting uses the same checkpoint so no full sort is atomic.
|
|
3880
|
+
local function createCooperativeCheckpoint()
|
|
3881
|
+
local operationsSinceClockCheck = 0
|
|
3882
|
+
local sliceStartedAt = os.clock()
|
|
3883
|
+
return function()
|
|
3884
|
+
operationsSinceClockCheck += 1
|
|
3885
|
+
if operationsSinceClockCheck < COOPERATIVE_CLOCK_CHECK_INTERVAL then
|
|
3886
|
+
return nil
|
|
3887
|
+
end
|
|
3888
|
+
operationsSinceClockCheck = 0
|
|
3889
|
+
if os.clock() - sliceStartedAt < MAX_COOPERATIVE_SLICE_SECONDS then
|
|
3890
|
+
return nil
|
|
3891
|
+
end
|
|
3892
|
+
task.wait()
|
|
3893
|
+
sliceStartedAt = os.clock()
|
|
3894
|
+
end
|
|
3895
|
+
end
|
|
3896
|
+
local function cooperativeSort(values, comesBefore, checkpoint)
|
|
3897
|
+
local count = #values
|
|
3898
|
+
if count < 2 then
|
|
3899
|
+
return nil
|
|
3900
|
+
end
|
|
3901
|
+
local function siftDown(start, lastIndex)
|
|
3902
|
+
local root = start
|
|
3903
|
+
while root * 2 + 1 <= lastIndex do
|
|
3904
|
+
checkpoint()
|
|
3905
|
+
local candidate = root
|
|
3906
|
+
local left = root * 2 + 1
|
|
3907
|
+
if comesBefore(values[candidate + 1], values[left + 1]) then
|
|
3908
|
+
candidate = left
|
|
3909
|
+
end
|
|
3910
|
+
local right = left + 1
|
|
3911
|
+
if right <= lastIndex and comesBefore(values[candidate + 1], values[right + 1]) then
|
|
3912
|
+
candidate = right
|
|
3913
|
+
end
|
|
3914
|
+
if candidate == root then
|
|
3915
|
+
return nil
|
|
3916
|
+
end
|
|
3917
|
+
local value = values[root + 1]
|
|
3918
|
+
values[root + 1] = values[candidate + 1]
|
|
3919
|
+
values[candidate + 1] = value
|
|
3920
|
+
root = candidate
|
|
3921
|
+
end
|
|
3922
|
+
end
|
|
3923
|
+
do
|
|
3924
|
+
local start = math.floor((count - 2) / 2)
|
|
3925
|
+
local _shouldIncrement = false
|
|
3926
|
+
while true do
|
|
3927
|
+
if _shouldIncrement then
|
|
3928
|
+
start -= 1
|
|
3929
|
+
else
|
|
3930
|
+
_shouldIncrement = true
|
|
3931
|
+
end
|
|
3932
|
+
if not (start >= 0) then
|
|
3933
|
+
break
|
|
3934
|
+
end
|
|
3935
|
+
siftDown(start, count - 1)
|
|
3936
|
+
checkpoint()
|
|
3937
|
+
end
|
|
3938
|
+
end
|
|
3939
|
+
do
|
|
3940
|
+
local lastIndex = count - 1
|
|
3941
|
+
local _shouldIncrement = false
|
|
3942
|
+
while true do
|
|
3943
|
+
if _shouldIncrement then
|
|
3944
|
+
lastIndex -= 1
|
|
3945
|
+
else
|
|
3946
|
+
_shouldIncrement = true
|
|
3947
|
+
end
|
|
3948
|
+
if not (lastIndex > 0) then
|
|
3949
|
+
break
|
|
3950
|
+
end
|
|
3951
|
+
local value = values[1]
|
|
3952
|
+
values[1] = values[lastIndex + 1]
|
|
3953
|
+
values[lastIndex + 1] = value
|
|
3954
|
+
checkpoint()
|
|
3955
|
+
siftDown(0, lastIndex - 1)
|
|
3956
|
+
end
|
|
3957
|
+
end
|
|
3958
|
+
end
|
|
3693
3959
|
local function normalizeDurationMs(value)
|
|
3694
3960
|
local _value = value
|
|
3695
3961
|
if not (type(_value) == "number") then
|
|
@@ -3817,7 +4083,7 @@ local function addFrameRaw(map, frameId, raw)
|
|
|
3817
4083
|
local _arg1 = _condition + raw
|
|
3818
4084
|
_map[_exp] = _arg1
|
|
3819
4085
|
end
|
|
3820
|
-
local function summarizeFrameImpact(frameRawById, analyzedFrames)
|
|
4086
|
+
local function summarizeFrameImpact(frameRawById, analyzedFrames, checkpoint)
|
|
3821
4087
|
local _condition = frameRawById == nil
|
|
3822
4088
|
if not _condition then
|
|
3823
4089
|
-- ▼ ReadonlyMap.size ▼
|
|
@@ -3842,6 +4108,7 @@ local function summarizeFrameImpact(frameRawById, analyzedFrames)
|
|
|
3842
4108
|
local maxUs = 0
|
|
3843
4109
|
local maxFrameId = 0
|
|
3844
4110
|
for frameId, raw in frameRawById do
|
|
4111
|
+
checkpoint()
|
|
3845
4112
|
local us = rawToUs(raw)
|
|
3846
4113
|
table.insert(values, us)
|
|
3847
4114
|
totalUs += us
|
|
@@ -3850,9 +4117,9 @@ local function summarizeFrameImpact(frameRawById, analyzedFrames)
|
|
|
3850
4117
|
maxFrameId = frameId
|
|
3851
4118
|
end
|
|
3852
4119
|
end
|
|
3853
|
-
|
|
4120
|
+
cooperativeSort(values, function(a, b)
|
|
3854
4121
|
return a < b
|
|
3855
|
-
end)
|
|
4122
|
+
end, checkpoint)
|
|
3856
4123
|
return {
|
|
3857
4124
|
active_frame_count = #values,
|
|
3858
4125
|
active_frame_pct = percent(#values, analyzedFrames),
|
|
@@ -3863,7 +4130,7 @@ local function summarizeFrameImpact(frameRawById, analyzedFrames)
|
|
|
3863
4130
|
max_frame_id = maxFrameId,
|
|
3864
4131
|
}
|
|
3865
4132
|
end
|
|
3866
|
-
local function encodeBase64(buf)
|
|
4133
|
+
local function encodeBase64(buf, checkpoint)
|
|
3867
4134
|
local len = buffer.len(buf)
|
|
3868
4135
|
local fullTriples = math.floor(len / 3)
|
|
3869
4136
|
local remaining = len - fullTriples * 3
|
|
@@ -3892,6 +4159,7 @@ local function encodeBase64(buf)
|
|
|
3892
4159
|
buffer.writeu8(out, di + 3, B64[bit32.band(b2, 63) + 1])
|
|
3893
4160
|
si += 3
|
|
3894
4161
|
di += 4
|
|
4162
|
+
checkpoint()
|
|
3895
4163
|
end
|
|
3896
4164
|
end
|
|
3897
4165
|
if remaining == 2 then
|
|
@@ -4002,7 +4270,7 @@ local function recommendedToolsForGroups(groups, targetRole)
|
|
|
4002
4270
|
end
|
|
4003
4271
|
return tools
|
|
4004
4272
|
end
|
|
4005
|
-
local function collectFrameSummary(session, startFrame, frameMax)
|
|
4273
|
+
local function collectFrameSummary(session, startFrame, frameMax, checkpoint)
|
|
4006
4274
|
local frameRows = {}
|
|
4007
4275
|
local durations = {}
|
|
4008
4276
|
local incompleteFrames = 0
|
|
@@ -4020,6 +4288,7 @@ local function collectFrameSummary(session, startFrame, frameMax)
|
|
|
4020
4288
|
if not (frameId <= frameMax) then
|
|
4021
4289
|
break
|
|
4022
4290
|
end
|
|
4291
|
+
checkpoint()
|
|
4023
4292
|
local ok, descOrErr = safeCall(function()
|
|
4024
4293
|
return session:GetFrameDesc(frameId)
|
|
4025
4294
|
end)
|
|
@@ -4059,15 +4328,16 @@ local function collectFrameSummary(session, startFrame, frameMax)
|
|
|
4059
4328
|
_frameId = frameId
|
|
4060
4329
|
end
|
|
4061
4330
|
end
|
|
4062
|
-
|
|
4331
|
+
cooperativeSort(durations, function(a, b)
|
|
4063
4332
|
return a < b
|
|
4064
|
-
end)
|
|
4065
|
-
|
|
4333
|
+
end, checkpoint)
|
|
4334
|
+
cooperativeSort(frameRows, function(a, b)
|
|
4066
4335
|
return (a.duration_us) > (b.duration_us)
|
|
4067
|
-
end)
|
|
4336
|
+
end, checkpoint)
|
|
4068
4337
|
local totalUs = 0
|
|
4069
4338
|
for _, duration in durations do
|
|
4070
4339
|
totalUs += duration
|
|
4340
|
+
checkpoint()
|
|
4071
4341
|
end
|
|
4072
4342
|
local topFrames = {}
|
|
4073
4343
|
do
|
|
@@ -4381,6 +4651,7 @@ local function captureMicroProfiler(requestData)
|
|
|
4381
4651
|
backend = backend,
|
|
4382
4652
|
}
|
|
4383
4653
|
end
|
|
4654
|
+
local checkpoint = createCooperativeCheckpoint()
|
|
4384
4655
|
local stacks = {}
|
|
4385
4656
|
local aggregates = {}
|
|
4386
4657
|
local timerThreadAggregates = {}
|
|
@@ -4396,7 +4667,11 @@ local function captureMicroProfiler(requestData)
|
|
|
4396
4667
|
local sampledFrameMax
|
|
4397
4668
|
local lastProcessedFrameId
|
|
4398
4669
|
local lastProcessedTimestampRaw
|
|
4399
|
-
while eventsSampled < maxEvents
|
|
4670
|
+
while eventsSampled < maxEvents do
|
|
4671
|
+
checkpoint()
|
|
4672
|
+
if not iterator:Step() then
|
|
4673
|
+
break
|
|
4674
|
+
end
|
|
4400
4675
|
eventsSampled += 1
|
|
4401
4676
|
local state = iterator:GetState()
|
|
4402
4677
|
if not state then
|
|
@@ -4437,6 +4712,7 @@ local function captureMicroProfiler(requestData)
|
|
|
4437
4712
|
exitEvents += 1
|
|
4438
4713
|
local entry
|
|
4439
4714
|
while #stack > 0 do
|
|
4715
|
+
checkpoint()
|
|
4440
4716
|
-- ▼ Array.pop ▼
|
|
4441
4717
|
local _length = #stack
|
|
4442
4718
|
local _result = stack[_length]
|
|
@@ -4514,7 +4790,7 @@ local function captureMicroProfiler(requestData)
|
|
|
4514
4790
|
_condition_2 = frameMax
|
|
4515
4791
|
end
|
|
4516
4792
|
local analysisFrameMax = _condition_2
|
|
4517
|
-
local frameSummary = collectFrameSummary(session, analysisFrameMin, analysisFrameMax)
|
|
4793
|
+
local frameSummary = collectFrameSummary(session, analysisFrameMin, analysisFrameMax, checkpoint)
|
|
4518
4794
|
local _frames = frameSummary.frames
|
|
4519
4795
|
local _condition_3 = type(_frames) == "number"
|
|
4520
4796
|
if _condition_3 then
|
|
@@ -4563,6 +4839,7 @@ local function captureMicroProfiler(requestData)
|
|
|
4563
4839
|
local omittedBelowThreshold = 0
|
|
4564
4840
|
local omittedByFilter = 0
|
|
4565
4841
|
for timerId, aggregate in aggregates do
|
|
4842
|
+
checkpoint()
|
|
4566
4843
|
local info = getTimerInfo(timerId)
|
|
4567
4844
|
local totalUs = rawToUs(aggregate.inclusive_raw)
|
|
4568
4845
|
if not includeIdle and isIdleTimer(info) then
|
|
@@ -4627,7 +4904,7 @@ local function captureMicroProfiler(requestData)
|
|
|
4627
4904
|
if maxToAvg ~= nil then
|
|
4628
4905
|
row.max_to_avg = maxToAvg
|
|
4629
4906
|
end
|
|
4630
|
-
local timerFrameImpact = summarizeFrameImpact(timerFrameAggregates[timerId], analysisFrameCount)
|
|
4907
|
+
local timerFrameImpact = summarizeFrameImpact(timerFrameAggregates[timerId], analysisFrameCount, checkpoint)
|
|
4631
4908
|
for key, value in pairs(timerFrameImpact) do
|
|
4632
4909
|
row[key] = value
|
|
4633
4910
|
end
|
|
@@ -4653,12 +4930,14 @@ local function captureMicroProfiler(requestData)
|
|
|
4653
4930
|
end
|
|
4654
4931
|
for frameId, raw in timerFrames do
|
|
4655
4932
|
addFrameRaw(groupFrames, frameId, raw)
|
|
4933
|
+
checkpoint()
|
|
4656
4934
|
end
|
|
4657
4935
|
end
|
|
4658
4936
|
local perThread = timerThreadAggregates[timerId]
|
|
4659
4937
|
if perThread ~= nil then
|
|
4660
4938
|
local timerThreadSummaryRows = {}
|
|
4661
4939
|
for threadId, threadAggregate in perThread do
|
|
4940
|
+
checkpoint()
|
|
4662
4941
|
local threadAggregateRow = threadAggregates[threadId]
|
|
4663
4942
|
if threadAggregateRow == nil then
|
|
4664
4943
|
threadAggregateRow = {
|
|
@@ -4717,9 +4996,9 @@ local function captureMicroProfiler(requestData)
|
|
|
4717
4996
|
}
|
|
4718
4997
|
table.insert(timerThreadSummaryRows, _arg0)
|
|
4719
4998
|
end
|
|
4720
|
-
|
|
4999
|
+
cooperativeSort(timerThreadSummaryRows, function(a, b)
|
|
4721
5000
|
return (a.inclusive_us) > (b.inclusive_us)
|
|
4722
|
-
end)
|
|
5001
|
+
end, checkpoint)
|
|
4723
5002
|
if maxRelatedTimers > 0 and #timerThreadSummaryRows > 0 then
|
|
4724
5003
|
local topTimerThreads = {}
|
|
4725
5004
|
do
|
|
@@ -4742,13 +5021,14 @@ local function captureMicroProfiler(requestData)
|
|
|
4742
5021
|
end
|
|
4743
5022
|
end
|
|
4744
5023
|
end
|
|
4745
|
-
|
|
5024
|
+
cooperativeSort(rows, function(a, b)
|
|
4746
5025
|
return (a.inclusive_us) > (b.inclusive_us)
|
|
4747
|
-
end)
|
|
5026
|
+
end, checkpoint)
|
|
4748
5027
|
local edgeRows = {}
|
|
4749
5028
|
local parentRelationsByChild = {}
|
|
4750
5029
|
local childRelationsByParent = {}
|
|
4751
5030
|
for _, edge in edgeAggregates do
|
|
5031
|
+
checkpoint()
|
|
4752
5032
|
local _parent_timer_id = edge.parent_timer_id
|
|
4753
5033
|
local parentRow = rowsByTimerId[_parent_timer_id]
|
|
4754
5034
|
local _child_timer_id = edge.child_timer_id
|
|
@@ -4819,21 +5099,24 @@ local function captureMicroProfiler(requestData)
|
|
|
4819
5099
|
}
|
|
4820
5100
|
table.insert(_childRelations, _arg0_1)
|
|
4821
5101
|
end
|
|
4822
|
-
|
|
5102
|
+
cooperativeSort(edgeRows, function(a, b)
|
|
4823
5103
|
return (a.inclusive_us) > (b.inclusive_us)
|
|
4824
|
-
end)
|
|
5104
|
+
end, checkpoint)
|
|
4825
5105
|
if maxRelatedTimers > 0 then
|
|
4826
5106
|
for _, relationRows in parentRelationsByChild do
|
|
4827
|
-
|
|
5107
|
+
checkpoint()
|
|
5108
|
+
cooperativeSort(relationRows, function(a, b)
|
|
4828
5109
|
return (a.inclusive_us) > (b.inclusive_us)
|
|
4829
|
-
end)
|
|
5110
|
+
end, checkpoint)
|
|
4830
5111
|
end
|
|
4831
5112
|
for _, relationRows in childRelationsByParent do
|
|
4832
|
-
|
|
5113
|
+
checkpoint()
|
|
5114
|
+
cooperativeSort(relationRows, function(a, b)
|
|
4833
5115
|
return (a.inclusive_us) > (b.inclusive_us)
|
|
4834
|
-
end)
|
|
5116
|
+
end, checkpoint)
|
|
4835
5117
|
end
|
|
4836
5118
|
for _, row in rows do
|
|
5119
|
+
checkpoint()
|
|
4837
5120
|
local timerId = row.timer_id
|
|
4838
5121
|
local parents = parentRelationsByChild[timerId]
|
|
4839
5122
|
local children = childRelationsByParent[timerId]
|
|
@@ -4900,10 +5183,11 @@ local function captureMicroProfiler(requestData)
|
|
|
4900
5183
|
local rowsByExclusive = {}
|
|
4901
5184
|
for _, row in rows do
|
|
4902
5185
|
table.insert(rowsByExclusive, row)
|
|
5186
|
+
checkpoint()
|
|
4903
5187
|
end
|
|
4904
|
-
|
|
5188
|
+
cooperativeSort(rowsByExclusive, function(a, b)
|
|
4905
5189
|
return (a.exclusive_us) > (b.exclusive_us)
|
|
4906
|
-
end)
|
|
5190
|
+
end, checkpoint)
|
|
4907
5191
|
local topTimersByExclusive = {}
|
|
4908
5192
|
do
|
|
4909
5193
|
local i = 0
|
|
@@ -4942,6 +5226,7 @@ local function captureMicroProfiler(requestData)
|
|
|
4942
5226
|
end
|
|
4943
5227
|
local threadRows = {}
|
|
4944
5228
|
for threadId, aggregate in threadAggregates do
|
|
5229
|
+
checkpoint()
|
|
4945
5230
|
local info = getThreadInfo(threadId)
|
|
4946
5231
|
local inclusiveUs = rawToUs(aggregate.inclusive_raw)
|
|
4947
5232
|
local exclusiveUs = rawToUs(aggregate.exclusive_raw)
|
|
@@ -4959,9 +5244,9 @@ local function captureMicroProfiler(requestData)
|
|
|
4959
5244
|
pct_of_analyzed_wall = percent(inclusiveUs, analysisDurationUs),
|
|
4960
5245
|
}
|
|
4961
5246
|
local timerRows = threadTimerRows[threadId] or {}
|
|
4962
|
-
|
|
5247
|
+
cooperativeSort(timerRows, function(a, b)
|
|
4963
5248
|
return (a.inclusive_us) > (b.inclusive_us)
|
|
4964
|
-
end)
|
|
5249
|
+
end, checkpoint)
|
|
4965
5250
|
local topThreadTimers = {}
|
|
4966
5251
|
do
|
|
4967
5252
|
local i = 0
|
|
@@ -4984,9 +5269,9 @@ local function captureMicroProfiler(requestData)
|
|
|
4984
5269
|
end
|
|
4985
5270
|
table.insert(threadRows, row)
|
|
4986
5271
|
end
|
|
4987
|
-
|
|
5272
|
+
cooperativeSort(threadRows, function(a, b)
|
|
4988
5273
|
return (a.inclusive_us) > (b.inclusive_us)
|
|
4989
|
-
end)
|
|
5274
|
+
end, checkpoint)
|
|
4990
5275
|
local topThreads = {}
|
|
4991
5276
|
do
|
|
4992
5277
|
local i = 0
|
|
@@ -5007,15 +5292,16 @@ local function captureMicroProfiler(requestData)
|
|
|
5007
5292
|
end
|
|
5008
5293
|
local groupRows = {}
|
|
5009
5294
|
for _, aggregate in groupAggregates do
|
|
5295
|
+
checkpoint()
|
|
5010
5296
|
local _group = aggregate.group
|
|
5011
5297
|
local _condition_5 = groupTimerRows[_group]
|
|
5012
5298
|
if _condition_5 == nil then
|
|
5013
5299
|
_condition_5 = {}
|
|
5014
5300
|
end
|
|
5015
5301
|
local timerRows = _condition_5
|
|
5016
|
-
|
|
5302
|
+
cooperativeSort(timerRows, function(a, b)
|
|
5017
5303
|
return (a.inclusive_us) > (b.inclusive_us)
|
|
5018
|
-
end)
|
|
5304
|
+
end, checkpoint)
|
|
5019
5305
|
local topGroupTimers = {}
|
|
5020
5306
|
do
|
|
5021
5307
|
local i = 0
|
|
@@ -5058,7 +5344,7 @@ local function captureMicroProfiler(requestData)
|
|
|
5058
5344
|
pct_of_analyzed_wall = percent(inclusiveUs, analysisDurationUs),
|
|
5059
5345
|
}
|
|
5060
5346
|
local _group_1 = aggregate.group
|
|
5061
|
-
local groupFrameImpact = summarizeFrameImpact(groupFrameAggregates[_group_1], analysisFrameCount)
|
|
5347
|
+
local groupFrameImpact = summarizeFrameImpact(groupFrameAggregates[_group_1], analysisFrameCount, checkpoint)
|
|
5062
5348
|
for key, value in pairs(groupFrameImpact) do
|
|
5063
5349
|
groupRow[key] = value
|
|
5064
5350
|
end
|
|
@@ -5067,9 +5353,9 @@ local function captureMicroProfiler(requestData)
|
|
|
5067
5353
|
end
|
|
5068
5354
|
table.insert(groupRows, groupRow)
|
|
5069
5355
|
end
|
|
5070
|
-
|
|
5356
|
+
cooperativeSort(groupRows, function(a, b)
|
|
5071
5357
|
return (a.inclusive_us) > (b.inclusive_us)
|
|
5072
|
-
end)
|
|
5358
|
+
end, checkpoint)
|
|
5073
5359
|
local topGroups = {}
|
|
5074
5360
|
do
|
|
5075
5361
|
local i = 0
|
|
@@ -5091,10 +5377,11 @@ local function captureMicroProfiler(requestData)
|
|
|
5091
5377
|
local groupRowsByExclusive = {}
|
|
5092
5378
|
for _, row in groupRows do
|
|
5093
5379
|
table.insert(groupRowsByExclusive, row)
|
|
5380
|
+
checkpoint()
|
|
5094
5381
|
end
|
|
5095
|
-
|
|
5382
|
+
cooperativeSort(groupRowsByExclusive, function(a, b)
|
|
5096
5383
|
return (a.exclusive_us) > (b.exclusive_us)
|
|
5097
|
-
end)
|
|
5384
|
+
end, checkpoint)
|
|
5098
5385
|
local topGroupsByExclusive = {}
|
|
5099
5386
|
do
|
|
5100
5387
|
local i = 0
|
|
@@ -5126,6 +5413,7 @@ local function captureMicroProfiler(requestData)
|
|
|
5126
5413
|
local openStackEntriesAtEnd = 0
|
|
5127
5414
|
for _, stack in stacks do
|
|
5128
5415
|
openStackEntriesAtEnd += #stack
|
|
5416
|
+
checkpoint()
|
|
5129
5417
|
end
|
|
5130
5418
|
local iteratorFinished = eventsSampled < maxEvents
|
|
5131
5419
|
local partialReasons = {}
|
|
@@ -5148,21 +5436,25 @@ local function captureMicroProfiler(requestData)
|
|
|
5148
5436
|
for _, row in rows do
|
|
5149
5437
|
local _arg0 = pickFields(row, timerFields)
|
|
5150
5438
|
table.insert(timerIndex, _arg0)
|
|
5439
|
+
checkpoint()
|
|
5151
5440
|
end
|
|
5152
5441
|
local groupIndex = {}
|
|
5153
5442
|
for _, row in groupRows do
|
|
5154
5443
|
local _arg0 = pickFields(row, groupFields)
|
|
5155
5444
|
table.insert(groupIndex, _arg0)
|
|
5445
|
+
checkpoint()
|
|
5156
5446
|
end
|
|
5157
5447
|
local threadIndex = {}
|
|
5158
5448
|
for _, row in threadRows do
|
|
5159
5449
|
local _arg0 = pickFields(row, threadFields)
|
|
5160
5450
|
table.insert(threadIndex, _arg0)
|
|
5451
|
+
checkpoint()
|
|
5161
5452
|
end
|
|
5162
5453
|
local edgeIndex = {}
|
|
5163
5454
|
for _, row in edgeRows do
|
|
5164
5455
|
local _arg0 = pickFields(row, edgeFields)
|
|
5165
5456
|
table.insert(edgeIndex, _arg0)
|
|
5457
|
+
checkpoint()
|
|
5166
5458
|
end
|
|
5167
5459
|
comparisonIndex = {
|
|
5168
5460
|
timers = timerIndex,
|
|
@@ -5291,7 +5583,7 @@ local function captureMicroProfiler(requestData)
|
|
|
5291
5583
|
result.snapshot_object_size_bytes = session:GetObjSize()
|
|
5292
5584
|
end
|
|
5293
5585
|
if includeRawBuffer then
|
|
5294
|
-
result.raw_snapshot_base64 = encodeBase64(snapshot)
|
|
5586
|
+
result.raw_snapshot_base64 = encodeBase64(snapshot, checkpoint)
|
|
5295
5587
|
end
|
|
5296
5588
|
iterator:Dispose()
|
|
5297
5589
|
session:Dispose()
|
|
@@ -6664,7 +6956,7 @@ end
|
|
|
6664
6956
|
local function setScriptSource(requestData)
|
|
6665
6957
|
local instancePath = requestData.instancePath
|
|
6666
6958
|
local newSource = requestData.source
|
|
6667
|
-
if not (instancePath ~= "" and instancePath) or not (newSource
|
|
6959
|
+
if not (instancePath ~= "" and instancePath) or not (type(newSource) == "string") then
|
|
6668
6960
|
return {
|
|
6669
6961
|
error = "Instance path and source are required",
|
|
6670
6962
|
}
|
|
@@ -9272,7 +9564,7 @@ return {
|
|
|
9272
9564
|
<Properties>
|
|
9273
9565
|
<string name="Name">State</string>
|
|
9274
9566
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
9275
|
-
local CURRENT_VERSION = "3.0.
|
|
9567
|
+
local CURRENT_VERSION = "3.0.1"
|
|
9276
9568
|
local PLUGIN_VARIANT = "main"
|
|
9277
9569
|
local BASE_PORT = 58741
|
|
9278
9570
|
local function createConnection(port)
|
|
File without changes
|
|
Binary file
|