pulse-rb 1.4.0 → 1.4.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/adapters/windui.lua +214 -87
- package/dist/index.js +17 -2
- package/package.json +1 -1
- package/pulse/helpers/log.lua +6 -1
- package/pulse/runtime.lua +94 -30
package/adapters/windui.lua
CHANGED
|
@@ -184,6 +184,9 @@ function _UIAdapter:CreateWindow(title, w, h, opts)
|
|
|
184
184
|
local tabProxy = { _wind = windTab }
|
|
185
185
|
function tabProxy:AddLeftGroupbox(title, gbIcon) return _mkSection(leftCol, title, gbIcon) end
|
|
186
186
|
function tabProxy:AddRightGroupbox(title, gbIcon) return _mkSection(rightCol, title, gbIcon) end
|
|
187
|
+
-- Raw column access — no Section wrapper (used by plain=true groupboxes)
|
|
188
|
+
function tabProxy:GetLeftColumn() return { _container = leftCol } end
|
|
189
|
+
function tabProxy:GetRightColumn() return { _container = rightCol } end
|
|
187
190
|
return tabProxy
|
|
188
191
|
end
|
|
189
192
|
|
|
@@ -587,14 +590,25 @@ task.spawn(function()
|
|
|
587
590
|
local _execVer = ""
|
|
588
591
|
pcall(function()
|
|
589
592
|
if type(rawget(_G, "identifyexecutor")) == "function" then
|
|
590
|
-
local n, v = identifyexecutor
|
|
591
|
-
_execName = tostring(n or "Unknown")
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
593
|
+
local ok, n, v = pcall(identifyexecutor)
|
|
594
|
+
if ok then _execName = tostring(n or "Unknown"); _execVer = tostring(v or "") end
|
|
595
|
+
end
|
|
596
|
+
if _execName == "Unknown" and type(rawget(_G, "getexecutorname")) == "function" then
|
|
597
|
+
local ok, n = pcall(getexecutorname)
|
|
598
|
+
if ok and n then _execName = tostring(n) end
|
|
599
|
+
end
|
|
600
|
+
if _execName == "Unknown" then
|
|
601
|
+
if rawget(_G, "syn") or rawget(_G, "Synapse") then _execName = "Synapse X"
|
|
602
|
+
elseif rawget(_G, "KRNL_LOADED") then _execName = "Krnl"
|
|
603
|
+
elseif rawget(_G, "DELTA_KEY") then _execName = "Delta"
|
|
604
|
+
elseif rawget(_G, "WAVE_LOADED") then _execName = "Wave"
|
|
605
|
+
elseif rawget(_G, "SOLARA_LOADED") then _execName = "Solara"
|
|
606
|
+
elseif rawget(_G, "XENO_LOADED") then _execName = "Xeno"
|
|
607
|
+
elseif rawget(_G, "Seliware") or rawget(_G, "seliware") then _execName = "Seliware"
|
|
608
|
+
elseif rawget(_G, "Celery") or rawget(_G, "celery") then _execName = "Celery"
|
|
609
|
+
elseif rawget(_G, "is_sirhurt_closure") then _execName = "Sirhurt"
|
|
610
|
+
elseif rawget(_G, "EXECUTOR_NAME") then _execName = tostring(rawget(_G, "EXECUTOR_NAME"))
|
|
611
|
+
end
|
|
598
612
|
end
|
|
599
613
|
end)
|
|
600
614
|
|
|
@@ -605,84 +619,131 @@ task.spawn(function()
|
|
|
605
619
|
if info and info.Name then _gameName = info.Name end
|
|
606
620
|
end)
|
|
607
621
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
local
|
|
614
|
-
|
|
615
|
-
|
|
622
|
+
-- Home tab — raw columns, no Section wrappers for a clean look.
|
|
623
|
+
-- Create the tab directly so we can get the raw VStack columns from _mkColumns.
|
|
624
|
+
local _homeWindTab
|
|
625
|
+
pcall(function() _homeWindTab = _windWindow:Tab({ Title = "Home", Icon = "house" }) end)
|
|
626
|
+
if not _UIAdapter._firstTab then _UIAdapter._firstTab = _homeWindTab end
|
|
627
|
+
local _homeLeft, _homeRight = _mkColumns(_homeWindTab)
|
|
628
|
+
|
|
629
|
+
-- Left: player info + description + discord (no Section box)
|
|
630
|
+
if _homeLeft then
|
|
631
|
+
-- Capture the returned paragraph element so we can try avatar injection via its Frame
|
|
632
|
+
local _paraRef = nil
|
|
616
633
|
pcall(function()
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
634
|
+
_paraRef = _homeLeft:Paragraph({
|
|
635
|
+
Title = "Welcome, " .. _dspName,
|
|
636
|
+
Desc = "@" .. _usrName,
|
|
620
637
|
})
|
|
621
638
|
end)
|
|
622
|
-
end
|
|
623
|
-
_welcomeGb._container:Paragraph({
|
|
624
|
-
Title = _dspName,
|
|
625
|
-
Desc = "@" .. _usrName,
|
|
626
|
-
})
|
|
627
639
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
640
|
+
-- Avatar injection: WindUI paragraph elements wrap a Roblox Frame internally.
|
|
641
|
+
-- Grab that Frame's parent (the list container) and add an ImageLabel there.
|
|
642
|
+
if _avatarUrl ~= "" and _paraRef then
|
|
643
|
+
task.delay(0.3, function()
|
|
644
|
+
pcall(function()
|
|
645
|
+
local paraFrame = nil
|
|
646
|
+
for _, k in ipairs({"Frame","Object","Instance","Container","Label","Content"}) do
|
|
647
|
+
local ok, v = pcall(function() return _paraRef[k] end)
|
|
648
|
+
if ok and type(v) == "userdata" then
|
|
649
|
+
local _, isGui = pcall(function() return v:IsA("GuiObject") end)
|
|
650
|
+
if isGui then paraFrame = v; break end
|
|
651
|
+
end
|
|
652
|
+
end
|
|
653
|
+
local contentFrame = paraFrame and paraFrame.Parent
|
|
654
|
+
if not (contentFrame and type(contentFrame) == "userdata") then return end
|
|
655
|
+
if contentFrame:FindFirstChild("_PulseAvatar") then return end
|
|
656
|
+
local img = Instance.new("ImageLabel")
|
|
657
|
+
img.Name = "_PulseAvatar"; img.Image = _avatarUrl
|
|
658
|
+
img.Size = UDim2.fromOffset(64, 64); img.BackgroundTransparency = 1
|
|
659
|
+
img.ScaleType = Enum.ScaleType.Crop; img.LayoutOrder = -999
|
|
660
|
+
Instance.new("UICorner", img).CornerRadius = UDim.new(1, 0)
|
|
661
|
+
img.Parent = contentFrame
|
|
662
|
+
end)
|
|
663
|
+
end)
|
|
664
|
+
end
|
|
632
665
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
666
|
+
if L.description and L.description ~= "" then
|
|
667
|
+
pcall(function() _homeLeft:Divider() end)
|
|
668
|
+
pcall(function() _homeLeft:Paragraph({ Title = "About", Desc = L.description }) end)
|
|
669
|
+
end
|
|
670
|
+
if L.discord and L.discord ~= "" then
|
|
671
|
+
pcall(function() _homeLeft:Divider() end)
|
|
672
|
+
pcall(function()
|
|
673
|
+
_homeLeft:Button({
|
|
674
|
+
Title = "Join Discord",
|
|
675
|
+
Desc = "Tap to copy the invite link",
|
|
676
|
+
Callback = function()
|
|
677
|
+
local ok = false
|
|
678
|
+
if not ok then pcall(function() setclipboard(L.discord); ok=true end) end
|
|
679
|
+
if not ok then pcall(function() toclipboard(L.discord); ok=true end) end
|
|
680
|
+
if not ok then pcall(function() writeclipboard(L.discord); ok=true end) end
|
|
681
|
+
_PulseNotify(ok and "Discord link copied!" or "No clipboard API", 3)
|
|
682
|
+
end,
|
|
683
|
+
})
|
|
684
|
+
end)
|
|
685
|
+
end
|
|
646
686
|
end
|
|
647
687
|
|
|
648
|
-
-- Right
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
688
|
+
-- Right: script info + executor + game (no Section box)
|
|
689
|
+
if _homeRight then
|
|
690
|
+
local _verStr = (L.version and L.version ~= "") and ("v" .. L.version) or ""
|
|
691
|
+
local _authStr = (L.author and L.author ~= "") and ("by " .. L.author) or ""
|
|
692
|
+
local _metaParts = {}
|
|
693
|
+
if _verStr ~= "" then _metaParts[#_metaParts+1] = _verStr end
|
|
694
|
+
if _authStr ~= "" then _metaParts[#_metaParts+1] = _authStr end
|
|
695
|
+
pcall(function()
|
|
696
|
+
_homeRight:Paragraph({
|
|
697
|
+
Title = L.title or "Hub",
|
|
698
|
+
Desc = #_metaParts > 0 and table.concat(_metaParts, " · ") or "—",
|
|
699
|
+
})
|
|
700
|
+
end)
|
|
701
|
+
pcall(function() _homeRight:Divider() end)
|
|
702
|
+
local _execStr = _execName .. (_execVer ~= "" and (" " .. _execVer) or "")
|
|
703
|
+
pcall(function() _homeRight:Paragraph({ Title = "Executor", Desc = _execStr }) end)
|
|
704
|
+
pcall(function() _homeRight:Paragraph({ Title = "Game", Desc = _gameName }) end)
|
|
705
|
+
pcall(function() _homeRight:Divider() end)
|
|
706
|
+
-- Server info: live player count + rejoin
|
|
707
|
+
local _playerCount = 0
|
|
708
|
+
pcall(function() _playerCount = #game:GetService("Players"):GetPlayers() end)
|
|
709
|
+
pcall(function()
|
|
710
|
+
_homeRight:Paragraph({
|
|
711
|
+
Title = "Server",
|
|
712
|
+
Desc = tostring(_playerCount) .. " player" .. (_playerCount == 1 and "" or "s") .. " · " .. tostring(game.PlaceId),
|
|
713
|
+
})
|
|
714
|
+
end)
|
|
715
|
+
pcall(function()
|
|
716
|
+
_homeRight:Button({
|
|
717
|
+
Title = "Rejoin Server",
|
|
718
|
+
Desc = "Leave and rejoin the current place",
|
|
719
|
+
Callback = function()
|
|
720
|
+
pcall(function()
|
|
721
|
+
game:GetService("TeleportService"):TeleportToPlaceInstance(
|
|
722
|
+
game.PlaceId, game.JobId,
|
|
723
|
+
game:GetService("Players").LocalPlayer
|
|
724
|
+
)
|
|
725
|
+
end)
|
|
726
|
+
end,
|
|
685
727
|
})
|
|
728
|
+
end)
|
|
729
|
+
if L.unsupported and L.unsupported ~= "" then
|
|
730
|
+
local _unsupList = {}
|
|
731
|
+
if type(L.unsupported) == "table" then
|
|
732
|
+
for _, v in ipairs(L.unsupported) do _unsupList[#_unsupList+1] = tostring(v) end
|
|
733
|
+
elseif type(L.unsupported) == "string" then
|
|
734
|
+
for part in L.unsupported:gmatch("[^,]+") do
|
|
735
|
+
_unsupList[#_unsupList+1] = part:match("^%s*(.-)%s*$")
|
|
736
|
+
end
|
|
737
|
+
end
|
|
738
|
+
if #_unsupList > 0 then
|
|
739
|
+
pcall(function() _homeRight:Divider() end)
|
|
740
|
+
pcall(function()
|
|
741
|
+
_homeRight:Paragraph({
|
|
742
|
+
Title = "Not compatible with:",
|
|
743
|
+
Desc = table.concat(_unsupList, ", "),
|
|
744
|
+
})
|
|
745
|
+
end)
|
|
746
|
+
end
|
|
686
747
|
end
|
|
687
748
|
end
|
|
688
749
|
|
|
@@ -691,7 +752,14 @@ task.spawn(function()
|
|
|
691
752
|
local tab = _win:AddTab(page.title, page.icon)
|
|
692
753
|
for _, gb in ipairs(page.layout or {}) do
|
|
693
754
|
local container
|
|
694
|
-
if gb.
|
|
755
|
+
if gb.plain then
|
|
756
|
+
-- No Section wrapper — widgets go directly onto the column VStack
|
|
757
|
+
if gb.side == "left" then
|
|
758
|
+
if tab.GetLeftColumn then container = tab:GetLeftColumn() end
|
|
759
|
+
else
|
|
760
|
+
if tab.GetRightColumn then container = tab:GetRightColumn() end
|
|
761
|
+
end
|
|
762
|
+
elseif gb.side == "left" then
|
|
695
763
|
container = tab:AddLeftGroupbox(gb.title, gb.icon)
|
|
696
764
|
else
|
|
697
765
|
container = tab:AddRightGroupbox(gb.title, gb.icon)
|
|
@@ -784,6 +852,19 @@ task.spawn(function()
|
|
|
784
852
|
key = _PULSE_TOGGLE_KEY,
|
|
785
853
|
action = function() _UIAdapter:ToggleWindow() end,
|
|
786
854
|
})
|
|
855
|
+
gb_menu._container:Button({
|
|
856
|
+
Title = "Rejoin",
|
|
857
|
+
Icon = "refresh-cw",
|
|
858
|
+
Desc = "Leave and rejoin the current place",
|
|
859
|
+
Callback = function()
|
|
860
|
+
pcall(function()
|
|
861
|
+
game:GetService("TeleportService"):TeleportToPlaceInstance(
|
|
862
|
+
game.PlaceId, game.JobId,
|
|
863
|
+
game:GetService("Players").LocalPlayer
|
|
864
|
+
)
|
|
865
|
+
end)
|
|
866
|
+
end,
|
|
867
|
+
})
|
|
787
868
|
|
|
788
869
|
-- Auto-load saved config on startup
|
|
789
870
|
task.spawn(function()
|
|
@@ -799,14 +880,19 @@ task.spawn(function()
|
|
|
799
880
|
local _devTab
|
|
800
881
|
pcall(function() _devTab = _windWindow:Tab({ Title = "Dev", Icon = "bug" }) end)
|
|
801
882
|
if _devTab then
|
|
802
|
-
local
|
|
803
|
-
pcall(function()
|
|
804
|
-
|
|
883
|
+
local _devLeft, _devRight
|
|
884
|
+
pcall(function()
|
|
885
|
+
local _devHs = _devTab:HStack({ AutoSpace = true })
|
|
886
|
+
_devLeft = _devHs:VStack({})
|
|
887
|
+
_devRight = _devHs:VStack({})
|
|
888
|
+
end)
|
|
889
|
+
_devLeft = _devLeft or _devTab
|
|
890
|
+
_devRight = _devRight or _devTab
|
|
805
891
|
|
|
806
|
-
-- Tools section
|
|
892
|
+
-- Tools section (left)
|
|
807
893
|
local _toolSect
|
|
808
894
|
pcall(function()
|
|
809
|
-
_toolSect =
|
|
895
|
+
_toolSect = _devLeft:Section({ Title="Tools", Icon="wrench", Box=true, BoxBorder=true, Opened=true })
|
|
810
896
|
end)
|
|
811
897
|
|
|
812
898
|
local _TOOLS = {
|
|
@@ -864,7 +950,7 @@ task.spawn(function()
|
|
|
864
950
|
-- Scanner section
|
|
865
951
|
local _scanSect
|
|
866
952
|
pcall(function()
|
|
867
|
-
_scanSect =
|
|
953
|
+
_scanSect = _devRight:Section({ Title="Scanner", Icon="scan", Box=true, BoxBorder=true, Opened=true })
|
|
868
954
|
end)
|
|
869
955
|
|
|
870
956
|
if _scanSect then
|
|
@@ -976,7 +1062,7 @@ task.spawn(function()
|
|
|
976
1062
|
-- Info section — framework version + signal counts for debugging
|
|
977
1063
|
local _infoSect
|
|
978
1064
|
pcall(function()
|
|
979
|
-
_infoSect =
|
|
1065
|
+
_infoSect = _devLeft:Section({ Title="Pulse Info", Icon="info", Box=true, BoxBorder=true, Opened=true })
|
|
980
1066
|
end)
|
|
981
1067
|
if _infoSect then
|
|
982
1068
|
local nPages = #_BundlePages
|
|
@@ -1000,6 +1086,47 @@ task.spawn(function()
|
|
|
1000
1086
|
end
|
|
1001
1087
|
end
|
|
1002
1088
|
|
|
1089
|
+
-- Heartbeat Monitor section — tick counts per component (left)
|
|
1090
|
+
-- Uses Pulse.Monitor which is independent of the log system.
|
|
1091
|
+
local _monSect
|
|
1092
|
+
pcall(function()
|
|
1093
|
+
_monSect = _devLeft:Section({ Title="Heartbeat Monitor", Icon="activity", Box=true, BoxBorder=true, Opened=true })
|
|
1094
|
+
end)
|
|
1095
|
+
if _monSect then
|
|
1096
|
+
local _monPara
|
|
1097
|
+
pcall(function()
|
|
1098
|
+
_monPara = _monSect:Paragraph({
|
|
1099
|
+
Title = "raw / cond (updates 2s)",
|
|
1100
|
+
Desc = "raw = every HB fire · cond = when() passed",
|
|
1101
|
+
})
|
|
1102
|
+
end)
|
|
1103
|
+
-- Reads _rawHb/_condHb plain-Lua counters set in runtime._hbBind.
|
|
1104
|
+
-- No dependency on Pulse.Monitor — works regardless of log system state.
|
|
1105
|
+
task.spawn(function()
|
|
1106
|
+
while _windWindow do
|
|
1107
|
+
task.wait(2)
|
|
1108
|
+
pcall(function()
|
|
1109
|
+
local lines = {}
|
|
1110
|
+
for cname, comp in pairs(_BundleComponents) do
|
|
1111
|
+
if type(comp) == "table" and comp._rawHb ~= nil then
|
|
1112
|
+
lines[#lines+1] = cname
|
|
1113
|
+
.. " raw=" .. tostring(comp._rawHb)
|
|
1114
|
+
.. " cond=" .. tostring(comp._condHb)
|
|
1115
|
+
end
|
|
1116
|
+
end
|
|
1117
|
+
if _monPara then
|
|
1118
|
+
if #lines == 0 then
|
|
1119
|
+
_monPara:SetDesc("No heartbeat components registered")
|
|
1120
|
+
else
|
|
1121
|
+
table.sort(lines)
|
|
1122
|
+
_monPara:SetDesc(table.concat(lines, "\n"))
|
|
1123
|
+
end
|
|
1124
|
+
end
|
|
1125
|
+
end)
|
|
1126
|
+
end
|
|
1127
|
+
end)
|
|
1128
|
+
end
|
|
1129
|
+
|
|
1003
1130
|
-- Console section — controls for the Roblox developer console (F9).
|
|
1004
1131
|
-- "Clear" wipes the output so only your next prints are visible.
|
|
1005
1132
|
-- "Print Logs" re-dumps all buffered Pulse.Log entries so you can read
|
|
@@ -1007,7 +1134,7 @@ task.spawn(function()
|
|
|
1007
1134
|
-- "Copy Logs" copies the full log buffer to clipboard.
|
|
1008
1135
|
local _consoleSect
|
|
1009
1136
|
pcall(function()
|
|
1010
|
-
_consoleSect =
|
|
1137
|
+
_consoleSect = _devRight:Section({ Title="Console (F9)", Icon="terminal", Box=true, BoxBorder=true, Opened=true })
|
|
1011
1138
|
end)
|
|
1012
1139
|
if _consoleSect and _BundlePulse and _BundlePulse.Log then
|
|
1013
1140
|
local _Log = _BundlePulse.Log
|
package/dist/index.js
CHANGED
|
@@ -56,7 +56,7 @@ var RB_VERSION, RB_HOME, IRONBREW2_DIR, IRONBREW2_REPO, ALWAYS_EXCLUDE, VALID_UI
|
|
|
56
56
|
var init_constants = __esm({
|
|
57
57
|
"src/constants.ts"() {
|
|
58
58
|
init_cjs_shims();
|
|
59
|
-
RB_VERSION = "1.4.
|
|
59
|
+
RB_VERSION = "1.4.1";
|
|
60
60
|
RB_HOME = path.join(os.homedir(), ".rb");
|
|
61
61
|
path.join(RB_HOME, "bin");
|
|
62
62
|
IRONBREW2_DIR = path.join(RB_HOME, "ironbrew2");
|
|
@@ -1373,6 +1373,21 @@ end)
|
|
|
1373
1373
|
}
|
|
1374
1374
|
parts.push(`for _k,_v in pairs(_P) do _G[_k]=_v end
|
|
1375
1375
|
`);
|
|
1376
|
+
parts.push(
|
|
1377
|
+
`do
|
|
1378
|
+
local _rs=game:GetService("RunService")
|
|
1379
|
+
local _lp_=game:GetService("Players").LocalPlayer
|
|
1380
|
+
local _uis_=game:GetService("UserInputService")
|
|
1381
|
+
_rs.Heartbeat:Connect(function(a,b) if _PulseRunHeartbeat then _PulseRunHeartbeat(a,b) end end)
|
|
1382
|
+
_rs.RenderStepped:Connect(function(a,b) if _PulseRunRenderStepped then _PulseRunRenderStepped(a,b) end end)
|
|
1383
|
+
_rs.Stepped:Connect(function(a,b) if _PulseRunStepped then _PulseRunStepped(a,b) end end)
|
|
1384
|
+
_lp_.CharacterAdded:Connect(function(c) if _PulseRunCharAdded then _PulseRunCharAdded(c) end end)
|
|
1385
|
+
_lp_.CharacterRemoving:Connect(function(c) if _PulseRunCharRemoving then _PulseRunCharRemoving(c) end end)
|
|
1386
|
+
_uis_.InputBegan:Connect(function(i,g) if _PulseRunInputBegan then _PulseRunInputBegan(i,g) end end)
|
|
1387
|
+
_uis_.InputEnded:Connect(function(i,g) if _PulseRunInputEnded then _PulseRunInputEnded(i,g) end end)
|
|
1388
|
+
end
|
|
1389
|
+
`
|
|
1390
|
+
);
|
|
1376
1391
|
parts.push(`local _A=loadstring(game:HttpGet("${base}/adapters/${ui}.lua"))(_P,_PULSE_LAYOUT)
|
|
1377
1392
|
`);
|
|
1378
1393
|
parts.push(`local signal,computed,defineComponent,on=_P.signal,_P.computed,_P.defineComponent,_P.on
|
|
@@ -2992,7 +3007,7 @@ async function cmdPublish(_args) {
|
|
|
2992
3007
|
}
|
|
2993
3008
|
}
|
|
2994
3009
|
bundleParts.push(
|
|
2995
|
-
"return {signal=signal,computed=computed,defineComponent=defineComponent,on=on,toggle=toggle,slider=slider,dropdown=dropdown,multidropdown=multidropdown,button=button,keybind=keybind,label=label,separator=separator,groupbox=groupbox,definePage=definePage,Pulse=Pulse,Signal=Signal,Computed=Computed,Component=Component,Components=Components,Store=Store,PulseEvent=PulseEvent,_PulseGetChar=_PulseGetChar,_PulseGetHRP=_PulseGetHRP,_PulseGetHumanoid=_PulseGetHumanoid,_PulseGetAlive=_PulseGetAlive,_PulseRS=_PulseRS,_PulseUIS=_PulseUIS,_PulseDestroy=_PulseDestroy,_PULSE_DEFAULTS=_PULSE_DEFAULTS,_Pages=_Pages}"
|
|
3010
|
+
"return {signal=signal,computed=computed,defineComponent=defineComponent,on=on,toggle=toggle,slider=slider,dropdown=dropdown,multidropdown=multidropdown,button=button,keybind=keybind,label=label,separator=separator,groupbox=groupbox,definePage=definePage,Pulse=Pulse,Signal=Signal,Computed=Computed,Component=Component,Components=Components,Store=Store,PulseEvent=PulseEvent,_PulseGetChar=_PulseGetChar,_PulseGetHRP=_PulseGetHRP,_PulseGetHumanoid=_PulseGetHumanoid,_PulseGetAlive=_PulseGetAlive,_PulseRS=_PulseRS,_PulseUIS=_PulseUIS,_PulseDestroy=_PulseDestroy,_PULSE_DEFAULTS=_PULSE_DEFAULTS,_Pages=_Pages,_PulseRunHeartbeat=_PulseRunHeartbeat,_PulseRunRenderStepped=_PulseRunRenderStepped,_PulseRunStepped=_PulseRunStepped,_PulseRunCharAdded=_PulseRunCharAdded,_PulseRunCharRemoving=_PulseRunCharRemoving,_PulseRunInputBegan=_PulseRunInputBegan,_PulseRunInputEnded=_PulseRunInputEnded}"
|
|
2996
3011
|
);
|
|
2997
3012
|
const bundleContent = Buffer.from(bundleParts.join("\n") + "\n", "utf8");
|
|
2998
3013
|
const uploads = [
|
package/package.json
CHANGED
package/pulse/helpers/log.lua
CHANGED
|
@@ -73,7 +73,12 @@ do
|
|
|
73
73
|
if #_buf > _bufMax then table.remove(_buf, 1) end
|
|
74
74
|
|
|
75
75
|
if _console then
|
|
76
|
-
|
|
76
|
+
-- Defer print outside the caller's thread (e.g. Heartbeat) so the
|
|
77
|
+
-- output always reaches the developer console regardless of context.
|
|
78
|
+
local _ln, _nw = line, n >= 3
|
|
79
|
+
task.spawn(function()
|
|
80
|
+
if _nw then warn(_ln) else print(_ln) end
|
|
81
|
+
end)
|
|
77
82
|
end
|
|
78
83
|
for _, fn in ipairs(_subs) do pcall(fn, entry) end
|
|
79
84
|
if _file then
|
package/pulse/runtime.lua
CHANGED
|
@@ -418,43 +418,106 @@ local function _needComp(name)
|
|
|
418
418
|
return _currentComponent
|
|
419
419
|
end
|
|
420
420
|
|
|
421
|
-
|
|
421
|
+
-- ── Sandboxing-safe event runner ─────────────────────────────────────────────
|
|
422
|
+
-- Executor loadstring() environments sandbox Roblox event :Connect() — calls
|
|
423
|
+
-- from inside the bundle never fire. Fix: store callbacks in plain Lua tables
|
|
424
|
+
-- here; the compiler emits user-scope Roblox connections that call _PulseRun*,
|
|
425
|
+
-- routing events into these runners from the non-sandboxed user script.
|
|
426
|
+
|
|
427
|
+
local _cbSeq = 0
|
|
428
|
+
local function _nextId() _cbSeq = _cbSeq + 1; return _cbSeq end
|
|
429
|
+
|
|
430
|
+
local _HbCbs = {} -- Heartbeat
|
|
431
|
+
local _RsCbs = {} -- RenderStepped
|
|
432
|
+
local _StCbs = {} -- Stepped
|
|
433
|
+
local _CaCbs = {} -- CharacterAdded / respawn
|
|
434
|
+
local _CrCbs = {} -- CharacterRemoving
|
|
435
|
+
local _IbCbs = {} -- InputBegan
|
|
436
|
+
local _IeCbs = {} -- InputEnded
|
|
437
|
+
|
|
438
|
+
-- Mock RBXScriptConnection: removing from the list on :Disconnect().
|
|
439
|
+
local function _mockConn(id, list)
|
|
440
|
+
return { Disconnect = function() list[id] = nil end }
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
local function _runFrameCbs(list, a, b)
|
|
444
|
+
for _, e in pairs(list) do
|
|
445
|
+
if e and e.comp then
|
|
446
|
+
e.comp._rawHb = (e.comp._rawHb or 0) + 1
|
|
447
|
+
local pass = not e.when or e.when()
|
|
448
|
+
if pass and e.every then
|
|
449
|
+
local t = type(e.every) == "table" and e.every() or e.every
|
|
450
|
+
local now = tick()
|
|
451
|
+
if (now - e.last) < t then pass = false else e.last = now end
|
|
452
|
+
end
|
|
453
|
+
if pass then
|
|
454
|
+
e.comp._condHb = (e.comp._condHb or 0) + 1
|
|
455
|
+
if Pulse and Pulse.Log then
|
|
456
|
+
Pulse.Log.throttle(e.comp._name, 5, "debug", "tick (condition passed)")
|
|
457
|
+
end
|
|
458
|
+
local ok, err = pcall(e.cb, a, b)
|
|
459
|
+
if not ok then
|
|
460
|
+
warn("[Pulse] "..tostring(e.comp._name).." error: "..tostring(err))
|
|
461
|
+
if Pulse and Pulse.Log then
|
|
462
|
+
Pulse.Log.error(e.comp._name, "event error: "..tostring(err))
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
local function _runEventCbs(list, a, b)
|
|
471
|
+
for _, e in pairs(list) do
|
|
472
|
+
if e then pcall(e.cb, a, b) end
|
|
473
|
+
end
|
|
474
|
+
end
|
|
422
475
|
|
|
423
|
-
|
|
476
|
+
-- Public runners — called by compiler-generated user-scope connections.
|
|
477
|
+
_PulseRunHeartbeat = function(a,b) _runFrameCbs(_HbCbs, a,b) end
|
|
478
|
+
_PulseRunRenderStepped = function(a,b) _runFrameCbs(_RsCbs, a,b) end
|
|
479
|
+
_PulseRunStepped = function(a,b) _runFrameCbs(_StCbs, a,b) end
|
|
480
|
+
_PulseRunCharAdded = function(c) _runEventCbs(_CaCbs, c) end
|
|
481
|
+
_PulseRunCharRemoving = function(c) _runEventCbs(_CrCbs, c) end
|
|
482
|
+
_PulseRunInputBegan = function(i,g) _runEventCbs(_IbCbs, i,g) end
|
|
483
|
+
_PulseRunInputEnded = function(i,g) _runEventCbs(_IeCbs, i,g) end
|
|
484
|
+
|
|
485
|
+
local function _hbBind(list, optsOrFn, fn)
|
|
424
486
|
local comp = _needComp("event")
|
|
425
487
|
local opts, cb
|
|
426
488
|
if type(optsOrFn) == "function" then cb=optsOrFn; opts={}
|
|
427
489
|
else opts=optsOrFn; cb=fn end
|
|
428
|
-
local
|
|
429
|
-
comp
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
local now=tick(); if (now-last)<t then return end; last=now
|
|
434
|
-
end
|
|
435
|
-
-- Throttled trace: confirms heartbeat is running when condition passes.
|
|
436
|
-
if Pulse and Pulse.Log then
|
|
437
|
-
Pulse.Log.throttle(comp._name, 5, "debug", "tick (condition passed)")
|
|
438
|
-
end
|
|
439
|
-
local ok,err=pcall(cb,a,b)
|
|
440
|
-
if not ok then
|
|
441
|
-
warn("[Pulse] "..comp._name.." event error: "..tostring(err))
|
|
442
|
-
if Pulse and Pulse.Log then
|
|
443
|
-
Pulse.Log.error(comp._name, "tick error: "..tostring(err))
|
|
444
|
-
end
|
|
445
|
-
end
|
|
446
|
-
end))
|
|
490
|
+
local id = _nextId()
|
|
491
|
+
comp._rawHb = comp._rawHb or 0
|
|
492
|
+
comp._condHb = comp._condHb or 0
|
|
493
|
+
list[id] = { comp=comp, when=opts.when, every=opts.every, last=0, cb=cb }
|
|
494
|
+
comp:bind("ev_"..id, _mockConn(id, list))
|
|
447
495
|
end
|
|
448
496
|
|
|
449
497
|
local _on = {}
|
|
450
|
-
_on.heartbeat = function(a,b) _hbBind(
|
|
451
|
-
_on.renderStepped = function(a,b) _hbBind(
|
|
452
|
-
_on.stepped = function(a,b) _hbBind(
|
|
453
|
-
_on.inputBegan = function(fn)
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
_on.
|
|
498
|
+
_on.heartbeat = function(a,b) _hbBind(_HbCbs, a,b) end
|
|
499
|
+
_on.renderStepped = function(a,b) _hbBind(_RsCbs, a,b) end
|
|
500
|
+
_on.stepped = function(a,b) _hbBind(_StCbs, a,b) end
|
|
501
|
+
_on.inputBegan = function(fn)
|
|
502
|
+
local c=_needComp("inputBegan"); local id=_nextId()
|
|
503
|
+
_IbCbs[id]={cb=fn}; c:bind("ib_"..id, _mockConn(id,_IbCbs))
|
|
504
|
+
end
|
|
505
|
+
_on.inputEnded = function(fn)
|
|
506
|
+
local c=_needComp("inputEnded"); local id=_nextId()
|
|
507
|
+
_IeCbs[id]={cb=fn}; c:bind("ie_"..id, _mockConn(id,_IeCbs))
|
|
508
|
+
end
|
|
509
|
+
_on.characterAdded = function(fn)
|
|
510
|
+
local c=_needComp("characterAdded"); local id=_nextId()
|
|
511
|
+
_CaCbs[id]={cb=fn}; c:bind("ca_"..id, _mockConn(id,_CaCbs))
|
|
512
|
+
end
|
|
513
|
+
_on.characterRemoving = function(fn)
|
|
514
|
+
local c=_needComp("characterRemoving"); local id=_nextId()
|
|
515
|
+
_CrCbs[id]={cb=fn}; c:bind("cr_"..id, _mockConn(id,_CrCbs))
|
|
516
|
+
end
|
|
517
|
+
_on.respawn = function(fn)
|
|
518
|
+
local c=_needComp("respawn"); local id=_nextId()
|
|
519
|
+
_CaCbs[id]={cb=fn}; c:bind("__respawn_"..c._name, _mockConn(id,_CaCbs))
|
|
520
|
+
end
|
|
458
521
|
_on.signal = function(sig,fn)
|
|
459
522
|
local comp = _needComp("signal")
|
|
460
523
|
comp:watch(sig, function(v)
|
|
@@ -464,7 +527,7 @@ _on.signal = function(sig,fn)
|
|
|
464
527
|
fn(v)
|
|
465
528
|
end)
|
|
466
529
|
end
|
|
467
|
-
_on.after = function(s,fn)
|
|
530
|
+
_on.after = function(s,fn) _needComp("after"):task(s,fn) end
|
|
468
531
|
|
|
469
532
|
local function _defineComponent(name, setup)
|
|
470
533
|
local comp = Component(name)
|
|
@@ -490,6 +553,7 @@ local function _groupbox(side, title, opts)
|
|
|
490
553
|
return { type="groupbox", side=side, title=title,
|
|
491
554
|
icon=(opts and opts.icon) or "",
|
|
492
555
|
mount=(opts and opts.mount) or nil,
|
|
556
|
+
plain=(opts and opts.plain) or false,
|
|
493
557
|
widgets=(opts and opts.widgets) or {} }
|
|
494
558
|
end
|
|
495
559
|
|