@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.
Files changed (48) hide show
  1. package/dist/index.js +217 -51
  2. package/package.json +2 -2
  3. package/studio-plugin/MCPPlugin.rbxmx +332 -40
  4. package/studio-plugin/.Carbon.rbxm.lock +0 -0
  5. package/studio-plugin/Carbon.rbxm +0 -0
  6. package/studio-plugin/INSTALLATION.md +0 -170
  7. package/studio-plugin/MCPInspectorPlugin.rbxmx +0 -169759
  8. package/studio-plugin/default.project.json +0 -19
  9. package/studio-plugin/dev.project.json +0 -23
  10. package/studio-plugin/include/LibMP.lua +0 -156378
  11. package/studio-plugin/inspector-icon.png +0 -0
  12. package/studio-plugin/package-lock.json +0 -706
  13. package/studio-plugin/package.json +0 -19
  14. package/studio-plugin/plugin.json +0 -10
  15. package/studio-plugin/src/modules/AssetSanitizationPolicy.ts +0 -127
  16. package/studio-plugin/src/modules/ClientBroker.ts +0 -450
  17. package/studio-plugin/src/modules/Communication.ts +0 -601
  18. package/studio-plugin/src/modules/EvalBridges.ts +0 -255
  19. package/studio-plugin/src/modules/HttpDiagnostics.ts +0 -50
  20. package/studio-plugin/src/modules/LuauExec.ts +0 -403
  21. package/studio-plugin/src/modules/Recording.ts +0 -28
  22. package/studio-plugin/src/modules/RenderMonitor.ts +0 -60
  23. package/studio-plugin/src/modules/RuntimeLogBuffer.ts +0 -210
  24. package/studio-plugin/src/modules/ServerUrlSettings.ts +0 -117
  25. package/studio-plugin/src/modules/State.ts +0 -39
  26. package/studio-plugin/src/modules/StopPlayMonitor.ts +0 -267
  27. package/studio-plugin/src/modules/UI.ts +0 -597
  28. package/studio-plugin/src/modules/Utils.ts +0 -527
  29. package/studio-plugin/src/modules/handlers/AssetHandlers.ts +0 -391
  30. package/studio-plugin/src/modules/handlers/BreakpointHandlers.ts +0 -460
  31. package/studio-plugin/src/modules/handlers/CaptureHandlers.ts +0 -170
  32. package/studio-plugin/src/modules/handlers/EvalRuntimeHandlers.ts +0 -149
  33. package/studio-plugin/src/modules/handlers/GenerateModelHandlers.ts +0 -168
  34. package/studio-plugin/src/modules/handlers/InputHandlers.ts +0 -163
  35. package/studio-plugin/src/modules/handlers/LogHandlers.ts +0 -14
  36. package/studio-plugin/src/modules/handlers/MemoryHandlers.ts +0 -44
  37. package/studio-plugin/src/modules/handlers/MetadataHandlers.ts +0 -96
  38. package/studio-plugin/src/modules/handlers/MicroProfilerHandlers.ts +0 -1263
  39. package/studio-plugin/src/modules/handlers/PropertyHandlers.ts +0 -62
  40. package/studio-plugin/src/modules/handlers/QueryHandlers.ts +0 -716
  41. package/studio-plugin/src/modules/handlers/SceneAnalysisHandlers.ts +0 -216
  42. package/studio-plugin/src/modules/handlers/ScriptHandlers.ts +0 -531
  43. package/studio-plugin/src/modules/handlers/ScriptProfilerHandlers.ts +0 -386
  44. package/studio-plugin/src/modules/handlers/SerializationHandlers.ts +0 -172
  45. package/studio-plugin/src/modules/handlers/TestHandlers.ts +0 -350
  46. package/studio-plugin/src/server/index.server.ts +0 -135
  47. package/studio-plugin/src/types/index.d.ts +0 -57
  48. 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.0" is replaced with the package version at package time
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.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
- table.sort(values, function(a, b)
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
- table.sort(durations, function(a, b)
4331
+ cooperativeSort(durations, function(a, b)
4063
4332
  return a < b
4064
- end)
4065
- table.sort(frameRows, function(a, b)
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 and iterator:Step() do
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
- table.sort(timerThreadSummaryRows, function(a, b)
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
- table.sort(rows, function(a, b)
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
- table.sort(edgeRows, function(a, b)
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
- table.sort(relationRows, function(a, b)
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
- table.sort(relationRows, function(a, b)
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
- table.sort(rowsByExclusive, function(a, b)
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
- table.sort(timerRows, function(a, b)
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
- table.sort(threadRows, function(a, b)
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
- table.sort(timerRows, function(a, b)
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
- table.sort(groupRows, function(a, b)
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
- table.sort(groupRowsByExclusive, function(a, b)
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 ~= "" and newSource) then
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.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