From 71d155e947796eb62b6a97b24b463f2e0365614c Mon Sep 17 00:00:00 2001 From: littensy <56808540+littensy@users.noreply.github.com> Date: Sun, 19 Apr 2026 18:46:17 -0700 Subject: [PATCH] chore: housekeeping --- .luaurc | 7 +- .lute/build.luau | 2 +- .lute/check.luau | 4 +- benches/bench.luau | 4 +- packages/charm-sync/src/client.luau | 4 +- packages/charm-sync/src/server.luau | 4 +- packages/charm-sync/src/validate.luau | 9 ++- packages/charm/src/init.luau | 95 +++++++++------------------ packages/charm/src/system.luau | 2 +- packages/deep-charm/src/init.luau | 2 +- rokit.toml | 2 +- 11 files changed, 49 insertions(+), 86 deletions(-) diff --git a/.luaurc b/.luaurc index 91ac9c9..c48b0f2 100644 --- a/.luaurc +++ b/.luaurc @@ -1,10 +1,7 @@ { "languageMode": "strict", "aliases": { - "lute": "~/.lute/typedefs/0.1.0/lute", - "std": "~/.lute/typedefs/0.1.0/std" - }, - "lint": { - "LocalShadow": false + "lute": "~/.lute/typedefs/1.0.0/lute", + "std": "~/.lute/typedefs/1.0.0/std" } } diff --git a/.lute/build.luau b/.lute/build.luau index f1b5876..b708003 100644 --- a/.lute/build.luau +++ b/.lute/build.luau @@ -1,6 +1,6 @@ local fs = require("@std/fs") local process = require("@lute/process") -fs.createdirectory("build", { makeparents = true }) +fs.createDirectory("build") process.run({ "rojo", "build", "--output=build/charm.rbxm", "build.project.json" }, { stdio = "inherit" }) diff --git a/.lute/check.luau b/.lute/check.luau index ef48a74..2c4ce7d 100644 --- a/.lute/check.luau +++ b/.lute/check.luau @@ -1,10 +1,10 @@ local fs = require("@std/fs") -local net = require("@lute/net") +local net = require("@std/net") local process = require("@lute/process") process.run({ "rojo", "sourcemap", "--output=sourcemap.json" }, { stdio = "inherit" }) -fs.writestringtofile( +fs.writeStringToFile( "roblox.d.luau", net.request("https://luau-lsp.pages.dev/globalTypes.None.d.luau", { method = "GET" }).body ) diff --git a/benches/bench.luau b/benches/bench.luau index 9c24c6e..ffb839d 100644 --- a/benches/bench.luau +++ b/benches/bench.luau @@ -20,8 +20,8 @@ local function run(name: string, test: () -> ()) table.insert(results, `{name}: \t{string.format("%.2f", elapsed / iter * 1000 * 1000)} us/iter`) - for name, elapsed in times do - table.insert(results, `{name}: \t{string.format("%.2f", elapsed / iter * 1000 * 1000)} us/iter`) + for name, duration in times do + table.insert(results, `{name}: \t{string.format("%.2f", duration / iter * 1000 * 1000)} us/iter`) end print(table.concat(results, " | ")) diff --git a/packages/charm-sync/src/client.luau b/packages/charm-sync/src/client.luau index 7b51359..a514b75 100644 --- a/packages/charm-sync/src/client.luau +++ b/packages/charm-sync/src/client.luau @@ -90,7 +90,7 @@ end @param payloads The state updates received from the server. ]] -local function patch(payloads: { SyncPayload }) +local function applyPatches(payloads: { SyncPayload }) for _, payload in payloads do if payload.type == "init" then Charm.batch(applyInitPayload, payload.data) @@ -104,5 +104,5 @@ return { addSignals = addSignals, removeSignals = removeSignals, removeAllSignals = removeAllSignals, - patch = patch, + patch = applyPatches, } diff --git a/packages/charm-sync/src/server.luau b/packages/charm-sync/src/server.luau index 0fea67b..ca04ba7 100644 --- a/packages/charm-sync/src/server.luau +++ b/packages/charm-sync/src/server.luau @@ -39,9 +39,9 @@ local syncCallback: ((client: any, payloads: { SyncPayload }) -> ())? support custom implementations of reactive proxies. ]] local function toRaw(value: T): T - local value: any = value + value = value :: types.ReactiveProxy if type(value) == "table" and type(value.__target) == "table" and type(value.__track) == "function" then - (value.__track :: () -> ())() + value.__track() return toRaw(value.__target) end return value diff --git a/packages/charm-sync/src/validate.luau b/packages/charm-sync/src/validate.luau index dcf5904..dfba716 100644 --- a/packages/charm-sync/src/validate.luau +++ b/packages/charm-sync/src/validate.luau @@ -1,11 +1,11 @@ local function isTableUnsafe(object: { [any]: any }) - local key = next(object) + local firstKey = next(object) - if key == nil then + if firstKey == nil then return false end - local keyType = type(key) + local keyType = type(firstKey) local maxKey = 0 -- Keys must all be the same type @@ -35,8 +35,7 @@ end local function validate(value: { [any]: any }) if isTableUnsafe(value) then warn( - `Cannot sync tables unsupported by remote events! See the table below for more info.\n\n` - .. "This can be for the following reasons:\n" + "Cannot sync tables unsupported by remote events! This can be for the following reasons:\n" .. "1. The table is a sparse array with non-sequential indices\n" .. "2. The table is a mixed table (not all keys have the same type)\n\n" .. "Read more: https://create.roblox.com/docs/scripting/events/remote#argument-limitations\n", diff --git a/packages/charm/src/init.luau b/packages/charm/src/init.luau index ec4570c..24a7cd7 100644 --- a/packages/charm/src/init.luau +++ b/packages/charm/src/init.luau @@ -17,10 +17,15 @@ local shallowPropagate = system.shallowPropagate local createReactiveSystem = system.createReactiveSystem export type Getter = () -> T + export type Setter = (update: Update) -> T + export type Atom = (...Update) -> T + export type Update = ((current: T) -> T) | T + export type Equals = (current: T, incoming: T) -> boolean + export type Cleanup = () -> () type EffectScopeNode = ReactiveNode & { @@ -93,20 +98,16 @@ local flags = { } --[[ - Wraps a function to prevent yielding when in strict mode. If the function - was created by this module, it will be excluded to omit unnecessary Charm - internals from stack traces. - - @param callback The user-created function to wrap. - @return A wrapped function that cannot yield in strict mode. + Wraps a user-created function to prevent yielding when in strict mode. To + shorten error stack traces, only functions created outside of this module + are wrapped. ]] local function wrapUserSpace(callback: (Args...) -> Results...): (Args...) -> Results... if not flags.strict then return callback end - -- If the callback is already wrapped, avoid double-wrapping to simplify - -- stack traces + -- If the sources match, the function was created in this module if debug.info(1, "s") == debug.info(callback, "s") then return callback end @@ -135,8 +136,6 @@ end --[[ Recursively freezes a table and all nested tables to enforce immutability. Tables with metatables are skipped, as they are assumed to be mutable. - - @param value Table to freeze ]] local function deepFreeze(value: any) if type(value) == "table" and not table.isfrozen(value) and getmetatable(value) == nil then @@ -149,8 +148,6 @@ end --[[ Returns the currently active subscriber (effect or computed node). - - @return Current active subscriber or `nil` ]] local function getActiveSub(): ReactiveNode? return activeSub @@ -159,9 +156,6 @@ end --[[ Sets the currently active subscriber (effect or computed node) and returns the previous subscriber. - - @param sub New subscriber to set as active - @return Previous active subscriber ]] local function setActiveSub(sub: ReactiveNode?) local prevSub = activeSub @@ -173,8 +167,6 @@ end Removes dependencies from the subscriber's dependency list. When called after tracking is complete, this ensures that only currently tracked dependencies remain linked. - - @param sub Effect or computed node ]] local function purgeDeps(sub: ReactiveNode) local depsTail = sub.depsTail @@ -186,13 +178,12 @@ end --[[ Runs all cleanup functions registered with the effect node. Any errors - thrown during cleanup are caught and logged as warnings. - - @param cleanups List of cleanup functions + thrown during cleanup are caught and logged in a single error after all + cleanups are complete. ]] local function runCleanups(cleanups: { Cleanup }) local hasErrors = false - local errors = {} + local errors: { string } = {} for index, cleanup in cleanups do cleanups[index] = nil @@ -215,8 +206,6 @@ end --[[ Checks if the effect node needs to run based on its flags and dependencies. Effects run if they are marked dirty or pending with dirty dependencies. - - @param effect Effect node to run ]] local function run(effect: EffectNode) local effectFlags = effect.flags @@ -311,9 +300,6 @@ end --[[ Updates a computed signal by re-evaluating its getter function. Returns whether the computed value changed. - - @param computed Computed node to update - @return Boolean indicating whether the computed value changed ]] local function updateComputed(computed: ComputedNode): boolean cycle += 1 @@ -339,9 +325,6 @@ end --[[ Updates a signal by applying its pending value. Returns whether the signal's value changed. - - @param signal Signal node to update - @return Boolean indicating whether the signal's value changed ]] local function updateSignal(signal: SignalNode): boolean signal.flags = MUTABLE @@ -369,28 +352,23 @@ end Clears all dependencies and subscribers from the effect node. After calling this, the node will no longer be subscribed to any dependencies and will be inactive, with all cleanup functions run and resources released. - - @param node Effect node ]] -local function stopEffect(node: EffectScopeNode) - node.depsTail = nil - node.flags = NONE +local function stopEffect(effect: EffectScopeNode) + effect.depsTail = nil + effect.flags = NONE - purgeDeps(node) + purgeDeps(effect) - local sub = node.subs + local sub = effect.subs if sub then - unlink(sub, node) + unlink(sub, effect) end - runCleanups(node.cleanups) + runCleanups(effect.cleanups) end --[[ Updates a signal or computed value and returns whether the value changed. - - @param node Signal or computed node - @return Boolean indicating whether the value changed ]] local function update(node: ReactiveNode): boolean if node.depsTail then @@ -403,8 +381,6 @@ end --[[ Queues an effect for execution, ensuring inner effects are notified in the correct order. - - @param effect Effect node to notify ]] local function notify(effect: ReactiveNode) local insertIndex = queuedLength @@ -443,8 +419,6 @@ end - Computed signals: Unlink dependencies and mark as dirty for potential future updates - Effects/scopes: Perform full cleanup to free resources - - @param node Reactive node (computed, effect, or effect scope) ]] local function unwatched(node: ReactiveNode) if bit32.band(node.flags, MUTABLE) == 0 then @@ -719,24 +693,24 @@ local function trigger(fn: () -> ...any) local prevSub = setActiveSub(sub) local success, err = pcall(wrapUserSpace(fn)) activeSub = prevSub - - if not success then - error(err, 2) - end + sub.flags = NONE local currentLink = sub.deps while currentLink do local dep = currentLink.dep - currentLink = unlink(currentLink, sub) local subs = dep.subs + currentLink = unlink(currentLink, sub) if subs then - sub.flags = NONE propagate(subs) shallowPropagate(subs) end end flush() + + if not success then + error(err, 2) + end end --[[ @@ -774,11 +748,9 @@ local function untracked(fn: (Args...) -> Results..., ...: local prevSub = setActiveSub(nil) local results: { any } = { pcall(wrapUserSpace(fn), ...) } activeSub = prevSub - if not results[1] then error(results[2], 2) end - return unpack(results, 2) else return fn(...) @@ -798,11 +770,9 @@ local function batch(fn: (Args...) -> Results..., ...: Args startBatch() local results: { any } = { pcall(wrapUserSpace(fn), ...) } endBatch() - if not results[1] then error(results[2], 2) end - return unpack(results, 2) end @@ -810,9 +780,6 @@ end Returns true if the given function is a signal getter, computed signal, or atom function. Used for determining whether a function should be wrapped in a computed signal so that it can be memoized in `listen()` and `subscribe()`. - - @param fn Function to check - @return `true` if `fn` is a signal getter, computed signal, or atom function, `false` otherwise ]] local function isSignal(fn: Getter): boolean local name = debug.info(fn, "n") @@ -830,7 +797,6 @@ end @see https://github.com/littensy/charm?tab=readme-ov-file#listengetter-callback ]] local function listen(getter: Getter, callback: (state: T, lastState: T?) -> ()): Cleanup - local callback = wrapUserSpace(callback) local tracked: Getter = if isSignal(getter) then getter else computed(getter :: (T?) -> T) local current: T @@ -852,7 +818,6 @@ end @see https://github.com/littensy/charm?tab=readme-ov-file#subscribegetter-callback ]] local function subscribe(getter: Getter, callback: (state: T, lastState: T) -> ()): Cleanup - local callback = wrapUserSpace(callback) local tracked: Getter = if isSignal(getter) then getter else computed(getter :: (T?) -> T) local current: T local firstRun = true @@ -883,8 +848,9 @@ end @see https://github.com/littensy/charm?tab=readme-ov-file#observegetter-callback ]] local function observe(getter: Getter<{ [K]: V }>, callback: (item: V, key: K) -> ...Cleanup?): Cleanup - local getter = wrapUserSpace(getter) - local callback = wrapUserSpace(callback) + getter = wrapUserSpace(getter) + callback = wrapUserSpace(callback) + local scopes: { [K]: Cleanup } = {} local stopObserving = false @@ -943,8 +909,9 @@ end @see https://github.com/littensy/charm?tab=readme-ov-file#mappedgetter-transform ]] local function mapped(getter: Getter<{ [KI]: VI }>, transform: (VI, KI) -> (VO, KO)): Getter<{ [KO]: VO }> - local getter = wrapUserSpace(getter) - local transform = wrapUserSpace(transform) + getter = wrapUserSpace(getter) + transform = wrapUserSpace(transform) + local nextInput: { [KI]: VI } = {} local keyMap: { [KI]: KO } = {} diff --git a/packages/charm/src/system.luau b/packages/charm/src/system.luau index 10ad273..787ba02 100644 --- a/packages/charm/src/system.luau +++ b/packages/charm/src/system.luau @@ -102,7 +102,7 @@ local function link(dep: ReactiveNode, sub: ReactiveNode, version: number) end local function unlink(link: Link, sub: ReactiveNode?): Link? - local sub = sub or link.sub + sub = sub or link.sub local dep = link.dep local prevDep = link.prevDep local nextDep = link.nextDep diff --git a/packages/deep-charm/src/init.luau b/packages/deep-charm/src/init.luau index ef9ab05..9eb9943 100644 --- a/packages/deep-charm/src/init.luau +++ b/packages/deep-charm/src/init.luau @@ -36,7 +36,7 @@ end ]] local function toRaw(value: T): T if isReactive(value) then - local value = value :: ReactiveProxy & T + value = value :: ReactiveProxy value.__track() return toRaw(value.__target) end diff --git a/rokit.toml b/rokit.toml index 2473ae6..60a6daf 100644 --- a/rokit.toml +++ b/rokit.toml @@ -4,4 +4,4 @@ rojo = "rojo-rbx/rojo@7.6.1" stylua = "johnnymorganz/stylua@2.4.1" selene = "Kampfkarren/selene@0.30.1" luau-lsp = "johnnymorganz/luau-lsp@1.66.0" -lute = "luau-lang/lute@0.1.0-nightly.20260331" +lute = "luau-lang/lute@1.0.0"