pulse-rb 1.4.1 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/windui.lua +66 -66
- package/dist/index.js +22 -69
- package/package.json +1 -1
- package/pulse/runtime.lua +4 -14
package/adapters/windui.lua
CHANGED
|
@@ -2,23 +2,36 @@
|
|
|
2
2
|
-- Wind UI adapter for Pulse components.
|
|
3
3
|
-- Docs: footagesus.github.io/treehub-web/docs/windui
|
|
4
4
|
-- Version: 1.6.64-fix (pinned)
|
|
5
|
-
|
|
6
|
-
--
|
|
7
|
-
--
|
|
8
|
-
--
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
--
|
|
6
|
+
-- Supports two load modes:
|
|
7
|
+
-- 1. Combined (rb publish): inlined into pulse.lua — _PULSE_LAYOUT, Components, _Pages, Pulse
|
|
8
|
+
-- are already in scope as upvalues. publish.ts strips everything above [PULSE_INLINE_START].
|
|
9
|
+
-- 2. Separate (legacy): loaded via its own loadstring(_P, _PULSE_LAYOUT) call.
|
|
10
|
+
-- _P carries the bundle table; we spread it into the local env.
|
|
11
|
+
local _isSeparate = type(select(1,...)) == "table"
|
|
12
|
+
if _isSeparate then
|
|
13
|
+
local _P = select(1,...)
|
|
14
|
+
local _PULSE_LAYOUT = select(2,...) or {}
|
|
11
15
|
local _env = (getfenv and getfenv(1)) or _ENV or _G
|
|
12
16
|
for _k, _v in pairs(_P) do _env[_k] = _v end
|
|
13
17
|
end
|
|
14
18
|
_PULSE_LAYOUT = _PULSE_LAYOUT or {}
|
|
15
19
|
|
|
16
|
-
--
|
|
17
|
-
--
|
|
18
|
-
--
|
|
19
|
-
|
|
20
|
-
local
|
|
21
|
-
local
|
|
20
|
+
-- [PULSE_INLINE_START]
|
|
21
|
+
-- Everything below this marker is included verbatim when bundled inline.
|
|
22
|
+
-- In combined mode: Components, _Pages, Pulse are direct upvalues from runtime.lua.
|
|
23
|
+
-- In separate mode: they were spread into the local env by the block above.
|
|
24
|
+
local _BundleComponents = Components or {}
|
|
25
|
+
local _BundlePages = _Pages or {}
|
|
26
|
+
local _BundlePulse = Pulse or {}
|
|
27
|
+
|
|
28
|
+
-- Auto-enable dev logging when layout.dev = true.
|
|
29
|
+
-- Runs synchronously (before any task.spawn) so all UI setup logs are captured.
|
|
30
|
+
if _PULSE_LAYOUT.dev and _BundlePulse and _BundlePulse.Log then
|
|
31
|
+
_BundlePulse.Log.enable()
|
|
32
|
+
_BundlePulse.Log.configure({ level = "debug", console = true })
|
|
33
|
+
_BundlePulse.Log.info("dev", "dev build active")
|
|
34
|
+
end
|
|
22
35
|
|
|
23
36
|
local _WINDUI_URL = "https://github.com/Footagesus/WindUI/releases/download/1.6.64-fix/main.lua"
|
|
24
37
|
local _PULSE_LOGO = "https://pulse-rb.vercel.app/img/logo.svg"
|
|
@@ -589,13 +602,12 @@ task.spawn(function()
|
|
|
589
602
|
local _execName = "Unknown"
|
|
590
603
|
local _execVer = ""
|
|
591
604
|
pcall(function()
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
if ok and n then _execName = tostring(n) end
|
|
605
|
+
-- pcall each call directly — avoids rawget missing __index-proxied executor globals
|
|
606
|
+
local ok, n, v = pcall(function() return identifyexecutor() end)
|
|
607
|
+
if ok and n then _execName = tostring(n); _execVer = tostring(v or "") end
|
|
608
|
+
if _execName == "Unknown" then
|
|
609
|
+
local ok2, n2 = pcall(function() return getexecutorname() end)
|
|
610
|
+
if ok2 and n2 then _execName = tostring(n2) end
|
|
599
611
|
end
|
|
600
612
|
if _execName == "Unknown" then
|
|
601
613
|
if rawget(_G, "syn") or rawget(_G, "Synapse") then _execName = "Synapse X"
|
|
@@ -1051,12 +1063,6 @@ task.spawn(function()
|
|
|
1051
1063
|
})
|
|
1052
1064
|
end)
|
|
1053
1065
|
|
|
1054
|
-
-- Auto-scan on inject
|
|
1055
|
-
task.spawn(function()
|
|
1056
|
-
if not game:IsLoaded() then game.Loaded:Wait() end
|
|
1057
|
-
task.wait(2)
|
|
1058
|
-
_doScan()
|
|
1059
|
-
end)
|
|
1060
1066
|
end
|
|
1061
1067
|
|
|
1062
1068
|
-- Info section — framework version + signal counts for debugging
|
|
@@ -1086,47 +1092,6 @@ task.spawn(function()
|
|
|
1086
1092
|
end
|
|
1087
1093
|
end
|
|
1088
1094
|
|
|
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
|
-
|
|
1130
1095
|
-- Console section — controls for the Roblox developer console (F9).
|
|
1131
1096
|
-- "Clear" wipes the output so only your next prints are visible.
|
|
1132
1097
|
-- "Print Logs" re-dumps all buffered Pulse.Log entries so you can read
|
|
@@ -1172,6 +1137,17 @@ task.spawn(function()
|
|
|
1172
1137
|
print("─────────────────────────────────────────")
|
|
1173
1138
|
end
|
|
1174
1139
|
|
|
1140
|
+
pcall(function()
|
|
1141
|
+
_consoleSect:Button({
|
|
1142
|
+
Title = "Open Console",
|
|
1143
|
+
Desc = "Show the Roblox developer console (F9)",
|
|
1144
|
+
Callback = function()
|
|
1145
|
+
pcall(function()
|
|
1146
|
+
game:GetService("StarterGui"):SetCore("DevConsoleVisible", true)
|
|
1147
|
+
end)
|
|
1148
|
+
end,
|
|
1149
|
+
})
|
|
1150
|
+
end)
|
|
1175
1151
|
pcall(function()
|
|
1176
1152
|
_consoleSect:Button({
|
|
1177
1153
|
Title = "Clear Console",
|
|
@@ -1207,6 +1183,30 @@ task.spawn(function()
|
|
|
1207
1183
|
end
|
|
1208
1184
|
end
|
|
1209
1185
|
|
|
1186
|
+
-- Apply defaults and notify ready — runs after all widgets are created so
|
|
1187
|
+
-- Toggles/Options are populated and SetValue calls land correctly.
|
|
1188
|
+
task.spawn(function()
|
|
1189
|
+
task.wait(0.5)
|
|
1190
|
+
for _, d in ipairs(_PULSE_DEFAULTS) do
|
|
1191
|
+
if d.set then pcall(d.set, d.value) end
|
|
1192
|
+
if d.type == "toggle" then
|
|
1193
|
+
pcall(function()
|
|
1194
|
+
local t = Toggles and Toggles[d.id]
|
|
1195
|
+
if t then t:SetValue(d.value) end
|
|
1196
|
+
end)
|
|
1197
|
+
else
|
|
1198
|
+
pcall(function()
|
|
1199
|
+
local t = Options and Options[d.id]
|
|
1200
|
+
if t then t:SetValue(d.value) end
|
|
1201
|
+
end)
|
|
1202
|
+
end
|
|
1203
|
+
task.wait(0.05)
|
|
1204
|
+
end
|
|
1205
|
+
if #_PULSE_DEFAULTS > 0 and _PulseNotify then
|
|
1206
|
+
_PulseNotify((L.title or "Hub") .. " loaded!", 5)
|
|
1207
|
+
end
|
|
1208
|
+
end)
|
|
1209
|
+
|
|
1210
1210
|
-- Re-select first tab (Home) — Settings is last so Wind UI leaves it active
|
|
1211
1211
|
task.defer(function()
|
|
1212
1212
|
if _UIAdapter._firstTab then
|
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.3";
|
|
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");
|
|
@@ -1110,7 +1110,7 @@ function rglob(dir, exts) {
|
|
|
1110
1110
|
walk(dir);
|
|
1111
1111
|
return results;
|
|
1112
1112
|
}
|
|
1113
|
-
var PULSE_DIR, PULSE_DEV_DIR, PULSE_UI_DIR, REEXEC_GUARD, DESTROY_REGISTRATION,
|
|
1113
|
+
var PULSE_DIR, PULSE_DEV_DIR, PULSE_UI_DIR, REEXEC_GUARD, DESTROY_REGISTRATION, Compiler;
|
|
1114
1114
|
var init_compiler = __esm({
|
|
1115
1115
|
"src/compiler.ts"() {
|
|
1116
1116
|
init_cjs_shims();
|
|
@@ -1130,43 +1130,6 @@ if _G.__AOT_R_DESTROY then pcall(_G.__AOT_R_DESTROY) end
|
|
|
1130
1130
|
`;
|
|
1131
1131
|
DESTROY_REGISTRATION = `-- Register this instance so the next re-execution can clean it up.
|
|
1132
1132
|
_G.__AOT_R_DESTROY = _PulseDestroy
|
|
1133
|
-
`;
|
|
1134
|
-
DEFAULTS_RUNNER = `task.spawn(function()
|
|
1135
|
-
task.wait(0.5)
|
|
1136
|
-
for _, d in ipairs(_PULSE_DEFAULTS) do
|
|
1137
|
-
local effectiveValue = d.value
|
|
1138
|
-
if d.type == "toggle" and Pulse.TestMode and Pulse.TestMode.isActive() then
|
|
1139
|
-
if not Pulse.TestMode.isTarget(d.id) then
|
|
1140
|
-
effectiveValue = false
|
|
1141
|
-
end
|
|
1142
|
-
end
|
|
1143
|
-
if d.set then pcall(d.set, effectiveValue) end
|
|
1144
|
-
if d.type == "toggle" then
|
|
1145
|
-
pcall(function()
|
|
1146
|
-
local t = Toggles and Toggles[d.id]
|
|
1147
|
-
if t then t:SetValue(effectiveValue) end
|
|
1148
|
-
end)
|
|
1149
|
-
else
|
|
1150
|
-
pcall(function()
|
|
1151
|
-
local t = Options and Options[d.id]
|
|
1152
|
-
if t then t:SetValue(d.value) end
|
|
1153
|
-
end)
|
|
1154
|
-
end
|
|
1155
|
-
task.wait(0.05)
|
|
1156
|
-
end
|
|
1157
|
-
if #_PULSE_DEFAULTS > 0 then
|
|
1158
|
-
local _notifyMsg = "AOT:Resistance Hub ready!"
|
|
1159
|
-
if Pulse.TestMode and Pulse.TestMode.isActive() then
|
|
1160
|
-
local _tt = Pulse.TestMode.getTargets()
|
|
1161
|
-
if #_tt > 0 then
|
|
1162
|
-
_notifyMsg = "Test mode: " .. table.concat(_tt, ", ")
|
|
1163
|
-
end
|
|
1164
|
-
end
|
|
1165
|
-
if _PulseNotify then
|
|
1166
|
-
_PulseNotify(_notifyMsg, 5)
|
|
1167
|
-
end
|
|
1168
|
-
end
|
|
1169
|
-
end)
|
|
1170
1133
|
`;
|
|
1171
1134
|
Compiler = class {
|
|
1172
1135
|
root;
|
|
@@ -1363,14 +1326,8 @@ end)
|
|
|
1363
1326
|
if (opts.dev) parts.push(`,dev=true`);
|
|
1364
1327
|
parts.push(`}
|
|
1365
1328
|
`);
|
|
1366
|
-
parts.push(`local _P=loadstring(game:HttpGet("${base}/
|
|
1367
|
-
`);
|
|
1368
|
-
if (opts.dev) {
|
|
1369
|
-
parts.push(`_PULSE_DEV = true
|
|
1329
|
+
parts.push(`local _P=loadstring(game:HttpGet("${base}/pulse.lua"))(_PULSE_LAYOUT)
|
|
1370
1330
|
`);
|
|
1371
|
-
parts.push(`if _P.Pulse and _P.Pulse.Log then _P.Pulse.Log.enable() end
|
|
1372
|
-
`);
|
|
1373
|
-
}
|
|
1374
1331
|
parts.push(`for _k,_v in pairs(_P) do _G[_k]=_v end
|
|
1375
1332
|
`);
|
|
1376
1333
|
parts.push(
|
|
@@ -1388,8 +1345,6 @@ end)
|
|
|
1388
1345
|
end
|
|
1389
1346
|
`
|
|
1390
1347
|
);
|
|
1391
|
-
parts.push(`local _A=loadstring(game:HttpGet("${base}/adapters/${ui}.lua"))(_P,_PULSE_LAYOUT)
|
|
1392
|
-
`);
|
|
1393
1348
|
parts.push(`local signal,computed,defineComponent,on=_P.signal,_P.computed,_P.defineComponent,_P.on
|
|
1394
1349
|
`);
|
|
1395
1350
|
parts.push(`local toggle,slider,dropdown,multidropdown=_P.toggle,_P.slider,_P.dropdown,_P.multidropdown
|
|
@@ -1465,16 +1420,6 @@ end
|
|
|
1465
1420
|
}
|
|
1466
1421
|
parts.push("\n");
|
|
1467
1422
|
}
|
|
1468
|
-
if (order.some((f) => f.endsWith(".rblua") || f.endsWith(".ts"))) {
|
|
1469
|
-
parts.push("-- [generated: defaults runner]\n");
|
|
1470
|
-
parts.push(DEFAULTS_RUNNER);
|
|
1471
|
-
parts.push("\n");
|
|
1472
|
-
}
|
|
1473
|
-
if (opts.dev) {
|
|
1474
|
-
parts.push(
|
|
1475
|
-
'-- [dev: log config]\nif Pulse and Pulse.Log then\n Pulse.Log.configure({ level = "debug", console = true })\n Pulse.Log.info("dev", "dev build active")\nend\n\n'
|
|
1476
|
-
);
|
|
1477
|
-
}
|
|
1478
1423
|
parts.push("-- [generated: destroy registration]\n");
|
|
1479
1424
|
parts.push(DESTROY_REGISTRATION);
|
|
1480
1425
|
parts.push("\n");
|
|
@@ -1614,7 +1559,12 @@ async function obfuscateSource(source, ib2Dir) {
|
|
|
1614
1559
|
if (stdout.startsWith("ERR:")) throw new Error(stdout.slice(4).trim());
|
|
1615
1560
|
if (result.status !== 0) throw new Error(stderr || stdout || `exit ${result.status}`);
|
|
1616
1561
|
if (!fs.existsSync(outPath)) throw new Error("IronBrew2 CLI produced no output file");
|
|
1617
|
-
|
|
1562
|
+
let out = fs.readFileSync(outPath, "utf8");
|
|
1563
|
+
if (out.startsWith("--[[")) {
|
|
1564
|
+
const closeIdx = out.indexOf("]]");
|
|
1565
|
+
if (closeIdx !== -1) out = out.slice(closeIdx + 2).replace(/^\r?\n/, "");
|
|
1566
|
+
}
|
|
1567
|
+
return out;
|
|
1618
1568
|
} finally {
|
|
1619
1569
|
try {
|
|
1620
1570
|
__require("fs").unlinkSync(tmpIn);
|
|
@@ -2997,7 +2947,9 @@ async function cmdPublish(_args) {
|
|
|
2997
2947
|
process.exit(1);
|
|
2998
2948
|
}
|
|
2999
2949
|
const bundleParts = [
|
|
3000
|
-
`-- Pulse v${RB_VERSION}
|
|
2950
|
+
`-- Pulse v${RB_VERSION} (runtime + helpers + UI adapter)`,
|
|
2951
|
+
`-- Single loadstring \u2014 no executor sandbox split between runtime and UI.`,
|
|
2952
|
+
`local _PULSE_LAYOUT = ... or {}`,
|
|
3001
2953
|
fs.readFileSync(runtimeFile, "utf8").trimEnd()
|
|
3002
2954
|
];
|
|
3003
2955
|
if (fs.existsSync(helpersDir)) {
|
|
@@ -3006,21 +2958,22 @@ async function cmdPublish(_args) {
|
|
|
3006
2958
|
bundleParts.push(fs.readFileSync(pathe.join(helpersDir, f), "utf8").trimEnd());
|
|
3007
2959
|
}
|
|
3008
2960
|
}
|
|
2961
|
+
const winduiPath = pathe.join(ADAPTERS, "windui.lua");
|
|
2962
|
+
if (fs.existsSync(winduiPath)) {
|
|
2963
|
+
const winduiSrc = fs.readFileSync(winduiPath, "utf8");
|
|
2964
|
+
const markerTag = "-- [PULSE_INLINE_START]";
|
|
2965
|
+
const markerIdx = winduiSrc.indexOf(markerTag);
|
|
2966
|
+
const winduiCore = markerIdx >= 0 ? winduiSrc.slice(markerIdx + markerTag.length).trimStart() : winduiSrc;
|
|
2967
|
+
bundleParts.push("-- [windui adapter inline]");
|
|
2968
|
+
bundleParts.push(winduiCore.trimEnd());
|
|
2969
|
+
}
|
|
3009
2970
|
bundleParts.push(
|
|
3010
2971
|
"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}"
|
|
3011
2972
|
);
|
|
3012
2973
|
const bundleContent = Buffer.from(bundleParts.join("\n") + "\n", "utf8");
|
|
3013
2974
|
const uploads = [
|
|
3014
|
-
{ remote: `${VERSION_PATH}/
|
|
2975
|
+
{ remote: `${VERSION_PATH}/pulse.lua`, content: bundleContent }
|
|
3015
2976
|
];
|
|
3016
|
-
for (const adapter of ["windui"]) {
|
|
3017
|
-
const p = pathe.join(ADAPTERS, `${adapter}.lua`);
|
|
3018
|
-
if (!fs.existsSync(p)) continue;
|
|
3019
|
-
uploads.push({
|
|
3020
|
-
remote: `${VERSION_PATH}/adapters/${adapter}.lua`,
|
|
3021
|
-
content: Buffer.from(fs.readFileSync(p, "utf8"), "utf8")
|
|
3022
|
-
});
|
|
3023
|
-
}
|
|
3024
2977
|
pSection(`Uploading to R2 ${gray("(v" + RB_VERSION + " \xB7 " + uploads.length + " files)")}`);
|
|
3025
2978
|
for (const { remote, content } of uploads) {
|
|
3026
2979
|
try {
|
package/package.json
CHANGED
package/pulse/runtime.lua
CHANGED
|
@@ -186,17 +186,6 @@ local function Component(name)
|
|
|
186
186
|
end)
|
|
187
187
|
end
|
|
188
188
|
|
|
189
|
-
-- Respawn listener (bound to connection tracker)
|
|
190
|
-
function c:onRespawn(fn)
|
|
191
|
-
local key = "__respawn_" .. name
|
|
192
|
-
local existing = self._connections[key]
|
|
193
|
-
if existing then
|
|
194
|
-
pcall(function() existing:Disconnect() end)
|
|
195
|
-
end
|
|
196
|
-
self._connections[key] = game:GetService("Players").LocalPlayer
|
|
197
|
-
.CharacterAdded:Connect(fn)
|
|
198
|
-
end
|
|
199
|
-
|
|
200
189
|
-- Lifecycle
|
|
201
190
|
function c:mount()
|
|
202
191
|
if self._mounted then return end
|
|
@@ -354,10 +343,11 @@ local function _signal(default)
|
|
|
354
343
|
_sigCounter = _sigCounter + 1
|
|
355
344
|
local s = Signal(default)
|
|
356
345
|
s._id = ((_currentComponent and _currentComponent._name) or "g") .. "_s" .. _sigCounter
|
|
357
|
-
s.set
|
|
358
|
-
s.
|
|
346
|
+
local _rawSet = s.set -- capture original before any override to avoid infinite recursion
|
|
347
|
+
s.set = function(_, v) _rawSet(s, v) end
|
|
348
|
+
s.toggle = function(_) _rawSet(s, not s:get()) end
|
|
359
349
|
s.watch = function(_, fn) return s:onChange(fn) end
|
|
360
|
-
s.update = function(_, fn) s
|
|
350
|
+
s.update = function(_, fn) _rawSet(s, fn(s:get())) end
|
|
361
351
|
return s
|
|
362
352
|
end
|
|
363
353
|
|