Hey this module is so useful for my game, I also added WaitForChild, WaitForDescendant and WaitForAncestor
--!strict
local Terms = {}
local Tree = {}
function Terms.Property<V>(object: Instance, name: string, value: V): boolean
local success: boolean, foundProperty: V? = pcall(function()
return (object :: any)[name]
end)
if success and (not value or foundProperty == value) then
return true
end
return false
end
function Terms.Attribute<V>(object: Instance, name: string, value: V): boolean
local foundProperty = object:GetAttribute(name)
if not value or foundProperty == value then
return true
end
return false
end
function Terms.Function(object: Instance, callback: (object: Instance) -> boolean?): boolean?
return callback(object)
end
function Terms.Tree(object: Instance, treeFunction: string, ...: any)
return Tree[treeFunction](object, ...)
end
function Tree.ObjectMeetsTerms(object: Instance, termArguments: {{any}}): boolean
for _, term in ipairs(termArguments) do
local termCloned = table.clone(term)
local termName = termCloned[1]
table.remove(termCloned, 1)
if not Terms[termName](object, table.unpack(termCloned)) then
return false
end
end
return true
end
function Tree.FindFirstElementOfList(list: {Instance}, terms: {{any}}): Instance?
for _, applicant in ipairs(list) do
if Tree.ObjectMeetsTerms(applicant, terms) then
return applicant
end
end
return nil
end
function Tree.FindFirstChild(object: Instance, terms: {{any}}): Instance?
return Tree.FindFirstElementOfList(object:GetChildren(), terms)
end
function Tree.FindFirstDescendant(object: Instance, terms: {{any}}): Instance?
return Tree.FindFirstElementOfList(object:GetDescendants(), Terms)
end
function Tree.FindFirstAncestor(object: Instance, terms: {{any}}): Instance?
local parent = object.Parent
while parent do
if Tree.ObjectMeetsTerms(parent, terms) then
return parent
end
parent = parent.Parent
end
return nil
end
function Tree.WaitForChild(object: Instance, terms: {{any}}, timeOut: number?): Instance
local found = Tree.FindFirstChild(object, terms)
if found then
return found
else
local connections: {RBXScriptConnection} = {}
local thread = coroutine.running()
local isResumed = false
local function resume(...: any)
if not isResumed then
isResumed = true
for _, connection in ipairs(connections) do
connection:Disconnect()
end
table.clear(connections)
task.spawn(thread, ...)
end
end
table.insert(connections, object.ChildAdded:Connect(function(child: Instance)
if Tree.ObjectMeetsTerms(child, terms) then
resume(child)
end
end))
table.insert(connections, object.AncestryChanged:Connect(function(_: Instance, parent: Instance?)
if not parent then
resume(nil)
end
end))
table.insert(connections, object.Destroying:Connect(function()
resume(nil)
end))
task.delay(timeOut or 60, function()
resume(nil)
end)
return coroutine.yield()
end
end
function Tree.WaitForDescendant(object: Instance, terms: {{any}}, timeOut: number?): Instance
local found = Tree.FindFirstDescendant(object, terms)
if found then
return found
else
local connections: {RBXScriptConnection} = {}
local thread = coroutine.running()
local isResumed = false
local function resume(...: any)
if not isResumed then
isResumed = true
for _, connection in ipairs(connections) do
connection:Disconnect()
end
table.clear(connections)
task.spawn(thread, ...)
end
end
table.insert(connections, object.DescendantAdded:Connect(function(descendant: Instance)
if Tree.ObjectMeetsTerms(descendant, terms) then
resume(descendant)
end
end))
table.insert(connections, object.AncestryChanged:Connect(function(_: Instance, parent: Instance?)
if not parent then
resume(nil)
end
end))
table.insert(connections, object.Destroying:Connect(function()
resume(nil)
end))
task.delay(timeOut or 60, function()
resume(nil)
end)
return coroutine.yield()
end
end
function Tree.WaitForAncestor(object: Instance, terms: {{any}}, timeOut: number?): Instance
local found = Tree.FindFirstDescendant(object, terms)
if found then
return found
else
local connections: {RBXScriptConnection} = {}
local thread = coroutine.running()
local isResumed = false
local function resume(...: any)
if not isResumed then
isResumed = true
for _, connection in ipairs(connections) do
connection:Disconnect()
end
table.clear(connections)
task.spawn(thread, ...)
end
end
table.insert(connections, object.AncestryChanged:Connect(function(_: Instance, parent: Instance?)
if parent then
local ancestor: Instance? = parent
while ancestor do
if Tree.ObjectMeetsTerms(ancestor, terms) then
resume(ancestor)
break
end
ancestor = ancestor.Parent
end
end
end))
table.insert(connections, object.Destroying:Connect(function()
resume(nil)
end))
task.delay(timeOut or 60, function()
resume(nil)
end)
return coroutine.yield()
end
end
export type Tree = {
FindFirstChild: (object: Instance, terms: {{any}}) -> Instance?,
FindFirstDescendant: (object: Instance, terms: {{any}}) -> Instance?,
FindFirstAncestor: (object: Instance, terms: {{any}}) -> Instance?,
WaitForChild: (object: Instance, terms: {{any}}, timeOut: number?) -> Instance,
WaitForDescendant: (object: Instance, terms: {{any}}, timeOut: number?) -> Instance,
WaitForAncestor: (object: Instance, terms: {{any}}, timeOut: number?) -> Instance
}
return (table.freeze(Tree) :: any) :: Tree
Also here’s an example about Terms.Function
local rootPart = Tree.WaitForChild(character, {
{"Property", "Name", "HumanoidRootPart"},
{"Function", function(object: Instance)
return object:IsA("BasePart") -- pass if the object is actually a BasePart
end}
}) :: BasePart