mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
Limit cleanup() calls to once per scope
This commit is contained in:
parent
2aebaf5abb
commit
0105e33fac
5 changed files with 63 additions and 5 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## cleanup()
|
||||
|
||||
Runs a callback anytime a reactive scope is re-ran.
|
||||
Runs a callback anytime a function scope is re-ran.
|
||||
|
||||
- **Type**
|
||||
|
||||
|
|
@ -10,6 +10,19 @@ Runs a callback anytime a reactive scope is re-ran.
|
|||
function cleanup(callback: () -> ())
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
The primary purpose of this function is to provide a means of cleaning up
|
||||
side effects caused by source updates and `watch()` updates.
|
||||
|
||||
The stack is inspected to find the function that calls `cleanup()`. The
|
||||
callback passed is called anytime the caller is re-ran, and when the caller
|
||||
finally garbage collects.
|
||||
|
||||
::: warning
|
||||
Only one `cleanup()` call is allowed per function scope.
|
||||
:::
|
||||
|
||||
- **Example**
|
||||
|
||||
```lua
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ Currently, strict mode will:
|
|||
4. Checks for `indexes()` and `values()` returning primitive values.
|
||||
5. Checks for duplicate nested properties at same depth.
|
||||
6. Better error reporting and stack traces.
|
||||
7. Checks for multiple `cleanup()` calls in the same function scope.
|
||||
|
||||
By rerunning sources and watchers, any side-effects are made more apparent.
|
||||
This also helps ensure that cleanups are being handled correctly.
|
||||
|
|
|
|||
|
|
@ -1,17 +1,23 @@
|
|||
if not game then script = require "test/relative-string" end
|
||||
|
||||
local flags = require(script.Parent.flags)
|
||||
local throw = require(script.Parent.throw)
|
||||
|
||||
local ref_to_id = {} :: { [string]: number }
|
||||
local id_to_ref = {} :: { [number]: string }
|
||||
local cleanup_callbacks = {} :: { [number]: () -> () } -- always dense
|
||||
local cleanup_lifetime = {} :: { [number]: unknown } -- can be sparse
|
||||
setmetatable(cleanup_lifetime :: any, { __mode = "v" })
|
||||
|
||||
local debug_caller_to_line = {} :: { [() -> ()]: number }
|
||||
setmetatable(debug_caller_to_line, { __mode = "k" })
|
||||
|
||||
local manual_mode = {
|
||||
caller = false :: false | () -> (),
|
||||
callbacks = {} :: { () -> () }
|
||||
}
|
||||
|
||||
-- todo: rare case where mem address is reused by another function on same line
|
||||
-- todo: rare case where mem address is reused by another function
|
||||
-- does this case handle itself?
|
||||
|
||||
local function cleanup_ref(ref: string, lifetime: unknown, callback: () -> ())
|
||||
|
|
@ -32,12 +38,20 @@ end
|
|||
|
||||
local function cleanup(callback: () -> ())
|
||||
local lifetime = debug.info(2, "f") -- `caller of cleanup() is lifetime of cleanup`
|
||||
|
||||
if flags.strict then
|
||||
local line = debug.info(2, "l")
|
||||
local cur_line = debug_caller_to_line[lifetime]
|
||||
if cur_line and cur_line ~= line then
|
||||
throw "only one cleanup call is allowed per function scope"
|
||||
end
|
||||
debug_caller_to_line[lifetime] = line
|
||||
end
|
||||
|
||||
if manual_mode.caller == lifetime then
|
||||
table.insert(manual_mode.callbacks, callback)
|
||||
else
|
||||
local ref = tostring(lifetime) .. line
|
||||
local ref = tostring(lifetime)
|
||||
cleanup_ref(ref, lifetime, callback)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -547,6 +547,26 @@ TEST("cleanup()", function()
|
|||
CHECK(objB.cleaned == 2)
|
||||
end
|
||||
|
||||
-- this is not allowed, test to verify behavior anyways
|
||||
do CASE "multiple cleanup"
|
||||
local state = source(1)
|
||||
|
||||
local queue = {}
|
||||
|
||||
watch(function()
|
||||
state()
|
||||
cleanup(function() table.insert(queue, 1) end)
|
||||
cleanup(function() table.insert(queue, 2) end)
|
||||
end)
|
||||
|
||||
CHECK(testkit.seq(queue, { 1 }))
|
||||
state(2)
|
||||
CHECK(testkit.seq(queue, { 1, 2, 1 }))
|
||||
state(3)
|
||||
CHECK(testkit.seq(queue, { 1, 2, 1, 2, 1 }))
|
||||
end
|
||||
|
||||
--[[
|
||||
do CASE "multiple cleanup"
|
||||
local state = source(1)
|
||||
|
||||
|
|
@ -574,6 +594,7 @@ TEST("cleanup()", function()
|
|||
--testkit.print2(queue)
|
||||
--CHECK(testkit.seq(queue, { 1, 2, 1, 2, 1, 2 }))
|
||||
end
|
||||
]]
|
||||
end)
|
||||
|
||||
TEST("create()", function()
|
||||
|
|
@ -1308,6 +1329,7 @@ TEST("strict", function()
|
|||
local derive = vide.derive
|
||||
local watch = vide.watch
|
||||
local indexes, values = vide.indexes, vide.values
|
||||
local cleanup = vide.cleanup
|
||||
|
||||
do CASE "error on derived callback yield"
|
||||
local state = source(1)
|
||||
|
|
@ -1410,6 +1432,15 @@ TEST("strict", function()
|
|||
|
||||
CHECK(ok)
|
||||
end
|
||||
|
||||
do CASE "multiple cleanup per scope"
|
||||
local ok = pcall(function()
|
||||
cleanup(function() end)
|
||||
cleanup(function() end)
|
||||
end)
|
||||
|
||||
CHECK(not ok)
|
||||
end
|
||||
end)
|
||||
|
||||
local ok = FINISH()
|
||||
|
|
|
|||
1
todo.md
1
todo.md
|
|
@ -1,7 +1,6 @@
|
|||
# todo
|
||||
|
||||
- cleanup codebase
|
||||
- limit `cleanup()` call to once per function scope?
|
||||
- better error reporting and stack traces in strict mode
|
||||
- auto-enable of strict mode depending on compiler optimizaton level
|
||||
- check smoothness of spring at high frequency updates
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue