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",
"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"
}
}

View file

@ -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" })

View file

@ -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
)

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`)
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, " | "))

View file

@ -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,
}

View file

@ -39,9 +39,9 @@ local syncCallback: ((client: any, payloads: { SyncPayload }) -> ())?
support custom implementations of reactive proxies.
]]
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
(value.__track :: () -> ())()
value.__track()
return toRaw(value.__target)
end
return value

View file

@ -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",

View file

@ -17,10 +17,15 @@ local shallowPropagate = system.shallowPropagate
local createReactiveSystem = system.createReactiveSystem
export type Getter<T> = () -> T
export type Setter<T> = (update: Update<T>) -> T
export type Atom<T = any> = (...Update<T>) -> T
export type Update<T> = ((current: T) -> T) | T
export type Equals<T> = (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<Args..., Results...>(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<Args..., Results...>(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<Args..., Results...>(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<any>): 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<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 current: T
@ -852,7 +818,6 @@ end
@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 callback = wrapUserSpace(callback)
local tracked: Getter<T> = 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<K, V>(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<KI, VI, KO, VO>(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 } = {}

View file

@ -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

View file

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

View file

@ -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"