Help with merging OOP modules

  1. What do you want to achieve? Keep it simple and clear!
    I want to merge my Component and Engine modules, since they are very similar. I want the Engine module to inherit from the Component module.

  2. What is the issue? Include screenshots / videos if possible!
    I can’t figure out a way of how to do it. It looks so simple but I just can’t get it.

  3. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    I tried using getmetatable and using separate registers but it didnt work.

Both modules store Instances of their objects in the tables called Instances

Component.Instances = {}

I use this code to add instances to the tables

table.insert(Component.Instances, self)
self.UID = uid
uid += 1

I need to store using uid because I use a lookup function to fetch instances using their children having the same UID.

function Component.lookup(instance:Instance): Component?
	for i, v in ipairs(Component.Instances) do
		if v.UID == instance:GetAttribute(lookupPrefix .. "UID") then --in this case lookup prefix is Component
			return v
		end
	end
end

my end goal here is for the Engine module to inherit from the Component module. The Engine modules has additional functions.

Component Module

local SS = game:GetService("ServerStorage")

local uid = 1
local lookupPrefix = "Component"

local Component = {}
Component.__index = Component
Component.Instances = {}

type self = {
	Body:Model,
	Tool:Tool,
	UID:number
}

export type Component = typeof(setmetatable({} :: self, Component))

function Component.new(name:string, position:Vector3): Component
	local self = setmetatable({} :: self, Component)
	
	table.insert(Component.Instances, self)
	self.UID = uid
	uid += 1
	
	local tool:Tool = Instance.new("Tool")
	tool.Name = name
	tool.CanBeDropped = false
	self.Tool = tool
	
	local handle:BasePart = Instance.new("Part")
	handle.Size = Vector3.one
	handle.Transparency = 1
	handle.Name = "Handle"
	handle.Position = position
	handle.CanTouch = false
	handle.Parent = tool
	
	local body:Model = SS.Components:FindFirstChild(name):Clone()
	body:PivotTo(CFrame.new(position))
	body.Parent = tool
	self.Body = body
	
	local weld:WeldConstraint = Instance.new("WeldConstraint")
	weld.Part0 = handle
	weld.Part1 = body.PrimaryPart
	weld.Parent = handle
	
	for i, v in ipairs(body:GetChildren()) do
		v:SetAttribute(lookupPrefix .. "UID", self.UID)
	end
	body:SetAttribute(lookupPrefix .. "UID", self.UID)
	
	tool.Parent = workspace
	tool.PrimaryPart = body.PrimaryPart
	
	tool.Destroying:Once(function()
		self:destroy()
	end)
	
	return self
end

function Component.destroy(self:Component): ()
	local selfuid = self.UID
	table.remove(Component.Instances, table.find(Component.Instances, self))
	self = nil
	print(lookupPrefix .. " UID: " .. selfuid, Component.Instances)
end

function Component.lookup(instance:Instance): Component?
	for i, v in ipairs(Component.Instances) do
		if v.UID == instance:GetAttribute(lookupPrefix .. "UID") then
			return v
		end
	end
end

return Component

Engine Module

local SS = game:GetService("ServerStorage")

local Component = require(script.Parent.Component)

local uid = 1
local lookupPrefix = "Engine"

local Engine = {}
Engine.__index = Engine
Engine.Instances = {}

type self = {
	Body:Model,
	Tool:Tool,
	UID:number
}

export type Engine = typeof(setmetatable({} :: self, Engine))

function Engine.new(name:string, position:Vector3): Engine
	local self = setmetatable({} :: self, Engine)

	table.insert(Engine.Instances, self)
	self.UID = uid
	uid += 1

	local tool:Tool = Instance.new("Tool")
	tool.Name = name
	tool.CanBeDropped = false
	self.Tool = tool

	local handle:BasePart = Instance.new("Part")
	handle.Size = Vector3.one
	handle.Transparency = 1
	handle.Name = "Handle"
	handle.Position = position
	handle.CanTouch = false
	handle.Parent = tool

	local body:Model = SS.Components:FindFirstChild(name):Clone()
	body:PivotTo(CFrame.new(position))
	body.Parent = tool
	self.Body = body

	local weld:WeldConstraint = Instance.new("WeldConstraint")
	weld.Part0 = handle
	weld.Part1 = body.PrimaryPart
	weld.Parent = handle

	for i, v in ipairs(body:GetChildren()) do
		v:SetAttribute(lookupPrefix .. "UID", self.UID)
	end
	body:SetAttribute(lookupPrefix .. "UID", self.UID)

	tool.Parent = workspace
	tool.PrimaryPart = body.PrimaryPart

	tool.Destroying:Once(function()
		self:destroy()
	end)

	return self
end

function Engine.destroy(self:Engine): ()
	local selfuid = self.UID
	table.remove(Engine.Instances, table.find(Engine.Instances, self))
	self = nil
	print(lookupPrefix .. " UID: " .. selfuid, Engine.Instances)
end

function Engine.lookup(instance:Instance): Engine?
	for i, v in ipairs(Engine.Instances) do
		if v.UID == instance:GetAttribute(lookupPrefix .. "UID") then
			return v
		end
	end
end

function Engine.PlaceComponent(self:Engine, component:Component.Component, slot:BasePart): ()
	local clone = component.Body:Clone()
	clone:PivotTo(slot.CFrame)
	clone.Parent = slot
	
	local weld:WeldConstraint = Instance.new("WeldConstraint")
	weld.Part0 = slot
	weld.Part1 = clone.PrimaryPart
	weld.Parent = slot
	
	slot.Transparency = 1
	
	component.Tool:Destroy()
	
	self:Update()
end

function Engine.RemoveComponent(self:Engine, body:Model, slot:BasePart): ()
	local component = Component.new(slot:GetAttribute("ComponentType"), slot.Position + Vector3.new(0, 5, 0))
	body:Destroy()
	
	slot.Transparency = 0.5
	
	self:Update()
end

function Engine.Update(self:Engine): ()
	local count = 0
	local filled = 0
	for i, v in ipairs(self.Body:GetChildren()) do
		if v.Name == "Slot" then
			count += 1
			if v:GetAttribute("Filled") == true then filled += 1; end
		end
	end
	
	self.Body:SetAttribute("Built", count == filled);
end

return Engine
1 Like

I’m also questioning myself if I should discard the engine modules all together and just use the component module.

maybe something like

type Engine = Component & {
-- Engine type code

}

& merges 2 tables together if you got errors then try replacing it with | (not 100% sure if it will work though)

1 Like

the problem is not with the merging but rather the .Instances part of each module. Is it not possible to complete without the instances being part of both the instances tables?

1 Like

do you mean to make the .Instances table shared between both modules?

So whenever I create an object of any class, I register it into the instances table. The problem now is that when I create an Engine Object, it will be registered in both the Component instances and Engine instances since I apply the register logic in both constructors. I only want the engine object to be registered in the engine instances only.

I’m not sure but the way I merged 2 of my modules is that I set a __index to the module you want to inherit from, so if it doesnt find it in the engine module then it will look for it in the component module?

Engine.Instances.__index = Component.Instances;

I’m not sure though, I havent touched OOP in some time?
I didnt test it, but in theory if it doesn’t find a valid value in the engine module then it’ll look into the components module and index in the module.instances