Custom Compiler Problem

For a few weeks now I’ve began writing a system to process a table into a runnable Logic Gate sequence.

How the compiler runs:

The base of every logic gate in my simulation is AND, NOT and OR gates. They use these types:

export type InputOutputObject =
	{
		['gate']: string,
		['index']: string,
	}
export type Gate = 
	{
		['outputs']: {InputOutputObject},
		['inputs']: {InputOutputObject},
		['proccessFunction']: ({[string]:boolean})->({[string]:boolean})
	}

InputOutputObject is a unique “address” for the output, each address in an entire set of gates has to be unique, as its used as an index.
Gate simply contains an array of InputOutputObjects with the inputs and outputs table. The index “proccessFunction” is a function that takes in a dictionary with the inputs and returns a dictionary with the outputs.

More complex gates

In the simulation, a feature borrowed from Sebastian Lague’s Digital Logic Sim, you can make your own gates based on a table of Gate objects, and a table containing all the “wires/connections” between the gates and the ports.

export type CustomGate = 
	{
		['gates']: {Gate},
		['inputs']: {string},
		['outputs']: {string},
		['gateName']: string,
		['gateColor']: Color3,
		['gateConnections']: {[string]: {InputOutputObject}}
	}

gates contains the table of Gate objects in the customGate.
inputs contains the list of inputs into the customGate
outputs contains the list of outputs from the customGate
gateName is the name of the gate (declared by player)
gateColor is simply the RGB colour of the gate object
gateConnections contains a dictionary referrencing the relations between the gates and the ports.

The compiler itself

The compiler takes the list of gates, the inputs into the simulation, and the outputs.

local function CompileBoard(allGates:{CustomGate},inputs:{string},outputs:{string})
	local storedConnections = {}
	local storedValues = {}
	for _, inputName in pairs(inputs) do
		storedConnections[inputName..'.base'] = signal.new()
		storedValues[inputName..'.base'] = false
	end

	for index, cGate in pairs(allGates) do
		local inp = cGate.inputs
		local out = cGate.outputs

		for _, outputName in pairs(storedConnections) do
			local conver = outputs..cGate.gateName
			if not storedConnections[conver] then storedConnections[conver] = signal.new()
			end
		end

		for _, gate in pairs(cGate.gates) do
			for _, out in pairs(gate.outputs) do
				storedConnections[conv(out)] = signal.new()
				storedValues[conv(out)] = false
			end
		end


		for index, cGate in pairs(allGates) do

			for _, gate in pairs(cGate.gates) do
				for _, inp in pairs(gate.inputs) do
					local aSig = storedConnections[conv(inp)]
					if aSig then
						aSig.Connect(function(s:true?)
							local function getInputs()
								local c = {}
								for i, v in pairs(gate.inputs) do
									
									local v = storedValues[conv(v)]
									c[v.index] = v
								end
								return c
							end
							local outputs = gate.proccessFunction(getInputs())
							for i, v in pairs(outputs) do
								storedValues[i] = v
								if s then
									storedConnections[i]:Fire(true)
								end
							end

						end)
					end
				end
			end
		end
	end
	
	local editFuncs = {}
	function editFuncs:StartUp()
		for i, v in pairs(storedConnections) do
			v:Fire()
		end
		for i, v in pairs(storedValues) do
			storedValues[i] = nil
		end
	end
	
	function editFuncs:SetToInput(inputIndex,newvalue:boolean)
		storedValues[inputIndex..'.base'] = newvalue
		storedConnections[inputIndex..'.base']:Fire(true)
	end
	
	function editFuncs:kill()
		for i, v in pairs(storedConnections) do
			v:Destroy()
		end
	end
	
	return editFuncs
end

So what’s the problem?

CustomGates can only be made with a combination of the basic Gate objects of AND, NOT and OR, meaning that custom gates can’t be used in custom gates, which would mean difficulties when making 4BIT+ gates.

I’ve tried to see what format I could use to store this, but I got no clue. I’ve even tried using GPT, still no clear answer.

If anyone who has an idea on how I could format it, please inform!

I hate Lua OOP with a passion and I won’t be able to help super quickly with specifics, but in OOP this is where you would use inheritance

You have a few options, here are some quick ones


Gate
    CustomGate

Where CustomGate inherits behavior of Gate. Then CustomGates count as Gates in the same way that Parts count as BaseParts. CustomGates can be made of Gates, which also includes Gate derivatives such as CustomGate.


BaseGate
    CustomGate
    Gate

This would be what you’d want if Gate contains things that you don’t want present in CustomGate. Both of them would inherit from BaseGate, so CustomGates could be made of BaseGates (which includes both).

Alright thanks! I’ll test that out. Although I’d also need to modify the compiler to take in CustomGates made of other CustomGates instead of just Gate objects, and I’m unsure on how to make that change in the compiler

Rewriting my entire compiler to install the new system, i’ll see how it goes when I’m finnished

After another rewrite I decided that the best way to process CustomGates was to convert them into a sequence of gates before-hand and then run it into the compiler, this method should mean a simplier compiler and most likely increased performance

Edit: Also each input/output node would be made of a string instead of an InputOutputObject, this string is simply calculated with the time the gate was added into the motherboard (os.time() and os.clock() combination)

Finialized my compiler code. Instead of gates containing their connections, there is now a list of “wires”, which contains a from and to values, which are addresses to the nodes.

type bDict = {[string]:boolean}
export type Gate = 
	{
		inputs: {string},
		outputs: {string},
		process: ((bDict)->(bDict))
	}

export type wire = 
	{
		fromIndex: string,
		toIndex: string,
	}

local function CompileBoard(gates:{Gate},wires:{wire},inputs:{string},outputs:{string})
	local signalTable = {}
	local valueTable = {}
	local gateReferances = {}
	
	local toProcessCalls = {}
	game.ReplicatedStorage.clock.Event:Connect(function()
		for i, v in pairs(toProcessCalls) do v() end
	end)
	
	local function AddIndex(i,g)
		signalTable[i] = signal.new()
		valueTable[i] = false
		gateReferances[i] = g
	end
	for i, gate in pairs(gates) do
		for _, input in pairs(gate.inputs) do
			AddIndex(input,i)
		end
		for _, output in pairs(gate.outputs) do
			AddIndex(output,i)
		end
	end
	
	for _, input in pairs(inputs) do
		signalTable[input] = signal.new()
		valueTable[input] = false
	end
	for _, output in pairs(outputs) do
		signalTable[outputs] = signal.new()
		valueTable[outputs] = false
	end
	for i, wire in pairs(wires) do
		signalTable[wire.fromIndex].Connect(function()
			local gate = gates[gateReferances[wire.toIndex]]
			local inputvalues = {}
			for i ,v in pairs(gate.inputs) do
				inputvalues[v] = valueTable[v]
			end
			local outputs = gate.process(inputvalues)
			toProcessCalls[#toProcessCalls+1] = function()
				for index, value in pairs(outputs) do
					valueTable[index] = value
					signalTable[index]:Fire()
				end
			end
		end)
	end
	
	return
		{
			['toggleInput'] = function(inputindex)
				valueTable[inputindex] = not valueTable[inputindex]
				signalTable[inputindex]:Fire()
			end,
			['kill'] = function()
				for i, v in pairs(signalTable) do
				v:Destroy()
				end
			end,
			['startup'] = function()
				for i, v in pairs(signalTable) do
				v:Fire()
			end
			end,
			['getIndexValue'] = function(index)
				return valueTable[index]
			end,
		}
end
2 Likes

An update on this game, I sort of forgot about it but I may revisit it soon. Latest release date will probably be May 2024