chore: housekeeping

This commit is contained in:
littensy 2026-04-19 18:46:17 -07:00
parent 6a4b2ded3e
commit 71d155e947
11 changed files with 49 additions and 86 deletions

View file

@ -1,10 +1,7 @@
{ {
"languageMode": "strict", "languageMode": "strict",
"aliases": { "aliases": {
"lute": "~/.lute/typedefs/0.1.0/lute", "lute": "~/.lute/typedefs/1.0.0/lute",
"std": "~/.lute/typedefs/0.1.0/std" "std": "~/.lute/typedefs/1.0.0/std"
},
"lint": {
"LocalShadow": false
} }
} }

View file

@ -1,6 +1,6 @@
local fs = require("@std/fs") local fs = require("@std/fs")
local process = require("@lute/process") 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" }) process.run({ "rojo", "build", "--output=build/charm.rbxm", "build.project.json" }, { stdio = "inherit" })

View file

@ -1,10 +1,10 @@
local fs = require("@std/fs") local fs = require("@std/fs")
local net = require("@lute/net") local net = require("@std/net")
local process = require("@lute/process") local process = require("@lute/process")
process.run({ "rojo", "sourcemap", "--output=sourcemap.json" }, { stdio = "inherit" }) process.run({ "rojo", "sourcemap", "--output=sourcemap.json" }, { stdio = "inherit" })
fs.writestringtofile( fs.writeStringToFile(
"roblox.d.luau", "roblox.d.luau",
net.request("https://luau-lsp.pages.dev/globalTypes.None.d.luau", { method = "GET" }).body net.request("https://luau-lsp.pages.dev/globalTypes.None.d.luau", { method = "GET" }).body
) )

View file

@ -20,8 +20,8 @@ local function run(name: string, test: () -> ())
table.insert(results, `{name}: \t{string.format("%.2f", elapsed / iter * 1000 * 1000)} us/iter`) table.insert(results, `{name}: \t{string.format("%.2f", elapsed / iter * 1000 * 1000)} us/iter`)
for name, elapsed in times do for name, duration in times do
table.insert(results, `{name}: \t{string.format("%.2f", elapsed / iter * 1000 * 1000)} us/iter`) table.insert(results, `{name}: \t{string.format("%.2f", duration / iter * 1000 * 1000)} us/iter`)
end end
print(table.concat(results, " | ")) print(table.concat(results, " | "))

View file

@ -90,7 +90,7 @@ end
@param payloads The state updates received from the server. @param payloads The state updates received from the server.
]] ]]
local function patch(payloads: { SyncPayload }) local function applyPatches(payloads: { SyncPayload })
for _, payload in payloads do for _, payload in payloads do
if payload.type == "init" then if payload.type == "init" then
Charm.batch(applyInitPayload, payload.data) Charm.batch(applyInitPayload, payload.data)
@ -104,5 +104,5 @@ return {
addSignals = addSignals, addSignals = addSignals,
removeSignals = removeSignals, removeSignals = removeSignals,
removeAllSignals = removeAllSignals, removeAllSignals = removeAllSignals,
patch = patch, patch = applyPatches,
} }

View file

@ -39,9 +39,9 @@ local syncCallback: ((client: any, payloads: { SyncPayload }) -> ())?
support custom implementations of reactive proxies. support custom implementations of reactive proxies.
]] ]]
local function toRaw<T>(value: T): T local function toRaw<T>(value: T): T
local value: any = value value = value :: types.ReactiveProxy<T>
if type(value) == "table" and type(value.__target) == "table" and type(value.__track) == "function" then if type(value) == "table" and type(value.__target) == "table" and type(value.__track) == "function" then
(value.__track :: () -> ())() value.__track()
return toRaw(value.__target) return toRaw(value.__target)
end end
return value return value

View file

@ -1,11 +1,11 @@
local function isTableUnsafe(object: { [any]: any }) local function isTableUnsafe(object: { [any]: any })
local key = next(object) local firstKey = next(object)
if key == nil then if firstKey == nil then
return false return false
end end
local keyType = type(key) local keyType = type(firstKey)
local maxKey = 0 local maxKey = 0
-- Keys must all be the same type -- Keys must all be the same type
@ -35,8 +35,7 @@ end
local function validate(value: { [any]: any }) local function validate(value: { [any]: any })
if isTableUnsafe(value) then if isTableUnsafe(value) then
warn( warn(
`Cannot sync tables unsupported by remote events! See the table below for more info.\n\n` "Cannot sync tables unsupported by remote events! This can be for the following reasons:\n"
.. "This can be for the following reasons:\n"
.. "1. The table is a sparse array with non-sequential indices\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" .. "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", .. "Read more: https://create.roblox.com/docs/scripting/events/remote#argument-limitations\n",

View file

@ -17,10 +17,15 @@ local shallowPropagate = system.shallowPropagate
local createReactiveSystem = system.createReactiveSystem local createReactiveSystem = system.createReactiveSystem
export type Getter<T> = () -> T export type Getter<T> = () -> T
export type Setter<T> = (update: Update<T>) -> T export type Setter<T> = (update: Update<T>) -> T
export type Atom<T = any> = (...Update<T>) -> T export type Atom<T = any> = (...Update<T>) -> T
export type Update<T> = ((current: T) -> T) | T export type Update<T> = ((current: T) -> T) | T
export type Equals<T> = (current: T, incoming: T) -> boolean export type Equals<T> = (current: T, incoming: T) -> boolean
export type Cleanup = () -> () export type Cleanup = () -> ()
type EffectScopeNode = ReactiveNode & { type EffectScopeNode = ReactiveNode & {
@ -93,20 +98,16 @@ local flags = {
} }
--[[ --[[
Wraps a function to prevent yielding when in strict mode. If the function Wraps a user-created function to prevent yielding when in strict mode. To
was created by this module, it will be excluded to omit unnecessary Charm shorten error stack traces, only functions created outside of this module
internals from stack traces. are wrapped.
@param callback The user-created function to wrap.
@return A wrapped function that cannot yield in strict mode.
]] ]]
local function wrapUserSpace<Args..., Results...>(callback: (Args...) -> Results...): (Args...) -> Results... local function wrapUserSpace<Args..., Results...>(callback: (Args...) -> Results...): (Args...) -> Results...
if not flags.strict then if not flags.strict then
return callback return callback
end end
-- If the callback is already wrapped, avoid double-wrapping to simplify -- If the sources match, the function was created in this module
-- stack traces
if debug.info(1, "s") == debug.info(callback, "s") then if debug.info(1, "s") == debug.info(callback, "s") then
return callback return callback
end end
@ -135,8 +136,6 @@ end
--[[ --[[
Recursively freezes a table and all nested tables to enforce immutability. Recursively freezes a table and all nested tables to enforce immutability.
Tables with metatables are skipped, as they are assumed to be mutable. Tables with metatables are skipped, as they are assumed to be mutable.
@param value Table to freeze
]] ]]
local function deepFreeze(value: any) local function deepFreeze(value: any)
if type(value) == "table" and not table.isfrozen(value) and getmetatable(value) == nil then 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). Returns the currently active subscriber (effect or computed node).
@return Current active subscriber or `nil`
]] ]]
local function getActiveSub(): ReactiveNode? local function getActiveSub(): ReactiveNode?
return activeSub return activeSub
@ -159,9 +156,6 @@ end
--[[ --[[
Sets the currently active subscriber (effect or computed node) and returns Sets the currently active subscriber (effect or computed node) and returns
the previous subscriber. the previous subscriber.
@param sub New subscriber to set as active
@return Previous active subscriber
]] ]]
local function setActiveSub(sub: ReactiveNode?) local function setActiveSub(sub: ReactiveNode?)
local prevSub = activeSub local prevSub = activeSub
@ -173,8 +167,6 @@ end
Removes dependencies from the subscriber's dependency list. When called Removes dependencies from the subscriber's dependency list. When called
after tracking is complete, this ensures that only currently tracked after tracking is complete, this ensures that only currently tracked
dependencies remain linked. dependencies remain linked.
@param sub Effect or computed node
]] ]]
local function purgeDeps(sub: ReactiveNode) local function purgeDeps(sub: ReactiveNode)
local depsTail = sub.depsTail local depsTail = sub.depsTail
@ -186,13 +178,12 @@ end
--[[ --[[
Runs all cleanup functions registered with the effect node. Any errors Runs all cleanup functions registered with the effect node. Any errors
thrown during cleanup are caught and logged as warnings. thrown during cleanup are caught and logged in a single error after all
cleanups are complete.
@param cleanups List of cleanup functions
]] ]]
local function runCleanups(cleanups: { Cleanup }) local function runCleanups(cleanups: { Cleanup })
local hasErrors = false local hasErrors = false
local errors = {} local errors: { string } = {}
for index, cleanup in cleanups do for index, cleanup in cleanups do
cleanups[index] = nil cleanups[index] = nil
@ -215,8 +206,6 @@ end
--[[ --[[
Checks if the effect node needs to run based on its flags and dependencies. 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. Effects run if they are marked dirty or pending with dirty dependencies.
@param effect Effect node to run
]] ]]
local function run(effect: EffectNode) local function run(effect: EffectNode)
local effectFlags = effect.flags local effectFlags = effect.flags
@ -311,9 +300,6 @@ end
--[[ --[[
Updates a computed signal by re-evaluating its getter function. Returns Updates a computed signal by re-evaluating its getter function. Returns
whether the computed value changed. 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 local function updateComputed(computed: ComputedNode): boolean
cycle += 1 cycle += 1
@ -339,9 +325,6 @@ end
--[[ --[[
Updates a signal by applying its pending value. Returns whether the Updates a signal by applying its pending value. Returns whether the
signal's value changed. 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 local function updateSignal(signal: SignalNode): boolean
signal.flags = MUTABLE signal.flags = MUTABLE
@ -369,28 +352,23 @@ end
Clears all dependencies and subscribers from the effect node. After calling 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 this, the node will no longer be subscribed to any dependencies and will be
inactive, with all cleanup functions run and resources released. inactive, with all cleanup functions run and resources released.
@param node Effect node
]] ]]
local function stopEffect(node: EffectScopeNode) local function stopEffect(effect: EffectScopeNode)
node.depsTail = nil effect.depsTail = nil
node.flags = NONE effect.flags = NONE
purgeDeps(node) purgeDeps(effect)
local sub = node.subs local sub = effect.subs
if sub then if sub then
unlink(sub, node) unlink(sub, effect)
end end
runCleanups(node.cleanups) runCleanups(effect.cleanups)
end end
--[[ --[[
Updates a signal or computed value and returns whether the value changed. 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 local function update(node: ReactiveNode): boolean
if node.depsTail then if node.depsTail then
@ -403,8 +381,6 @@ end
--[[ --[[
Queues an effect for execution, ensuring inner effects are notified in the Queues an effect for execution, ensuring inner effects are notified in the
correct order. correct order.
@param effect Effect node to notify
]] ]]
local function notify(effect: ReactiveNode) local function notify(effect: ReactiveNode)
local insertIndex = queuedLength local insertIndex = queuedLength
@ -443,8 +419,6 @@ end
- Computed signals: Unlink dependencies and mark as dirty for potential - Computed signals: Unlink dependencies and mark as dirty for potential
future updates future updates
- Effects/scopes: Perform full cleanup to free resources - Effects/scopes: Perform full cleanup to free resources
@param node Reactive node (computed, effect, or effect scope)
]] ]]
local function unwatched(node: ReactiveNode) local function unwatched(node: ReactiveNode)
if bit32.band(node.flags, MUTABLE) == 0 then if bit32.band(node.flags, MUTABLE) == 0 then
@ -719,24 +693,24 @@ local function trigger(fn: () -> ...any)
local prevSub = setActiveSub(sub) local prevSub = setActiveSub(sub)
local success, err = pcall(wrapUserSpace(fn)) local success, err = pcall(wrapUserSpace(fn))
activeSub = prevSub activeSub = prevSub
sub.flags = NONE
if not success then
error(err, 2)
end
local currentLink = sub.deps local currentLink = sub.deps
while currentLink do while currentLink do
local dep = currentLink.dep local dep = currentLink.dep
currentLink = unlink(currentLink, sub)
local subs = dep.subs local subs = dep.subs
currentLink = unlink(currentLink, sub)
if subs then if subs then
sub.flags = NONE
propagate(subs) propagate(subs)
shallowPropagate(subs) shallowPropagate(subs)
end end
end end
flush() flush()
if not success then
error(err, 2)
end
end end
--[[ --[[
@ -774,11 +748,9 @@ local function untracked<Args..., Results...>(fn: (Args...) -> Results..., ...:
local prevSub = setActiveSub(nil) local prevSub = setActiveSub(nil)
local results: { any } = { pcall(wrapUserSpace(fn), ...) } local results: { any } = { pcall(wrapUserSpace(fn), ...) }
activeSub = prevSub activeSub = prevSub
if not results[1] then if not results[1] then
error(results[2], 2) error(results[2], 2)
end end
return unpack(results, 2) return unpack(results, 2)
else else
return fn(...) return fn(...)
@ -798,11 +770,9 @@ local function batch<Args..., Results...>(fn: (Args...) -> Results..., ...: Args
startBatch() startBatch()
local results: { any } = { pcall(wrapUserSpace(fn), ...) } local results: { any } = { pcall(wrapUserSpace(fn), ...) }
endBatch() endBatch()
if not results[1] then if not results[1] then
error(results[2], 2) error(results[2], 2)
end end
return unpack(results, 2) return unpack(results, 2)
end end
@ -810,9 +780,6 @@ end
Returns true if the given function is a signal getter, computed signal, or 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 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()`. 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<any>): boolean local function isSignal(fn: Getter<any>): boolean
local name = debug.info(fn, "n") local name = debug.info(fn, "n")
@ -830,7 +797,6 @@ end
@see https://github.com/littensy/charm?tab=readme-ov-file#listengetter-callback @see https://github.com/littensy/charm?tab=readme-ov-file#listengetter-callback
]] ]]
local function listen<T>(getter: Getter<T>, callback: (state: T, lastState: T?) -> ()): Cleanup local function listen<T>(getter: Getter<T>, callback: (state: T, lastState: T?) -> ()): Cleanup
local callback = wrapUserSpace(callback)
local tracked: Getter<T> = if isSignal(getter) then getter else computed(getter :: (T?) -> T) local tracked: Getter<T> = if isSignal(getter) then getter else computed(getter :: (T?) -> T)
local current: T local current: T
@ -852,7 +818,6 @@ end
@see https://github.com/littensy/charm?tab=readme-ov-file#subscribegetter-callback @see https://github.com/littensy/charm?tab=readme-ov-file#subscribegetter-callback
]] ]]
local function subscribe<T>(getter: Getter<T>, callback: (state: T, lastState: T) -> ()): Cleanup local function subscribe<T>(getter: Getter<T>, callback: (state: T, lastState: T) -> ()): Cleanup
local callback = wrapUserSpace(callback)
local tracked: Getter<T> = if isSignal(getter) then getter else computed(getter :: (T?) -> T) local tracked: Getter<T> = if isSignal(getter) then getter else computed(getter :: (T?) -> T)
local current: T local current: T
local firstRun = true local firstRun = true
@ -883,8 +848,9 @@ end
@see https://github.com/littensy/charm?tab=readme-ov-file#observegetter-callback @see https://github.com/littensy/charm?tab=readme-ov-file#observegetter-callback
]] ]]
local function observe<K, V>(getter: Getter<{ [K]: V }>, callback: (item: V, key: K) -> ...Cleanup?): Cleanup local function observe<K, V>(getter: Getter<{ [K]: V }>, callback: (item: V, key: K) -> ...Cleanup?): Cleanup
local getter = wrapUserSpace(getter) getter = wrapUserSpace(getter)
local callback = wrapUserSpace(callback) callback = wrapUserSpace(callback)
local scopes: { [K]: Cleanup } = {} local scopes: { [K]: Cleanup } = {}
local stopObserving = false local stopObserving = false
@ -943,8 +909,9 @@ end
@see https://github.com/littensy/charm?tab=readme-ov-file#mappedgetter-transform @see https://github.com/littensy/charm?tab=readme-ov-file#mappedgetter-transform
]] ]]
local function mapped<KI, VI, KO, VO>(getter: Getter<{ [KI]: VI }>, transform: (VI, KI) -> (VO, KO)): Getter<{ [KO]: VO }> local function mapped<KI, VI, KO, VO>(getter: Getter<{ [KI]: VI }>, transform: (VI, KI) -> (VO, KO)): Getter<{ [KO]: VO }>
local getter = wrapUserSpace(getter) getter = wrapUserSpace(getter)
local transform = wrapUserSpace(transform) transform = wrapUserSpace(transform)
local nextInput: { [KI]: VI } = {} local nextInput: { [KI]: VI } = {}
local keyMap: { [KI]: KO } = {} local keyMap: { [KI]: KO } = {}

View file

@ -102,7 +102,7 @@ local function link(dep: ReactiveNode, sub: ReactiveNode, version: number)
end end
local function unlink(link: Link, sub: ReactiveNode?): Link? local function unlink(link: Link, sub: ReactiveNode?): Link?
local sub = sub or link.sub sub = sub or link.sub
local dep = link.dep local dep = link.dep
local prevDep = link.prevDep local prevDep = link.prevDep
local nextDep = link.nextDep local nextDep = link.nextDep

View file

@ -36,7 +36,7 @@ end
]] ]]
local function toRaw<T>(value: T): T local function toRaw<T>(value: T): T
if isReactive(value) then if isReactive(value) then
local value = value :: ReactiveProxy<T> & T value = value :: ReactiveProxy<T>
value.__track() value.__track()
return toRaw(value.__target) return toRaw(value.__target)
end end

View file

@ -4,4 +4,4 @@ rojo = "rojo-rbx/rojo@7.6.1"
stylua = "johnnymorganz/stylua@2.4.1" stylua = "johnnymorganz/stylua@2.4.1"
selene = "Kampfkarren/selene@0.30.1" selene = "Kampfkarren/selene@0.30.1"
luau-lsp = "johnnymorganz/luau-lsp@1.66.0" luau-lsp = "johnnymorganz/luau-lsp@1.66.0"
lute = "luau-lang/lute@0.1.0-nightly.20260331" lute = "luau-lang/lute@1.0.0"