commit b3176c4ccf4106d6d9c6aed3b94da44b63a91688
Author: Eagle517 <15026800+Eagle517@users.noreply.github.com>
Date: Thu Apr 18 23:00:32 2019 -0600
Initial commit
diff --git a/StartBackend.bat b/StartBackend.bat
new file mode 100644
index 0000000..0dd6b81
--- /dev/null
+++ b/StartBackend.bat
@@ -0,0 +1,4 @@
+@echo off
+cd "sim"
+luajit "main.lua"
+pause
\ No newline at end of file
diff --git a/sim/gate.lua b/sim/gate.lua
new file mode 100644
index 0000000..e6f9acf
--- /dev/null
+++ b/sim/gate.lua
@@ -0,0 +1,53 @@
+Gate = {}
+
+function Gate:new(objref, definition)
+ local o = {
+ objref = objref,
+ definition = definition,
+ ports = {}
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+function Gate:addport(port)
+ self.ports[#self.ports+1] = port
+ port.gate = self
+end
+
+function Gate:setportstate(index, state)
+ self.ports[index]:setstate(state)
+end
+
+-- function Gate:cb(...)
+-- local args = {...}
+-- local str = tostring(#args)
+
+-- for i, v in ipairs(args) do
+-- v = bool_to_int[v] or tostring(v)
+-- str = str .. "\t" .. tostring(v)
+-- end
+
+-- sim:queuecallback(self, str)
+-- end
+
+function Gate:cb(str)
+ sim:queuecallback(self, str)
+end
+
+function Gate:queue(delay)
+ sim:queuegatelater(self, delay)
+end
+
+function Gate:testlogic(n)
+ local time = os.clock()
+ for i = 1, n do
+ self.definition.logic(self)
+ end
+ client:send("TEST\t" .. (os.clock()-time) .. "\n")
+end
+
+function Gate:gettick()
+ return sim.currenttick
+end
diff --git a/sim/gatedef.lua b/sim/gatedef.lua
new file mode 100644
index 0000000..1ce8618
--- /dev/null
+++ b/sim/gatedef.lua
@@ -0,0 +1,87 @@
+
+require "utility"
+
+GateDefinition = {
+ ports = {},
+ logic = function(gate) end,
+ input = function(gate, argv) end
+}
+
+function GateDefinition:new(objref, name, description, init, logic, input, global, ports)
+ local o = {
+ objref = objref,
+ name = name,
+ description = description,
+ ports = ports or {}
+ }
+
+ init = collapseescape(init)
+ logic = collapseescape(logic)
+ input = collapseescape(input)
+ global = collapseescape(global)
+
+ local initfunc = loadstring(tostring(init))
+ if initfunc~=nil then
+ o.init = initfunc() or function()end
+ else
+ print("Error loading init func for ".. (name or ""))
+ end
+
+ local logicfunc = loadstring(tostring(logic))
+ if logicfunc ~= nil then
+ o.logic = logicfunc() or function()end
+ else
+ print("Error loading logic function for " .. (name or ""))
+ print(logic)
+ end
+
+ local inputfunc = loadstring(tostring(input))
+ if inputfunc ~= nil then
+ o.input = inputfunc() or function()end
+ else
+ print("Error loading input function for " .. (name or ""))
+ end
+
+ local globalfunc = loadstring(tostring(global))
+ if globalfunc~=nil then
+ globalfunc()
+ else
+ print("Error loading global function for ".. (name or ""))
+ end
+
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+function GateDefinition:constructgate(objref, position, rotation)
+ local gate = Gate:new(objref, self)
+
+ for i = 1, #self.ports do
+ local port = self.ports[i]
+ local type = port.type
+ local pos = {port.position[1], port.position[2], port.position[3]}
+ local dir = port.direction
+
+ if dir < 4 then
+ dir = (dir + rotation) % 4
+ end
+
+ local x = pos[1]
+
+ if rotation == 1 then
+ pos[1] = pos[2]
+ pos[2] = -x
+ elseif rotation == 2 then
+ pos[1] = -pos[1]
+ pos[2] = -pos[2]
+ elseif rotation == 3 then
+ pos[1] = -pos[2]
+ pos[2] = x
+ end
+
+ gate:addport(Port:new(type, dir, {position[1]+pos[1], position[2]+pos[2], position[3]+pos[3]}, port.causeupdate))
+ end
+
+ return gate
+end
diff --git a/sim/group.lua b/sim/group.lua
new file mode 100644
index 0000000..88123ab
--- /dev/null
+++ b/sim/group.lua
@@ -0,0 +1,147 @@
+Group = {}
+
+function Group:new()
+ local o = {
+ state = false,
+ fxstate = false,
+ updatetick = 0,
+ wires = {},
+ out_ports = {},
+ in_ports = {},
+
+ nwires = 0,
+ nout_ports = 0,
+ nin_ports = 0
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+function Group:getsize()
+ return self.nwires + self.nout_ports + self.nin_ports
+end
+
+function Group:addwire(wire)
+ if wire.group ~= self then
+ if wire.group ~= nil then
+ self:mergewith(wire.group)
+ else
+ self.wires[wire] = wire
+ self.nwires = self.nwires + 1
+
+ wire.group = self
+ wire:update()
+ sim:queuegroup(self)
+ end
+ end
+end
+
+function Group:removewire(wire)
+ wire.group = nil
+ self.wires[wire] = nil
+
+ for k, wire in pairs(self.wires) do
+ wire.group = nil
+ end
+
+ for k, port in pairs(self.out_ports) do
+ port.group = nil
+ end
+
+ for k, port in pairs(self.in_ports) do
+ port.group = nil
+ end
+
+ for k, wire in pairs(self.wires) do
+ sim:connectwire(wire)
+ end
+
+ for k, port in pairs(self.out_ports) do
+ sim:connectport(port)
+ end
+
+ for k, port in pairs(self.in_ports) do
+ sim:connectport(port)
+ end
+
+ self.wires = {}
+ self.out_ports = {}
+ self.in_ports = {}
+
+ self.nwires = 0
+ self.nout_ports = 0
+ self.nin_ports = 0
+end
+
+function Group:addport(port)
+ port.group = self
+
+ if port.type == PortTypes.output then
+ self.out_ports[port] = port
+ self.nout_ports = self.nout_ports + 1
+ sim:queuegroup(self)
+ elseif port.type == PortTypes.input then
+ self.in_ports[port] = port
+ self.nin_ports = self.nin_ports + 1
+ port:setinputstate(self.state)
+ end
+end
+
+function Group:removeport(port)
+ if port.type == PortTypes.output then
+ self.out_ports[port] = nil
+ self.nout_ports = self.nout_ports - 1
+ elseif port.type == PortTypes.input then
+ self.in_ports[port] = nil
+ self.nin_ports = self.nin_ports - 1
+ end
+
+ sim:queuegroup(self)
+end
+
+function Group:mergewith(group)
+ if self:getsize() >= group:getsize() then
+ group:mergeinto(self)
+ return self
+ else
+ self:mergeinto(group)
+ return group
+ end
+end
+
+function Group:mergeinto(group)
+ for k, wire in pairs(self.wires) do
+ wire.group = nil
+ group:addwire(wire)
+ end
+
+ for k, port in pairs(self.out_ports) do
+ group:addport(port)
+ end
+
+ for k, port in pairs(self.in_ports) do
+ group:addport(port)
+ end
+
+ self.wires = {}
+ self.out_ports = {}
+ self.in_ports = {}
+
+ self.nwires = 0
+ self.nout_ports = 0
+ self.nin_ports = 0
+end
+
+function Group:setstate(state)
+ if state ~= self.state then
+ self.state = state
+ self.updatetick = sim.currenttick
+
+ for k, port in pairs(self.in_ports) do
+ port:setinputstate(state)
+ end
+
+ sim:queuegroupfx(self)
+ end
+end
diff --git a/sim/main.lua b/sim/main.lua
new file mode 100644
index 0000000..8a96c8e
--- /dev/null
+++ b/sim/main.lua
@@ -0,0 +1,312 @@
+dofile("simulation.lua")
+dofile("group.lua")
+dofile("wire.lua")
+dofile("gatedef.lua")
+dofile("gate.lua")
+dofile("port.lua")
+
+OPT_TICK_ENABLED = true
+OPT_TICK_TIME = 0
+OPT_FX_UPDATES = true
+OPT_FX_TIME = 0.03
+
+bool_to_int = {[false] = 0, [true] = 1}
+
+local lastticktime = 0
+local ticks = 0
+local tickrate = 0
+local lastmeasuretime = 0
+local lastfxtime = 0
+
+local avgticks = {}
+local totalticks = 0
+
+sim = Simulation:new()
+
+local units = {
+ "uTz",
+ "mTz",
+ "Tz",
+ "kTz",
+ "MTz",
+ "GTz",
+}
+
+local function round(x)
+ return math.floor(x+0.5)
+end
+
+local function unitize(v)
+ local unit = 1
+ v = v*1000000
+
+ while v >= 1000 do
+ v = v/1000
+ unit = unit+1
+ end
+
+ local s
+ if v >= 100 then
+ s = "" .. round(v/10)*10
+ elseif v >= 10 then
+ s = "" .. round(v)
+ elseif v >= 1 then
+ s = "" .. round(v*10)/10
+ if #s == 1 then s = s .. ".0" end
+ else
+ s = 0
+ end
+
+ return s .. " " .. units[unit]
+end
+
+function vectotable(vec)
+ local tbl = {}
+ for comp in string.gmatch(vec, "([^%s]+)") do
+ tbl[#tbl+1] = tonumber(comp)
+ end
+ return tbl
+end
+
+function tabletostring(table)
+ local str = tostring(table[1])
+ for i = 2, #table do
+ str = str .. " " .. tostring(table[i])
+ end
+ return str
+end
+
+function toboolean(value)
+ local num = tonumber(value)
+ if num == 1 then
+ return true
+ else
+ return false
+ end
+end
+
+local function acceptclient()
+ client = server:accept()
+ client:settimeout(0)
+ local ip, port = client:getsockname()
+ print("Connection from " .. ip .. ":" .. port)
+end
+
+local socket = require("socket")
+server = assert(socket.bind("*", 25000))
+client = nil
+
+local ip, port = server:getsockname()
+print("Server listening on " .. ip .. ":" .. port)
+
+acceptclient()
+
+while 1 do
+ local line, err = client:receive()
+
+ if not err then
+ local data = {}
+ local i = 1
+ line = line:gsub(";;", "; ;")
+ for str in string.gmatch(line, "([^;]+)") do
+ data[i] = str
+ i = i + 1
+ end
+
+ local i = 1
+ while i <= #data do
+ if data[i] == "W" then
+ local min = vectotable(data[i+3])
+ local max = vectotable(data[i+4])
+ local bounds = {min[1], min[2], min[3], max[1], max[2], max[3]}
+
+ local wire = Wire:new(tonumber(data[i+1]), tonumber(data[i+2]), bounds)
+ sim:addwire(wire)
+
+ i = i + 4
+ elseif data[i] == "G" then
+ local objref = tonumber(data[i+1])
+ local definition = sim:getdefinitionbyref(tonumber(data[i+2])) or GateDefinition
+ local position = vectotable(data[i+3])
+ local rotation = tonumber(data[i+4])
+ local gate = definition:constructgate(objref, position, rotation)
+
+ sim:addgate(gate)
+ --print(gate.objref)
+ gate.definition.init(gate)
+ gate.definition.logic(gate)
+
+ i = i + 4
+ elseif data[i] == "RW" then
+ sim:removewire(tonumber(data[i+1]))
+ i = i + 1
+ elseif data[i] == "RG" then
+ sim:removegate(tonumber(data[i+1]))
+ i = i + 1
+ elseif data[i] == "GD" then
+ --print(table.concat(data, "\n", i, math.min(#data, i+100)))
+ local objref = tonumber(data[i+1])
+ local name = data[i+2]
+ local desc = data[i+3]
+ local init = data[i+4]
+ local logic = data[i+5]
+ local input = data[i+6]
+ local global = data[i+7]
+ local numports = tonumber(data[i+8])
+ local ports = {}
+
+ for a = i+9, numports*5+i+8, 5 do
+ local port = {
+ type = tonumber(data[a]),
+ position = vectotable(data[a+1]),
+ direction = tonumber(data[a+2]),
+ causeupdate = toboolean(data[a+3]),
+ name = data[a+4],
+ }
+ ports[#ports+1] = port
+
+ if not port.direction then print(line) end
+ end
+
+ local definition = GateDefinition:new(objref, name, desc, init, logic, input, global, ports)
+ sim:addgatedefinition(definition)
+
+ i = i + 8 + numports*5
+ elseif data[i] == "SL" then
+ local wire = sim:getwirebyref(tonumber(data[i+1]))
+ if wire ~= nil then
+ wire:setlayer(tonumber(data[i+2]))
+ end
+
+ i = i + 2
+ elseif data[i] == "SP" then
+ local gate = sim:getgatebyref(tonumber(data[i+1]))
+ if gate ~= nil then
+ gate.ports[tonumber(data[i+2])]:setstate(toboolean(data[i+3]))
+ end
+
+ i = i + 3
+ elseif data[i] == "SG" then
+ local wire = sim:getwirebyref(tonumber(data[i+1]))
+ if wire ~= nil then
+ wire.group:setstate(toboolean(data[i+2]))
+ end
+
+ i = i + 2
+ elseif data[i] == "OPT" then
+ local option = data[i+1]
+ local value = tonumber(data[i+2])
+
+ if option == "TICK_ENABLED" then
+ OPT_TICK_ENABLED = toboolean(value)
+ elseif option == "TICK_TIME" then
+ if value < 0 or value > 999999 then
+ value = 0
+ end
+ OPT_TICK_TIME = value
+ elseif option == "FX_UPDATES" then
+ OPT_FX_UPDATES = toboolean(value)
+ elseif option == "FX_TIME" then
+ if value < 0 or value > 999999 then
+ value = 0
+ end
+ OPT_FX_TIME = value
+ end
+
+ i = i + 2
+ elseif data[i] == "GINFO" then
+ local userid = data[i+1]
+ local objref = tonumber(data[i+2])
+
+ local obj = sim:getwirebyref(objref) or sim:getgatebyref(objref)
+
+ if obj ~= nil then
+ local info = ""
+
+ if obj.logictype == 0 then
+ local numportsi = 0; for k, wire in pairs(obj.group.in_ports ) do numportsi = numportsi+1 end
+ local numportso = 0; for k, wire in pairs(obj.group.out_ports) do numportso = numportso+1 end
+
+ info = "\\c5WIRE
" .. (obj.group.state and "\\c2ON" or "\\c0OFF") .. "\n" ..
+ "IN PORTS: " ..numportsi.."\n"..
+ "OUT PORTS: "..numportso
+ ;
+ else
+ info = "\\c5" .. obj.definition.name .. "
"
+ for i = 1, #obj.ports do
+ info = info .. (obj.ports[i].state and "\\c2" or "\\c0") .. obj.definition.ports[i].name .. (i ~= #obj.ports and " " or "")
+ end
+ end
+
+ if info ~= "" then
+ client:send("GINFO\t" .. userid .. "\t" .. expandescape(info) .. "\n")
+ end
+ end
+
+ i = i + 2
+ elseif data[i] == "SINFO" then
+ client:send("SINFO\t" .. data[i+1] .. "\t" .. sim.nwires .. "\t" .. sim.ngates .. "\t" .. sim.ninports .. "\t" .. sim.noutports .. "\n")
+ i = i + 1
+ elseif data[i] == "TICK" then
+ sim:tick()
+ ticks = ticks + 1
+ elseif data[i] == "TEST" then
+ local gate = sim:getgatebyref(tonumber(data[i+1]))
+ gate:testlogic(tonumber(data[i+2]))
+ i = i + 2
+ elseif data[i] == "IN" then
+ local gate = sim:getgatebyref(tonumber(data[i+1]))
+ local argc = tonumber(data[i+2])
+ local argv = {}
+ for a = i+3, i+3+argc-1 do
+ argv[#argv+1] = collapseescape(data[a])
+ end
+ sim:queuegateinput(gate, argv)
+
+ i = i+2+argc
+ end
+
+ i = i + 1
+ end
+ elseif err == "closed" then
+ sim = Simulation:new()
+ acceptclient()
+ end
+
+ local time = os.clock()
+
+ if OPT_TICK_ENABLED then
+ if time - lastticktime >= OPT_TICK_TIME then
+ sim:tick()
+ ticks = ticks + 1
+ lastticktime = time
+
+ local timetonext = time+OPT_TICK_TIME-os.clock()
+
+ local sleeptime = timetonext*0.9
+ if sleeptime>0 then socket.sleep(sleeptime) end
+ end
+ else
+ socket.sleep(0.1)
+ end
+
+ if time-lastfxtime >= OPT_FX_TIME then
+ sim:sendfxupdate()
+ sim:sendcallbacks()
+ lastfxtime = time
+ end
+
+ if time-lastmeasuretime >= 0.1 then
+ if #avgticks >= 20 then
+ totalticks = totalticks - table.remove(avgticks, 1)
+ end
+
+ table.insert(avgticks, ticks)
+ totalticks = totalticks + ticks
+
+ ticks = 0
+
+ client:send("TPS\t" .. unitize((totalticks/#avgticks)/0.1) .. "\n")
+ lastmeasuretime = os.clock()
+ end
+end
diff --git a/sim/port.lua b/sim/port.lua
new file mode 100644
index 0000000..9ee5662
--- /dev/null
+++ b/sim/port.lua
@@ -0,0 +1,67 @@
+PortTypes = {
+ output = 0,
+ input = 1
+}
+
+PortDirections = {
+ [0] = {-1, 0, 0},
+ [1] = {0, 1, 0},
+ [2] = {1, 0, 0},
+ [3] = {0, -1, 0},
+ [4] = {0, 0, 1},
+ [5] = {0, 0, -1}
+}
+
+Port = {
+ logictype = 1,
+}
+
+function Port:new(type, direction, position, causeupdate)
+ local o = {
+ type = type,
+ direction = direction,
+ position = position,
+ causeupdate = causeupdate,
+ state = false,
+ gate = nil,
+ group = nil,
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+function Port:setstate(state)
+ if state ~= self.state then
+ self.state = state
+ sim:queuegroup(self.group)
+ end
+end
+
+function Port:setinputstate(state)
+ if state ~= self.state then
+ self.state = state
+ if self.causeupdate then
+ sim:queuegate(self.gate)
+ end
+ end
+end
+
+function Port:getconnectionposition()
+ local offset = PortDirections[self.direction]
+ return {self.position[1]+offset[1], self.position[2]+offset[2], self.position[3]+offset[3]}
+end
+
+function Port:isrising()
+ if self.group == nil then
+ return false
+ end
+ return self.group.state and (self.group.updatetick == sim.currenttick)
+end
+
+function Port:isfalling()
+ if self.group == nil then
+ return false
+ end
+ return self.group.state == false and (self.group.updatetick == sim.currenttick)
+end
diff --git a/sim/simulation.lua b/sim/simulation.lua
new file mode 100644
index 0000000..3afadb2
--- /dev/null
+++ b/sim/simulation.lua
@@ -0,0 +1,343 @@
+
+require "utility"
+
+Simulation = {}
+
+function Simulation:new()
+ local o = {
+ definitions = {},
+ wires = {},
+ gates = {},
+
+ nwires = 0,
+ ngates = 0,
+ ninports = 0,
+ noutports = 0,
+
+ groupqueue = {},
+ groupfxqueue = {},
+ gatequeue = {},
+ initqueue = {},
+ inputqueue = {},
+ tickqueue = {},
+
+ callbacks = {},
+
+ currenttick = 0
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+function Simulation:addtoworld(obj, x, y, z)
+ if self[x] == nil then
+ self[x] = {}
+ end
+
+ if self[x][y] == nil then
+ self[x][y] = {}
+ end
+
+ if self[x][y][z] == nil then
+ self[x][y][z] = {}
+ end
+
+ self[x][y][z][obj] = obj
+end
+
+function Simulation:getfromworld(x, y, z)
+ if self[x] == nil or self[x][y] == nil or self[x][y][z] == nil then
+ return {}
+ else
+ return self[x][y][z]
+ end
+end
+
+function Simulation:getdefinitionbyref(objref)
+ return self.definitions[objref]
+end
+
+function Simulation:getgatebyref(objref)
+ return self.gates[objref]
+end
+
+function Simulation:getwirebyref(objref)
+ return self.wires[objref]
+end
+
+function Simulation:addgatedefinition(definition)
+ self.definitions[definition.objref] = definition
+end
+
+function Simulation:addwire(wire)
+ self.wires[wire.objref] = wire
+
+ for x = wire.bounds[1]+1, wire.bounds[4]-1, 2 do
+ for z = wire.bounds[3]+1, wire.bounds[6]-1, 2 do
+ self:addtoworld(wire, x, wire.bounds[2], z)
+ self:addtoworld(wire, x, wire.bounds[5], z)
+ end
+ end
+
+ for y = wire.bounds[2]+1, wire.bounds[5]-1, 2 do
+ for z = wire.bounds[3]+1, wire.bounds[6]-1, 2 do
+ self:addtoworld(wire, wire.bounds[1], y, z)
+ self:addtoworld(wire, wire.bounds[4], y, z)
+ end
+ end
+
+ for x = wire.bounds[1]+1, wire.bounds[4]-1, 2 do
+ for y = wire.bounds[2]+1, wire.bounds[5]-1, 2 do
+ self:addtoworld(wire, x, y, wire.bounds[3])
+ self:addtoworld(wire, x, y, wire.bounds[6])
+ end
+ end
+
+ self.nwires = self.nwires + 1
+ self:connectwire(wire)
+end
+
+function Simulation:addgate(gate)
+ self.gates[gate.objref] = gate
+
+ for k, port in pairs(gate.ports) do
+ local offset = port:getconnectionposition()
+ self:addtoworld(port, offset[1], offset[2], offset[3])
+ self:connectport(port)
+
+ if port.type == PortTypes.input then
+ self.ninports = self.ninports + 1
+ elseif port.type == PortTypes.output then
+ self.noutports = self.noutports + 1
+ end
+ end
+
+ self.ngates = self.ngates + 1
+end
+
+function Simulation:removewire(objref)
+ local wire = self.wires[objref]
+ if wire ~= nil then
+ self.wires[objref] = nil
+
+ for x = wire.bounds[1]+1, wire.bounds[4]-1, 2 do
+ for z = wire.bounds[3]+1, wire.bounds[6]-1, 2 do
+ sim[x][wire.bounds[2]][z][wire] = nil
+ sim[x][wire.bounds[5]][z][wire] = nil
+ end
+ end
+
+ for y = wire.bounds[2]+1, wire.bounds[5]-1, 2 do
+ for z = wire.bounds[3]+1, wire.bounds[6]-1, 2 do
+ sim[wire.bounds[1]][y][z][wire] = nil
+ sim[wire.bounds[4]][y][z][wire] = nil
+ end
+ end
+
+ for x = wire.bounds[1]+1, wire.bounds[4]-1, 2 do
+ for y = wire.bounds[2]+1, wire.bounds[5]-1, 2 do
+ sim[x][y][wire.bounds[3]][wire] = nil
+ sim[x][y][wire.bounds[6]][wire] = nil
+ end
+ end
+
+ self.nwires = self.nwires - 1
+ wire.group:removewire(wire)
+ end
+end
+
+function Simulation:removegate(objref)
+ local gate = self.gates[objref]
+ if gate ~= nil then
+ for k, port in pairs(gate.ports) do
+ local pos = port:getconnectionposition()
+ self[pos[1]][pos[2]][pos[3]][port] = nil
+ port.group:removeport(port)
+
+ if port.type == PortTypes.input then
+ self.ninports = self.ninports - 1
+ elseif port.type == PortTypes.output then
+ self.noutports = self.noutports - 1
+ end
+ end
+ end
+
+ self.gates[objref] = nil
+ self.ngates = self.ngates - 1
+end
+
+function Simulation:connectwireat(wire, x, y, z)
+ local objs = self:getfromworld(x, y, z)
+ for k, obj in pairs(objs) do
+ if obj ~= wire and obj.group ~= nil then
+ if obj.logictype == 0 and obj.layer == wire.layer then
+ if obj.layer == wire.layer then
+ obj.group:addwire(wire)
+ end
+ elseif obj.logictype == 1 then
+ obj.group:addwire(wire)
+ end
+ end
+ end
+end
+
+function Simulation:connectwire(wire)
+ for x = wire.bounds[1]+1, wire.bounds[4]-1, 2 do
+ for z = wire.bounds[3]+1, wire.bounds[6]-1, 2 do
+ self:connectwireat(wire, x, wire.bounds[2], z)
+ self:connectwireat(wire, x, wire.bounds[5], z)
+ end
+ end
+
+ for y = wire.bounds[2]+1, wire.bounds[5]-1, 2 do
+ for z = wire.bounds[3]+1, wire.bounds[6]-1, 2 do
+ self:connectwireat(wire, wire.bounds[1], y, z)
+ self:connectwireat(wire, wire.bounds[4], y, z)
+ end
+ end
+
+ for x = wire.bounds[1]+1, wire.bounds[4]-1, 2 do
+ for y = wire.bounds[2]+1, wire.bounds[5]-1, 2 do
+ self:connectwireat(wire, x, y, wire.bounds[3])
+ self:connectwireat(wire, x, y, wire.bounds[6])
+ end
+ end
+
+ if wire.group == nil then
+ Group:new():addwire(wire)
+ end
+end
+
+function Simulation:connectport(port)
+ local connpos = port:getconnectionposition()
+ local objs = self:getfromworld(connpos[1], connpos[2], connpos[3])
+ for k, obj in pairs(objs) do
+ if obj ~= port and obj.group ~= nil then
+ obj.group:addport(port)
+ end
+ end
+
+ if port.group == nil then
+ Group:new():addport(port)
+ end
+end
+
+function Simulation:queuegate(gate)
+ self.gatequeue[gate] = gate
+end
+
+function Simulation:queuegatelater(gate, delay)
+ local tick = self.currenttick + delay
+ if self.tickqueue[tick] == nil then
+ self.tickqueue[tick] = {}
+ end
+ table.insert(self.tickqueue[tick], gate)
+end
+
+function Simulation:queuegateinput(gate, argv)
+ self.inputqueue[gate] = self.inputqueue[gate] or {}
+ table.insert(self.inputqueue[gate], argv)
+end
+
+function Simulation:queuegateinit(gate)
+ self.initqueue[gate] = gate
+end
+
+function Simulation:queuegroup(group)
+ self.groupqueue[group] = group
+end
+
+function Simulation:queuegroupfx(group)
+ self.groupfxqueue[group] = group
+end
+
+function Simulation:queuecallback(gate, ...)
+ self.callbacks[gate.objref] = {...}
+end
+
+function Simulation:tick()
+ for k, group in pairs(self.groupqueue) do
+ local newstate = false
+ for j, port in pairs(group.out_ports) do
+ newstate = newstate or port.state
+ if newstate then
+ break
+ end
+ end
+
+ group:setstate(newstate)
+ end
+ self.groupqueue = {}
+
+ for k, gate in pairs(self.initqueue) do
+ gate.definition.init(gate)
+ end
+ self.initqueue = {}
+
+ for gate, inputs in pairs(self.inputqueue) do
+ for inputidx, argv in ipairs(inputs) do
+ gate.definition.input(gate, argv)
+ end
+ end
+ self.inputqueue = {}
+
+ if self.tickqueue[self.currenttick] ~= nil then
+ for i, gate in ipairs(self.tickqueue[self.currenttick]) do
+ self:queuegate(gate)
+ end
+ self.tickqueue[self.currenttick] = nil
+ end
+
+ for k, gate in pairs(self.gatequeue) do
+ gate.definition.logic(gate)
+ end
+ self.gatequeue = {}
+
+ self.currenttick = self.currenttick + 1
+end
+
+function Simulation:sendfxupdate()
+ for k, group in pairs(self.groupfxqueue) do
+ if group.state ~= group.fxstate then
+ group.fxstate = group.state
+
+ local data = bool_to_int[group.state]
+
+ if OPT_FX_UPDATES then
+ for i, wire in pairs(group.wires) do
+ data = data .. "\t" .. wire.objref
+ end
+ else
+ for i, wire in pairs(group.wires) do
+ if wire.isvisual then
+ data = data .. "\t" .. wire.objref
+ end
+ end
+ end
+
+ client:send("WU\t" .. data .. "\n")
+ end
+ end
+
+ self.groupfxqueue = {}
+end
+
+function Simulation:sendcallbacks()
+ if next(self.callbacks) ~= nil then
+ local data = "CB"
+
+ for objref, args in pairs(self.callbacks) do
+ local argc = 0
+ local escargs = {}
+ for argidx, argv in ipairs(args) do
+ table.insert(escargs, expandescape(tostring(argv)))
+ argc = argc+1
+ end
+ data = data .. "\t" .. objref .. "\t"..argc..(#escargs>0 and ("\t"..table.concat(escargs, "\t")) or "")
+ end
+
+ client:send(data .. "\n")
+ self.callbacks = {}
+ end
+end
diff --git a/sim/utility.lua b/sim/utility.lua
new file mode 100644
index 0000000..039fd84
--- /dev/null
+++ b/sim/utility.lua
@@ -0,0 +1,53 @@
+
+local escapes = {
+ {"\\", "b"},
+ {"\t", "t"},
+ {"\n", "n"},
+ {"\r", "r"},
+ {"\'", "a"},
+ {"\"", "q"},
+ {";" , "s"},
+ {":" , "c"},
+}
+
+function expandescape(str)
+ local ostrt = {}
+
+ local len = #str
+ for i=1, len do
+ local ci = str:sub(i, i)
+
+ local co = ci
+ for escidx, esc in ipairs(escapes) do
+ if ci==esc[1] then co = "\\"..esc[2] end
+ end
+
+ table.insert(ostrt, co)
+ end
+
+ return table.concat(ostrt)
+end
+
+function collapseescape(str)
+ local ostrt = {}
+
+ local i = 1
+ local len = #str
+ while i<=len do
+ local ci = str:sub(i, i)
+
+ local co = ci
+ if ci=="\\" and i