Hi! I’ve been working on a project where I have to translate instances to a reactive node tree, but I ran into useAfterDestroy error. I’ve been struggling with it for the past 2 days despite simplifying my code to just a single self-contained script. Also, for the record, I did read Fusion’s documentation and watched a video covering scopes.
Source code
--!strict
local Fusion = require("@self/Fusion")
local localPlayer = game:GetService("Players").LocalPlayer
assert(localPlayer, "This script is intended to run on a client")
local terrain = workspace.Terrain
assert(terrain, "Terrain must be present")
local function insert<T>(
reactiveArray: Fusion.Value<{T}>,
item: T
): ()
local array = Fusion.peek(reactiveArray) :: {T}
table.insert(array, item)
reactiveArray:set(array, true) -- Trigger update
end
local function removeByItem<T>(
reactiveArray: Fusion.Value<{T}>,
item: T
): boolean
local array = Fusion.peek(reactiveArray) :: {T}
local index = table.find(array, item)
if not index then
return false -- Nothing has been removed
end
table.remove(array, index)
reactiveArray:set(array, true) -- Trigger update
return true
end
local function unsafeReadProperty(
instance: Instance,
property: string
): unknown
return (instance :: any)[property]
end
local function useProperty(
scope: Fusion.Scope,
instance: Instance,
property: string
): Fusion.UsedAs<unknown>
local value = scope:Value(unsafeReadProperty(instance, property))
table.insert(scope, instance:GetPropertyChangedSignal(property):Connect(function(): ()
value:set(unsafeReadProperty(instance, property))
end))
return value
end
local function useChildren(
scope: Fusion.Scope,
instance: Instance
): Fusion.UsedAs<{Instance}>
local children = scope:Value(instance:GetChildren()) :: Fusion.Value<{Instance}>
table.insert(scope, instance.ChildAdded:Connect(function(child): ()
insert(children, child)
end))
table.insert(scope, instance.ChildRemoved:Connect(function(child): ()
removeByItem(children, child)
end))
return children
end
type Node = {
read name: Fusion.UsedAs<string>;
read children: Fusion.UsedAs<{Node}>;
}
local function buildNode(
scope: Fusion.Scope,
instance: Instance,
registry: {[Instance]: Node?}
): Node
-- Prevent infinite feedback loop
local existing = registry[instance]
if existing then
return existing
end
print("create", instance)
table.insert(scope, function()
print("delete", instance)
registry[instance] = nil -- Unregister node
end)
local name = useProperty(scope, instance, "Name") :: Fusion.UsedAs<string>
-- Get child instances and convert them to nodes
local childInstances = useChildren(scope, instance)
local childNodes = scope:ForValues(childInstances, function(
use: Fusion.Use,
childScope: Fusion.Scope,
childInstance: Instance
): Node
return buildNode(childScope, childInstance, registry)
end)
local node: Node = {
name = name,
children = childNodes
}
registry[instance] = node -- Register node
return node
end
local function buildUserInterface(
scope: Fusion.Scope,
node: Node,
callback: (scope: Fusion.Scope, node: Node) -> Fusion.Child
): Fusion.Child
return scope:New("Frame")({
AutomaticSize = Enum.AutomaticSize.XY,
[Fusion.Children] = {
scope:New("UIListLayout")({
SortOrder = Enum.SortOrder.LayoutOrder,
FillDirection = Enum.FillDirection.Vertical
}),
callback(scope, node),
scope:ForValues(node.children, function(
use: Fusion.Use,
childScope: Fusion.Scope,
childNode: Node
): Fusion.Child
return buildUserInterface(childScope, childNode, callback)
end)
}
})
end
local function main(scope: Fusion.Scope): Fusion.Child
local rootNode = buildNode(scope, terrain, {})
return scope:New("ScreenGui")({
ZIndexBehavior = Enum.ZIndexBehavior.Sibling,
ResetOnSpawn = false,
AutoLocalize = false,
Parent = localPlayer.PlayerGui,
[Fusion.Children] = {
buildUserInterface(scope, rootNode, function(
scope: Fusion.Child,
node: Node
): Fusion.Child
return scope:New("TextLabel")({
AutomaticSize = Enum.AutomaticSize.XY,
BackgroundTransparency = 1,
Text = node.name,
TextSize = 14,
TextXAlignment = Enum.TextXAlignment.Left
})
end)
}
})
end
main(Fusion:scoped())
Full Error
[Fusion] Error in callback:
[Fusion] The Value (bound to the Text property) is no longer valid - it was destroyed before the TextLabel instance. See discussion #292 on GitHub for advice.
ID: useAfterDestroy
Learn more: https://elttob.uk/Fusion/0.3/api-reference/general/errors/#useafterdestroy (while processing value table: [memory address])
ID: callbackError
Learn more: https://elttob.uk/Fusion/0.3/api-reference/general/errors/#callbackerror
---- Stack trace ----
ReplicatedStorage.NodeTree.Fusion.External:91 function logError
ReplicatedStorage.NodeTree.Fusion.Memory.checkLifetime:121 function bOutlivesA
ReplicatedStorage.NodeTree.Fusion.Instances.applyInstanceProps:82 function bindProperty
ReplicatedStorage.NodeTree.Fusion.Instances.applyInstanceProps:114 function applyInstanceProps
ReplicatedStorage.NodeTree.Fusion.Instances.New:47
ReplicatedStorage.NodeTree:153
ReplicatedStorage.NodeTree:127 function buildUserInterface
ReplicatedStorage.NodeTree:133
ReplicatedStorage.NodeTree.Fusion.State.ForValues:59
ReplicatedStorage.NodeTree.Fusion.State.Computed:113 function _evaluate
ReplicatedStorage.NodeTree.Fusion.Graph.evaluate:44 function evaluate
ReplicatedStorage.NodeTree.Fusion.Graph.depend:24 function depend
ReplicatedStorage.NodeTree.Fusion.State.For:99
ReplicatedStorage.NodeTree.Fusion.State.ForValues:40 function useOutputPair
ReplicatedStorage.NodeTree.Fusion.State.For.Disassembly:98 function populate
ReplicatedStorage.NodeTree.Fusion.State.For:93 function _evaluate
ReplicatedStorage.NodeTree.Fusion.Graph.evaluate:44 function evaluate
ReplicatedStorage.NodeTree.Fusion.Graph.evaluate:30 function evaluate
ReplicatedStorage.NodeTree.Fusion.Graph.change:77 function change
ReplicatedStorage.NodeTree.Fusion.State.Value:74 function set
ReplicatedStorage.NodeTree:30 function removeByItem
ReplicatedStorage.NodeTree:64
Reproduction steps:
- Create a
Scriptwith context set toClient. - Parent the script to
ReplicatedStorage. - Place Fusion v0.3 module under the script.
- Start play test
- Repeatably insert and re-parent instances under
Terrainuntil an error occurs.
Alternatively, you can skip the first 3 steps, by downloading the script:
repo.rbxm (54.3 KB)