Feedback on custom class module

I made myself a class module so I can easily create custom objects. It has some of the functionality of Roblox instances, like parents/children, ClassName, and Name.

Also, I used __newindex to create read-only values (_name) and make values that I can both read from and set to while allowing set to have a callback.

The callbacks are all listed in setFunctions. Currently, I only have one for Parent, which adds it to the _children list of its parent, if the parent is a custom instance too.

Is this the best way to accomplish my goal? Should I add anything to it?

local require = require(game:GetService("ReplicatedStorage").Require)
local events = require("Events")

local setFunctions = {
	Parent = function(self, value)
		self:_addAsChild(value)
	end,
	
	_childAdded = function(self, _, value)
		self:_addChild(value)
	end
}

local instance = {}
instance.__index = instance

instance.__newindex = function(self, key, value)
	if key:sub(1, 1) == "_" then return end
	if setFunctions[key] then setFunctions[key](self, value) end
	
	rawset(self, key, value)
	self.Changed:Fire(key, value)
	
	if self._propertyChangedEvents[key] then
		self._propertyChangedEvents[key]:Fire(value)
	end
end

function instance.new(name)
	return setmetatable({
		Changed = events.new(),
		ClassName = name,
		Parent = nil,
		Name = nil,
		
		_waitForChildEvents = {},
		_propertyChangedEvents = {},
		_children = {},
	}, instance)
end

function instance:_addAsChild(parent)
	if not parent._children then return end
	table.insert(parent._children, self)
	
	parent:_addChild(self)
end

function instance:GetChildren()
	return self._children
end

function instance:GetPropertyChangedSignal(property)
	local newEvent = self._propertyChangedEvents[property] or events.new()
	self._propertyChangedEvents[property] = newEvent
	
	return newEvent
end

function instance:WaitForChild(name)
	warn("Use of WaitForChild. Have you considered using events?")
	if self._children[name] then return self._children[name] end
	
	local waitEvent = self._waitForChildEvents[name] or events.new()
	self._waitForChildEvents[name] = waitEvent
	
	return waitEvent:Wait()
end

function instance:Destroy()
	for i, item in ipairs(self._children) do	
		if item.Destroy then
			item:Destroy()
		else
			self:_clear(item)
		end
	end
	
	if self.Parent._children then
		table.remove(self.Parent._children, table.find(self.Parent._children, self))
	end
	
	self:_clear(self)
	setmetatable(self, nil)
end

function instance:_addChild(value)
	if not self._waitForChildEvents[value.Name or "_&unusableName"] then return end
	self._waitForChildEvents[value.Name]:Fire(value)
	
	self._waitForChildEvents[value.Name]:Destroy()
end

function instance:_clear(obj)
	for x = 1, #self do
		self[x] = nil
	end
end

setmetatable(instance, {
	__call = function(self, ...)
		return self.new(...)
	end
})

return instance
--// thanks for stopping by

and here’s the code where i test it:

local instance = require("Instance")

local newInstance = instance.new("hm")
print(newInstance.ClassName) --> hm

local thisInstance = instance.new("hm2")
thisInstance.Parent = newInstance

print(newInstance:GetChildren()[1].ClassName == thisInstance.ClassName) --> true
print(thisInstance.Parent == newInstance) --> true

thisInstance:Destroy()
print(newInstance:GetChildren()[1]) --> nil 
--// (supposed to be nil since it was destroyed)

coroutine.wrap(function()
	wait(4)
	local thirdInstance = instance.new("hm3")
	thirdInstance.Name = "third"
	thirdInstance.Parent = newInstance
end)()

local third = newInstance:WaitForChild("third")
print(third) --> table: debugId
7 Likes

I think it looks good. But what I would do with this:

I would return a copy of this table. This is more consistent with the way Roblox does it, and so you can’t do local children = instance:GetChildren() and mess up the original _children table by inserting or removing or whatever you want.

1 Like