Roblox Engine API Expander v2

Poll

Hey! I know it’s been a while, I’ve been with school and stuff but I’m still working on the project. Would y’all want me to add indexing children from Instances to the Instances registered under the Engine API Expander?

  • Yes
  • No

0 voters

I’m also working on new methods.

Quick question what is the difference between GetChildren and GetSiblings

1 Like

GetChildren gets all the Instances parented to a specific Instance while GetSiblings gets all the children of the parent of an Instance. For example:

local Instances = require(script.Parent.Parent)
local workspace = Instances:Register(workspace)
print(workspace:GetChildren()) -- Returns {[1] = "Baseplate", [2] = "Terrain", [3] = "SpawnLocation", [4] = "Camera"}, etc.
print(workspace:GetSiblings()) -- Returns all the services parented to game (workspace's parent), e.g. {[1] = "Players", [2] = "Lighting", [3] = "ReplicatedStorage"}, etc.

Thank you for the explanation, I was confused on what the difference between the two.

1 Like

I’m encountering an error where the module can only be used on the server due to the nature of HTTP requests. i’m not sure if it’s something on my end or it’s with the module utilizing HTTP requests

here’s what my code looks like in the server and client scripts:

local Instances = require(game.ReplicatedStorage.Instances)
local workspace = Instances:Register(workspace)

print(workspace:GetSiblings())

here’s the output:
image

1 Like

That’s interesting, it is not an issue on your end, it’s due to the nature of HTTP requests, I’ll have to make a fix for this.

1 Like

Update

Added Dependencies

  • Wrote Http module to handle HTTP requests in a more flexible way.

Added Features

  • Added indexing children from Instances registered under the Engine API Expander.
  • Added support for client sided scripts.

Added Signals

  • Added .Removing signal.

Improved Examples

  • Set default parent of the Instances module to ReplicatedStorage.

Bug Fixes

  • HTTP requests don’t fail anymore on the client.
  • Improved checks to prevent errors.
1 Like

I rewrote your code to use metatables so you don’t have to access an APIDump (Not typed though):

local Signal = require(script.GoodSignal)

local API = {}

function API:Register<T>(i: T): T
	assert(i ~= nil, "You didn't pass anything to register!")
	assert(typeof(i) == "Instance", "You didn't pass an instance to register!")

	local Classes = {}
	local newClass = setmetatable({}, {
		__index = function(self, key, ...)
			if Classes[key] then
				return Classes[key]
			else
				if typeof(i[key]) == "function" then
					return function(self, ...)
						return (i[key] :: (any) -> ())(i, ...)
					end
				end
				return i[key]
			end
		end,
	})

	local Parent: Instance? = i.Parent

	if Parent then
		newClass["SiblingRemoved"] = Signal.new()
		newClass["SiblingAdded"] = Signal.new()
		newClass["Removing"] = Signal.new()
		local connection1
		connection1 = Parent.ChildAdded:Connect(function(...)
			if not newClass then
				connection1:Disconnect()
				return
			end
			newClass.SiblingAdded:Fire(...)
		end)
		local connection2
		connection2 = Parent.ChildRemoved:Connect(function(...)
			if not newClass then
				connection2:Disconnect()
				return
			end
			newClass.SiblingRemoved:Fire(...)
		end)
	end

	i.AncestryChanged:Connect(function(self, newParent) --this one gets carbage collected properly :thumbs_up:
		if not newParent then
			if not newClass then
				return
			end
			newClass.Removing:Fire()
		end
	end)

	function Classes:WaitForChildWhichIsA(className, timeOut): Instance? | nil
		local defaultTimeOut = 5
		if not timeOut then
			timeOut = defaultTimeOut
		end
		while task.wait(1) do
			if timeOut <= 0 then
				warn(`Infinite yield possible on '{i.Name}:WaitForChildWhichIsA("{className}")'`)
				return nil
			end
			for _, inst in pairs(i:GetChildren()) do
				if inst:IsA(className) then
					return inst
				end
			end
			timeOut -= 1
		end
		return nil
	end

	function Classes:GetChildrenOfClass(className: string): { Instance? }
		local instancesOfClass = {}
		for _, child in pairs(i:GetChildren()) do
			if child.ClassName == className then
				table.insert(instancesOfClass, child)
			end
		end
		return instancesOfClass
	end

	function Classes:GetDescendantsOfClass(className: string): { Instance? }
		local descendantsOfClass = {}
		for _, descendant in pairs(i:GetDescendants()) do
			if descendant.ClassName == className then
				table.insert(descendantsOfClass, descendant)
			end
		end
		return descendantsOfClass
	end

	function Classes:GetChildrenWhichAre(t: string): { Instance? }
		local instancesWhichAre = {}
		for _, child in pairs(i:GetChildren()) do
			if child:IsA(t) then
				table.insert(instancesWhichAre, child)
			end
		end
		return instancesWhichAre
	end

	function Classes:GetDescendantsWhichAre(t: string): { Instance? }
		local descendantsWhichAre = {}
		for _, descendant in pairs(i:GetDescendants()) do
			if descendant:IsA(t) then
				table.insert(descendantsWhichAre, descendant)
			end
		end
		return descendantsWhichAre
	end

	function Classes:FindFirstSibling(name: string): Instance?
		return (Parent and Parent:FindFirstChild(name))
	end

	function Classes:FindFirstSiblingOfClass(class: string): Instance?
		return (Parent and Parent:FindFirstChildOfClass(class))
	end

	function Classes:FindFirstSiblingWhichIsA(className: string): Instance?
		return (Parent and Parent:FindFirstChildWhichIsA(className))
	end

	function Classes:GetSiblings(): { Instance }
		return (Parent and Parent:GetChildren())
	end

	function Classes:IsSiblingOf(sibling: Instance): boolean
		return (Parent == sibling.Parent)
	end

	export type Classes = typeof(Classes)
	return newClass :: Classes & typeof(i)
end

return API

But then I also wrote my own typed version with OOP:

local Signal = require(script.Parent.Signal)
local Tree = require(script.Parent.Tree)
local Trove = require(script.Parent.Trove)

type APIExpanderImpl = {
	__index: APIExpanderImpl,
	new: <T>(instance: T) -> APIExpander & T,
	WaitForChildWhichIsA: (self: APIExpander, className: string, timeOut: number) -> Instance,
	GetChildrenOfClass: (self: APIExpander, className: string) -> { Instance },
	GetDescendantsOfClass: (self: APIExpander, className: string) -> { Instance },
	GetChildrenWhichAre: (self: APIExpander, className: string) -> { Instance },
	GetDescendantsWhichAre: (self: APIExpander, className: string) -> { Instance },
	FindFirstSibling: (self: APIExpander, name: string) -> Instance?,
	FindFirstSiblingOfClass: (self: APIExpander, className: string) -> Instance?,
	FindFirstSiblingWhichIsA: (self: APIExpander, className: string) -> Instance?,
	GetSiblings: (self: APIExpander) -> { Instance },
	GetSiblingsWhichAre: (self: APIExpander, className: string) -> { Instance },
	IsSiblingOf: (self: APIExpander, instance: Instance) -> boolean,
	Find: (self: APIExpander, pathToInstance: string, assertIsA: string) -> Instance?,
	Await: (self: APIExpander, pathToInstance: string, timeOut: number, assertIsA: string) -> Instance,
	Exists: (self: APIExpander, pathToInstance: string, assertIsA: string) -> boolean,
	_destroy: (self: APIExpander) -> nil,
}

type APIExpanderProperties = {
	Instance: Instance,
	_trove: typeof(Trove.new()),
	SiblingAdded: RBXScriptConnection,
	SiblingRemoved: RBXScriptConnection,
}
type APIExpander = typeof(setmetatable({} :: APIExpanderProperties, {} :: APIExpanderImpl))

local Expand: APIExpanderImpl = {} :: APIExpanderImpl
Expand.__index = function(self, key)
	if Expand[key] then
		return Expand[key]
	end

	if not self.Instance[key] then
		error(`Attempt to index {self.Instance} with key {key} that does not exist`)
	end

	if typeof(self.Instance[key]) == "function" then
		return function(self, ...)
			return (self.Instance[key] :: (any) -> ())(self.Instance, ...)
		end
	end
	return self.Instance[key]
end :: any

function Expand.new<T>(instance)
	local self = setmetatable({}, Expand)

	self.Instance = instance
	self._trove = Trove.new()
	self.SiblingAdded = self._trove:Add(Signal.new())
	self.SiblingRemoved = self._trove:Add(Signal.new())
	self.Instance.Destroying:Connect(function()
		self:Destroy()
	end)

	local parent = self.Instance.Parent
	if parent then
		self.SiblingAdded = Signal.new()
		self.SiblingRemoved = Signal.new()

		self._trove:Add(parent.ChildAdded:Connect(function(child)
			self.SiblingAdded:Fire(child)
		end))

		self._trove:Add(parent.ChildRemoved:Connect(function(child)
			self.SiblingRemoved:Fire(child)
		end))
	end

	return self
end

function Expand:WaitForChildWhichIsA(className, timeOut)
	timeOut = timeOut or 5
	while task.wait(1) do
		if timeOut <= 0 then
			warn(
				"Infinite yield possible on "
					.. "'"
					.. self.Instance.Name
					.. ':WaitForChildWhichIsA("'
					.. className
					.. '")'
					.. "'"
			)
			return nil
		end

		for _, instance in self.Instance:GetChildren() do
			if instance:IsA(className) then
				return instance
			end
		end
		timeOut -= 1
	end

	return nil
end

function Expand:GetChildrenOfClass(className)
	local children = {}
	for _, instance in self.Instance:GetChildren() do
		if instance:IsA(className) then
			table.insert(children, instance)
		end
	end
	return children
end

function Expand:GetDescendantsOfClass(className)
	local descendants = {}
	for _, descendant in self.Instance:GetChildren() do
		if descendant:IsA(className) then
			table.insert(descendants, descendant)
		end
	end
	return descendants
end

function Expand:GetChildrenWhichAre(className)
	local children = {}
	for _, instance in self.Instance:GetChildren() do
		if instance:IsA(className) then
			table.insert(children, instance)
		end
	end
	return children
end

function Expand:GetDescendantsWhichAre(className)
	local descendants = {}
	for _, descendant in self.Instance:GetDescendants() do
		if descendant:IsA(className) then
			table.insert(descendants, descendant)
		end
	end
	return descendants
end

function Expand:GetSiblingsWhichAre(className)
	if not self.Instance.Parent then
		return nil
	end
	local siblings = {}
	for _, sibling in self.Instance.Parent:GetChildren() do
		if sibling:IsA(className) then
			table.insert(siblings, sibling)
		end
	end
	return siblings
end

function Expand:FindFirstSibling(name)
	return (self.Instance.Parent and self.Instance.Parent:FindFirstChild(name)) or nil
end

function Expand:FindFirstSiblingOfClass(className)
	return (self.Instance.Parent and self.Instance.Parent:FindFirstChildOfClass(className)) or nil
end

function Expand:FindFirstSiblingWhichIsA(className)
	return (self.Instance.Parent and self.Instance.Parent:FindFirstChildWhichIsA(className)) or nil
end

function Expand:GetSiblings()
	return (self.Instance.Parent and self.Instance.Parent:GetChildren())
end

function Expand:IsSiblingOf(sibling)
	return (self.Instance.Parent == sibling.Parent)
end

function Expand:Find(pathToInstance, assertIsA)
	return Tree.Find(self.Instance, pathToInstance, assertIsA)
end

function Expand:Await(pathToInstance, timeOut, assertIsA)
	return Tree.Await(self.Instance, pathToInstance, timeOut, assertIsA)
end

function Expand:Exists(pathToInstance, assertIsA)
	return Tree.Exists(self.Instance, pathToInstance, assertIsA)
end

function Expand:_destroy()
	self._trove:Destroy()
	setmetatable(self, nil)
	table.clear(self)
	self = nil
end

return Expand.new

And published it to wally: Package

1 Like

Make a pull request to the repository.

1 Like

First or second version? (Second version has more dependencies)

What does the second one depend on?

1 Like

Trove, Signal, Tree from sleitnick

they can be found here: RbxUtil | RbxUtil

1 Like

You could do a pull request with the second code and the dependencies.

1 Like

Merged the pull request :+1: :tada:

1 Like

my king devx with an amazing release

1 Like