diff --git a/data_static/fpgachip/_helloworld_.txt b/data_static/fpgachip/_helloworld_.txt
new file mode 100644
index 0000000000..81b7b63c68
--- /dev/null
+++ b/data_static/fpgachip/_helloworld_.txt
@@ -0,0 +1 @@
+~'ExecutionInterval":n0.1;'Position":{n2.6361454580615;6.2933851740375}'ExecuteOn":{~'Timed":b1'Trigger":b0'Inputs":b1}'Name":'Hello World!"'Nodes":{{~'ioName":'A"'gate":'normal-input"'connections":{}'type":'fpga"'y":n-20;'x":n-45}{~'ioName":'B"'gate":'normal-input"'connections":{}'type":'fpga"'y":n-5;'x":n-45}{~'gate":'+"'connections":{{n1;1}{n2;1}}'type":'wire"'y":n-20;'x":n-5}{~'ioName":'Add"'gate":'normal-output"'connections":{{n3;1}}'type":'fpga"'y":n-20;'x":n35}{~'ioName":'RotatedBy"'gate":'angle-input"'connections":{}'type":'fpga"'y":n40;'x":n-45}{~'ioName":'Vector"'gate":'vector-input"'connections":{}'type":'fpga"'y":n25;'x":n-45}{~'gate":'vector_rotate"'connections":{{n6;1}{n5;1}}'type":'wire"'y":n30;'x":n-5}{~'ioName":'Rotated"'gate":'string-output"'connections":{{n9;1}}'type":'fpga"'y":n30;'x":n35}{~'gate":'vector_tostr"'connections":{{n7;1}}'type":'wire"'y":n30;'x":n15}{~'ioName":'Text"'gate":'string-output"'connections":{{n11;1}}'type":'fpga"'y":n55;'x":n35}{~'gate":'string-constant"'connections":{}'value":'Hello World!"'type":'fpga"'y":n55;'x":n-5}{~'x":n20;'connections":{}'value":'Hello world!"'type":'editor"'y":n0;'visual":'label"}@{~'x":n20;'connections":{}'value":'Here's a comment"'type":'editor"'visual":'comment"'y":n5}}'Zoom":n6.6919658530683;
\ No newline at end of file
diff --git a/data_static/fpgahelp.txt b/data_static/fpgahelp.txt
new file mode 100644
index 0000000000..41d8e3ef7d
--- /dev/null
+++ b/data_static/fpgahelp.txt
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
The FPGA can be made to execute on different conditions.
+ As default, it will execute if one of the inputs changes, or if it has a timed gate (as marked by red) inside it.
+ It should be noted that if 2 different inputs change the same tick, the FPGA will execute twice,
+ where the first execution will be with an old value for one of the inputs. This is due to how Wiremod works, where only one input is triggered at a time.
+
+
+ To further customize chip execution, there are 3 different ways a chip can trigger an execution.
+
+
Inputs
+
+
+ The chip executes when an input is changed, and propagates the changes inside it, updating the gates affected by the input change.
+ Gates that aren't affected by the input change, will not execute.
+
+
+
Timed
+
+
+ Timed execution only affects gates which are timed (marked by red).
+ This includes gates such as 'OS Time' and 'Entity Position', which share the property that their output is time dependant.
+ For these gates to always have the correct output, timed execution needs to be on.
+ The frequency that these timed gates are updated with can be controlled with the Execution Interval setting.
+
+
+
Trigger In
+
+
+ For the greatest control over executions, the other options can be turned off and this one turned on.
+ The gate will get a "Trigger" input, which when set to something other than 0, will cause the chip to execute everything necessary.
+ The FPGA keeps a "lazy queue", such that it knows which gates will need to execute when the "Trigger" input is triggered.
+ This includes all timed gates, and input gates which have had their value changed since last trigger.
+
+
+
+
Special execution gates
+
+
+ To further customize how a chip executes, some special execution gates have been included.
+ They can be found under FPGA/Execution
+
Execution Delta
+
+
+ This chip will return the time between the current execution and the last one.
+ Useful for time critical circuitry - such as levitating - or calculations where the time difference is required.
+
+
+
Execution Count
+
+
+ Increments by one each time the chip executes
+
+
+
Last (Normal/Vector/Angle/String)
+
+
+ Mainly designed to allow looping circuitry. For example, a memory gate feeding it's own value + 1 into itself will produce an infinite loop.
+ If a Last gate is put somewhere in the loop, it will allow it to be executed. It does this by using the value the gate connected to it's input had last execution,
+ 'disengaging' the infinite loop.
+
+
+
Previous (Normal/Vector/Angle/String)
+
+
+ An alternative to the Last gate, that functions a bit differently. This gate will output the value the connected gate had the previous tick,
+ which differs from the Last gate behaviour both because multiple executions can happen each tick, which will cause the Last gate to change, but not the Previous gate.
+ The most important difference, is that the Previous gate will trigger a new execution during next tick, with the updated value.
+ This can cause a chain reaction, if this execution changes the Previous gate, causing it to trigger next tick again.
+ To avoid such chain reactions, the value should somehow stabilize - but the internal circuitry decides that.
+
+
+
Last Timed (Normal/Vector/Angle/String)
+
+
+ Alternative form of the Last gate, this one can trigger an execute if the FPGA is set to trigger on Timed.
+ This is useful for loops that are meant to execute every Execution Interval, where you don't care if the value has actually changed or not.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lua/autorun/wire_load.lua b/lua/autorun/wire_load.lua
index cd791db6be..6cf4e42a25 100644
--- a/lua/autorun/wire_load.lua
+++ b/lua/autorun/wire_load.lua
@@ -30,6 +30,8 @@ if SERVER then
AddCSLuaFile("wire/wireshared.lua")
AddCSLuaFile("wire/wirenet.lua")
AddCSLuaFile("wire/wiregates.lua")
+ AddCSLuaFile("wire/fpgagates.lua")
+ AddCSLuaFile("wire/cpugates.lua")
AddCSLuaFile("wire/wiremonitors.lua")
AddCSLuaFile("wire/gpulib.lua")
AddCSLuaFile("wire/timedpairs.lua")
@@ -63,6 +65,10 @@ if SERVER then
AddCSLuaFile("wire/client/text_editor/texteditor.lua")
AddCSLuaFile("wire/client/text_editor/wire_expression2_editor.lua")
+ -- node editor
+ AddCSLuaFile("wire/client/node_editor/nodeeditor.lua")
+ AddCSLuaFile("wire/client/node_editor/wire_fpga_editor.lua")
+
for _, filename in ipairs(file.Find("wire/client/text_editor/modes/*.lua","LUA")) do
AddCSLuaFile("wire/client/text_editor/modes/" .. filename)
end
@@ -74,6 +80,8 @@ include("wire/wireshared.lua")
include("wire/wirenet.lua")
include("wire/wire_paths.lua")
include("wire/wiregates.lua")
+include("wire/fpgagates.lua")
+include("wire/cpugates.lua")
include("wire/wiremonitors.lua")
include("wire/gpulib.lua")
include("wire/timedpairs.lua")
@@ -120,6 +128,8 @@ if CLIENT then
include("wire/client/thrusterlib.lua")
include("wire/client/rendertarget_fix.lua")
include("wire/client/customspawnmenu.lua")
+ include("wire/client/node_editor/nodeeditor.lua")
+ include("wire/client/node_editor/wire_fpga_editor.lua")
end
if SERVER then print("Wiremod " .. select(2, WireLib.GetVersion()) .. " loaded") end
diff --git a/lua/entities/gmod_wire_fpga/cl_init.lua b/lua/entities/gmod_wire_fpga/cl_init.lua
new file mode 100644
index 0000000000..eb1812b72d
--- /dev/null
+++ b/lua/entities/gmod_wire_fpga/cl_init.lua
@@ -0,0 +1,277 @@
+include("shared.lua")
+
+--------------------------------------------------------------------------------
+-- Drawing
+--------------------------------------------------------------------------------
+
+function ENT:GetWorldTipBodySize()
+ local data = self:GetOverlayData()
+ if not data then return 100, 20 end
+
+ local w_total,h_total = surface.GetTextSize(data.name)
+
+ if data.errorMessage then
+ local str = data.errorMessage
+ local w,h = surface.GetTextSize(str)
+ w_total = math.max(w_total, w)
+ h_total = h_total + h + 10
+ end
+
+ local timebench = data.timebench
+ local timebenchPeak = data.timebenchPeak
+
+ -- cpu time text
+ local str = string.format("cpu time: %ius", timebench * 1000000)
+ local w,h = surface.GetTextSize(str)
+ w_total = math.max(w_total, w)
+ h_total = h_total + h + 10
+
+ local str = string.format("peak cpu time: %ius", timebenchPeak * 1000000)
+ local w,h = surface.GetTextSize(str)
+ w_total = math.max(w_total, w)
+ h_total = h_total + h + 10
+
+ return w_total, h_total
+end
+
+
+function ENT:DrawWorldTipBody(pos)
+ local data = self:GetOverlayData()
+ if not data then return end
+
+ local name = data.name
+
+ local white = Color(255,255,255,255)
+ local error = Color(255,0,0,255)
+ local cputime = Color(150,150,255,255)
+ local cputimeavg = Color(130,240,130,255)
+ local black = Color(0,0,0,255)
+
+ local w_total, yoffset = 0, pos.min.y
+
+ -------------------
+ -- Name
+ -------------------
+ local w,h = surface.GetTextSize(name)
+ h = h + pos.edgesize
+ h = math.min(h, pos.size.h - pos.footersize.h)
+
+ render.SetScissorRect(pos.min.x + 16, pos.min.y, pos.max.x - 16, pos.min.y + h, true)
+ draw.DrawText(name, "GModWorldtip", pos.min.x + pos.size.w/2, yoffset + 10, white, TEXT_ALIGN_CENTER)
+ render.SetScissorRect(0, 0, ScrW(), ScrH(), false)
+
+ w_total = math.max(w_total, w)
+ yoffset = yoffset + h
+
+ -- Error message
+ if data.errorMessage then
+ local str = "("..data.errorMessage..")"
+ draw.DrawText(str, "GModWorldtip", pos.min.x + pos.size.w/2, yoffset, error, TEXT_ALIGN_CENTER)
+ yoffset = yoffset + 30
+ end
+
+ --Line
+ surface.SetDrawColor(black)
+ surface.DrawLine(pos.min.x, yoffset, pos.max.x, yoffset)
+
+ -------------------
+ -- prfcount/benchmarking/etc
+ -------------------
+ local timebench = data.timebench
+ local timebenchPeak = data.timebenchPeak
+
+ -- cpu time text
+ local str = string.format("cpu time: %ius", timebench * 1000000)
+ draw.DrawText(str, "GModWorldtip", pos.min.x + pos.size.w/2, yoffset + 8, cputimeavg, TEXT_ALIGN_CENTER)
+ -- cpu peak time text
+ local str = string.format("peak cpu time: %ius", timebenchPeak * 1000000)
+ draw.DrawText(str, "GModWorldtip", pos.min.x + pos.size.w/2, yoffset + 8 + 25, cputime, TEXT_ALIGN_CENTER)
+
+
+ -------------------
+ -- inside view
+ -------------------
+ if LocalPlayer():KeyDown(IN_USE) then
+ if self.ViewData then
+ self:DrawInsideViewBackground()
+ self:DrawInsideView()
+ end
+ end
+end
+
+--------------------------------------------------------------------------------
+-- Inside view
+--------------------------------------------------------------------------------
+
+--only square for now
+--x start, x end, y start, y end
+--in relation to screen center
+FPGAInsideViewPosition = {
+ 50,
+ 850,
+ -400,
+ 400
+}
+
+function ENT:DrawInsideViewBackground()
+ local x1 = ScrW()/2 + FPGAInsideViewPosition[1]
+ local x2 = ScrW()/2 + FPGAInsideViewPosition[2]
+ local y1 = ScrH()/2 + FPGAInsideViewPosition[3]
+ local y2 = ScrH()/2 + FPGAInsideViewPosition[4]
+
+ draw.NoTexture()
+ surface.SetDrawColor(Color(25,25,25,240))
+
+ local poly = {
+ {x = x1, y = y1, u = 0, v = 0 },
+ {x = x2, y = y1, u = 0, v = 0 },
+ {x = x2, y = y2, u = 0, v = 0 },
+ {x = x1, y = y2, u = 0, v = 0 },
+ }
+
+ render.CullMode(MATERIAL_CULLMODE_CCW)
+ surface.DrawPoly(poly)
+
+ surface.SetDrawColor(Color(0,0,0,255))
+
+ for i=1,#poly-1 do
+ surface.DrawLine(poly[i].x, poly[i].y, poly[i+1].x, poly[i+1].y)
+ end
+ surface.DrawLine(poly[#poly].x, poly[#poly].y, poly[1].x, poly[1].y)
+end
+
+function ENT:DrawInsideView()
+ local centerX = ScrW()/2 + (FPGAInsideViewPosition[2] - FPGAInsideViewPosition[1])/2 + FPGAInsideViewPosition[1]
+ local centerY = ScrH()/2 + (FPGAInsideViewPosition[4] - FPGAInsideViewPosition[3])/2 + FPGAInsideViewPosition[3]
+ local scaleX = (FPGAInsideViewPosition[2] - FPGAInsideViewPosition[1])-20
+ local scaleY = (FPGAInsideViewPosition[4] - FPGAInsideViewPosition[3])-20
+
+ local scale = math.min(scaleX, scaleY)
+
+ local nodeSize = FPGANodeSize/self.ViewData.Scale * scale
+
+ --to make sure we don't draw outside the edges
+ render.SetScissorRect(
+ FPGAInsideViewPosition[1] + ScrW()/2 + 1,
+ FPGAInsideViewPosition[3] + ScrH()/2 + 1,
+ FPGAInsideViewPosition[2] + ScrW()/2 - 1,
+ FPGAInsideViewPosition[4] + ScrH()/2 - 1,
+ true
+ )
+
+ --edges
+ for _, edge in pairs(self.ViewData.Edges) do
+ surface.SetDrawColor(FPGATypeColor[edge.type])
+ surface.DrawLine(
+ centerX + edge.from.x * scale, centerY + edge.from.y * scale,
+ centerX + edge.to.x * scale, centerY + edge.to.y * scale
+ )
+ end
+
+ --nodes
+ surface.SetDrawColor(Color(100, 100, 100, 255))
+ for _, node in pairs(self.ViewData.Nodes) do
+ surface.DrawRect(centerX + node.x * scale, centerY + node.y * scale, nodeSize, nodeSize * node.size)
+ end
+
+ --labels
+ surface.SetFont("FPGALabel")
+ surface.SetTextColor(Color(255, 255, 255, 255))
+ for _, label in pairs(self.ViewData.Labels) do
+
+ local tx, ty = surface.GetTextSize(label.text)
+ surface.SetTextPos(centerX + label.x * scale - tx/2, centerY + label.y * scale - ty/2)
+
+ surface.DrawText(label.text)
+ end
+
+ render.SetScissorRect(0, 0, ScrW(), ScrH(), false)
+end
+
+
+
+function ENT:ConstructInsideView(viewData)
+ self.ViewData = {}
+
+ -- get bounds
+ local b
+ if viewData.Nodes[1] then
+ b = {viewData.Nodes[1].x, viewData.Nodes[1].x, viewData.Nodes[1].y, viewData.Nodes[1].y}
+ else
+ b = {0, 1, 0, 1}
+ end
+ for _, node in pairs(viewData.Nodes) do
+ b[1] = math.min(b[1], node.x)
+ b[2] = math.max(b[2], node.x + FPGANodeSize)
+ b[3] = math.min(b[3], node.y)
+ b[4] = math.max(b[4], node.y + node.s * FPGANodeSize)
+ end
+ borderIsLabel = {false,false,false,false}
+ if viewData.Labels then
+ for _, label in pairs(viewData.Labels) do
+ b[1] = math.min(b[1], label.x)
+ b[2] = math.max(b[2], label.x)
+ b[3] = math.min(b[3], label.y)
+ b[4] = math.max(b[4], label.y)
+ end
+ end
+
+ local xSize = b[2]-b[1]
+ local ySize = b[4]-b[3]
+ self.ViewData.Size = {x = xSize, y = ySize}
+ self.ViewData.Scale = math.max(math.max(xSize, ySize), 100)
+ self.ViewData.Center = {b[1] + xSize/2, b[3] + ySize/2}
+
+ self.ViewData.Nodes = {}
+ for _, node in pairs(viewData.Nodes) do
+ table.insert(self.ViewData.Nodes, {
+ x = (node.x - self.ViewData.Center[1]) / self.ViewData.Scale,
+ y = (node.y - self.ViewData.Center[2]) / self.ViewData.Scale,
+ size = node.s
+ })
+ end
+
+ self.ViewData.Labels = {}
+ if viewData.Labels then
+ for _, label in pairs(viewData.Labels) do
+ table.insert(self.ViewData.Labels, {
+ x = (label.x - self.ViewData.Center[1]) / self.ViewData.Scale,
+ y = (label.y - self.ViewData.Center[2]) / self.ViewData.Scale,
+ text = label.t
+ })
+ end
+ end
+
+ self.ViewData.Edges = {}
+ if viewData.Edges then
+ for _, edge in pairs(viewData.Edges) do
+ table.insert(self.ViewData.Edges, {
+ from = {
+ x = (edge.sX - self.ViewData.Center[1]) / self.ViewData.Scale,
+ y = (edge.sY - self.ViewData.Center[2]) / self.ViewData.Scale,
+ },
+ to = {
+ x = (edge.eX - self.ViewData.Center[1]) / self.ViewData.Scale,
+ y = (edge.eY - self.ViewData.Center[2]) / self.ViewData.Scale,
+ },
+ type = FPGATypeEnumLookup[edge.t]
+ })
+ end
+ end
+end
+
+net.Receive("wire_fpga_view_data", function (len)
+ local ent = net.ReadEntity()
+ if IsValid(ent) then
+ local dataLength = net.ReadUInt(16)
+ local data = net.ReadData(dataLength)
+
+ local ok, data = pcall(util.Decompress, data)
+ if not ok then return end
+
+ ok, data = pcall(WireLib.von.deserialize, data)
+ if ok then
+ ent:ConstructInsideView(data)
+ end
+ end
+end)
\ No newline at end of file
diff --git a/lua/entities/gmod_wire_fpga/init.lua b/lua/entities/gmod_wire_fpga/init.lua
new file mode 100644
index 0000000000..6d63e0afe6
--- /dev/null
+++ b/lua/entities/gmod_wire_fpga/init.lua
@@ -0,0 +1,941 @@
+AddCSLuaFile("cl_init.lua")
+AddCSLuaFile("shared.lua")
+include("shared.lua")
+
+DEFINE_BASECLASS("base_wire_entity")
+
+--HELPERS
+local function getGate(node)
+ if node.type == "wire" then
+ return GateActions[node.gate]
+ elseif node.type == "fpga" then
+ return FPGAGateActions[node.gate]
+ elseif node.type == "cpu" then
+ return CPUGateActions[node.gate]
+ end
+end
+
+local function getInputType(gate, inputNum)
+ if gate.inputtypes then
+ return gate.inputtypes[inputNum] or "NORMAL"
+ else
+ return "NORMAL"
+ end
+end
+
+local function getOutputType(gate, outputNum)
+ if gate.outputtypes then
+ return gate.outputtypes[outputNum] or "NORMAL"
+ else
+ return "NORMAL"
+ end
+end
+
+local function getDefaultValues(node)
+ local gate = getGate(node)
+
+ values = {}
+ for inputNum, name in pairs(gate.inputs) do
+ local type = getInputType(gate, inputNum)
+
+ values[inputNum] = FPGADefaultValueForType[type]
+ end
+
+ return values
+end
+
+--CONVAR
+fpga_quota_avg = nil
+fpga_quota_spike = nil
+
+do
+ local wire_fpga_quota_avg = GetConVar("wire_fpga_quota_avg")
+ local wire_fpga_quota_spike = GetConVar("wire_fpga_quota_spike")
+
+ local function updateQuotas()
+ fpga_quota_avg = wire_fpga_quota_avg:GetInt() * 0.000001
+ fpga_quota_spike = wire_fpga_quota_spike:GetInt() * 0.000001
+ end
+
+ cvars.AddChangeCallback("wire_fpga_quota_avg", updateQuotas)
+ cvars.AddChangeCallback("wire_fpga_quota_spike", updateQuotas)
+ updateQuotas()
+end
+
+
+
+function ENT:UpdateOverlay(clear)
+ if clear then
+ self:SetOverlayData( {
+ name = "(none)",
+ timebench = 0,
+ timebenchPeak = 0,
+ errorMessage = nil,
+ })
+ else
+ local timepeak = self.timepeak
+ if self.timebench < 0.000001 then
+ timepeak = 0
+ end
+ self:SetOverlayData( {
+ name = self.name,
+ timebench = self.timebench,
+ timebenchPeak = timepeak,
+ errorMessage = self.ErrorMessage,
+ })
+ end
+end
+
+function ENT:ThrowCompileError(message, overlay)
+ self:SetColor(Color(255, 0, 0, self:GetColor().a))
+ self.CompileError = true
+ self.ErrorMessage = overlay
+ self:UpdateOverlay(false)
+
+ WireLib.ClientError("FPGA: Compilation error - " .. message, WireLib.GetOwner(self))
+end
+function ENT:ThrowExecutionError(message, overlay)
+ self:SetColor(Color(255, 0, 0, self:GetColor().a))
+ self.ExecutionError = true
+ self.ErrorMessage = overlay
+ self:UpdateOverlay(false)
+
+ WireLib.ClientError("FPGA: Execution error - " .. message, WireLib.GetOwner(self))
+end
+
+--------------------------------------------------------
+--INIT
+--------------------------------------------------------
+function ENT:Initialize()
+ self:PhysicsInit(SOLID_VPHYSICS)
+ self:SetMoveType(MOVETYPE_VPHYSICS)
+ self:SetSolid(SOLID_VPHYSICS)
+
+ self.Debug = false
+
+ self.time = 0
+ self.timebench = 0
+ self.timepeak = 0
+
+ self.LastTimedUpdate = 0
+ self.ExecutionCount = 0
+
+ self.Uploaded = false
+ self.ExecutionError = false
+ self.CompileError = false
+ self.ErrorMessage = nil
+
+ self.ExecutionInterval = 0.015
+ self.ExecuteOnInputs = false
+ self.ExecuteOnTimed = false
+ self.ExecuteOnTrigger = false
+
+ self.Data = nil
+ self.ViewData = nil
+ self.Hash = -1
+
+ self.Inputs = WireLib.CreateInputs(self, {})
+ self.Outputs = WireLib.CreateOutputs(self, {})
+
+ self.Gates = {}
+ self.LastGateValues = {}
+
+ self.Nodes = {}
+ self.InputNames = {}
+ self.InputTypes = {}
+ self.InputIds = {}
+ self.OutputNames = {}
+ self.OutputTypes = {}
+ self.OutputIds = {}
+
+ self.TimedNodes = {}
+
+ self.NodeGetsInputFrom = {}
+
+ self:UpdateOverlay(true)
+
+ self:GetOptions()
+end
+
+--------------------------------------------------------
+--DUPE
+--------------------------------------------------------
+function ENT:BuildDupeInfo()
+ self.UploadData = self.Data
+ return BaseClass.BuildDupeInfo(self) or {}
+end
+
+function ENT:Setup(data)
+ if data then
+ -- entity was duplicated
+ self:Upload(data)
+ end
+end
+
+duplicator.RegisterEntityClass("gmod_wire_fpga", WireLib.MakeWireEnt, "Data", "UploadData")
+
+--------------------------------------------------------
+--RECONSTRUCTION
+--------------------------------------------------------
+function ENT:GetOriginal()
+ if self.Data then
+ return WireLib.von.serialize(self.Data)
+ else
+ return WireLib.von.serialize({})
+ end
+end
+
+--------------------------------------------------------
+--OPTIONS
+--------------------------------------------------------
+
+function ENT:GetOptions()
+ local ply = self:GetPlayer()
+
+ if FPGAPlayerOptions[ply] then
+ --set options
+ self.Options = FPGAPlayerOptions[ply]
+ else
+ --set to default
+ self.Options = {
+ allow_inside_view = false
+ }
+ end
+end
+
+function ENT:AllowsInsideView()
+ return self.Options.allow_inside_view
+end
+
+--------------------------------------------------------
+--VIEW DATA SYNTHESIZATION
+--------------------------------------------------------
+function ENT:CreateTimeHash(str)
+ self.Hash = tonumber(util.CRC(self:GetOriginal() .. CurTime())) or -1
+end
+
+function ENT:GetTimeHash()
+ return self.Hash
+end
+
+function ENT:GetViewData()
+ return self.ViewData
+end
+
+function ENT:SynthesizeViewData(data)
+ if not data.Nodes then return end
+
+ local viewData = {}
+
+ viewData.Nodes = {}
+ viewData.Labels = {}
+ viewData.Edges = {}
+ for nodeId, node in pairs(data.Nodes) do
+ local gate = getGate(node)
+
+ if not gate then
+ --special case, label
+ if node.type == "editor" and node.visual == "label" then
+ table.insert(viewData.Labels, {
+ x = math.Round(node.x),
+ y = math.Round(node.y),
+ t = node.value
+ })
+ end
+ continue
+ end
+
+ local ports
+ if gate.outputs then
+ ports = math.max(#gate.inputs, #gate.outputs)
+ else
+ ports = #gate.inputs
+ end
+
+ table.insert(viewData.Nodes, {
+ x = math.Round(node.x),
+ y = math.Round(node.y),
+ s = ports
+ })
+
+ for inputNum, connection in pairs(node.connections) do
+ local fromNodeId = connection[1]
+ local fromNode = data.Nodes[fromNodeId]
+ local outputNum = connection[2]
+
+ table.insert(viewData.Edges, {
+ sX = math.Round(fromNode.x + FPGANodeSize),
+ sY = math.Round(fromNode.y + (outputNum - 0.5) * FPGANodeSize),
+ eX = math.Round(node.x),
+ eY = math.Round(node.y + (inputNum - 0.5) * FPGANodeSize),
+ t = FPGATypeEnum[getInputType(gate, inputNum)]
+ })
+ end
+ end
+
+ self.ViewData = WireLib.von.serialize(viewData)
+end
+
+
+--------------------------------------------------------
+--VALIDATION
+--------------------------------------------------------
+function ENT:ValidateData(data)
+ --Check if nodes are even there
+ if not data.Nodes then return "missing nodes" end
+
+ --Check that gates exist
+ --Check if gate is banned
+ --Check that there are no duplicate input names, or duplicate output names
+ local connections = {} --Make connection table for later use
+ local inputNames = {}
+ local outputNames = {}
+ for nodeId, node in pairs(data.Nodes) do
+ local gate = getGate(node)
+
+ if node.visual then continue end
+ if gate == nil then return "invalid gate" end
+ if gate.is_banned then return "banned gate" end
+
+ if gate.isInput then
+ if not node.ioName then return "missing input name" end
+ if inputNames[node.ioName] then return "duplicate input name" end
+ if node.ioName == "Trigger" then return "'Trigger' input name is reserved" end
+ inputNames[node.ioName] = true
+ elseif gate.isOutput then
+ if not node.ioName then return "missing output name" end
+ if outputNames[node.ioName] then return "duplicate output name" end
+ outputNames[node.ioName] = true
+ end
+
+ connections[nodeId] = node.connections
+ end
+
+ --Check for out of bounds input/output
+ --Type Check
+ --Check that connections are valid (that the destination node exists)
+ for nodeId, nodeConnections in pairs(connections) do
+ local inGate = getGate(data.Nodes[nodeId])
+
+ for inputNum, connection in pairs(nodeConnections) do
+ local outNode = data.Nodes[connection[1]]
+ if not outNode then return "connection exists to invalid node" end
+ local outGate = getGate(outNode)
+ local outputNum = connection[2]
+
+ --bound check
+ if inputNum < 1 or inputNum > #inGate.inputs then return "connection on nonexistant input" end
+ if outGate.outputs then
+ if connection[2] < 1 or connection[2] > #outGate.outputs then return "connection on nonexistant output" end
+ else
+ if connection[2] != 1 then return "connection on nonexistant output" end
+ end
+
+ --type check
+ if getInputType(inGate, inputNum) != getOutputType(outGate, outputNum) then
+ return "type mismatch between input and output " .. inGate.name .. " ["..getInputType(inGate, inputNum).."]" .. " and " .. outGate.name .. " ["..getOutputType(outGate, outputNum).."]"
+ end
+ end
+ end
+
+ --no errors
+ return nil
+end
+
+--------------------------------------------------------
+--COMPILATION
+--------------------------------------------------------
+-- Node 'compiler'
+-- Flip connections, generate input output tabels
+function ENT:CompileData(data)
+ --Make node table and connection table [from][output] = {to, input}
+ local nodes = {}
+ local edges = {}
+ local inputs = {}
+ local inputTypes = {}
+ local outputs = {}
+ local outputTypes = {}
+ local inputIds = {}
+ local outputIds = {}
+ local nodeGetsInputFrom = {}
+ local timedNodes = {}
+ local neverActiveNodes = {}
+ local postCycleNodes = {}
+ local postExecutionNodes = {}
+
+ for nodeId, node in pairs(data.Nodes) do
+ if node.visual then continue end
+
+ nodes[nodeId] = {
+ type = node.type,
+ gate = node.gate,
+ ioName = node.ioName,
+ value = node.value,
+ }
+ for input, connection in pairs(node.connections) do
+ fromNode = connection[1]
+ fromOutput = connection[2]
+ if not edges[fromNode] then edges[fromNode] = {} end
+ if not edges[fromNode][fromOutput] then edges[fromNode][fromOutput] = {} end
+
+ table.insert(edges[fromNode][fromOutput], {nodeId, input})
+ end
+
+ nodeGetsInputFrom[nodeId] = node.connections
+
+ --get gate
+ local gate = getGate(node)
+
+ --timed
+ if gate.timed then table.insert(timedNodes, nodeId) end
+
+ --never active
+ if gate.neverActive then neverActiveNodes[nodeId] = nodes[nodeId] end
+
+ --postcycle
+ if gate.postCycle then postCycleNodes[nodeId] = nodes[nodeId] end
+
+ --postexecution
+ if gate.postExecution then postExecutionNodes[nodeId] = nodes[nodeId] end
+
+ --io
+ if node.type == "fpga" then
+ if gate.isInput then
+ inputIds[node.ioName] = nodeId
+ table.insert(inputs, node.ioName)
+ table.insert(inputTypes, gate.outputtypes[1])
+ end
+ if gate.isOutput then
+ outputIds[node.ioName] = nodeId
+ table.insert(outputs, node.ioName)
+ table.insert(outputTypes, gate.inputtypes[1])
+ end
+ end
+ end
+
+ --Integrate connection table into node table
+ for nodeId, node in pairs(nodes) do
+ nodes[nodeId].connections = edges[nodeId]
+ end
+
+ self.Nodes = nodes
+ self.InputNames = inputs
+ self.InputTypes = inputTypes
+ self.InputIds = inputIds
+ self.OutputNames = outputs
+ self.OutputTypes = outputTypes
+ self.OutputIds = outputIds
+
+ self.NodeGetsInputFrom = nodeGetsInputFrom
+ self.TimedNodes = timedNodes
+
+ self.NeverActiveNodes = neverActiveNodes
+ self.PostCycleNodes = postCycleNodes
+ self.PostExecutionNodes = postExecutionNodes
+
+ self.QueuedNodes = {}
+ self.LazyQueuedNodes = {}
+end
+
+--------------------------------------------------------
+--UPLOAD
+--------------------------------------------------------
+function ENT:Upload(data)
+ self.Uploaded = false
+ self.CompileError = false
+ self.ExecutionError = false
+ self.ErrorMessage = nil
+
+ --Name
+ if data.Name then
+ self.name = data.Name
+ else
+ self.name = "(corrupt)"
+ end
+ --Execution interval
+ if data.ExecutionInterval then
+ self.ExecutionInterval = math.max(data.ExecutionInterval, 0.001)
+ else
+ self.ExecutionInterval = 0.1
+ end
+ --Executes on
+ if data.ExecuteOn then
+ self.ExecuteOnInputs = data.ExecuteOn.Inputs
+ self.ExecuteOnTimed = data.ExecuteOn.Timed
+ self.ExecuteOnTrigger = data.ExecuteOn.Trigger
+ else
+ self.ExecuteOnInputs = true
+ self.ExecuteOnTimed = true
+ self.ExecuteOnTrigger = false
+ end
+
+ self:UpdateOverlay(false)
+
+ --validate
+ local invalid = self:ValidateData(data)
+ if invalid then
+ self:ThrowCompileError("failed to validate on server, "..invalid, "failed to validate")
+ self.Inputs = WireLib.AdjustSpecialInputs(self, {}, {}, "")
+ self.Outputs = WireLib.AdjustSpecialOutputs(self, {}, {}, "")
+ return
+ end
+
+ --view data
+ self:SynthesizeViewData(data)
+
+ --hash
+ self:CreateTimeHash(data)
+
+ --Compile
+ self:CompileData(data)
+
+ if self.ExecuteOnTrigger then
+ local modifiedInputNames = {"Trigger"}
+ local modifiedInputTypes = {"NORMAL"}
+ for _, name in pairs(self.InputNames) do
+ table.insert(modifiedInputNames, name)
+ end
+ for _, type in pairs(self.InputTypes) do
+ table.insert(modifiedInputTypes, type)
+ end
+ self.Inputs = WireLib.AdjustSpecialInputs(self, modifiedInputNames, modifiedInputTypes, "")
+ else
+ self.Inputs = WireLib.AdjustSpecialInputs(self, self.InputNames, self.InputTypes, "")
+ end
+ self.Outputs = WireLib.AdjustSpecialOutputs(self, self.OutputNames, self.OutputTypes, "")
+
+ --Initialize inputs to default values
+ self.InputValues = {}
+ for k, iname in pairs(self.InputNames) do
+ local inputNodeId = self.InputIds[iname]
+ local value = self.Inputs[iname].Value
+ self.InputValues[inputNodeId] = value
+ end
+
+ self.Data = data
+
+ self.Uploaded = true
+
+ self:Reset()
+end
+
+
+
+--------------------------------------------------------
+--RESET
+--------------------------------------------------------
+function ENT:ResetGates()
+ --Set gates to default values again
+ self.Values = {}
+ for nodeId, node in pairs(self.Nodes) do
+ self.Values[nodeId] = getDefaultValues(node)
+ end
+
+ --Functions for gates
+ local owner = self:GetPlayer()
+ local getOwner = function () return owner end
+ local ent = self
+ local getSelf = function () return ent end
+ local getExecutionDelta = function () return ent.CurrentExecution - ent.LastExecution end
+ local getExecutionCount = function () return ent.ExecutionCount end
+ --Reset gate table
+ self.Gates = {}
+ for nodeId, node in pairs(self.Nodes) do
+ local gate = getGate(node)
+
+ local tempGate = {}
+ tempGate.GetPlayer = getOwner
+ if gate.specialFunctions then
+ tempGate.GetSelf = getSelf
+ tempGate.GetExecutionDelta = getExecutionDelta
+ tempGate.GetExecutionCount = getExecutionCount
+ end
+ if gate.reset then
+ gate.reset(tempGate)
+ end
+ self.Gates[nodeId] = tempGate
+ end
+end
+
+function ENT:Reset()
+ if self.CompilationError or not self.Uploaded then return end
+ self:SetColor(Color(255, 255, 255, self:GetColor().a))
+ self.ExecutionError = false
+ self.ErrorMessage = nil
+ self.time = 0
+ self.timebench = 0
+ self.timepeak = 0
+ self.LastTimedUpdate = 0
+ self.ExecutionCount = 0
+ self.QueuedNodes = {}
+
+ self:ResetGates()
+
+ --Run all nodes again (to properly propagate)
+ local allNodes = {}
+ for nodeId, node in pairs(self.Nodes) do
+ table.insert(allNodes, nodeId)
+ end
+
+ self.LastExecution = SysTime()
+ self:RunProtected(allNodes)
+end
+
+--------------------------------------------------------
+--EXECUTION TRIGGERING
+--------------------------------------------------------
+function ENT:TriggerInput(iname, value)
+ if self.CompilationError or self.ExecutionError or not self.Uploaded then return end
+
+ if iname == "Trigger" then
+ if value != 0 then self:RunProtected({}) end
+ return
+ end
+
+ local nodeId = self.InputIds[iname]
+ self.InputValues[nodeId] = value
+
+ if self.ExecuteOnInputs then
+ self:RunProtected({nodeId})
+ else
+ self.LazyQueuedNodes[nodeId] = true
+ end
+end
+
+function ENT:Think()
+ BaseClass.Think(self)
+
+ if not self.Uploaded then return end
+ if self.CompilationError or self.ExecutionError then
+ self:UpdateOverlay(false)
+ return
+ end
+ self:NextThink(CurTime())
+
+ --Get options (maybe do this less frequently)
+ self:GetOptions()
+
+ --Time benchmarking
+ self.timebench = self.timebench * 0.98 + self.time * 0.02
+ self.time = 0
+
+ --Limiting
+ if self.timebench > fpga_quota_avg then
+ self:ThrowExecutionError("exceeded cpu time limit", "cpu time limit exceeded")
+ return
+ elseif fpga_quota_spike > 0 and self.time > fpga_quota_spike then
+ self:ThrowExecutionError("exceeded spike cpu time limit", "spike cpu time limit exceeded")
+ return
+ end
+
+ --postexecution hook
+ for nodeId, node in pairs(self.PostExecutionNodes) do
+ local gate = getGate(node)
+ if gate.postExecution(self.Gates[nodeId]) then
+ self.QueuedNodes[nodeId] = true
+ end
+ if node.connections then
+ local value = self:CalculateNode(node, nodeId, gate)
+ self:Propagate(node, value)
+ end
+ end
+
+ --Update timed gates (and queued nodes)
+ local nodesToRun = {}
+ if not table.IsEmpty(self.TimedNodes) and SysTime() >= self.LastTimedUpdate + self.ExecutionInterval then
+ self.LastTimedUpdate = SysTime()
+ for _, nodeId in pairs(self.TimedNodes) do
+ if self.ExecuteOnTimed then
+ table.insert(nodesToRun, nodeId)
+ else
+ self.LazyQueuedNodes[nodeId] = true
+ end
+ end
+ end
+
+ --Run queued nodes immediately
+ for nodeId, _ in pairs(self.QueuedNodes) do
+ table.insert(nodesToRun, nodeId)
+ end
+ self.QueuedNodes = {}
+
+ if #nodesToRun > 0 then self:RunProtected(nodesToRun) end
+
+ self:UpdateOverlay(false)
+ return true
+end
+
+--------------------------------------------------------
+--RUNNING
+--------------------------------------------------------
+function ENT:RunProtected(changedNodes)
+ local ok = pcall(self.Run, self, changedNodes)
+
+ if not ok then
+ local gate = getGate(FPGANodeCurrentlyInQueue)
+ self:ThrowExecutionError("runtime error at gate " .. gate.name, "runtime error")
+ end
+end
+
+FPGANodeCurrentlyInQueue = nil
+function ENT:Run(changedNodes)
+ if self.Debug then print("\n================================================================================") end
+
+ --Extra
+ if self.ExecutionCount != nil then
+ self.ExecutionCount = self.ExecutionCount + 1
+ end
+ local bench = SysTime()
+ self.CurrentExecution = bench
+
+ --Lazy queued nodes are nodes that need to run
+ --but can wait until the next execution
+ if not table.IsEmpty(self.LazyQueuedNodes) then
+ for nodeId, _ in pairs(self.LazyQueuedNodes) do
+ table.insert(changedNodes, nodeId)
+ end
+
+ self.LazyQueuedNodes = {}
+ end
+
+ -----------------------------------------
+ --PREPARATION
+ -----------------------------------------
+ local activeNodes = {}
+ local activeNodesQueue = {}
+ local nodesInQueue = {}
+ local nodeQueue = {}
+
+ --Find out which nodes will be visited
+ for nodeId, node in pairs(self.Nodes) do
+ activeNodes[nodeId] = false
+ end
+ for k, id in pairs(changedNodes) do
+ table.insert(activeNodesQueue, id)
+ activeNodes[id] = true
+ end
+ while #activeNodesQueue > 0 do
+ local nodeId = table.remove(activeNodesQueue, 1)
+ local node = self.Nodes[nodeId]
+ --propergate output value to inputs
+ if node.connections then
+ for outputNum, connections in pairs(node.connections) do
+ for k, connection in pairs(connections) do
+ --add connected nodes to queue (and active nodes)
+ if activeNodes[connection[1]] == false then
+ table.insert(activeNodesQueue, connection[1])
+ activeNodes[connection[1]] = true
+ end
+ end
+ end
+ end
+ end
+ for nodeId, active in pairs(activeNodes) do
+ local gate = getGate(self.Nodes[nodeId])
+ if active and gate.neverActive then
+ activeNodes[nodeId] = false
+ end
+ if gate.alwaysActive then
+ activeNodes[nodeId] = true
+ table.insert(nodeQueue, nodeId)
+ nodesInQueue[nodeId] = true
+ end
+ end
+
+ if self.Debug then print(table.ToString(activeNodes, "activeNodes", false)) end
+
+ --Initialize nodesInQueue set
+ for nodeId, node in pairs(self.Nodes) do
+ nodesInQueue[nodeId] = false
+ end
+
+ --Initialize nodeQueue with changed inputs
+ for k, id in pairs(changedNodes) do
+ table.insert(nodeQueue, id)
+ nodesInQueue[id] = true
+ end
+
+ --Initialize nodesVisited set
+ local nodesVisited = {}
+
+ if self.Debug then
+ for nodeId, node in pairs(self.Nodes) do
+ print(nodeId .. table.ToString(node, "", false))
+ end
+ end
+
+ -----------------------------------------
+ --EXECUTION
+ -----------------------------------------
+ local loopCount = 0
+ local loopDetectionNodeId = nil
+ local loopDetectionSize = 0
+ while #nodeQueue > 0 do
+ loopCount = loopCount + 1
+ if loopCount > 50000 then
+ self.timepeak = SysTime() - bench
+ self.timebench = self.timepeak
+ self:ThrowExecutionError("stuck in loop for too long", "stuck in loop")
+ return
+ end
+ if self.Debug then
+ print()
+ print(table.ToString(nodeQueue, "nodeQueue", false))
+ print(table.ToString(nodesInQueue, "nodesInQueue", false))
+ print(table.ToString(nodesVisited, "nodesVisited", false))
+ end
+
+ local nodeId = table.remove(nodeQueue, 1)
+ local node = self.Nodes[nodeId]
+ FPGANodeCurrentlyInQueue = node
+
+ --get gate
+ local gate = getGate(node)
+
+ --gate value logic
+ local value
+
+ if gate.isInput then
+ value = {self.InputValues[nodeId]}
+ elseif gate.isConstant then
+ if gate.outputtypes[1] == "STRING" then
+ value = { WireLib.ParseEscapes(node.value) }
+ else
+ value = {node.value}
+ end
+ else
+ if nodeId == loopDetectionNodeId and #nodeQueue == loopDetectionSize then
+ --infinite loop...
+ self:ThrowExecutionError("stuck in infinite loop", "infinite loop")
+ break
+ end
+
+ --neverActive gates don't wait for their input gates to finish
+ --if !gate.neverActive then
+ local executeLater = false
+ --if input hasnt arrived, send this node to the back of the queue
+ for inputId, connection in pairs(self.NodeGetsInputFrom[nodeId]) do
+ nodeId2 = connection[1]
+ outputNum = connection[2]
+
+ --if node hasnt been visited yet and its going to be visited
+ if not nodesVisited[nodeId2] and activeNodes[nodeId2] then
+ executeLater = true
+ if loopDetectionNodeId == nil then
+ loopDetectionNodeId = nodeId
+ loopDetectionSize = #nodeQueue
+ end
+ break
+ end
+ end
+ --skip node
+ if executeLater then
+ --add connected nodes to queue
+ if node.connections then
+ for outputNum, connections in pairs(node.connections) do
+ for k, connection in pairs(connections) do
+ if nodesInQueue[connection[1]] == false then
+ table.insert(nodeQueue, connection[1])
+ nodesInQueue[connection[1]] = true
+ end
+ end
+ end
+ end
+ -- send this node to the back of the queue (potential infinite looping???)
+ table.insert(nodeQueue, nodeId)
+ continue
+ end
+ --end
+
+ if self.Debug then print(table.ToString(self.Values[nodeId], "", false)) end
+
+ loopDetectionNodeId = nil
+
+ --output logic
+ if gate.isOutput then
+ if self.Debug then print(node.ioName .. " outputs " .. table.ToString(self.Values[nodeId], "", false)) end
+ WireLib.TriggerOutput(self, node.ioName, self.Values[nodeId][1])
+ continue
+ else
+ value = self:CalculateNode(node, nodeId, gate)
+ end
+ end
+
+ if self.Debug then print(table.ToString(value, "output", false)) end
+
+ --for future reference, we've visited this node
+ nodesVisited[nodeId] = true
+
+ self:PropagateAndAddToQueue(node, value, nodeQueue, nodesInQueue)
+ end
+
+ --postcycle hook
+ for nodeId, node in pairs(self.PostCycleNodes) do
+ local gate = getGate(node)
+ gate.postCycle(self.Gates[nodeId])
+ local value = self:CalculateNode(node, nodeId, gate)
+ self:Propagate(node, value)
+ end
+
+ --keep track of time spent this tick
+ self.LastExecution = bench
+ self.time = self.time + (SysTime() - bench)
+ self.timepeak = SysTime() - bench
+end
+
+
+function ENT:CalculateNode(node, nodeId, gate)
+ local value
+ --compact gates only calculate with connected inputs
+ if gate.compact_inputs then
+ --find connected inputs, and assign current values
+ activeValues = {}
+ for inputNum, _ in pairs(self.Data.Nodes[nodeId].connections) do
+ table.insert(activeValues, self.Values[nodeId][inputNum])
+ end
+
+ value = {gate.output(self.Gates[nodeId], unpack(activeValues))}
+ else
+ --normal gates
+ value = {gate.output(self.Gates[nodeId], unpack(self.Values[nodeId]))}
+ end
+
+ --Error correction - for dumb designed gates... (entity owner gate)
+ if #value == 0 then value = getDefaultValues(node) end
+
+ return value
+end
+
+function ENT:Propagate(node, value)
+ if node.connections then
+ for outputNum, connections in pairs(node.connections) do
+ for k, connection in pairs(connections) do
+ toNode = connection[1]
+ toInput = connection[2]
+
+ --send values to nodes
+ self.Values[toNode][toInput] = value[outputNum]
+ end
+ end
+ end
+end
+
+function ENT:PropagateAndAddToQueue(node, value, nodeQueue, nodesInQueue)
+ if node.connections then
+ for outputNum, connections in pairs(node.connections) do
+ for k, connection in pairs(connections) do
+ toNode = connection[1]
+ toInput = connection[2]
+
+ --send values to nodes
+ self.Values[toNode][toInput] = value[outputNum]
+
+ --add connected nodes to queue
+ if nodesInQueue[connection[1]] == false then
+ table.insert(nodeQueue, connection[1])
+ nodesInQueue[connection[1]] = true
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lua/entities/gmod_wire_fpga/shared.lua b/lua/entities/gmod_wire_fpga/shared.lua
new file mode 100644
index 0000000000..e382b58f4a
--- /dev/null
+++ b/lua/entities/gmod_wire_fpga/shared.lua
@@ -0,0 +1,145 @@
+DEFINE_BASECLASS("base_wire_entity")
+
+ENT.PrintName = "Wire FPGA"
+ENT.Author = ""
+ENT.Contact = ""
+ENT.Purpose = ""
+ENT.Instructions = ""
+
+ENT.WireDebugName = "FPGA"
+
+CreateConVar("wire_fpga_quota_avg", "2000", {FCVAR_REPLICATED})
+CreateConVar("wire_fpga_quota_spike", "-1", {FCVAR_REPLICATED})
+
+
+
+--------------------------------------------------------------------------------
+-- Globals
+--------------------------------------------------------------------------------
+FPGADefaultValueForType = {
+ NORMAL = 0,
+ VECTOR2 = nil, --no
+ VECTOR = Vector(0, 0, 0),
+ VECTOR4 = nil, --no
+ ANGLE = Angle(0, 0, 0),
+ STRING = "",
+ ARRAY = {},
+ ENTITY = NULL,
+ RANGER = nil,
+ WIRELINK = nil
+}
+
+FPGATypeEnum = {
+ NORMAL = 1,
+ VECTOR2 = 2,
+ VECTOR = 3,
+ VECTOR4 = 4,
+ ANGLE = 5,
+ STRING = 6,
+ ARRAY = 7,
+ ENTITY = 8,
+ RANGER = 9,
+ WIRELINK = 10,
+}
+
+FPGATypeEnumLookup = {
+ "NORMAL",
+ "VECTOR2",
+ "VECTOR",
+ "VECTOR4",
+ "ANGLE",
+ "STRING",
+ "ARRAY",
+ "ENTITY",
+ "RANGER",
+ "WIRELINK",
+}
+
+FPGANodeSize = 5
+
+
+
+
+
+--------------------------------------------------------------------------------
+-- Option syncing ((Server ->) Client -> Server)
+--------------------------------------------------------------------------------
+if CLIENT then
+ function FPGASendOptionsToServer(options)
+ net.Start("wire_fpga_options")
+ net.WriteString(options)
+ net.SendToServer()
+ end
+
+ -- Received request to send options
+ net.Receive("wire_fpga_options", function(len)
+ local options = FPGAGetOptions()
+
+ FPGASendOptionsToServer(options)
+ end)
+end
+
+if SERVER then
+ FPGAPlayerOptions = {}
+
+ util.AddNetworkString("wire_fpga_options")
+
+ -- Request options from player
+ timer.Create("WireFPGACheckForOptions", 1, 0, function()
+ for _, ply in ipairs(player.GetAll()) do
+ if not IsValid(ply) then continue end
+
+ if not FPGAPlayerOptions[ply] then
+ net.Start("wire_fpga_options")
+ net.Send(ply)
+ end
+ end
+ end)
+
+ -- Receive options from player
+ net.Receive("wire_fpga_options", function(len, ply)
+ local ok, options = pcall(WireLib.von.deserialize, net.ReadString())
+
+ if ok then
+ FPGAPlayerOptions[ply] = options
+ end
+ end)
+end
+
+--------------------------------------------------------------------------------
+-- Inside view syncing (Server -> Client)
+--------------------------------------------------------------------------------
+if SERVER then
+ FPGAPlayerHasHash = {}
+
+ util.AddNetworkString("wire_fpga_view_data")
+
+ timer.Create("WireFPGAViewDataUpdate", 0.1, 0, function()
+ for _, ply in ipairs(player.GetAll()) do
+ if not IsValid(ply) then continue end --don't know why this happens, but it does
+
+ if not ply:KeyDown(IN_USE) then continue end
+
+ local ent = ply:GetEyeTrace().Entity
+
+ if IsValid(ent) and ent:GetClass() == "gmod_wire_fpga" and ent:AllowsInsideView() and ent:GetViewData() then
+ if not FPGAPlayerHasHash[ply] then FPGAPlayerHasHash[ply] = {} end
+
+ if FPGAPlayerHasHash[ply][ent:GetTimeHash()] then
+ --player already has this inside view
+ continue
+ end
+
+ FPGAPlayerHasHash[ply][ent:GetTimeHash()] = true
+
+ local data = util.Compress(ent:GetViewData())
+
+ net.Start("wire_fpga_view_data")
+ net.WriteEntity(ent)
+ net.WriteUInt(#data, 16)
+ net.WriteData(data, #data)
+ net.Send(ply)
+ end
+ end
+ end)
+end
\ No newline at end of file
diff --git a/lua/wire/client/node_editor/nodeeditor.lua b/lua/wire/client/node_editor/nodeeditor.lua
new file mode 100644
index 0000000000..cbe339620a
--- /dev/null
+++ b/lua/wire/client/node_editor/nodeeditor.lua
@@ -0,0 +1,1732 @@
+local Editor = {}
+
+FPGATypeColor = {
+ NORMAL = Color(190, 190, 255, 255), --Very light blue nearing white
+ VECTOR2 = Color(150, 255, 255, 255), --Light blue
+ VECTOR = Color(70, 160, 255, 255), --Blue
+ VECTOR4 = Color(0, 50, 255, 255), --Dark blue
+ ANGLE = Color(100, 200, 100, 255), --Light green
+ STRING = Color(250, 160, 90, 255), --Orange
+ ARRAY = Color(20, 110, 20, 255), --Dark green
+ ENTITY = Color(255, 100, 100, 255), --Dark red
+ RANGER = Color(130, 100, 60, 255), --Brown
+ WIRELINK = Color(200, 80, 200, 255), --Deep purple
+}
+
+--GATE HELPERS
+local function getGate(node)
+ if node.type == "wire" then
+ return GateActions[node.gate]
+ elseif node.type == "fpga" then
+ return FPGAGateActions[node.gate]
+ elseif node.type == "cpu" then
+ return CPUGateActions[node.gate]
+ end
+end
+
+local function getInputType(gate, inputNum)
+ if gate.inputtypes then
+ return gate.inputtypes[inputNum] or "NORMAL"
+ else
+ return "NORMAL"
+ end
+end
+
+local function getOutputType(gate, outputNum)
+ if gate.outputtypes then
+ return gate.outputtypes[outputNum] or "NORMAL"
+ else
+ return "NORMAL"
+ end
+end
+
+function Editor:Init()
+ self.Nodes = {}
+
+ self.AlignToGrid = false
+
+ self.DraggingWorld = false
+ self.DraggingNode = nil
+ self.DraggingOffset = { 0, 0 }
+
+ self.DrawingConnection = false
+ self.DrawingFromInput = false
+ self.DrawingFromOutput = false
+ self.DrawingConnectionFrom = nil
+
+ self.DrawingSelection = nil
+ self.SelectedNodes = {}
+ self.SelectedNodeCount = 0
+
+ self.LastMousePos = { 0, 0 }
+ self.MouseDown = false
+
+ self.SelectedInMenu = nil
+
+ self.GateSize = FPGANodeSize
+ self.IOSize = 2
+
+ self.BackgroundColor = Color(26, 26, 26, 255)
+ self.SelectionColor = Color(220, 220, 100, 255)
+
+ self.NodeColor = Color(100, 100, 100, 255)
+ self.InputNodeColor = Color(80, 90, 80, 255)
+ self.OutputNodeColor = Color(80, 80, 90, 255)
+ self.TimedNodeColor = Color(110, 70, 70, 255)
+ self.SelectedNodeColor = Color(150, 150, 100, 255)
+
+ self.VisualTextColor = Color(255, 255, 255, 255)
+ self.SelectedVisualTextColor = Color(255, 255, 150, 255)
+
+ self.ZoomHideThreshold = 2
+ self.ZoomThreshold = 7
+
+ self.C = {}
+ self:InitComponents()
+end
+
+function Editor:GetParent()
+ return self.ParentPanel
+end
+
+surface.CreateFont( "FPGAText", {
+ font = "Tahoma",
+ extended = false,
+ size = 16,
+ weight = 500,
+ blursize = 0,
+ scanlines = 0,
+ antialias = true,
+ underline = false,
+ italic = false,
+ strikeout = false,
+ symbol = false,
+ rotary = false,
+ shadow = false,
+ additive = false,
+ outline = false,
+})
+surface.CreateFont( "FPGAIO", {
+ font = "Tahoma",
+ extended = false,
+ size = 12,
+ weight = 500,
+ blursize = 0,
+ scanlines = 0,
+ antialias = true,
+ underline = false,
+ italic = false,
+ strikeout = false,
+ symbol = false,
+ rotary = false,
+ shadow = false,
+ additive = false,
+ outline = false,
+})
+surface.CreateFont( "FPGATextBig", {
+ font = "Tahoma",
+ extended = false,
+ size = 20,
+ weight = 1000,
+ blursize = 0,
+ scanlines = 0,
+ antialias = true,
+ underline = false,
+ italic = false,
+ strikeout = false,
+ symbol = false,
+ rotary = false,
+ shadow = false,
+ additive = false,
+ outline = false,
+})
+surface.CreateFont( "FPGAIOBig", {
+ font = "Tahoma",
+ extended = false,
+ size = 18,
+ weight = 200,
+ blursize = 0,
+ scanlines = 0,
+ antialias = true,
+ underline = false,
+ italic = false,
+ strikeout = false,
+ symbol = false,
+ rotary = false,
+ shadow = false,
+ additive = false,
+ outline = false,
+})
+surface.CreateFont( "FPGALabel", {
+ font = "Bahnschrift",
+ extended = false,
+ size = 25,
+ weight = 1000,
+ blursize = 0,
+ scanlines = 0,
+ antialias = true,
+ underline = false,
+ italic = true,
+ strikeout = false,
+ symbol = false,
+ rotary = false,
+ shadow = false,
+ additive = false,
+ outline = false,
+})
+
+--------------------------------------------------------
+--COMPONENTS
+--------------------------------------------------------
+function Editor:InitComponents()
+ local this = self
+
+ self.C = {}
+
+ self.C.TopBar = vgui.Create("DPanel", self)
+ self.C.TopBar:Dock(TOP)
+ self.C.TopBar:SetHeight(36)
+ self.C.TopBar:DockPadding(5, 18, 5, 4)
+ self.C.TopBar:SetBackgroundColor(Color(176.5, 180, 185, 255))
+
+ local x = 7
+ self.C.NameLabel = vgui.Create("DLabel", self.C.TopBar)
+ self.C.NameLabel:SetText("Chip Name")
+ self.C.NameLabel:SizeToContents()
+ self.C.NameLabel:SetTextColor(Color(255, 255, 255, 255))
+ self.C.NameLabel:SetPos(x, 4)
+ self.C.Name = vgui.Create("DTextEntry", self.C.TopBar)
+ self.C.Name:SetEditable(true)
+ self.C.Name:SetSize(140, 15)
+ self.C.Name:SetPos(x - 2, 18)
+
+ self.C.Name.OnLoseFocus = function (pnl)
+ if string.len(pnl:GetValue()) == 0 then
+ pnl:SetText("gate")
+ end
+ this:RequestFocus()
+ end
+ x = x + 160
+
+ self.C.ExecutionIntervalLabel = vgui.Create("DLabel", self.C.TopBar)
+ self.C.ExecutionIntervalLabel:SetText("Execution Interval")
+ self.C.ExecutionIntervalLabel:SizeToContents()
+ self.C.ExecutionIntervalLabel:SetTextColor(Color(255, 255, 255, 255))
+ self.C.ExecutionIntervalLabel:SetPos(x, 4)
+ self.C.ExecutionIntervalLabel2 = vgui.Create("DLabel", self.C.TopBar)
+ self.C.ExecutionIntervalLabel2:SetText("every s")
+ self.C.ExecutionIntervalLabel2:SizeToContents()
+ self.C.ExecutionIntervalLabel2:SetTextColor(Color(255, 255, 255, 255))
+ self.C.ExecutionIntervalLabel2:SetPos(x, 18)
+ self.C.ExecutionInterval = vgui.Create("DNumberWang", self.C.TopBar)
+ self.C.ExecutionInterval:SetInterval(0.01)
+ self.C.ExecutionInterval:SetDecimals(3)
+ self.C.ExecutionInterval:SetMax(1)
+ self.C.ExecutionInterval:SetMin(0.001)
+ self.C.ExecutionInterval:SetValue(0.1)
+ self.C.ExecutionInterval:SetSize(40, 15)
+ self.C.ExecutionInterval:SetPos(x + 31, 18)
+
+ self.C.ExecutionInterval.OnLoseFocus = function (pnl)
+ this:RequestFocus()
+ end
+ x = x + 110
+
+ self.C.ExecuteOnLabel = vgui.Create("DLabel", self.C.TopBar)
+ self.C.ExecuteOnLabel:SetText("Execute on")
+ self.C.ExecuteOnLabel:SizeToContents()
+ self.C.ExecuteOnLabel:SetTextColor(Color(255, 255, 255, 255))
+ self.C.ExecuteOnLabel:SetPos(x, 4)
+ self.C.ExecuteOnInputs = vgui.Create("DCheckBoxLabel", self.C.TopBar)
+ self.C.ExecuteOnInputs:SetPos(x, 18)
+ self.C.ExecuteOnInputs:SetText("Inputs")
+ self.C.ExecuteOnInputs:SetTextColor(Color(240, 240, 240, 255))
+ self.C.ExecuteOnInputs:SetValue(true)
+ self.C.ExecuteOnInputs:SizeToContents()
+ self.C.ExecuteOnTimed = vgui.Create("DCheckBoxLabel", self.C.TopBar)
+ self.C.ExecuteOnTimed:SetPos(x + 60, 18)
+ self.C.ExecuteOnTimed:SetText("Timed")
+ self.C.ExecuteOnTimed:SetTextColor(Color(240, 240, 240, 255))
+ self.C.ExecuteOnTimed:SetValue(true)
+ self.C.ExecuteOnTimed:SizeToContents()
+ self.C.ExecuteOnTrigger = vgui.Create("DCheckBoxLabel", self.C.TopBar)
+ self.C.ExecuteOnTrigger:SetPos(x + 120, 18)
+ self.C.ExecuteOnTrigger:SetText("Trigger In")
+ self.C.ExecuteOnTrigger:SetTextColor(Color(240, 240, 240, 255))
+ self.C.ExecuteOnTrigger:SetValue(false)
+ self.C.ExecuteOnTrigger:SizeToContents()
+
+ --Gate spawning
+ self.C.Holder = vgui.Create("DPanel", self)
+ self.C.Holder:SetWidth(300)
+ self.C.Holder:Dock(RIGHT)
+ self.C.Holder:SetBackgroundColor(Color(170, 174, 179, 255))
+
+ self.C.Tree = vgui.Create("DTree", self.C.Holder)
+ self.C.Tree:Dock(FILL)
+ self.C.Tree:DockMargin(2, 0, 2, 2)
+
+
+ --Gate searching
+ self.C.Search = vgui.Create("DTextEntry", self.C.Holder)
+ self.C.Search:Dock(TOP)
+ self.C.Search:DockMargin(2, 0, 2, 0)
+ self.C.Search:SetValue("Search...")
+
+ local oldOnGetFocus = self.C.Search.OnGetFocus
+ function self.C.Search:OnGetFocus()
+ if self:GetValue() == "Search..." then -- If "Search...", erase it
+ self:SetValue("")
+ end
+ oldOnGetFocus(self)
+ end
+
+ -- On lose focus
+ local oldOnLoseFocus = self.C.Search.OnLoseFocus
+ function self.C.Search:OnLoseFocus()
+ if self:GetValue() == "" then -- if empty, reset "Search..." text
+ timer.Simple(0, function() self:SetValue("Search...") end)
+ end
+ oldOnLoseFocus(self)
+ this:RequestFocus()
+ end
+
+ self.C.SearchList = vgui.Create("DListView", self.C.Holder)
+ self.C.SearchList:AddColumn("Gate Name")
+ self.C.SearchList:AddColumn("Type"):SetWidth(10)
+ self.C.SearchList:AddColumn("Category"):SetWidth(35)
+
+ -- Searching algorithm
+ local function Search(text)
+ text = string.lower(text)
+
+ local results = {}
+ for action, gate in pairs(FPGAGateActions) do
+ local name = gate.name
+ local lowname = string.lower(name)
+ if string.find(lowname, text, 1, true) then -- If it has ANY match at all
+ results[#results + 1] = { name = gate.name, group = gate.group, type = "fpga", action = action, dist = WireLib.levenshtein(text, lowname) }
+ end
+ end
+ for action, gate in pairs(CPUGateActions) do
+ local name = gate.name
+ local lowname = string.lower(name)
+ if string.find(lowname, text, 1, true) then -- If it has ANY match at all
+ results[#results + 1] = { name = gate.name, group = gate.group, type = "cpu", action = action, dist = WireLib.levenshtein(text, lowname) }
+ end
+ end
+ for action, gate in pairs(GateActions) do
+ local name = gate.name
+ local lowname = string.lower(name)
+ if string.find(lowname, text, 1, true) then -- If it has ANY match at all
+ results[#results + 1] = { name = gate.name, group = gate.group, type = "wire", action = action, dist = WireLib.levenshtein(text, lowname) }
+ end
+ end
+
+ table.SortByMember(results, "dist", true)
+
+ return results
+ end
+
+ -- Main searching
+ local searching
+ function self.C.Search:OnTextChanged()
+ local text = self:GetValue()
+ if text != "" then
+ if not searching then
+ searching = true
+ local x, y = this.C.Tree:GetPos()
+ local w, h = this.C.Tree:GetSize()
+ this.C.SearchList:SetPos(x + w, y)
+ this.C.SearchList:MoveTo(x, y, 0.1, 0, 1)
+ this.C.SearchList:SetSize(w, h)
+ this.C.SearchList:SetVisible(true)
+ end
+ local results = Search(text)
+ this.C.SearchList:Clear()
+ for i = 1, #results do
+ local result = results[i]
+
+ local type
+ if result.type == "wire" then type = "Wire"
+ elseif result.type == "fpga" then type = "FPGA"
+ elseif result.type == "cpu" then type = "CPU"
+ end
+ local line = this.C.SearchList:AddLine(result.name, type, result.group)
+
+ if this.SelectedInMenu then
+ if this.SelectedInMenu.type == result.type and this.SelectedInMenu.gate == result.action then
+ line:SetSelected(true)
+ end
+ end
+
+ line.action = result.action
+ line.type = result.type
+ end
+ else
+ if searching then
+ searching = false
+ local x, y = this.C.Tree:GetPos()
+ local w, h = this.C.Tree:GetSize()
+ this.C.SearchList:SetPos(x, y)
+ this.C.SearchList:MoveTo(x + w, y, 0.1, 0, 1)
+ this.C.SearchList:SetSize(w, h)
+ timer.Create("fpga_customspawnmenu_hidesearchlist", 0.1, 1, function()
+ if IsValid(this.C.SearchList) then
+ this.C.SearchList:SetVisible(false)
+ end
+ end)
+ end
+ this.C.SearchList:Clear()
+ end
+ end
+
+ function self.C.SearchList:OnClickLine(line)
+ -- Deselect old
+ local t = self:GetSelected()
+ if t and next(t) then
+ t[1]:SetSelected(false)
+ end
+
+ line:SetSelected(true) -- Select new
+ this.SelectedInMenu = { type = line.type, gate = line.action }
+ this:RequestFocus()
+ end
+
+ function self.C.Search:OnEnter()
+ if #this.C.SearchList:GetLines() > 0 then
+ this.C.SearchList:OnClickLine(this.C.SearchList:GetLine(1))
+ end
+ this:RequestFocus()
+ end
+
+ -- Set sizes & other settings
+ self.C.SearchList:SetVisible(false)
+ self.C.SearchList:SetMultiSelect(false)
+
+ --utility
+ local function FillSubTree(editor, tree, node, temp, type, sortByName)
+ node.Icon:SetImage("icon16/folder.png")
+
+ local subtree = {}
+ for k, v in pairs(temp) do
+ subtree[#subtree + 1] = { action = k, gate = v, name = v.name, order = v.order }
+ end
+
+ if sortByName then
+ table.SortByMember(subtree, "name", true)
+ else
+ table.SortByMember(subtree, "order", true)
+ end
+
+ for index = 1, #subtree do
+ local action, gate = subtree[index].action, subtree[index].gate
+ local node2 = node:AddNode(gate.name or "No name found :(")
+ node2.name = gate.name
+ node2.action = action
+ function node2:DoClick()
+ editor.SelectedInMenu = { type = type, gate = self.action }
+ end
+ node2.Icon:SetImage("icon16/newspaper.png")
+ end
+ tree:InvalidateLayout()
+ end
+
+ local function addGates(editor, gates, name, key, icon)
+ local CategoriesSorted = {}
+
+ for gatetype, gatefuncs in pairs(gates) do
+ local allowed_gates = {}
+ local any_allowed = false
+ for k, v in pairs(gatefuncs) do
+ if not v.is_banned then
+ allowed_gates[k] = v
+ any_allowed = true
+ end
+ end
+ if any_allowed then
+ CategoriesSorted[#CategoriesSorted + 1] = { gatetype = gatetype, gatefuncs = allowed_gates }
+ end
+ end
+
+ table.sort(CategoriesSorted, function(a, b) return a.gatetype < b.gatetype end)
+
+ local parentNode = self.C.Tree:AddNode(name, icon)
+ function parentNode:DoClick()
+ self:SetExpanded(not self.m_bExpanded)
+ end
+
+ for i = 1, #CategoriesSorted do
+ local gatetype = CategoriesSorted[i].gatetype
+ local gatefuncs = CategoriesSorted[i].gatefuncs
+
+ local node = parentNode:AddNode(gatetype)
+ node.Icon:SetImage("icon16/folder.png")
+ FillSubTree(self, self.C.Tree, node, gatefuncs, key, false)
+ function node:DoClick()
+ self:SetExpanded(not self.m_bExpanded)
+ end
+ end
+ end
+
+ --EDITOR extras
+ local labelNode = self.C.Tree:AddNode("Label", "icon16/text_allcaps.png")
+ function labelNode:DoClick()
+ this.SelectedInMenu = { type = "editor", visual = "label", gate = nil }
+ end
+ local commentNode = self.C.Tree:AddNode("Comment", "icon16/comment.png")
+ function commentNode:DoClick()
+ this.SelectedInMenu = { type = "editor", visual = "comment", gate = nil }
+ end
+
+ --FPGA gates
+ addGates(self, FPGAGatesSorted, "FPGA", "fpga", "icon16/bricks.png")
+
+ --CPU gates
+ addGates(self, CPUGatesSorted, "CPU", "cpu", "icon16/computer.png")
+
+ --WIREMOD gates
+ addGates(self, WireGatesSorted, "Wire", "wire", "icon16/connect.png")
+end
+
+
+--------------------------------------------------------
+--INTERACTION
+--------------------------------------------------------
+function Editor:GetData()
+ return WireLib.von.serialize({
+ Name = self.C.Name:GetValue(),
+ Nodes = self.Nodes,
+ Position = self.Position,
+ Zoom = self.Zoom,
+ ExecutionInterval = self.C.ExecutionInterval:GetValue(),
+ ExecuteOn = {
+ Inputs = self.C.ExecuteOnInputs:GetChecked(),
+ Timed = self.C.ExecuteOnTimed:GetChecked(),
+ Trigger = self.C.ExecuteOnTrigger:GetChecked()
+ }
+ }, false)
+end
+
+function Editor:SetData(data)
+ local ok, data = pcall(WireLib.von.deserialize, data)
+ if not ok then
+ self:ClearData()
+ self.C.Name:SetValue("corrupt")
+ return
+ end
+
+ if data.Nodes then self.Nodes = data.Nodes else self.Nodes = {} end
+
+ if data.Name then self.C.Name:SetValue(data.Name) else self.C.Name:SetValue("gate") end
+
+ if data.ExecutionInterval then
+ self.C.ExecutionInterval:SetValue(data.ExecutionInterval)
+ else
+ self.C.ExecutionInterval:SetValue(0.01)
+ end
+
+ if data.ExecuteOn then
+ self.C.ExecuteOnInputs:SetValue(data.ExecuteOn.Inputs)
+ self.C.ExecuteOnTimed:SetValue(data.ExecuteOn.Timed)
+ self.C.ExecuteOnTrigger:SetValue(data.ExecuteOn.Trigger)
+ else
+ self.C.ExecuteOnInputs:SetValue(true)
+ self.C.ExecuteOnTimed:SetValue(true)
+ self.C.ExecuteOnTrigger:SetValue(false)
+ end
+
+ if data.Position then self.Position = data.Position else self.Position = { 0, 0 } end
+ if data.Zoom then self.Zoom = data.Zoom else self.Zoom = 5 end
+
+ self.InputNameCounter = 0
+ self.OutputNameCounter = 0
+ for nodeId, node in pairs(self.Nodes) do
+ local gate = getGate(node)
+ if not node.visual then
+ if not gate then self:DeleteNode(nodeId)
+ elseif gate.isInput then self.InputNameCounter = self.InputNameCounter + 1
+ elseif gate.isOutput then self.OutputNameCounter = self.OutputNameCounter + 1 end
+ end
+ end
+end
+
+function Editor:ClearData()
+ self.C.Name:SetValue("gate")
+ self.Nodes = {}
+ self.Position = { 0, 0 }
+ self.Zoom = 5
+ self.InputNameCounter = 0
+ self.OutputNameCounter = 0
+end
+
+function Editor:GetName()
+ return self.C.Name:GetValue()
+end
+
+function Editor:HasNodes()
+ return #self.Nodes > 0
+end
+
+--------------------------------------------------------
+--NODE INFO
+--------------------------------------------------------
+--EDITOR NODE
+function Editor:GetVisual(node)
+ if node.type == "editor" then
+ if node.visual == "label" then
+ return { method = "text", font = "FPGALabel", default = "Label" }
+ elseif node.visual == "comment" then
+ return { method = "text", font = "auto", default = "Comment" }
+ end
+ end
+ return nil
+end
+
+--GATES (further up)
+
+--------------------------------------------------------
+--UTILITY
+--------------------------------------------------------
+function Editor:PosToScr(x, y)
+ return (self:GetWide() - 300) / 2 - (self.Position[1] - x) * self.Zoom, self:GetTall() / 2 - (self.Position[2] - y) * self.Zoom
+end
+
+function Editor:ScrToPos(x, y)
+ return self.Position[1] - ((self:GetWide() - 300) / 2 - x) / self.Zoom, self.Position[2] - (self:GetTall() / 2 - y) / self.Zoom
+end
+
+function Editor:AlignPosToGrid(x, y)
+ return math.Round(x / self.GateSize) * self.GateSize, math.Round(y / self.GateSize) * self.GateSize
+end
+
+function Editor:NodeInputPos(node, input)
+ return node.x - self.GateSize / 2 - self.IOSize / 2, node.y + (input - 1) * self.GateSize
+end
+
+function Editor:NodeOutputPos(node, output)
+ return node.x + self.GateSize / 2 + self.IOSize / 2, node.y + (output - 1) * self.GateSize
+end
+
+--------------------------------------------------------
+--DETECTION
+--------------------------------------------------------
+function Editor:GetNodeAt(x, y)
+ local gx, gy = self:ScrToPos(x, y)
+
+ for k, node in pairs(self.Nodes) do
+ local gate = getGate(node)
+
+ if gate then
+ --gates
+ local amountOfInputs = 0
+ if gate.inputs then
+ amountOfInputs = #gate.inputs
+ end
+ local amountOfOutputs = 1
+ if gate.outputs then
+ amountOfOutputs = #gate.outputs
+ end
+
+ local height = math.max(amountOfInputs, amountOfOutputs)
+
+ if gx < node.x - self.GateSize / 2 then continue end
+ if gx > node.x + self.GateSize / 2 then continue end
+ if gy < node.y - self.GateSize / 2 then continue end
+ if gy > node.y - self.GateSize / 2 + self.GateSize * height then continue end
+ end
+
+ local visual = self:GetVisual(node)
+ if visual then
+ --editor nodes
+
+ if visual.method == "text" then
+ if visual.font == "auto" then
+ if (self.Zoom > self.ZoomThreshold) then
+ surface.SetFont("FPGATextBig")
+ elseif (self.Zoom <= self.ZoomHideThreshold) then
+ continue
+ else
+ surface.SetFont("FPGAText")
+ end
+ else
+ surface.SetFont(visual.font)
+ end
+ local tx, ty = surface.GetTextSize(node.value)
+
+ if gx < node.x - (tx / 2) / self.Zoom then continue end
+ if gx > node.x + (tx / 2) / self.Zoom then continue end
+ if gy < node.y - (ty / 2) / self.Zoom then continue end
+ if gy > node.y + (ty / 2) / self.Zoom then continue end
+ else
+ continue
+ end
+ end
+
+ return k
+ end
+
+ return nil
+end
+
+function Editor:GetNodeInputAt(x, y)
+ local gx, gy = self:ScrToPos(x, y)
+
+ for k, node in pairs(self.Nodes) do
+ local gate = getGate(node)
+
+ if not gate then continue end
+
+ if gx < node.x - self.GateSize / 2 - self.IOSize then continue end
+ if gx > node.x + self.GateSize / 2 + self.IOSize then continue end
+ if gy < node.y - self.GateSize / 2 then continue end
+ if gy > node.y - self.GateSize / 2 + self.GateSize * #gate.inputs then continue end
+
+ for inputNum, _ in pairs(gate.inputs) do
+ local ix, iy = self:NodeInputPos(node, inputNum)
+
+ if gx < ix - self.IOSize / 2 then continue end
+ if gx > ix + self.IOSize / 2 then continue end
+ if gy < iy - self.IOSize / 2 then continue end
+ if gy > iy + self.IOSize / 2 then continue end
+
+ return k, inputNum
+ end
+ end
+
+ return nil
+end
+
+function Editor:GetNodeOutputAt(x, y)
+ local gx, gy = self:ScrToPos(x, y)
+
+ for k, node in pairs(self.Nodes) do
+ local gate = getGate(node)
+
+ if not gate then continue end
+
+ if gx < node.x - self.GateSize / 2 - self.IOSize then continue end
+ if gx > node.x + self.GateSize / 2 + self.IOSize then continue end
+ if gy < node.y - self.GateSize / 2 then continue end
+ if gate.outputs then
+ if gy > node.y - self.GateSize / 2 + self.GateSize * #gate.outputs then continue end
+ else
+ if gy > node.y + self.GateSize / 2 then continue end
+ end
+
+ if gate.outputs then
+ for outputNum, _ in pairs(gate.outputs) do
+ local ix, iy = self:NodeOutputPos(node, outputNum)
+
+ if gx < ix - self.IOSize / 2 then continue end
+ if gx > ix + self.IOSize / 2 then continue end
+ if gy < iy - self.IOSize / 2 then continue end
+ if gy > iy + self.IOSize / 2 then continue end
+
+ return k, outputNum
+ end
+ else
+ local ix, iy = self:NodeOutputPos(node, 1)
+
+ if gx < ix - self.IOSize / 2 then continue end
+ if gx > ix + self.IOSize / 2 then continue end
+ if gy < iy - self.IOSize / 2 then continue end
+ if gy > iy + self.IOSize / 2 then continue end
+
+ return k, 1
+ end
+ end
+
+ return nil
+end
+
+--------------------------------------------------------
+--DRAWING
+--------------------------------------------------------
+function Editor:PaintConnection(nodeFrom, output, nodeTo, input, type)
+ local x1, y1 = self:NodeOutputPos(nodeFrom, output)
+ local x2, y2 = self:NodeInputPos(nodeTo, input)
+
+ local sx1, sy1 = self:PosToScr(x1, y1)
+ local sx2, sy2 = self:PosToScr(x2, y2)
+
+ surface.SetDrawColor(FPGATypeColor[type])
+ surface.DrawLine(sx1, sy1, sx2, sy2)
+end
+
+function Editor:PaintConnections()
+ for _, node in pairs(self.Nodes) do
+ local gate = getGate(node)
+ if not gate then continue end
+ for inputNum, connectedTo in pairs(node.connections) do
+ self:PaintConnection(self.Nodes[connectedTo[1]], connectedTo[2], node, inputNum, getInputType(gate, inputNum))
+ end
+ end
+end
+
+function Editor:PaintInput(x, y, type, name, ioSize)
+ surface.SetDrawColor(FPGATypeColor[type])
+ surface.DrawRect(x, y, ioSize * 2, ioSize)
+
+ if (self.Zoom > self.ZoomHideThreshold) then
+ local tx, ty = surface.GetTextSize(name)
+ surface.SetTextPos(x - tx - ioSize * 0.3, y + ioSize / 2 - ty / 2)
+ surface.DrawText(name)
+ end
+end
+
+function Editor:PaintOutput(x, y, type, name, ioSize)
+ surface.SetDrawColor(FPGATypeColor[type])
+ surface.DrawRect(x, y, ioSize * 2, ioSize)
+
+ if (self.Zoom > self.ZoomHideThreshold) then
+ local _, ty = surface.GetTextSize(name)
+ surface.SetTextPos(x + ioSize * 2.3, y + ioSize / 2 - ty / 2)
+ surface.DrawText(name)
+ end
+end
+
+function Editor:PaintGate(nodeId, node, gate)
+ local amountOfInputs = 0
+ if gate.inputs then
+ amountOfInputs = #gate.inputs
+ end
+ local amountOfOutputs = 1
+ if gate.outputs then
+ amountOfOutputs = #gate.outputs
+ end
+
+ local x, y = self:PosToScr(node.x, node.y)
+
+ local size = self.Zoom * self.GateSize
+ local ioSize = self.Zoom * self.IOSize
+
+ -- Inputs
+ if (self.Zoom > self.ZoomThreshold) then
+ surface.SetFont("FPGAIOBig")
+ else
+ surface.SetFont("FPGAIO")
+ end
+ surface.SetTextColor(255, 255, 255)
+
+
+ if gate.inputs then
+ for inputNum, inputName in pairs(gate.inputs) do
+ local nx = x - size / 2 - ioSize
+ local ny = y - ioSize / 2 + (inputNum-1) * size
+
+ self:PaintInput(nx, ny, getInputType(gate, inputNum), inputName, ioSize)
+ end
+ end
+
+ -- Output
+ if gate.outputs then
+ for outputNum, outputName in pairs(gate.outputs) do
+ local nx = x + size / 2 - ioSize
+ local ny = y - ioSize / 2 + (outputNum - 1) * size
+
+ self:PaintOutput(nx, ny, getOutputType(gate, outputNum), outputName, ioSize)
+ end
+ else
+ local nx = x + size / 2 - ioSize
+ local ny = y - ioSize / 2
+
+ self:PaintOutput(nx, ny, getOutputType(gate, 1), "Out", ioSize)
+ end
+
+ -- Body
+ local height = math.max(amountOfInputs, amountOfOutputs, 1)
+
+ if self.SelectedNodes[nodeId] then
+ surface.SetDrawColor(self.SelectedNodeColor)
+ else
+ if gate.isInput then
+ surface.SetDrawColor(self.InputNodeColor)
+ elseif gate.isOutput then
+ surface.SetDrawColor(self.OutputNodeColor)
+ elseif gate.timed then
+ surface.SetDrawColor(self.TimedNodeColor)
+ else
+ surface.SetDrawColor(self.NodeColor)
+ end
+ end
+ surface.DrawRect(x - size / 2, y - size / 2, size, size * height)
+
+ -- Name
+ if (self.Zoom > self.ZoomThreshold) then
+ surface.SetFont("FPGATextBig")
+ else
+ surface.SetFont("FPGAText")
+ end
+ surface.SetTextColor(255, 255, 255)
+ if (self.Zoom > self.ZoomHideThreshold) then
+ local tx, ty = surface.GetTextSize(gate.name)
+ surface.SetTextPos(x - tx / 2, y - ty / 2 - size / 1.2)
+ surface.DrawText(gate.name)
+
+ surface.SetTextColor(200, 200, 200)
+ -- Input
+ if node.ioName then
+ local tx, ty = surface.GetTextSize(node.ioName)
+ surface.SetTextPos(x - tx / 2, y - ty / 2 + size / 1.2)
+ surface.DrawText(node.ioName)
+ -- Constant
+ elseif node.value then
+ local s = tostring(node.value)
+ local tx, ty = surface.GetTextSize(s)
+ surface.SetTextPos(x - tx / 2, y - ty / 2 + size / 1.2)
+ surface.DrawText(s)
+ end
+ end
+end
+
+function Editor:PaintEditorNode(nodeId, node, visual)
+ local x, y = self:PosToScr(node.x, node.y)
+
+ if visual.method == "text" then
+ if visual.font == "auto" then
+ if (self.Zoom > self.ZoomThreshold) then
+ surface.SetFont("FPGATextBig")
+ elseif (self.Zoom <= self.ZoomHideThreshold) then
+ return
+ else
+ surface.SetFont("FPGAText")
+ end
+ else
+ surface.SetFont(visual.font)
+ end
+
+ if self.SelectedNodes[nodeId] then
+ surface.SetTextColor(self.SelectedVisualTextColor)
+ else
+ surface.SetTextColor(self.VisualTextColor)
+ end
+
+ local tx, ty = surface.GetTextSize(node.value)
+ surface.SetTextPos(x - tx / 2, y - ty / 2)
+
+ surface.DrawText(node.value)
+ end
+end
+
+function Editor:PaintNodes()
+ for nodeId, node in pairs(self.Nodes) do
+ local gate = getGate(node)
+ if gate then
+ self:PaintGate(nodeId, node, gate)
+ continue
+ end
+
+ local visual = self:GetVisual(node)
+ if visual then
+ self:PaintEditorNode(nodeId, node, visual)
+ end
+ end
+end
+
+function Editor:PaintHelp()
+ local x, y = self:PosToScr(0, 0)
+
+ surface.SetFont("FPGAText")
+ surface.SetTextColor(255, 255, 255)
+
+ local helpText = [[Drag gates and draw selections with the left mouse button,
+ and drag around the plane with the right mouse button.
+ Connect inputs and outputs by left clicking on either, and dragging to the other.
+ By double clicking on an input or output, you can draw multiple connections at once.
+
+ 'C' creates a gate at the cursor position (select which gate on the right menu)
+ 'X' deletes the gate under the cursor (or with a selection, deletes all selected gates)
+ 'E' edits the gate under the cursor (input/output names, constant values)
+ 'G' toggles align to grid
+
+ 'Ctrl + C' copies the selected gates (relative to mouse position)
+ 'Ctrl + V' pastes the copied gates (relative to mouse position)
+
+
+ To create inputs and outputs for the FPGA chip, use the gates found in 'FPGA/Input & Output'
+ ]]
+
+ for line in helpText:gmatch("([^\n]*)\n?") do
+ local tx, ty = surface.GetTextSize(line)
+ surface.SetTextPos(x - tx / 2, y - ty / 2)
+ surface.DrawText(line)
+ y = y + ty
+ end
+end
+
+function Editor:Paint()
+ surface.SetDrawColor(self.BackgroundColor)
+ surface.DrawRect(0, 36, self:GetWide() - 300, self:GetTall() - 36)
+
+ self:PaintNodes()
+ self:PaintConnections()
+
+ if #self.Nodes == 0 then
+ self:PaintHelp()
+ end
+
+ -- detects if mouse is let go outside of the window
+ if not input.IsMouseDown(MOUSE_RIGHT) then
+ self.DraggingWorld = nil
+ end
+ if not input.IsMouseDown(MOUSE_LEFT) then
+ self.DraggingNode = nil
+ self.DrawingConnection = nil
+ self.DrawingSelection = nil
+ end
+
+ -- moving the plane
+ if self.DraggingWorld then
+ local x, y = self:CursorPos()
+ local dx, dy = self.LastMousePos[1] - x, self.LastMousePos[2] - y
+ self.Position = { self.Position[1] + dx * (1 / self.Zoom), self.Position[2] + dy * (1 / self.Zoom) }
+ end
+ -- moving a node
+ if self.DraggingNode then
+ local x, y = self:CursorPos()
+ local gx, gy = self:ScrToPos(x, y)
+ gx = gx + self.DraggingOffset[1]
+ gy = gy + self.DraggingOffset[2]
+
+ if self.AlignToGrid then
+ gx, gy = self:AlignPosToGrid(gx, gy)
+ end
+
+
+ local cx, cy = self.Nodes[self.DraggingNode].x, self.Nodes[self.DraggingNode].y
+
+ if self.SelectedNodes[self.DraggingNode] and self.SelectedNodeCount > 0 then
+ for selectedNodeId, selectedNode in pairs(self.SelectedNodes) do
+ local sox, soy = self.Nodes[selectedNodeId].x - cx, self.Nodes[selectedNodeId].y - cy
+ self.Nodes[selectedNodeId].x = gx + sox
+ self.Nodes[selectedNodeId].y = gy + soy
+ end
+ else
+ self.SelectedNodes = {}
+ self.Nodes[self.DraggingNode].x = gx
+ self.Nodes[self.DraggingNode].y = gy
+ end
+ end
+ -- drawing a connection
+ if self.DrawingConnection then
+ local nodeId = self.DrawingConnectionFrom[1]
+ local node = self.Nodes[nodeId]
+ local gate = getGate(node)
+
+ local drawingConnectionFrom = { self.DrawingConnectionFrom[2] }
+ local selectedPort = self.DrawingConnectionFrom[2]
+ if self.DrawingConnectionAll then
+ drawingConnectionFrom = {}
+ local ports
+ if self.DrawingFromInput then ports = gate.inputs
+ elseif self.DrawingFromOutput then ports = gate.outputs or { "Out" } end
+ for portNum, portName in pairs(ports) do
+ drawingConnectionFrom[portNum] = portNum
+ end
+ end
+
+ local x, y = 0, 0
+ for _, inputNum in pairs(drawingConnectionFrom) do
+ local type = "NORMAL"
+ if self.DrawingFromInput then
+ x, y = self:NodeInputPos(node, inputNum)
+ type = getInputType(gate, inputNum)
+ elseif self.DrawingFromOutput then
+ x, y = self:NodeOutputPos(node, inputNum)
+ type = getOutputType(gate, inputNum)
+ end
+ local sx, sy = self:PosToScr(x, y)
+ local mx, my = self:CursorPos()
+ surface.SetDrawColor(FPGATypeColor[type])
+ surface.DrawLine(sx, sy, mx, my + (inputNum - selectedPort) * self.GateSize * self.Zoom)
+ end
+ end
+ -- selecting
+ if self.DrawingSelection then
+ local sx, sy = self:PosToScr(self.DrawingSelection[1], self.DrawingSelection[2])
+ local mx, my = self:CursorPos()
+
+ local x, y = math.min(sx, mx), math.min(sy, my)
+ local w, h = math.abs(sx - mx), math.abs(sy - my)
+
+ surface.SetDrawColor(self.SelectionColor)
+ surface.DrawOutlinedRect(x, y, w, h)
+ end
+
+ self:PaintOverlay()
+
+ local x, y = self:CursorPos()
+ self.LastMousePos = { x, y }
+end
+
+function Editor:PaintDebug()
+ surface.SetFont("Default")
+ surface.SetTextColor(255, 255, 255)
+ surface.SetTextPos(10, 50)
+ surface.DrawText(self.Position[1] .. ", " .. self.Position[2])
+ surface.SetTextPos(10, 70)
+ surface.DrawText(self.Zoom)
+end
+
+function Editor:PaintOverlay()
+ surface.SetFont("FPGAText")
+ local y = 43
+ local xOffset = self:GetWide() - 310
+
+ if self.AlignToGrid then
+ surface.SetTextColor(100, 180, 255)
+ local tx, _ = surface.GetTextSize("Align to grid")
+ surface.SetTextPos(xOffset - tx, y)
+ surface.DrawText("Align to grid")
+ y = y + 20
+ end
+
+ if self.SelectedNodeCount > 0 then
+ surface.SetTextColor(255, 255, 120)
+ local text = self.SelectedNodeCount
+ if self.SelectedNodeCount == 1 then
+ text = text .. " node selected"
+ else
+ text = text .. " nodes selected"
+ end
+ local tx, _ = surface.GetTextSize(text)
+ surface.SetTextPos(xOffset - tx, y)
+ surface.DrawText(text)
+ y = y + 20
+ end
+
+ local copyDataSize = self:GetParent():GetCopyDataSize()
+ if copyDataSize > 0 then
+ surface.SetTextColor(120, 255, 120)
+ local text = copyDataSize
+ if copyDataSize == 1 then
+ text = text .. " node in paste buffer"
+ else
+ text = text .. " nodes in paste buffer"
+ end
+ local tx, _ = surface.GetTextSize(text)
+ surface.SetTextPos(xOffset - tx, y)
+ surface.DrawText(text)
+ y = y + 20
+ end
+end
+
+
+--------------------------------------------------------
+--ACTIONS
+--------------------------------------------------------
+function Editor:GetInputName()
+ self.InputNameCounter = self.InputNameCounter + 1
+ return "In" .. self.InputNameCounter
+end
+
+function Editor:GetOutputName()
+ self.OutputNameCounter = self.OutputNameCounter + 1
+ return "Out" .. self.OutputNameCounter
+end
+
+function Editor:CreateNode(selectedInMenu, x, y)
+ node = {
+ type = selectedInMenu.type,
+ gate = selectedInMenu.gate,
+ visual = selectedInMenu.visual,
+ x = x,
+ y = y,
+ connections = {}
+ }
+
+ if self.AlignToGrid then
+ node.x, node.y = self:AlignPosToGrid(node.x, node.y)
+ end
+
+ if selectedInMenu.gate then
+ local gateInfo = getGate(node)
+
+ if gateInfo.isInput then
+ node.ioName = self:GetInputName()
+ elseif gateInfo.isOutput then
+ node.ioName = self:GetOutputName()
+ elseif gateInfo.isConstant then
+ local type = getOutputType(gateInfo, 1)
+ node.value = FPGADefaultValueForType[type]
+ end
+ elseif selectedInMenu.visual then
+ node.value = self:GetVisual(node).default
+ end
+
+ table.insert(self.Nodes, node)
+end
+
+function Editor:DeleteNode(nodeId)
+ --remove all connections to this node
+ for k1, node in pairs(self.Nodes) do
+ for inputNum, connection in pairs(node.connections) do
+ if connection[1] == nodeId then
+ node.connections[inputNum] = nil
+ end
+ end
+ end
+
+ --finally remove node
+ self.Nodes[nodeId] = nil
+end
+
+function Editor:CopyNodes(nodeIds)
+ local nodeIdLookup = {}
+ local i = 1
+ for nodeId, _ in pairs(nodeIds) do
+ nodeIdLookup[nodeId] = i
+ i = i + 1
+ end
+
+ local nodeAmount = table.Count(nodeIds)
+ local copyBuffer = {}
+ local copyOffset = { 0, 0 }
+ for nodeId, _ in pairs(nodeIds) do
+ local node = self.Nodes[nodeId]
+ local gate = getGate(node)
+
+ local nodeCopy = {
+ type = node.type,
+ gate = node.gate,
+ x = node.x,
+ y = node.y,
+ connections = {}
+ }
+
+ if gate then
+ if gate.isInput then
+ nodeCopy.ioName = node.ioName
+ elseif gate.isOutput then
+ nodeCopy.ioName = node.ioName
+ elseif gate.isConstant then
+ nodeCopy.value = node.value
+ end
+ elseif node.visual then
+ nodeCopy.visual = node.visual
+ if node.visual == "label" or node.visual == "comment" then
+ nodeCopy.value = node.value
+ end
+ end
+
+ for inputNum, connection in pairs(node.connections) do
+ if nodeIds[connection[1]] then
+ nodeCopy.connections[inputNum] = { nodeIdLookup[connection[1]], connection[2] }
+ end
+ end
+
+ table.insert(copyBuffer, nodeCopy)
+
+ copyOffset = { copyOffset[1] + node.x / nodeAmount, copyOffset[2] + node.y / nodeAmount }
+ end
+
+ self:GetParent():SetCopyData(copyBuffer, copyOffset)
+end
+
+function Editor:PasteNodes(x, y)
+ local copyData = self:GetParent():GetCopyData()
+ local copyBuffer = copyData[1]
+ local copyOffset = copyData[2]
+
+ if not copyBuffer then return end
+
+ local nodeIdLookup = {}
+ self.SelectedNodes = {}
+ self.SelectedNodeCount = 0
+ local i = #self.Nodes + 1
+ for copyNodeId, _ in pairs(copyBuffer) do
+ while self.Nodes[i] do
+ i = i + 1
+ end
+
+ nodeIdLookup[copyNodeId] = i
+ self.SelectedNodes[i] = true
+ self.SelectedNodeCount = self.SelectedNodeCount + 1
+ i = i + 1
+ end
+
+ for copyNodeId, copyNode in pairs(copyBuffer) do
+ local nodeCopy = {
+ type = copyNode.type,
+ gate = copyNode.gate,
+ connections = {}
+ }
+
+ local gate = getGate(copyNode)
+ if gate then
+ if gate.isInput then
+ nodeCopy.ioName = copyNode.ioName
+ elseif gate.isOutput then
+ nodeCopy.ioName = copyNode.ioName
+ elseif gate.isConstant then
+ nodeCopy.value = copyNode.value
+ end
+ elseif copyNode.visual then
+ nodeCopy.visual = copyNode.visual
+ if copyNode.visual == "label" or copyNode.visual == "comment" then
+ nodeCopy.value = copyNode.value
+ end
+ end
+
+ for inputNum, connection in pairs(copyNode.connections) do
+ nodeCopy.connections[inputNum] = { nodeIdLookup[connection[1]], connection[2] }
+ end
+
+ nodeCopy.x = (copyNode.x - copyOffset[1]) + x
+ nodeCopy.y = (copyNode.y - copyOffset[2]) + y
+
+ self.Nodes[nodeIdLookup[copyNodeId]] = nodeCopy
+ end
+end
+
+--------------------------------------------------------
+--EVENTS
+--------------------------------------------------------
+--KEYBOARD
+function Editor:OnKeyCodePressed(code)
+ local x, y = self:CursorPos()
+ local control = input.IsKeyDown(KEY_LCONTROL) or input.IsKeyDown(KEY_RCONTROL)
+
+ if control then
+ if code == KEY_C then
+ --Copy
+ if self.SelectedNodeCount > 0 then
+ self:CopyNodes(self.SelectedNodes)
+ else
+ self:GetParent():ClearCopyData()
+ end
+ elseif code == KEY_V then
+ --Paste
+ local gx, gy = self:ScrToPos(x, y)
+ self:PasteNodes(gx, gy)
+ end
+ elseif code == KEY_X then
+ --Delete
+ if self.SelectedNodeCount > 0 then
+ for selectedNodeId, selectedNode in pairs(self.SelectedNodes) do
+ self:DeleteNode(selectedNodeId)
+ end
+ self.SelectedNodes = {}
+ self.SelectedNodeCount = 0
+ else
+ local nodeId = self:GetNodeAt(x, y)
+ if nodeId then
+ self:DeleteNode(nodeId)
+ end
+ end
+ elseif code == KEY_C then
+ --Create
+ if self.SelectedInMenu then
+ local gx, gy = self:ScrToPos(x, y)
+ self:CreateNode(self.SelectedInMenu, gx, gy)
+ end
+ elseif code == KEY_E and not self.EditingNode then
+ --Edit
+ local nodeId = self:GetNodeAt(x, y)
+ if nodeId then
+ local node = self.Nodes[nodeId]
+ local gate = getGate(node)
+
+ if gate then
+ if gate.isInput or gate.isOutput then
+ self.EditingNode = true
+ self:OpenNamingWindow(node, x, y)
+ elseif gate.isConstant then
+ self.EditingNode = true
+ self:OpenConstantSetWindow(node, x, y, gate.outputtypes[1])
+ end
+ return
+ end
+
+ local visual = self:GetVisual(node)
+ if visual then
+ if visual.method == "text" then
+ self.EditingNode = true
+ self:OpenNamingWindow(node, x, y)
+ end
+ return
+ end
+ end
+ elseif code == KEY_G then
+ self.AlignToGrid = not self.AlignToGrid
+ end
+end
+
+--MOUSE
+function Editor:OnMouseWheeled(delta)
+ local sx, sy = self:CursorPos()
+
+ if sx > 0 and sy > 36 and sx < self:GetWide() - 300 and sy < self:GetTall() - 36 then
+ self.Zoom = self.Zoom + delta * 0.1 * self.Zoom
+ if self.Zoom < 0.1 then self.Zoom = 0.1 end
+ if self.Zoom > 10 then self.Zoom = 10 end
+ end
+end
+
+function Editor:OnMousePressed(code)
+ self:RequestFocus() --Fix for weird bug, remove once resolved
+
+ if code == MOUSE_LEFT then
+ self.MouseDown = true
+
+ --double click detection
+ local doubleClick
+ if self.LastClick then
+ doubleClick = SysTime() - self.LastClick < 0.3
+ else doubleClick = false end
+ self.LastClick = SysTime()
+
+ local x, y = self:CursorPos()
+
+ --NODE DRAGGING
+ local nodeId = self:GetNodeAt(x, y)
+ if nodeId then
+ self.DraggingNode = nodeId
+ local gx, gy = self:ScrToPos(x, y)
+ self.DraggingOffset = { self.Nodes[nodeId].x - gx, self.Nodes[nodeId].y - gy }
+ else
+ --CONNECTION DRAWING
+ local nodeId, inputNum = self:GetNodeInputAt(x, y)
+ if nodeId then
+ self:BeginDrawingConnection(nodeId, inputNum, nil, doubleClick)
+ else
+ local nodeId, outputNum = self:GetNodeOutputAt(x, y)
+ if nodeId then
+ self:BeginDrawingConnection(nodeId, nil, outputNum, doubleClick)
+ else
+ --SELECTION DRAWING
+ local gx, gy = self:ScrToPos(x, y)
+ self.DrawingSelection = { gx, gy }
+ end
+ end
+
+ end
+ elseif code == MOUSE_RIGHT then
+ -- PLANE DRAGGING
+ self.DraggingWorld = true
+ end
+end
+
+function Editor:OnMouseReleased(code)
+ local x, y = self:CursorPos()
+
+ if code == MOUSE_LEFT then
+ self.MouseDown = false
+ self.DraggingNode = nil
+
+ if self.DrawingConnection then
+ self:OnDrawConnectionFinished(x, y)
+ elseif self.DrawingSelection then
+ self:OnDrawSelectionFinished(x, y)
+ end
+ elseif code == MOUSE_RIGHT then
+ self.DraggingWorld = false
+ end
+
+end
+
+--EDITOR EVENTS
+function Editor:BeginDrawingConnection(nodeId, inputNum, outputNum, doubleClick)
+ self.DrawingConnectionAll = doubleClick
+
+ if inputNum then
+ --check if something is connected to this input
+ node = self.Nodes[nodeId]
+ Input = node.connections[inputNum]
+
+ --Input already connected
+ if Input then
+ local connectedNode, connectedOutput = Input[1], Input[2]
+ node.connections[inputNum] = nil
+ self.DrawingConnectionFrom = { connectedNode, connectedOutput }
+ self.DrawingFromOutput = true
+ self.DrawingConnectionAll = false
+ else
+ --input not connected
+ self.DrawingConnectionFrom = { nodeId, inputNum }
+ self.DrawingFromInput = true
+ end
+
+ self.DrawingConnection = true
+ end
+
+ if outputNum then
+ self.DrawingConnection = true
+ self.DrawingFromOutput = true
+ self.DrawingConnectionFrom = { nodeId, outputNum }
+ end
+end
+
+function Editor:OnDrawSelectionFinished(x, y)
+ local gx, gy = self.DrawingSelection[1], self.DrawingSelection[2]
+ local mx, my = self:CursorPos()
+ local mgx, mgy = self:ScrToPos(mx, my)
+
+ local lx, ly = math.min(gx, mgx), math.min(gy, mgy)
+ local ux, uy = math.max(gx, mgx), math.max(gy, mgy)
+
+ self.SelectedNodes = {}
+ self.SelectedNodeCount = 0
+ for nodeId, node in pairs(self.Nodes) do
+ if node.x < lx then continue end
+ if node.x > ux then continue end
+ if node.y < ly then continue end
+ if node.y > uy then continue end
+
+ self.SelectedNodes[nodeId] = true
+ self.SelectedNodeCount = self.SelectedNodeCount + 1
+ end
+
+ self.DrawingSelection = nil
+end
+
+function Editor:OnDrawConnectionFinished(x, y)
+ local fromNodeId = self.DrawingConnectionFrom[1]
+ local fromNode = self.Nodes[fromNodeId]
+ local fromGate = getGate(fromNode)
+
+ local drawingConnectionFrom = { self.DrawingConnectionFrom[2] }
+ local selectedPort = self.DrawingConnectionFrom[2]
+ if self.DrawingConnectionAll then
+ drawingConnectionFrom = {}
+ local ports
+ if self.DrawingFromInput then ports = fromGate.inputs
+ elseif self.DrawingFromOutput then ports = fromGate.outputs or { "Out" } end
+ for portNum, _ in pairs(ports) do
+ drawingConnectionFrom[portNum] = portNum
+ end
+ end
+
+ local inputNode = fromNode
+ local outputNodeId = fromNodeId
+ local outputNode = fromNode
+ for _, portNum in pairs(drawingConnectionFrom) do
+ local nodeId, inputNum, outputNum
+ if self.DrawingFromOutput then
+ nodeId, inputNum = self:GetNodeInputAt(x, y + (portNum - selectedPort) * self.GateSize * self.Zoom)
+ outputNum = portNum
+ elseif self.DrawingFromInput then
+ nodeId, outputNum = self:GetNodeOutputAt(x, y + (portNum - selectedPort) * self.GateSize * self.Zoom)
+ inputNum = portNum
+ end
+
+ if nodeId then
+ if self.DrawingFromOutput then
+ inputNode = self.Nodes[nodeId]
+ elseif self.DrawingFromInput then
+ outputNode = self.Nodes[nodeId]
+ outputNodeId = nodeId
+ end
+
+ --check type
+ local inputType, outputType
+ if self.DrawingFromOutput then
+ inputType = getInputType(getGate(inputNode), inputNum)
+ outputType = getOutputType(fromGate, outputNum)
+ elseif self.DrawingFromInput then
+ inputType = getInputType(fromGate, inputNum)
+ outputType = getOutputType(getGate(outputNode), outputNum)
+ end
+
+ if inputType == outputType and inputNode != outputNode then
+ --connect up
+ inputNode.connections[inputNum] = { outputNodeId, outputNum }
+ end
+ end
+ end
+
+ self.DrawingConnection = false
+ self.DrawingFromInput = false
+ self.DrawingFromOutput = false
+end
+
+--------------------------------------------------------
+--EXTRA WINDOWS
+--------------------------------------------------------
+function Editor:CreateNamingWindow()
+ self.NamingWindow = vgui.Create("DFrame", self)
+ local pnl = self.NamingWindow
+ pnl:SetSize(300, 55)
+ pnl:ShowCloseButton(true)
+ pnl:SetDeleteOnClose(false)
+ pnl:MakePopup()
+ pnl:SetVisible(false)
+ pnl:SetTitle("Edit")
+ pnl:SetScreenLock(true)
+ do
+ local old = pnl.Close
+ function pnl.Close()
+ self.ForceDrawCursor = false
+ self.EditingNode = false
+ old(pnl)
+ end
+ end
+
+ self.NamingNameEntry = vgui.Create("DTextEntry", pnl)
+ self.NamingNameEntry:Dock(BOTTOM)
+ self.NamingNameEntry:SetSize(175, 20)
+ self.NamingNameEntry:RequestFocus()
+end
+
+function Editor:OpenNamingWindow(node, x, y)
+ if not self.NamingWindow then self:CreateNamingWindow() end
+
+ if node.gate then
+ self.NamingNameEntry:SetText(node.ioName)
+ self.NamingNameEntry.OnEnter = function(pnl)
+ node.ioName = pnl:GetValue()
+ pnl:RequestFocus()
+ pnl:GetParent():Close()
+ end
+ elseif node.visual then
+ self.NamingNameEntry:SetText(node.value)
+ self.NamingNameEntry.OnEnter = function(pnl)
+ node.value = pnl:GetValue()
+ pnl:RequestFocus()
+ pnl:GetParent():Close()
+ end
+ else
+ return
+ end
+
+ self.NamingWindow:SetVisible(true)
+ self.NamingWindow:MakePopup() -- This will move it above the E2 editor frame if it is behind it.
+ self.ForceDrawCursor = true
+
+ local px, py = self:GetParent():GetPos()
+ self.NamingWindow:SetPos(px + x + 80, py + y + 30)
+
+ local inputField = self.NamingNameEntry
+ local this = self
+ inputField.OnLoseFocus = function (pnl)
+ timer.Simple(0, function () if not pnl:GetParent():HasFocus() and this.EditingNode then pnl:OnEnter() end end)
+ pnl:GetParent():MoveToFront()
+ end
+
+ self.NamingWindow.OnFocusChanged = function (pnl, gained)
+ if not gained then
+ timer.Simple(0, function () if not inputField:HasFocus() and this.EditingNode then inputField:OnEnter() end end)
+ pnl:MoveToFront()
+ end
+ end
+end
+
+function Editor:CreateConstantSetWindow()
+ self.ConstantSetWindow = vgui.Create("DFrame", self)
+ local pnl = self.ConstantSetWindow
+ pnl:SetSize(200, 55)
+ pnl:ShowCloseButton(true)
+ pnl:SetDeleteOnClose(false)
+ pnl:MakePopup()
+ pnl:SetVisible(false)
+ pnl:SetTitle("Set constant value")
+ pnl:SetScreenLock(true)
+
+ self.ConstantSetNormal = vgui.Create("DNumberWang", pnl)
+ self.ConstantSetNormal:Dock(BOTTOM)
+ self.ConstantSetNormal:SetSize(175, 20)
+ self.ConstantSetNormal:SetMinMax(-10 ^ 100, 10 ^ 100)
+ self.ConstantSetNormal:SetVisible(false)
+ self.ConstantSetString = vgui.Create("DTextEntry", pnl)
+ self.ConstantSetString:Dock(BOTTOM)
+ self.ConstantSetString:SetSize(175, 20)
+ self.ConstantSetString:SetVisible(false)
+
+ do
+ local old = pnl.Close
+ function pnl.Close()
+ self.ForceDrawCursor = false
+ self.EditingNode = false
+ old(pnl)
+ end
+ end
+end
+
+local function validateVector(string)
+ local x,y,z = string.match(string, "^ *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *$")
+ return tonumber(x) != nil and tonumber(y) != nil and tonumber(z) != nil, x, y, z
+end
+
+function Editor:OpenConstantSetWindow(node, x, y, type)
+ if not self.ConstantSetWindow then self:CreateConstantSetWindow() end
+ self.ConstantSetNormal:SetVisible(false)
+ self.ConstantSetNormal.OnEnter = function () end
+ self.ConstantSetString:SetVisible(false)
+ self.ConstantSetString.OnEnter = function () end
+ self.ConstantSetString:SetValue("")
+ self.ConstantSetWindow:SetVisible(true)
+ self.ConstantSetWindow:MakePopup() -- This will move it above the FPGA editor if it is behind it.
+ self.ForceDrawCursor = true
+
+ local px, py = self:GetParent():GetPos()
+ self.ConstantSetWindow:SetPos(px + x + 80, py + y + 30)
+
+ if type == "NORMAL" then
+ self.ConstantSetNormal:SetVisible(true)
+ self.ConstantSetNormal:SetValue(node.value)
+ self.ConstantSetNormal:RequestFocus()
+ local func = function(pnl)
+ node.value = pnl:GetValue()
+ pnl:SetVisible(false)
+ pnl:GetParent():Close()
+ end
+ self.ConstantSetNormal.OnEnter = func
+ elseif type == "STRING" then
+ self.ConstantSetString:SetVisible(true)
+ self.ConstantSetString:SetText(node.value)
+ self.ConstantSetString:RequestFocus()
+ local func = function(pnl)
+ node.value = pnl:GetValue()
+ pnl:SetVisible(false)
+ pnl:GetParent():Close()
+ end
+ self.ConstantSetString.OnEnter = func
+ elseif type == "VECTOR" then
+ self.ConstantSetString:SetVisible(true)
+ self.ConstantSetString:SetText(node.value.x .. ", " .. node.value.y .. ", " .. node.value.z)
+ self.ConstantSetString:RequestFocus()
+ local func = function(pnl)
+ valid, x, y, z = validateVector(pnl:GetValue())
+ if valid then
+ node.value = Vector(x, y, z)
+ pnl:SetVisible(false)
+ pnl:GetParent():Close()
+ end
+ end
+ self.ConstantSetString.OnEnter = func
+ elseif type == "ANGLE" then
+ self.ConstantSetString:SetVisible(true)
+ self.ConstantSetString:SetText(node.value.p .. ", " .. node.value.y .. ", " .. node.value.r)
+ self.ConstantSetString:RequestFocus()
+ local func = function(pnl)
+ valid, p, y, r = validateVector(pnl:GetValue())
+ if valid then
+ node.value = Angle(p, y, r)
+ pnl:SetVisible(false)
+ pnl:GetParent():Close()
+ end
+ end
+ self.ConstantSetString.OnEnter = func
+ end
+
+ local inputField = self.ConstantSetString
+ if type == "NORMAL" then
+ inputField = self.ConstantSetNormal
+ end
+
+ local this = self
+ inputField.OnLoseFocus = function (pnl)
+ timer.Simple(0, function () if not pnl:GetParent():HasFocus() and this.EditingNode then pnl:OnEnter() end end)
+ pnl:GetParent():MoveToFront()
+ end
+
+ self.ConstantSetWindow.OnFocusChanged = function (pnl, gained)
+ if not gained then
+ timer.Simple(0, function () if not inputField:HasFocus() and this.EditingNode then inputField:OnEnter() end end)
+ pnl:MoveToFront()
+ end
+ end
+end
+
+vgui.Register("FPGAEditor", Editor, "Panel");
\ No newline at end of file
diff --git a/lua/wire/client/node_editor/wire_fpga_editor.lua b/lua/wire/client/node_editor/wire_fpga_editor.lua
new file mode 100644
index 0000000000..92ef1d8e36
--- /dev/null
+++ b/lua/wire/client/node_editor/wire_fpga_editor.lua
@@ -0,0 +1,1247 @@
+local Editor = {}
+
+Editor.NewTabOnOpen = CreateClientConVar("wire_fpga_new_tab_on_open", "1", true, false)
+
+surface.CreateFont("DefaultBold", {
+ font = "Tahoma",
+ size = 12,
+ weight = 700,
+ antialias = true,
+ additive = false,
+})
+
+------------------------------------------------------------------------
+
+local invalid_filename_chars = {
+ ["*"] = "",
+ ["?"] = "",
+ [">"] = "",
+ ["<"] = "",
+ ["|"] = "",
+ ["\\"] = "",
+ ['"'] = "",
+ [" "] = "_",
+}
+
+-- overwritten commands
+function Editor:Init()
+ -- don't use any of the default DFrame UI components
+ for _, v in pairs(self:GetChildren()) do v:Remove() end
+ self.Title = ""
+ self.subTitle = ""
+ self.LastClick = 0
+ self.GuiClick = 0
+ self.SimpleGUI = false
+ self.Location = ""
+
+ self.C = {}
+ self.Components = {}
+
+ -- Load border colors, position, & size
+ self:LoadEditorSettings()
+
+ local fontTable = {
+ font = "default",
+ size = 11,
+ weight = 300,
+ antialias = false,
+ additive = false,
+ }
+ surface.CreateFont("E2SmallFont", fontTable)
+ self.logo = surface.GetTextureID("vgui/e2logo")
+
+ self:InitComponents()
+
+ -- This turns off the engine drawing
+ self:SetPaintBackgroundEnabled(false)
+ self:SetPaintBorderEnabled(false)
+
+ self:SetV(false)
+
+ self:InitShutdownHook()
+end
+
+local size = CreateClientConVar("wire_fpga_editor_size", "800_600", true, false)
+local pos = CreateClientConVar("wire_fpga_editor_pos", "-1_-1", true, false)
+
+function Editor:LoadEditorSettings()
+
+ -- Position & Size
+ local w, h = size:GetString():match("(%d+)_(%d+)")
+ w = tonumber(w)
+ h = tonumber(h)
+
+ self:SetSize(w, h)
+
+ local x, y = pos:GetString():match("(%-?%d+)_(%-?%d+)")
+ x = tonumber(x)
+ y = tonumber(y)
+
+ if x == -1 and y == -1 then
+ self:Center()
+ else
+ self:SetPos(x, y)
+ end
+
+ if x < 0 or y < 0 or x + w > ScrW() or y + h > ScrH() then -- If the editor is outside the screen, reset it
+ local width, height = math.min(ScrW() - 200, 800), math.min(ScrH() - 200, 620)
+ self:SetPos((ScrW() - width) / 2, (ScrH() - height) / 2)
+ self:SetSize(width, height)
+
+ self:SaveEditorSettings()
+ end
+end
+
+function Editor:SaveEditorSettings()
+
+ -- Position & Size
+ local w, h = self:GetSize()
+ RunConsoleCommand("wire_fpga_editor_size", w .. "_" .. h)
+
+ local x, y = self:GetPos()
+ RunConsoleCommand("wire_fpga_editor_pos", x .. "_" .. y)
+end
+
+
+function Editor:PaintOver()
+ surface.SetFont("DefaultBold")
+ surface.SetTextColor(255, 255, 255, 255)
+ surface.SetTextPos(10, 6)
+ surface.DrawText(self.Title .. self.subTitle)
+ surface.SetDrawColor(255, 255, 255, 255)
+ surface.SetTextPos(0, 0)
+ surface.SetFont("Default")
+ return true
+end
+
+function Editor:PerformLayout()
+ local w, h = self:GetSize()
+
+ for i = 1, #self.Components do
+ local c = self.Components[i]
+ local c_x, c_y, c_w, c_h = c.Bounds.x, c.Bounds.y, c.Bounds.w, c.Bounds.h
+ if (c_x < 0) then c_x = w + c_x end
+ if (c_y < 0) then c_y = h + c_y end
+ if (c_w < 0) then c_w = w + c_w - c_x end
+ if (c_h < 0) then c_h = h + c_h - c_y end
+ c:SetPos(c_x, c_y)
+ c:SetSize(c_w, c_h)
+ end
+end
+
+function Editor:OnMousePressed(mousecode)
+ if mousecode ~= 107 then return end -- do nothing if mouseclick is other than left-click
+ if not self.pressed then
+ self.pressed = true
+ self.p_x, self.p_y = self:GetPos()
+ self.p_w, self.p_h = self:GetSize()
+ self.p_mx = gui.MouseX()
+ self.p_my = gui.MouseY()
+ self.p_mode = self:getMode()
+ if self.p_mode == "drag" then
+ if self.GuiClick > CurTime() - 0.2 then
+ self:fullscreen()
+ self.pressed = false
+ self.GuiClick = 0
+ else
+ self.GuiClick = CurTime()
+ end
+ end
+ end
+end
+
+function Editor:OnMouseReleased(mousecode)
+ if mousecode ~= 107 then return end -- do nothing if mouseclick is other than left-click
+ self.pressed = false
+end
+
+function Editor:Think()
+ if self.fs then return end
+ if self.pressed then
+ if not input.IsMouseDown(MOUSE_LEFT) then -- needs this if you let go of the mouse outside the panel
+ self.pressed = false
+ end
+ local movedX = gui.MouseX() - self.p_mx
+ local movedY = gui.MouseY() - self.p_my
+ if self.p_mode == "drag" then
+ local x = self.p_x + movedX
+ local y = self.p_y + movedY
+ if (x < 10 and x > -10) then x = 0 end
+ if (y < 10 and y > -10) then y = 0 end
+ if (x + self.p_w < ScrW() + 10 and x + self.p_w > ScrW() - 10) then x = ScrW() - self.p_w end
+ if (y + self.p_h < ScrH() + 10 and y + self.p_h > ScrH() - 10) then y = ScrH() - self.p_h end
+ self:SetPos(x, y)
+ end
+ if self.p_mode == "sizeBR" then
+ local w = self.p_w + movedX
+ local h = self.p_h + movedY
+ if (self.p_x + w < ScrW() + 10 and self.p_x + w > ScrW() - 10) then w = ScrW() - self.p_x end
+ if (self.p_y + h < ScrH() + 10 and self.p_y + h > ScrH() - 10) then h = ScrH() - self.p_y end
+ if (w < 300) then w = 300 end
+ if (h < 200) then h = 200 end
+ self:SetSize(w, h)
+ end
+ if self.p_mode == "sizeR" then
+ local w = self.p_w + movedX
+ if (w < 300) then w = 300 end
+ self:SetWide(w)
+ end
+ if self.p_mode == "sizeB" then
+ local h = self.p_h + movedY
+ if (h < 200) then h = 200 end
+ self:SetTall(h)
+ end
+ end
+ if not self.pressed then
+ local cursor = "arrow"
+ local mode = self:getMode()
+ if (mode == "sizeBR") then cursor = "sizenwse"
+ elseif (mode == "sizeR") then cursor = "sizewe"
+ elseif (mode == "sizeB") then cursor = "sizens"
+ end
+ if cursor ~= self.cursor then
+ self.cursor = cursor
+ self:SetCursor(self.cursor)
+ end
+ end
+
+ local x, y = self:GetPos()
+ local w, h = self:GetSize()
+
+ if w < 518 then w = 518 end
+ if h < 200 then h = 200 end
+ if x < 0 then x = 0 end
+ if y < 0 then y = 0 end
+ if x + w > ScrW() then x = ScrW() - w end
+ if y + h > ScrH() then y = ScrH() - h end
+ if y < 0 then y = 0 end
+ if x < 0 then x = 0 end
+ if w > ScrW() then w = ScrW() end
+ if h > ScrH() then h = ScrH() end
+
+ self:SetPos(x, y)
+ self:SetSize(w, h)
+end
+
+-- special functions
+
+function Editor:fullscreen()
+ if self.fs then
+ self:SetPos(self.preX, self.preY)
+ self:SetSize(self.preW, self.preH)
+ self.fs = false
+ else
+ self.preX, self.preY = self:GetPos()
+ self.preW, self.preH = self:GetSize()
+ self:SetPos(0, 0)
+ self:SetSize(ScrW(), ScrH())
+ self.fs = true
+ end
+end
+
+function Editor:getMode()
+ local x, y = self:GetPos()
+ local w, h = self:GetSize()
+ local ix = gui.MouseX() - x
+ local iy = gui.MouseY() - y
+
+ if (ix < 0 or ix > w or iy < 0 or iy > h) then return end -- if the mouse is outside the box
+ if (iy < 22) then
+ return "drag"
+ end
+ if (iy > h - 10) then
+ if (ix > w - 20) then return "sizeBR" end
+ return "sizeB"
+ end
+ if (ix > w - 10) then
+ if (iy > h - 20) then return "sizeBR" end
+ return "sizeR"
+ end
+end
+
+function Editor:addComponent(panel, x, y, w, h)
+ assert(not panel.Bounds)
+ panel.Bounds = { x = x, y = y, w = w, h = h }
+ self.Components[#self.Components + 1] = panel
+ return panel
+end
+
+function Editor:GetLastTab() return self.LastTab end
+
+function Editor:SetLastTab(Tab) self.LastTab = Tab end
+
+function Editor:GetActiveTab() return self.C.TabHolder:GetActiveTab() end
+
+function Editor:GetNumTabs() return #self.C.TabHolder.Items end
+
+function Editor:SetActiveTab(val)
+ if self:GetActiveTab() == val then
+ val:GetPanel():RequestFocus()
+ return
+ end
+ self:SetLastTab(self:GetActiveTab())
+ if isnumber(val) then
+ self.C.TabHolder:SetActiveTab(self.C.TabHolder.Items[val].Tab)
+ self:GetCurrentEditor():RequestFocus()
+ elseif val and val:IsValid() then
+ self.C.TabHolder:SetActiveTab(val)
+ val:GetPanel():RequestFocus()
+ end
+
+ self:UpdateActiveTabTitle()
+end
+
+function Editor:ExtractNameFromEditor()
+ return self:GetCurrentEditor():GetName()
+end
+
+function Editor:UpdateActiveTabTitle()
+ local title = self:GetChosenFile()
+ local tabtext = self:ExtractNameFromEditor()
+
+ if title then self:SubTitle("Editing: " .. title) else self:SubTitle() end
+ if tabtext then
+ if self:GetActiveTab():GetText() ~= tabtext then
+ self:GetActiveTab():SetText(tabtext)
+ self.C.TabHolder.tabScroller:InvalidateLayout()
+ end
+ end
+end
+
+function Editor:GetActiveTabIndex()
+ local tab = self:GetActiveTab()
+ for k, v in pairs(self.C.TabHolder.Items) do
+ if tab == v.Tab then
+ return k
+ end
+ end
+ return -1
+end
+
+
+function Editor:SetActiveTabIndex(index)
+ local tab = self.C.TabHolder.Items[index].Tab
+
+ if not tab then return end
+
+ self:SetActiveTab(tab)
+end
+
+local old
+function Editor:FixTabFadeTime()
+ if old ~= nil then return end -- It's already being fixed
+ old = self.C.TabHolder:GetFadeTime()
+ self.C.TabHolder:SetFadeTime(0)
+ timer.Simple(old, function() self.C.TabHolder:SetFadeTime(old) old = nil end)
+end
+
+function Editor:CreateTab(chosenfile)
+ local editor = vgui.Create("FPGAEditor")
+ editor.ParentPanel = self
+
+ local sheet = self.C.TabHolder:AddSheet(chosenfile, editor)
+ editor.chosenfile = chosenfile
+
+ sheet.Tab.OnMousePressed = function(pnl, keycode, ...)
+
+ if keycode == MOUSE_MIDDLE then
+ self:CloseTab(pnl)
+ return
+ elseif keycode == MOUSE_RIGHT then
+ local menu = DermaMenu()
+ menu:AddOption("Close", function()
+ self:CloseTab(pnl)
+ end)
+ menu:AddOption("Close all others", function()
+ self:FixTabFadeTime()
+ self:SetActiveTab(pnl)
+ for i = self:GetNumTabs(), 1, -1 do
+ if self.C.TabHolder.Items[i] ~= sheet then
+ self:CloseTab(i)
+ end
+ end
+ end)
+ menu:AddSpacer()
+ menu:AddOption("Save", function()
+ self:FixTabFadeTime()
+ local old = self:GetLastTab()
+ local currentTab = self:GetActiveTab()
+ self:SetActiveTab(pnl)
+ self:SaveFile(self:GetChosenFile(), false)
+ self:SetActiveTab(currentTab)
+ self:SetLastTab(old)
+ end)
+ menu:AddOption("Save As", function()
+ self:FixTabFadeTime()
+ self:SetActiveTab(pnl)
+ self:SaveFile(self:GetChosenFile(), false, true)
+ end)
+ menu:AddOption("Reload", function()
+ self:FixTabFadeTime()
+ local old = self:GetLastTab()
+ self:SetActiveTab(pnl)
+ self:LoadFile(editor.chosenfile, false)
+ self:SetActiveTab(self:GetLastTab())
+ self:SetLastTab(old)
+ self:UpdateActiveTabTitle()
+ end)
+ menu:AddSpacer()
+ menu:AddOption("Copy file path to clipboard", function()
+ if editor.chosenfile and editor.chosenfile ~= "" then
+ SetClipboardText(editor.chosenfile)
+ end
+ end)
+ menu:AddOption("Copy all file paths to clipboard", function()
+ local str = ""
+ for i = 1, self:GetNumTabs() do
+ local chosenfile = self:GetEditor(i).chosenfile
+ if chosenfile and chosenfile ~= "" then
+ str = str .. chosenfile .. ";"
+ end
+ end
+ str = str:sub(1, -2)
+ SetClipboardText(str)
+ end)
+ menu:Open()
+ return
+ end
+
+ self:SetActiveTab(pnl)
+ end
+
+ editor.OnTextChanged = function(panel)
+ timer.Create("fpgaautosave", 5, 1, function()
+ self:AutoSave()
+ end)
+ end
+ editor.OnShortcut = function(_, code)
+ if code == KEY_S then
+ self:SaveFile(self:GetChosenFile())
+ end
+ end
+ editor:RequestFocus()
+
+ self:OnTabCreated(sheet) -- Call a function that you can override to do custom stuff to each tab.
+
+ return sheet
+end
+
+function Editor:OnTabCreated(sheet) end
+
+-- This function is made to be overwritten
+
+function Editor:GetNextAvailableTab()
+ local activetab = self:GetActiveTab()
+ for _, v in pairs(self.C.TabHolder.Items) do
+ if v.Tab and v.Tab:IsValid() and v.Tab ~= activetab then
+ return v.Tab
+ end
+ end
+end
+
+function Editor:NewTab()
+ local sheet = self:CreateTab("gate")
+ self:SetActiveTab(sheet.Tab)
+
+ self:NewChip(true)
+end
+
+function Editor:CloseTab(_tab)
+ local activetab, sheetindex
+ if _tab then
+ if isnumber(_tab) then
+ local temp = self.C.TabHolder.Items[_tab]
+ if temp then
+ activetab = temp.Tab
+ sheetindex = _tab
+ else
+ return
+ end
+ else
+ activetab = _tab
+ -- Find the sheet index
+ for k, v in pairs(self.C.TabHolder.Items) do
+ if activetab == v.Tab then
+ sheetindex = k
+ break
+ end
+ end
+ end
+ else
+ activetab = self:GetActiveTab()
+ -- Find the sheet index
+ for k, v in pairs(self.C.TabHolder.Items) do
+ if activetab == v.Tab then
+ sheetindex = k
+ break
+ end
+ end
+ end
+
+ self:AutoSave()
+
+ -- There's only one tab open, no need to actually close any tabs
+ if self:GetNumTabs() == 1 then
+ activetab:SetText("gate")
+ self.C.TabHolder:InvalidateLayout()
+ self:NewChip(true)
+ return
+ end
+
+ -- Find the panel (for the scroller)
+ local tabscroller_sheetindex
+ for k, v in pairs(self.C.TabHolder.tabScroller.Panels) do
+ if v == activetab then
+ tabscroller_sheetindex = k
+ break
+ end
+ end
+
+ self:FixTabFadeTime()
+
+ if activetab == self:GetActiveTab() then -- We're about to close the current tab
+ if self:GetLastTab() and self:GetLastTab():IsValid() then -- If the previous tab was saved
+ if activetab == self:GetLastTab() then -- If the previous tab is equal to the current tab
+ local othertab = self:GetNextAvailableTab() -- Find another tab
+ if othertab and othertab:IsValid() then -- If that other tab is valid, use it
+ self:SetActiveTab(othertab)
+ self:SetLastTab()
+ else -- Reset the current tab (backup)
+ self:GetActiveTab():SetText("gate")
+ self.C.TabHolder:InvalidateLayout()
+ self:NewChip(true)
+ return
+ end
+ else -- Change to the previous tab
+ self:SetActiveTab(self:GetLastTab())
+ self:SetLastTab()
+ end
+ else -- If the previous tab wasn't saved
+ local othertab = self:GetNextAvailableTab() -- Find another tab
+ if othertab and othertab:IsValid() then -- If that other tab is valid, use it
+ self:SetActiveTab(othertab)
+ else -- Reset the current tab (backup)
+ self:GetActiveTab():SetText("gate")
+ self.C.TabHolder:InvalidateLayout()
+ self:NewChip(true)
+ return
+ end
+ end
+ end
+
+ self:OnTabClosed(activetab) -- Call a function that you can override to do custom stuff to each tab.
+
+ activetab:GetPanel():Remove()
+ activetab:Remove()
+ table.remove(self.C.TabHolder.Items, sheetindex)
+ table.remove(self.C.TabHolder.tabScroller.Panels, tabscroller_sheetindex)
+
+ self.C.TabHolder.tabScroller:InvalidateLayout()
+ local w, h = self.C.TabHolder:GetSize()
+ self.C.TabHolder:SetSize(w + 1, h) -- +1 so it updates
+end
+
+function Editor:OnTabClosed(sheet) end
+
+-- This function is made to be overwritten
+
+-- initialization commands
+function Editor:InitComponents()
+ self.Components = {}
+ self.C = {}
+
+ local function PaintFlatButton(panel, w, h)
+ if not (panel:IsHovered() or panel:IsDown()) then return end
+ derma.SkinHook("Paint", "Button", panel, w, h)
+ end
+
+ local DMenuButton = vgui.RegisterTable({
+ Init = function(panel)
+ panel:SetText("")
+ panel:SetSize(24, 20)
+ panel:Dock(LEFT)
+ end,
+ Paint = PaintFlatButton,
+ DoClick = function(panel)
+ local name = panel:GetName()
+ local f = name and name ~= "" and self[name] or nil
+ if f then f(self) end
+ end
+ }, "DButton")
+
+ -- addComponent( panel, x, y, w, h )
+ -- if x, y, w, h is minus, it will stay relative to right or buttom border
+ self.C.Close = self:addComponent(vgui.Create("DButton", self), -45-4, 0, 45, 22) -- Close button
+ self.C.Inf = self:addComponent(vgui.CreateFromTable(DMenuButton, self), -45-4-26, 0, 24, 22) -- Info button
+ self.C.ConBut = self:addComponent(vgui.CreateFromTable(DMenuButton, self), -45-4-24-26, 0, 24, 22) -- Control panel open/close
+
+ self.C.Divider = vgui.Create("DHorizontalDivider", self)
+
+ self.C.Browser = vgui.Create("wire_expression2_browser", self.C.Divider) -- Expression 2 file browser
+ do
+ local pnl = self.C.Browser.SearchBox
+ local old = pnl.OnLoseFocus
+
+ function pnl.OnLoseFocus()
+ old(pnl)
+ self:GetCurrentEditor():RequestFocus()
+ end
+ end
+
+
+ self.C.MainPane = vgui.Create("DPanel", self.C.Divider)
+ self.C.Menu = vgui.Create("DPanel", self.C.MainPane)
+ self.C.TabHolder = vgui.Create("DPropertySheet", self.C.MainPane)
+
+ self.C.Btoggle = vgui.CreateFromTable(DMenuButton, self.C.Menu) -- Toggle Browser being shown
+ self.C.Sav = vgui.CreateFromTable(DMenuButton, self.C.Menu) -- Save button
+ self.C.NewTab = vgui.CreateFromTable(DMenuButton, self.C.Menu, "NewTab") -- New tab button
+ self.C.CloseTab = vgui.CreateFromTable(DMenuButton, self.C.Menu, "CloseTab") -- Close tab button
+ self.C.Reload = vgui.CreateFromTable(DMenuButton, self.C.Menu) -- Reload tab button
+ self.C.SaE = vgui.Create("DButton", self.C.Menu) -- Save & Exit button
+ self.C.SavAs = vgui.Create("DButton", self.C.Menu) -- Save As button
+
+ self.C.Control = self:addComponent(vgui.Create("Panel", self), -350, 52, 342, -32) -- Control Panel
+ self.C.Credit = self:addComponent(vgui.Create("DTextEntry", self), -160, 52, 150, 190) -- Credit box
+ self.C.Credit:SetEditable(false)
+
+ -- extra component options
+
+ self.C.Divider:SetLeft(self.C.Browser)
+ self.C.Divider:SetRight(self.C.MainPane)
+ self.C.Divider:Dock(FILL)
+ self.C.Divider:SetDividerWidth(4)
+ self.C.Divider:SetCookieName("wire_fpga_editor_divider")
+ self.C.Divider:SetLeftMin(0)
+
+ local DoNothing = function() end
+ self.C.MainPane.Paint = DoNothing
+
+ self.C.Menu:Dock(TOP)
+ self.C.TabHolder:Dock(FILL)
+
+ self.C.TabHolder:SetPadding(1)
+
+ self.C.Menu:SetHeight(24)
+ self.C.Menu:DockPadding(2,2,2,2)
+
+ self.C.SaE:SetSize(80, 20)
+ self.C.SaE:Dock(RIGHT)
+ self.C.SavAs:SetSize(51, 20)
+ self.C.SavAs:Dock(RIGHT)
+
+ self.C.Inf:Dock(NODOCK)
+ self.C.ConBut:Dock(NODOCK)
+
+ self.C.Close:SetText("r")
+ self.C.Close:SetFont("Marlett")
+ self.C.Close.DoClick = function(btn) self:Close() end
+
+ self.C.ConBut:SetImage("icon16/wrench.png")
+ self.C.ConBut:SetText("")
+ self.C.ConBut.Paint = PaintFlatButton
+ self.C.ConBut.DoClick = function() self.C.Control:SetVisible(not self.C.Control:IsVisible()) end
+
+ self.C.Inf:SetImage("icon16/information.png")
+ self.C.Inf.Paint = PaintFlatButton
+ self.C.Inf.DoClick = function(btn)
+ self.C.Credit:SetVisible(not self.C.Credit:IsVisible())
+ end
+
+
+ self.C.Sav:SetImage("icon16/disk.png")
+ self.C.Sav.DoClick = function(button) self:SaveFile(self:GetChosenFile()) end
+ self.C.Sav:SetTooltip( "Save" )
+
+ self.C.NewTab:SetImage("icon16/page_white_add.png")
+ self.C.NewTab.DoClick = function(button) self:NewTab() end
+ self.C.NewTab:SetTooltip( "New tab" )
+
+ self.C.CloseTab:SetImage("icon16/page_white_delete.png")
+ self.C.CloseTab.DoClick = function(button) self:CloseTab() end
+ self.C.CloseTab:SetTooltip( "Close tab" )
+
+ self.C.Reload:SetImage("icon16/page_refresh.png")
+ self.C.Reload:SetTooltip( "Refresh file" )
+ self.C.Reload.DoClick = function(button)
+ self:LoadFile(self:GetChosenFile(), false)
+ self:UpdateActiveTabTitle()
+ end
+
+ self.C.SaE:SetText("Save and Exit")
+ self.C.SaE.DoClick = function(button) self:SaveFile(self:GetChosenFile(), true) end
+
+ self.C.SavAs:SetText("Save As")
+ self.C.SavAs.DoClick = function(button) self:SaveFile(self:GetChosenFile(), false, true) end
+
+ --Helper
+ self.C.Helper = vgui.Create("DFrame", self)
+ self.C.Helper:SetSize(1200, 700)
+ self.C.Helper:Center()
+ self.C.Helper:ShowCloseButton(true)
+ self.C.Helper:SetDeleteOnClose(false)
+ self.C.Helper:SetVisible(false)
+ self.C.Helper:SetTitle("FPGA Help")
+ self.C.Helper:SetScreenLock(true)
+ local html = vgui.Create("DHTML" , self.C.Helper)
+ html:Dock(FILL)
+ html:SetHTML(file.Read("data_static/fpgahelp.txt", "GAME") or "")
+ html:SetAllowLua(false)
+
+ self.C.Help = vgui.Create("Button", self.C.Menu)
+ self.C.Help:SetSize(80, 20)
+ self.C.Help:Dock(RIGHT)
+ self.C.Help:DockMargin(0,0,20,0)
+ self.C.Help:SetText("Help")
+ self.C.Help.DoClick = function()
+ self.C.Helper:SetVisible(true)
+ self.C.Helper:MakePopup()
+ end
+
+ self.C.Browser:AddRightClick(self.C.Browser.filemenu, 4, "Save to", function()
+ Derma_Query("Overwrite this file?", "Save To",
+ "Overwrite", function()
+ self:SaveFile(self.C.Browser.File.FileDir)
+ end,
+ "Cancel")
+ end)
+ self.C.Browser.OnFileOpen = function(_, filepath, newtab)
+ self:Open(filepath, nil, newtab)
+ end
+
+ self.C.Btoggle:SetImage("icon16/application_side_contract.png")
+ function self.C.Btoggle.DoClick(button)
+ if button.hide then
+ self.C.Divider:LoadCookies()
+ else
+ self.C.Divider:SetLeftWidth(0)
+ end
+ self.C.Divider:InvalidateLayout()
+ button:InvalidateLayout()
+ end
+
+ local oldBtoggleLayout = self.C.Btoggle.PerformLayout
+ function self.C.Btoggle.PerformLayout(button)
+ oldBtoggleLayout(button)
+ if self.C.Divider:GetLeftWidth() > 0 then
+ button.hide = false
+ button:SetImage("icon16/application_side_contract.png")
+ else
+ button.hide = true
+ button:SetImage("icon16/application_side_expand.png")
+ end
+ end
+
+ self.C.Credit:SetTextColor(Color(0, 0, 0, 255))
+ self.C.Credit:SetText("\t\tCREDITS\n\n\tEditor by: \tSyranide and Shandolum\n\n\tTabs (and more) added by Divran.\n\n\tFixed for GMod13 By Ninja101 \n\n\tRewritten into a node editor by Lysdal") -- Sure why not ;)
+ self.C.Credit:SetMultiline(true)
+ self.C.Credit:SetVisible(false)
+
+ self:InitControlPanel(self.C.Control) -- making it seperate for better overview
+ self.C.Control:SetVisible(false)
+
+ self:CreateTab("gate")
+end
+
+function Editor:AutoSave()
+ local buffer = self:GetData()
+ if self.savebuffer == buffer or buffer == "" then return end
+ self.savebuffer = buffer
+ file.CreateDir(self.Location)
+ file.Write(self.Location .. "/_autosave_.txt", buffer)
+end
+
+function Editor:AddControlPanelTab(label, icon, tooltip)
+ local frame = self.C.Control
+ local panel = vgui.Create("DPanel")
+ local ret = frame.TabHolder:AddSheet(label, panel, icon, false, false, tooltip)
+ local old = ret.Tab.OnMousePressed
+ function ret.Tab.OnMousePressed(...)
+ timer.Simple(0.1,function() frame:ResizeAll() end) -- timers solve everything
+ old(...)
+ end
+
+ ret.Panel:SetBackgroundColor(Color(96, 96, 96, 255))
+
+ return ret
+end
+
+function Editor:InitControlPanel(frame)
+ -- Add a property sheet to hold the tabs
+ local tabholder = vgui.Create("DPropertySheet", frame)
+ tabholder:SetPos(2, 4)
+ frame.TabHolder = tabholder
+
+ -- They need to be resized one at a time... dirty fix incoming (If you know of a nicer way to do this, don't hesitate to fix it.)
+ local function callNext(t, n)
+ local obj = t[n]
+ local pnl = obj[1]
+ if pnl and pnl:IsValid() then
+ local x, y = obj[2], obj[3]
+ pnl:SetPos(x, y)
+ local w, h = pnl:GetParent():GetSize()
+ local wofs, hofs = w - x * 2, h - y * 2
+ pnl:SetSize(wofs, hofs)
+ end
+ n = n + 1
+ if n <= #t then
+ timer.Simple(0, function() callNext(t, n) end)
+ end
+ end
+
+ function frame:ResizeAll()
+ timer.Simple(0, function()
+ callNext(self.ResizeObjects, 1)
+ end)
+ end
+
+ -- Resize them at the right times
+ local oldFrameSetSize = frame.SetSize
+ function frame:SetSize(...)
+ self:ResizeAll()
+ oldFrameSetSize(self, ...)
+ end
+
+ local oldFrameSetVisible = frame.SetVisible
+ function frame:SetVisible(...)
+ self:ResizeAll()
+ oldFrameSetVisible(self, ...)
+ end
+
+ -- Function to add more objects to resize automatically
+ frame.ResizeObjects = {}
+ function frame:AddResizeObject(...)
+ self.ResizeObjects[#self.ResizeObjects + 1] = { ... }
+ end
+
+ -- Our first object to auto resize is the tabholder. This sets it to position 2,4 and with a width and height offset of w-4, h-8.
+ frame:AddResizeObject(tabholder, 2, 4)
+
+ -- ------------------------------------------- EDITOR TAB
+ local sheet = self:AddControlPanelTab("Editor", "icon16/wrench.png", "Options for the editor itself.")
+
+ -- WINDOW BORDER COLORS
+
+ local dlist = vgui.Create("DPanelList", sheet.Panel)
+ dlist.Paint = function() end
+ frame:AddResizeObject(dlist, 4, 4)
+ dlist:EnableVerticalScrollbar(true)
+
+ local NewTabOnOpen = vgui.Create("DCheckBoxLabel")
+ dlist:AddItem(NewTabOnOpen)
+ NewTabOnOpen:SetConVar("wire_fpga_new_tab_on_open")
+ NewTabOnOpen:SetText("New tab on open")
+ NewTabOnOpen:SizeToContents()
+ NewTabOnOpen:SetTooltip("Enable/disable loaded files opening in a new tab.\nIf disabled, loaded files will be opened in the current tab.")
+
+ local SaveTabsOnClose = vgui.Create("DCheckBoxLabel")
+ dlist:AddItem(SaveTabsOnClose)
+ SaveTabsOnClose:SetConVar("wire_fpga_editor_savetabs")
+ SaveTabsOnClose:SetText("Save tabs on close")
+ SaveTabsOnClose:SizeToContents()
+ SaveTabsOnClose:SetTooltip("Save the currently opened tab file paths on shutdown.\nOnly saves tabs whose files are saved.")
+
+ local OpenOldTabs = vgui.Create("DCheckBoxLabel")
+ dlist:AddItem(OpenOldTabs)
+ OpenOldTabs:SetConVar("wire_fpga_editor_openoldtabs")
+ OpenOldTabs:SetText("Open old tabs on load")
+ OpenOldTabs:SizeToContents()
+ OpenOldTabs:SetTooltip("Open the tabs from the last session on load.\nOnly tabs whose files were saved before disconnecting from the server are stored.")
+
+ local WorldClicker = vgui.Create("DCheckBoxLabel")
+ dlist:AddItem(WorldClicker)
+ WorldClicker:SetConVar("wire_fpga_editor_worldclicker")
+ WorldClicker:SetText("Enable Clicking Outside Editor")
+ WorldClicker:SizeToContents()
+ function WorldClicker.OnChange(pnl, bVal)
+ self:GetParent():SetWorldClicker(bVal)
+ end
+
+ --------------------------------------------- FPGA TAB
+ sheet = self:AddControlPanelTab("FPGA", "icon16/computer.png", "Options for FPGA.")
+
+ dlist = vgui.Create("DPanelList", sheet.Panel)
+ dlist.Paint = function() end
+ frame:AddResizeObject(dlist, 4, 4)
+ dlist:EnableVerticalScrollbar(true)
+
+ local AllowInsideView = vgui.Create("DCheckBoxLabel")
+ dlist:AddItem(AllowInsideView)
+ AllowInsideView:SetConVar("wire_fpga_allow_inside_view")
+ AllowInsideView:SetText("Allow inside view")
+ AllowInsideView:SizeToContents()
+ AllowInsideView:SetTooltip("Other people will be able to hover over your FPGAs and see the internal gates. They won't be able to download your chip, but just see a simplified visual representation.")
+
+
+ dlist:InvalidateLayout()
+end
+
+----- FPGA Options ------------------
+local wire_fpga_allow_inside_view = CreateClientConVar("wire_fpga_allow_inside_view", "0", true, false)
+
+function FPGAGetOptions()
+ return WireLib.von.serialize({
+ allow_inside_view = wire_fpga_allow_inside_view:GetBool() or false
+ }, false)
+end
+
+function FPGASendOptions()
+ FPGASendOptionsToServer(FPGAGetOptions())
+end
+
+cvars.AddChangeCallback("wire_fpga_allow_inside_view", FPGASendOptions)
+-------------------------------------
+
+
+function Editor:NewChip(incurrent)
+ if not incurrent and self.NewTabOnOpen:GetBool() then
+ self:NewTab()
+ else
+ self:AutoSave()
+ self:ChosenFile()
+
+ -- Set title
+ self:GetActiveTab():SetText("gate")
+
+ self.C.TabHolder:InvalidateLayout()
+ self:ClearData()
+ end
+end
+
+local wire_fpga_editor_savetabs = CreateClientConVar("wire_fpga_editor_savetabs", "1", true, false)
+
+local id = 0
+function Editor:InitShutdownHook()
+ id = id + 1
+
+ -- save code when shutting down
+ hook.Add("ShutDown", "wire_fpga_ShutDown" .. id, function()
+ local buffer = self:GetData()
+ if not self:GetCurrentEditor():HasNodes() then return end
+
+ file.CreateDir(self.Location)
+ file.Write(self.Location .. "/_shutdown_.txt", buffer)
+
+ if wire_fpga_editor_savetabs:GetBool() then
+ self:SaveTabs()
+ end
+ end)
+end
+
+function Editor:SaveTabs()
+ local strtabs = ""
+ local tabs = {}
+ for i=1, self:GetNumTabs() do
+ local chosenfile = self:GetEditor(i).chosenfile
+ if chosenfile and chosenfile ~= "" and not tabs[chosenfile] then
+ strtabs = strtabs .. chosenfile .. ";"
+ tabs[chosenfile] = true -- Prevent duplicates
+ end
+ end
+
+ strtabs = strtabs:sub(1, -2)
+
+ file.CreateDir(self.Location)
+ file.Write(self.Location .. "/_tabs_.txt", strtabs)
+end
+
+local wire_fpga_editor_openoldtabs = CreateClientConVar("wire_fpga_editor_openoldtabs", "1", true, false)
+
+function Editor:OpenOldTabs()
+ if not file.Exists(self.Location .. "/_tabs_.txt", "DATA") then return end
+
+ -- Read file
+ local tabs = file.Read(self.Location .. "/_tabs_.txt")
+ if not tabs or tabs == "" then return end
+
+ -- Explode around ;
+ tabs = string.Explode(";", tabs)
+ if not tabs or #tabs == 0 then return end
+
+ -- Temporarily remove fade time
+ self:FixTabFadeTime()
+
+ local is_first = true
+ for _, v in pairs(tabs) do
+ if v and v ~= "" then
+ if (file.Exists(v, "DATA")) then
+ -- Open it in a new tab
+ self:LoadFile(v, true)
+
+ -- If this is the first loop, close the initial tab.
+ if (is_first) then
+ timer.Simple(0, function()
+ self:CloseTab(1)
+ end)
+ is_first = false
+ end
+ end
+ end
+ end
+end
+
+function Editor:SubTitle(sub)
+ if not sub then self.subTitle = ""
+ else self.subTitle = " - " .. sub
+ end
+end
+
+local wire_fpga_editor_worldclicker = CreateClientConVar("wire_fpga_editor_worldclicker", "0", true, false)
+function Editor:SetV(bool)
+ if bool then
+ self:MakePopup()
+ self:InvalidateLayout(true)
+ end
+ self:SetVisible(bool)
+ self:SetKeyboardInputEnabled(bool)
+ self:GetParent():SetWorldClicker(wire_fpga_editor_worldclicker:GetBool() and bool) -- Enable this on the background so we can update FPGA's without closing the editor
+end
+
+function Editor:GetChosenFile()
+ return self:GetCurrentEditor().chosenfile
+end
+
+function Editor:ChosenFile(Line)
+ self:GetCurrentEditor().chosenfile = Line
+ if Line then
+ self:SubTitle("Editing: " .. Line)
+ else
+ self:SubTitle()
+ end
+end
+
+function Editor:FindOpenFile(FilePath)
+ for i = 1, self:GetNumTabs() do
+ local ed = self:GetEditor(i)
+ if ed.chosenfile == FilePath then
+ return ed
+ end
+ end
+end
+
+function Editor:ExtractName()
+ self.savefilefn = self:ExtractNameFromEditor()
+ return
+end
+
+function Editor:ClearCopyData()
+ self.copyBuffer = nil
+ self.copyBufferSize = 0
+ self.copyOffset = nil
+end
+
+function Editor:SetCopyData(buffer, offset)
+ self.copyBuffer = buffer
+ self.copyBufferSize = table.Count(buffer)
+ self.copyOffset = offset
+end
+
+function Editor:GetCopyData()
+ if self.copyBuffer then
+ return {self.copyBuffer, self.copyOffset}
+ else
+ return {nil, nil}
+ end
+end
+
+function Editor:GetCopyDataSize()
+ if self.copyBufferSize then
+ return self.copyBufferSize
+ end
+ return 0
+end
+
+
+function Editor:SetData(data)
+ self:GetCurrentEditor():SetData(data)
+ self.savebuffer = self:GetData()
+ self:ExtractName()
+end
+
+function Editor:ClearData()
+ self:GetCurrentEditor():ClearData()
+ self.savebuffer = self:GetData()
+end
+
+function Editor:GetEditor(n)
+ if self.C.TabHolder.Items[n] then
+ return self.C.TabHolder.Items[n].Panel
+ end
+end
+
+function Editor:GetData()
+ local data = self:GetCurrentEditor():GetData()
+
+ local last_data = ""
+ if #data < 64 then
+ last_data = data
+ else
+ last_data = data:sub(-64 + #data % 8)
+ end
+
+ FPGASetToolInfo(self:ExtractNameFromEditor(), #data, last_data)
+ return data
+end
+
+function Editor:GetCurrentEditor()
+ return self:GetActiveTab():GetPanel()
+end
+
+function Editor:Open(Line, data, forcenewtab)
+ if self:IsVisible() and not Line and not data then self:Close() end
+ hook.Run("WireFPGAEditorOpen", self, Line, data, forcenewtab)
+ self:SetV(true)
+ self.C.SaE:SetText("Save and Exit")
+ if data then
+ if not forcenewtab then
+ for i = 1, self:GetNumTabs() do
+ if self:GetEditor(i).chosenfile == Line then
+ self:SetActiveTab(i)
+ self:SetData(data)
+ return
+ elseif self:GetEditor(i):GetValue() == data then
+ self:SetActiveTab(i)
+ return
+ end
+ end
+ end
+
+ local tab
+ if self.NewTabOnOpen:GetBool() or forcenewtab then
+ tab = self:CreateTab("Download").Tab
+ else
+ tab = self:GetActiveTab()
+ end
+ self:SetActiveTab(tab)
+
+ self:ChosenFile()
+ self:SetData(data)
+
+ self:UpdateActiveTabTitle()
+
+ if Line then self:SubTitle("Editing: " .. Line) end
+ return
+ end
+ if Line then self:LoadFile(Line, forcenewtab) return end
+end
+
+function Editor:SaveFile(Line, close, SaveAs)
+ self:ExtractName()
+
+ if close and self.chip then
+ self:Close()
+ return
+ end
+ if not Line or SaveAs or Line == self.Location .. "/" .. ".txt" then
+ local str
+ if self.C.Browser.File then
+ str = self.C.Browser.File.FileDir -- Get FileDir
+ if str and str ~= "" then -- Check if not nil
+
+ -- Remove "expression2/" or "cpuchip/" etc
+ local n, _ = str:find("/", 1, true)
+ str = str:sub(n + 1, -1)
+
+ if str and str ~= "" then -- Check if not nil
+ if str:Right(4) == ".txt" then -- If it's a file
+ str = string.GetPathFromFilename(str):Left(-2) -- Get the file path instead
+ if not str or str == "" then
+ str = nil
+ end
+ end
+ else
+ str = nil
+ end
+ else
+ str = nil
+ end
+ end
+ Derma_StringRequestNoBlur("Save to New File", "", (str ~= nil and str .. "/" or "") .. self.savefilefn,
+ function(strTextOut)
+ strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars):lower()
+ self:SaveFile(self.Location .. "/" .. strTextOut .. ".txt", close)
+ end)
+ return
+ end
+
+ file.CreateDir(string.GetPathFromFilename(Line))
+ file.Write(Line, self:GetData())
+
+ surface.PlaySound("ambient/water/drip3.wav")
+
+ if not self.chip then self:ChosenFile(Line) end
+
+ self:UpdateActiveTabTitle()
+
+ if close then
+ GAMEMODE:AddNotify("FPGA saved as " .. Line .. ".", NOTIFY_GENERIC, 7)
+ self:Close()
+ end
+end
+
+function Editor:LoadFile(Line, forcenewtab)
+ if not Line or file.IsDir(Line, "DATA") then return end
+
+ local f = file.Open(Line, "r", "DATA")
+ if not f then
+ ErrorNoHalt("Erroring opening file: " .. Line)
+ else
+ local str = f:Read(f:Size()) or ""
+ f:Close()
+ self:AutoSave()
+ if not forcenewtab then
+ for i = 1, self:GetNumTabs() do
+ if self:GetEditor(i).chosenfile == Line then
+ self:SetActiveTab(i)
+ if forcenewtab ~= nil then self:SetData(str) end
+ return
+ elseif self:GetEditor(i):GetData() == str then
+ self:SetActiveTab(i)
+ return
+ end
+ end
+ end
+
+ local tab
+ if self.NewTabOnOpen:GetBool() or forcenewtab then
+ tab = self:CreateTab("").Tab
+ else
+ tab = self:GetActiveTab()
+ end
+ self:SetActiveTab(tab)
+ self:ChosenFile(Line)
+ self:SetData(str)
+ self:UpdateActiveTabTitle()
+ end
+end
+
+function Editor:Close()
+ timer.Stop("fpgaautosave")
+ self:AutoSave()
+
+ self:ExtractName()
+ self:SetV(false)
+ self.chip = false
+
+ self:SaveEditorSettings()
+
+ hook.Run("WireFPGAEditorClose", self)
+end
+
+
+function Editor:Setup(nTitle, nLocation)
+ self.Title = nTitle
+ self.Location = nLocation
+ self.C.Browser:Setup(nLocation)
+
+ self:NewChip(true) -- Opens initial tab, in case OpenOldTabs is disabled or fails.
+
+ if wire_fpga_editor_openoldtabs:GetBool() then
+ self:OpenOldTabs()
+ end
+
+ self:InvalidateLayout()
+end
+
+vgui.Register("FPGAEditorFrame", Editor, "DFrame")
+if SERVER then MsgC(Color(0, 100, 255), "FPGA Editor loaded!\n") end
\ No newline at end of file
diff --git a/lua/wire/cpu_gates/arithmetic.lua b/lua/wire/cpu_gates/arithmetic.lua
new file mode 100644
index 0000000000..9d8eedb327
--- /dev/null
+++ b/lua/wire/cpu_gates/arithmetic.lua
@@ -0,0 +1,115 @@
+CPUGateActions("Arithmetic")
+local i = 1
+
+CPUGateActions["arithmetic-half-adder"] = {
+ order = i,
+ name = "Half Adder",
+ inputs = {"A", "B"},
+ outputs = {"Sum", "Carry"},
+ output = function(gate, A, B)
+ if A ~= 0 and B ~= 0 then
+ return 0, 1
+ elseif A ~= 0 or B ~= 0 then
+ return 1, 0
+ else
+ return 0, 0
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["arithmetic-full-adder"] = {
+ order = i,
+ name = "Full Adder",
+ inputs = {"A", "B", "Carry"},
+ outputs = {"Sum", "Carry"},
+ output = function(gate, A, B, C)
+ local v = (A ~= 0 and 1 or 0) + (B ~= 0 and 1 or 0) + (C ~= 0 and 1 or 0)
+ if v == 0 then return 0, 0
+ elseif v == 1 then return 1, 0
+ elseif v == 2 then return 0, 1
+ else return 1, 1
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["arithmetic-half-subtractor"] = {
+ order = i,
+ name = "Half Subtractor",
+ inputs = {"A", "B"},
+ outputs = {"Difference", "Borrow"},
+ output = function(gate, A, B)
+ if A == 0 and B ~= 0 then
+ return 1, 1
+ elseif A ~= 0 and B == 0 then
+ return 1, 0
+ else
+ return 0, 0
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["arithmetic-full-subtractor"] = {
+ order = i,
+ name = "Full Subtractor",
+ inputs = {"A", "B", "Borrow"},
+ outputs = {"Difference", "Borrow"},
+ output = function(gate, A, B, C)
+ local a, b, c = A ~= 0, B ~= 0, C ~= 0
+ if not a and not b and c then
+ return 1, 1
+ elseif not a and b and not c then
+ return 1, 1
+ elseif not a and b and c then
+ return 0, 1
+ elseif a and not b and not c then
+ return 1, 0
+ elseif a and b and c then
+ return 1, 1
+ else
+ return 0, 0
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["arithmetic-4-bit-adder"] = {
+ order = i,
+ name = "4-bit Adder",
+ inputs = {"[4-bit] A", "[4-bit] B", "Carry"},
+ outputs = {"Sum [4-bit]", "Carry"},
+ output = function(gate, A, B, C)
+ local v = bit.band(A, 15) + bit.band(B, 15) + (C ~= 0 and 1 or 0)
+
+ return bit.band(v, 15), v > 15 and 1 or 0
+ end
+}
+
+
+i = i + 1
+CPUGateActions["arithmetic-8-bit-adder"] = {
+ order = i,
+ name = "8-bit Adder",
+ inputs = {"[8-bit] A", "[8-bit] B", "Carry"},
+ outputs = {"Sum [8-bit]", "Carry"},
+ output = function(gate, A, B, C)
+ local v = bit.band(A, 255) + bit.band(B, 255) + (C ~= 0 and 1 or 0)
+
+ return bit.band(v, 255), v > 255 and 1 or 0
+ end
+}
+
+i = i + 1
+CPUGateActions["arithmetic-16-bit-adder"] = {
+ order = i,
+ name = "16-bit Adder",
+ inputs = {"[16-bit] A", "[16-bit] B", "Carry"},
+ outputs = {"Sum [16-bit]", "Carry"},
+ output = function(gate, A, B, C)
+ local v = bit.band(A, 65535) + bit.band(B, 65535) + (C ~= 0 and 1 or 0)
+
+ return bit.band(v, 65535), v > 65535 and 1 or 0
+ end
+}
\ No newline at end of file
diff --git a/lua/wire/cpu_gates/bit_operations.lua b/lua/wire/cpu_gates/bit_operations.lua
new file mode 100644
index 0000000000..43d7bb2613
--- /dev/null
+++ b/lua/wire/cpu_gates/bit_operations.lua
@@ -0,0 +1,45 @@
+CPUGateActions("Bitwise Operations")
+local i = 1
+
+CPUGateActions["bit-not"] = {
+ order = i,
+ name = "Not",
+ inputs = {"A"},
+ outputs = {"~A"},
+ output = function(gate, A)
+ return bit.bnot(A)
+ end
+}
+
+i = i + 1
+CPUGateActions["bit-or"] = {
+ order = i,
+ name = "Or",
+ inputs = {"A", "B"},
+ outputs = {"A|B"},
+ output = function(gate, A, B)
+ return bit.bor(A, B)
+ end
+}
+
+i = i + 1
+CPUGateActions["bit-and"] = {
+ order = i,
+ name = "And",
+ inputs = {"A", "B"},
+ outputs = {"A&B"},
+ output = function(gate, A, B)
+ return bit.band(A, B)
+ end
+}
+
+i = i + 1
+CPUGateActions["bit-xor"] = {
+ order = i,
+ name = "Xor",
+ inputs = {"A", "B"},
+ outputs = {"A^B"},
+ output = function(gate, A, B)
+ return bit.bor(A, B)
+ end
+}
\ No newline at end of file
diff --git a/lua/wire/cpu_gates/conversion.lua b/lua/wire/cpu_gates/conversion.lua
new file mode 100644
index 0000000000..329f3c7246
--- /dev/null
+++ b/lua/wire/cpu_gates/conversion.lua
@@ -0,0 +1,195 @@
+CPUGateActions("Conversion")
+local i = 1
+
+CPUGateActions["normal-to-4bit"] = {
+ order = i,
+ name = "To 4-bit",
+ inputs = {"A"},
+ outputs = {"Bit 4", "Bit 3", "Bit 2", "Bit 1"},
+ output = function(gate, value)
+ local bits = {}
+ local check = 8
+ for i = 1, 4 do
+ if bit.band(value, check) > 0 then
+ bits[i] = 1
+ else
+ bits[i] = 0
+ end
+ check = check / 2
+ end
+
+ return unpack(bits)
+ end
+}
+
+i = i + 1
+CPUGateActions["normal-to-8bit"] = {
+ order = i,
+ name = "To 8-bit",
+ inputs = {"A"},
+ outputs = {"Bit 8", "Bit 7", "Bit 6", "Bit 5", "Bit 4", "Bit 3", "Bit 2", "Bit 1"},
+ output = function(gate, value)
+ local bits = {}
+ local check = 128
+ for i = 1, 8 do
+ if bit.band(value, check) > 0 then
+ bits[i] = 1
+ else
+ bits[i] = 0
+ end
+ check = check / 2
+ end
+
+ return unpack(bits)
+ end
+}
+
+i = i + 1
+CPUGateActions["normal-to-16bit"] = {
+ order = i,
+ name = "To 16-bit",
+ inputs = {"A"},
+ outputs = {"Bit 16", "Bit 15", "Bit 14", "Bit 13", "Bit 12", "Bit 11", "Bit 10", "Bit 9", "Bit 8", "Bit 7", "Bit 6", "Bit 5", "Bit 4", "Bit 3", "Bit 2", "Bit 1"},
+ output = function(gate, value)
+ local bits = {}
+ local check = 32768
+ for i = 1, 16 do
+ if bit.band(value, check) > 0 then
+ bits[i] = 1
+ else
+ bits[i] = 0
+ end
+ check = check / 2
+ end
+
+ return unpack(bits)
+ end
+}
+
+i = i + 1
+CPUGateActions["4bit-to-normal"] = {
+ order = i,
+ name = "From 4-bit",
+ inputs = {"Bit 4", "Bit 3", "Bit 2", "Bit 1"},
+ output = function(gate, B4, B3, B2, B1)
+ local acc = 0
+ if B1 >= 1 then acc = acc + 1 end
+ if B2 >= 1 then acc = acc + 2 end
+ if B3 >= 1 then acc = acc + 4 end
+ if B4 >= 1 then acc = acc + 8 end
+
+ return acc
+ end
+}
+
+i = i + 1
+CPUGateActions["8bit-to-normal"] = {
+ order = i,
+ name = "From 8-bit",
+ inputs = {"Bit 8", "Bit 7", "Bit 6", "Bit 5", "Bit 4", "Bit 3", "Bit 2", "Bit 1"},
+ output = function(gate, B8, B7, B6, B5, B4, B3, B2, B1)
+ local acc = 0
+ if B1 >= 1 then acc = acc + 1 end
+ if B2 >= 1 then acc = acc + 2 end
+ if B3 >= 1 then acc = acc + 4 end
+ if B4 >= 1 then acc = acc + 8 end
+ if B5 >= 1 then acc = acc + 16 end
+ if B6 >= 1 then acc = acc + 32 end
+ if B7 >= 1 then acc = acc + 64 end
+ if B8 >= 1 then acc = acc + 128 end
+
+ return acc
+ end
+}
+
+i = i + 1
+CPUGateActions["16bit-to-normal"] = {
+ order = i,
+ name = "From 16-bit",
+ inputs = {"Bit 16", "Bit 15", "Bit 14", "Bit 13", "Bit 12", "Bit 11", "Bit 10", "Bit 9", "Bit 8", "Bit 7", "Bit 6", "Bit 5", "Bit 4", "Bit 3", "Bit 2", "Bit 1"},
+ output = function(gate, B16, B15, B14, B13, B12, B11, B10, B9, B8, B7, B6, B5, B4, B3, B2, B1)
+ local acc = 0
+ if B1 >= 1 then acc = acc + 1 end
+ if B2 >= 1 then acc = acc + 2 end
+ if B3 >= 1 then acc = acc + 4 end
+ if B4 >= 1 then acc = acc + 8 end
+ if B5 >= 1 then acc = acc + 16 end
+ if B6 >= 1 then acc = acc + 32 end
+ if B7 >= 1 then acc = acc + 64 end
+ if B8 >= 1 then acc = acc + 128 end
+ if B9 >= 1 then acc = acc + 256 end
+ if B10 >= 1 then acc = acc + 512 end
+ if B11 >= 1 then acc = acc + 1024 end
+ if B12 >= 1 then acc = acc + 2048 end
+ if B13 >= 1 then acc = acc + 4096 end
+ if B14 >= 1 then acc = acc + 8192 end
+ if B15 >= 1 then acc = acc + 16384 end
+ if B16 >= 1 then acc = acc + 32768 end
+
+ return acc
+ end
+}
+
+i = i + 1
+CPUGateActions["signed-4bit-to-normal"] = {
+ order = i,
+ name = "From signed 4-bit",
+ inputs = {"Bit 4", "Bit 3", "Bit 2", "Bit 1"},
+ output = function(gate, B4, B3, B2, B1)
+ local acc = 0
+ if B1 >= 1 then acc = acc + 1 end
+ if B2 >= 1 then acc = acc + 2 end
+ if B3 >= 1 then acc = acc + 4 end
+ if B4 >= 1 then acc = acc - 8 end
+
+ return acc
+ end
+}
+
+i = i + 1
+CPUGateActions["signed-8bit-to-normal"] = {
+ order = i,
+ name = "From signed 8-bit",
+ inputs = {"Bit 8", "Bit 7", "Bit 6", "Bit 5", "Bit 4", "Bit 3", "Bit 2", "Bit 1"},
+ output = function(gate, B8, B7, B6, B5, B4, B3, B2, B1)
+ local acc = 0
+ if B1 >= 1 then acc = acc + 1 end
+ if B2 >= 1 then acc = acc + 2 end
+ if B3 >= 1 then acc = acc + 4 end
+ if B4 >= 1 then acc = acc + 8 end
+ if B5 >= 1 then acc = acc + 16 end
+ if B6 >= 1 then acc = acc + 32 end
+ if B7 >= 1 then acc = acc + 64 end
+ if B8 >= 1 then acc = acc - 128 end
+
+ return acc
+ end
+}
+
+i = i + 1
+CPUGateActions["signed-16bit-to-normal"] = {
+ order = i,
+ name = "From signed 16-bit",
+ inputs = {"Bit 16", "Bit 15", "Bit 14", "Bit 13", "Bit 12", "Bit 11", "Bit 10", "Bit 9", "Bit 8", "Bit 7", "Bit 6", "Bit 5", "Bit 4", "Bit 3", "Bit 2", "Bit 1"},
+ output = function(gate, B16, B15, B14, B13, B12, B11, B10, B9, B8, B7, B6, B5, B4, B3, B2, B1)
+ local acc = 0
+ if B1 >= 1 then acc = acc + 1 end
+ if B2 >= 1 then acc = acc + 2 end
+ if B3 >= 1 then acc = acc + 4 end
+ if B4 >= 1 then acc = acc + 8 end
+ if B5 >= 1 then acc = acc + 16 end
+ if B6 >= 1 then acc = acc + 32 end
+ if B7 >= 1 then acc = acc + 64 end
+ if B8 >= 1 then acc = acc + 128 end
+ if B9 >= 1 then acc = acc + 256 end
+ if B10 >= 1 then acc = acc + 512 end
+ if B11 >= 1 then acc = acc + 1024 end
+ if B12 >= 1 then acc = acc + 2048 end
+ if B13 >= 1 then acc = acc + 4096 end
+ if B14 >= 1 then acc = acc + 8192 end
+ if B15 >= 1 then acc = acc + 16384 end
+ if B16 >= 1 then acc = acc - 32768 end
+
+ return acc
+ end
+}
\ No newline at end of file
diff --git a/lua/wire/cpu_gates/logic.lua b/lua/wire/cpu_gates/logic.lua
new file mode 100644
index 0000000000..759831e8de
--- /dev/null
+++ b/lua/wire/cpu_gates/logic.lua
@@ -0,0 +1,244 @@
+CPUGateActions("Logic")
+local i = 1
+
+CPUGateActions["logic-buffer"] = {
+ order = i,
+ name = "Buffer",
+ inputs = {"A"},
+ outputs = {"A"},
+ output = function(gate, A)
+ if A == 0 then
+ return 0
+ else
+ return 1
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["logic-not"] = {
+ order = i,
+ name = "NOT",
+ inputs = {"A"},
+ outputs = {"~A"},
+ output = function(gate, A)
+ if A == 0 then
+ return 1
+ else
+ return 0
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["logic-and"] = {
+ order = i,
+ name = "AND",
+ inputs = {"A", "B"},
+ outputs = {"A&B"},
+ output = function(gate, A, B)
+ if A ~= 0 and B ~= 0 then
+ return 1
+ else
+ return 0
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["logic-or"] = {
+ order = i,
+ name = "OR",
+ inputs = {"A", "B"},
+ outputs = {"A|B"},
+ output = function(gate, A, B)
+ if A ~= 0 or B ~= 0 then
+ return 1
+ else
+ return 0
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["logic-nand"] = {
+ order = i,
+ name = "NAND",
+ inputs = {"A", "B"},
+ outputs = {"~(A|B)"},
+ output = function(gate, A, B)
+ if A ~= 0 and B ~= 0 then
+ return 0
+ else
+ return 1
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["logic-nor"] = {
+ order = i,
+ name = "NOR",
+ inputs = {"A", "B"},
+ outputs = {"~(A|B)"},
+ output = function(gate, A, B)
+ if A ~= 0 or B ~= 0 then
+ return 0
+ else
+ return 1
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["logic-xor"] = {
+ order = i,
+ name = "XOR",
+ inputs = {"A", "B"},
+ outputs = {"A^B"},
+ output = function(gate, A, B)
+ if (A ~= 0 and B == 0) or (A == 0 and B ~= 0) then
+ return 1
+ else
+ return 0
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["logic-xnor"] = {
+ order = i,
+ name = "XNOR",
+ inputs = {"A", "B"},
+ outputs = {"~(A^B)"},
+ output = function(gate, A, B)
+ if (A ~= 0 and B == 0) or (A == 0 and B ~= 0) then
+ return 0
+ else
+ return 1
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["logic-not-4"] = {
+ order = i,
+ name = "4-bit NOT",
+ inputs = {"A", "B", "C", "D"},
+ outputs = {"~A", "~B", "~C", "~D"},
+ output = function(gate, A, B, C, D)
+ return A == 0, B == 0, C == 0, D == 0
+ end
+}
+
+i = i + 1
+CPUGateActions["logic-not-8"] = {
+ order = i,
+ name = "8-bit NOT",
+ inputs = {"A", "B", "C", "D", "E", "F", "G", "H"},
+ outputs = {"~A", "~B", "~C", "~D", "~E", "~F", "~G", "~H"},
+ output = function(gate, A, B, C, D, E, F, G, H)
+ return A == 0, B == 0, C == 0, D == 0, E == 0, F == 0, G == 0, H == 0
+ end
+}
+
+i = i + 1
+CPUGateActions["logic-not-16"] = {
+ order = i,
+ name = "16-bit NOT",
+ inputs = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P"},
+ outputs = {"~A", "~B", "~C", "~D", "~E", "~F", "~G", "~H", "~I", "~J", "~K", "~L", "~M", "~N", "~O", "~P"},
+ output = function(gate, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P)
+ return A == 0, B == 0, C == 0, D == 0, E == 0, F == 0, G == 0, H == 0, I == 0, J == 0, K == 0, L == 0, M == 0, N == 0, O == 0, P == 0
+ end
+}
+
+i = i + 1
+CPUGateActions["logic-and-3"] = {
+ order = i,
+ name = "3-way AND",
+ inputs = {"A", "B", "C"},
+ outputs = {"A&B&C"},
+ output = function(gate, A, B, C)
+ if A ~= 0 and B ~= 0 and C ~= 0 then
+ return 1
+ else
+ return 0
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["logic-and-4"] = {
+ order = i,
+ name = "4-way AND",
+ inputs = {"A", "B", "C", "D"},
+ outputs = {"A&B&C&D"},
+ output = function(gate, A, B, C, D)
+ if A ~= 0 and B ~= 0 and C ~= 0 and D ~= 0 then
+ return 1
+ else
+ return 0
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["logic-and-8"] = {
+ order = i,
+ name = "8-way AND",
+ inputs = {"A", "B", "C", "D", "E", "F", "G", "H"},
+ outputs = {"A&B&C&D&E&F&G&H"},
+ output = function(gate, A, B, C, D, E, F, G, H)
+ if A ~= 0 and B ~= 0 and C ~= 0 and D ~= 0 and E ~= 0 and F ~= 0 and G ~= 0 and H ~= 0 then
+ return 1
+ else
+ return 0
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["logic-or-3"] = {
+ order = i,
+ name = "3-way OR",
+ inputs = {"A", "B", "C"},
+ outputs = {"A|B|C"},
+ output = function(gate, A, B, C)
+ if A ~= 0 or B ~= 0 or C ~= 0 then
+ return 1
+ else
+ return 0
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["logic-or-4"] = {
+ order = i,
+ name = "4-way OR",
+ inputs = {"A", "B", "C", "D"},
+ outputs = {"A|B|C|D"},
+ output = function(gate, A, B, C, D)
+ if A ~= 0 or B ~= 0 or C ~= 0 or D ~= 0 then
+ return 1
+ else
+ return 0
+ end
+ end
+}
+
+i = i + 1
+CPUGateActions["logic-or-8"] = {
+ order = i,
+ name = "8-way OR",
+ inputs = {"A", "B", "C", "D", "E", "F", "G", "H"},
+ outputs = {"A|B|C|D|E|F|G|H"},
+ output = function(gate, A, B, C, D, E, F, G, H)
+ if A ~= 0 or B ~= 0 or C ~= 0 or D ~= 0 or E ~= 0 or F ~= 0 or G ~= 0 or H ~= 0 then
+ return 1
+ else
+ return 0
+ end
+ end
+}
\ No newline at end of file
diff --git a/lua/wire/cpu_gates/memory.lua b/lua/wire/cpu_gates/memory.lua
new file mode 100644
index 0000000000..3b43e741db
--- /dev/null
+++ b/lua/wire/cpu_gates/memory.lua
@@ -0,0 +1,67 @@
+CPUGateActions("Memory")
+local i = 1
+
+CPUGateActions["memory-program-counter-edge-trigger"] = {
+ order = i,
+ name = "Program Counter (Edge Triggered)",
+ inputs = {"Increment", "Load", "LoadAddress", "Reset", "Clock"},
+ outputs = {"Address"},
+ output = function(gate, Increment, Load, LoadAddress, Reset, Clock)
+ local clock = (Clock ~= 0)
+ if (gate.PrevClock ~= clock and clock) then
+ if Increment ~= 0 then
+ gate.Address = gate.Address + 1
+ end
+ if Load ~= 0 then
+ gate.Address = math.floor(LoadAddress)
+ end
+ if Reset ~= 0 then
+ gate.Address = 0
+ end
+ end
+
+ gate.PrevClock = clock
+ return gate.Address
+ end,
+ reset = function(gate)
+ gate.Address = 0
+ gate.PrevClock = nil
+ end
+}
+
+i = i + 1
+CPUGateActions["memory-register"] = {
+ order = i,
+ name = "Register",
+ inputs = {"Data", "Clock"},
+ output = function(gate, Data, Clock)
+ if (Clock ~= 0) then
+ gate.Value = Data
+ end
+ return gate.Value
+ end,
+ reset = function(gate)
+ gate.Value = 0
+ end,
+}
+
+i = i + 1
+CPUGateActions["memory-register-edge-trigger"] = {
+ order = i,
+ name = "Register (Edge Triggered)",
+ inputs = {"Data", "Clock"},
+ output = function(gate, Data, Clock)
+ local clock = (Clock ~= 0)
+ if (gate.PrevClock ~= clock) then
+ gate.PrevClock = clock
+ if (clock) then
+ gate.Value = Data
+ end
+ end
+ return gate.Value
+ end,
+ reset = function(gate)
+ gate.Value = 0
+ gate.PrevClock = nil
+ end,
+}
\ No newline at end of file
diff --git a/lua/wire/cpu_gates/selection.lua b/lua/wire/cpu_gates/selection.lua
new file mode 100644
index 0000000000..00f27aecde
--- /dev/null
+++ b/lua/wire/cpu_gates/selection.lua
@@ -0,0 +1,141 @@
+CPUGateActions("Selection")
+local i = 1
+
+CPUGateActions["selection-2-mux"] = {
+ order = i,
+ name = "2-to-1 Mux",
+ inputs = {"Select", "A", "B"},
+ output = function(gate, S, ...)
+ local s = math.floor(S)
+
+ if (s >= 0) and (s < 2) then
+ return ({...})[s+1]
+ end
+
+ return 0
+ end
+}
+
+i = i + 1
+CPUGateActions["selection-4-mux"] = {
+ order = i,
+ name = "4-to-1 Mux",
+ inputs = {"Select", "A", "B", "C", "D"},
+ output = function(gate, S, ...)
+ local s = math.floor(S)
+
+ if (s >= 0) and (s < 4) then
+ return ({...})[s+1]
+ end
+
+ return 0
+ end
+}
+
+i = i + 1
+CPUGateActions["selection-8-mux"] = {
+ order = i,
+ name = "8-to-1 Mux",
+ inputs = {"Select", "A", "B", "C", "D", "E", "F", "G", "H"},
+ output = function(gate, S, ...)
+ local s = math.floor(S)
+
+ if (s >= 0) and (s < 8) then
+ return ({...})[s+1]
+ end
+
+ return 0
+ end
+}
+
+i = i + 1
+CPUGateActions["selection-16-mux"] = {
+ order = i,
+ name = "16-to-1 Mux",
+ inputs = {"Select", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P"},
+ output = function(gate, S, ...)
+ local s = math.floor(S)
+
+ if (s >= 0) and (s < 16) then
+ return ({...})[s+1]
+ end
+
+ return 0
+ end
+}
+
+i = i + 1
+CPUGateActions["selection-2-demux"] = {
+ order = i,
+ name = "1-to-2 Demux",
+ inputs = {"Select", "In"},
+ outputs = {"A", "B"},
+ output = function(gate, S, I)
+ local s = math.floor(S)
+
+ local result = {0, 0}
+
+ if (s >= 0) and (s < 2) then
+ result[s+1] = I
+ end
+
+ return unpack(result)
+ end
+}
+
+i = i + 1
+CPUGateActions["selection-4-demux"] = {
+ order = i,
+ name = "1-to-4 Demux",
+ inputs = {"Select", "In"},
+ outputs = {"A", "B", "C", "D"},
+ output = function(gate, S, I)
+ local s = math.floor(S)
+
+ local result = {0, 0, 0, 0}
+
+ if (s >= 0) and (s < 4) then
+ result[s+1] = I
+ end
+
+ return unpack(result)
+ end
+}
+
+i = i + 1
+CPUGateActions["selection-8-demux"] = {
+ order = i,
+ name = "1-to-8 Demux",
+ inputs = {"Select", "In"},
+ outputs = {"A", "B", "C", "D", "E", "F", "G", "H"},
+ output = function(gate, S, I)
+ local s = math.floor(S)
+
+ local result = {0, 0, 0, 0, 0, 0, 0, 0}
+
+ if (s >= 0) and (s < 8) then
+ result[s+1] = I
+ end
+
+ return unpack(result)
+ end
+}
+
+i = i + 1
+CPUGateActions["selection-16-demux"] = {
+ order = i,
+ name = "1-to-16 Demux",
+ inputs = {"Select", "In"},
+ outputs = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P"},
+ output = function(gate, S, I)
+ local s = math.floor(S)
+
+ local result = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+
+ if (s >= 0) and (s < 16) then
+ result[s+1] = I
+ end
+
+ return unpack(result)
+ end
+}
\ No newline at end of file
diff --git a/lua/wire/cpugates.lua b/lua/wire/cpugates.lua
new file mode 100644
index 0000000000..cdc5ff9bc3
--- /dev/null
+++ b/lua/wire/cpugates.lua
@@ -0,0 +1,38 @@
+local gates = {}
+
+local gamt
+gamt = {
+ curcat = "DEFAULT",
+ __newindex = function(t,k,v)
+ if not v.group then
+ v.group = gamt.curcat
+ end
+ rawset(gates,k,v)
+ end,
+ __index = function(t,k)
+ return rawget(gates,k)
+ end,
+ __call = function(t,s) --call the table to set a default category
+ gamt.curcat = s or "DEFAULT"
+ end
+}
+
+function LoadCPUGates()
+ CPUGateActions = {}
+ setmetatable(CPUGateActions,gamt)
+ local entries = file.Find( "wire/cpu_gates/*.lua", "LUA" )
+ for _,v in pairs(entries) do
+ include("cpu_gates/"..v)
+ if (SERVER) then AddCSLuaFile("cpu_gates/"..v) end
+ end
+ CPUGateActions = gates
+
+ CPUGatesSorted = {}
+ for name,gate in pairs(CPUGateActions) do
+ if not CPUGatesSorted[gate.group] then
+ CPUGatesSorted[gate.group] = {}
+ end
+ CPUGatesSorted[gate.group][name] = gate
+ end
+end
+LoadCPUGates()
\ No newline at end of file
diff --git a/lua/wire/default_data_generator.lua b/lua/wire/default_data_generator.lua
index dcd8076730..265015eb10 100644
--- a/lua/wire/default_data_generator.lua
+++ b/lua/wire/default_data_generator.lua
@@ -17,11 +17,12 @@ end
function WireLib.GenerateDefaultData()
-- When adding new folders that need to be generated, add them to this list
RecursivelyGenerateFolder("data_static/expression2/")
+ RecursivelyGenerateFolder("data_static/fpgachip/")
RecursivelyGenerateFolder("data_static/soundlists/")
end
-- Regenerate data files on every structure update
-local DataVersion = 1
+local DataVersion = 2
if cookie.GetNumber("wire_data_version", 0) < DataVersion then
cookie.Set("wire_data_version", tostring(DataVersion))
diff --git a/lua/wire/fpga_gates/constants.lua b/lua/wire/fpga_gates/constants.lua
new file mode 100644
index 0000000000..996ebd5d01
--- /dev/null
+++ b/lua/wire/fpga_gates/constants.lua
@@ -0,0 +1,78 @@
+FPGAGateActions("Constant Values")
+local i = 1
+
+FPGAGateActions["normal-constant"] = {
+ order = i,
+ name = "Constant Normal",
+ inputs = {},
+ outputs = {"Out"},
+ outputtypes = {"NORMAL"},
+ isConstant = true
+}
+
+i = i + 1
+FPGAGateActions["vector-constant"] = {
+ order = i,
+ name = "Constant Vector",
+ inputs = {},
+ outputs = {"Out"},
+ outputtypes = {"VECTOR"},
+ isConstant = true
+}
+
+i = i + 1
+FPGAGateActions["angle-constant"] = {
+ order = i,
+ name = "Constant Angle",
+ inputs = {},
+ outputs = {"Out"},
+ outputtypes = {"ANGLE"},
+ isConstant = true
+}
+
+i = i + 1
+FPGAGateActions["string-constant"] = {
+ order = i,
+ name = "Constant String",
+ inputs = {},
+ outputs = {"Out"},
+ outputtypes = {"STRING"},
+ isConstant = true
+}
+
+i = i + 1
+FPGAGateActions["entity-self"] = {
+ order = i,
+ name = "Self",
+ inputs = {},
+ outputs = {"Out"},
+ outputtypes = {"ENTITY"},
+ specialFunctions = true,
+ output = function(gate)
+ return gate:GetSelf()
+ end
+}
+
+i = i + 1
+FPGAGateActions["entity-owner"] = {
+ order = i,
+ name = "Owner",
+ inputs = {},
+ outputs = {"Out"},
+ outputtypes = {"ENTITY"},
+ output = function(gate)
+ return gate:GetPlayer()
+ end
+}
+
+i = i + 1
+FPGAGateActions["server-tickrate"] = {
+ order = i,
+ name = "Tickrate",
+ inputs = {},
+ outputs = {"Out"},
+ outputtypes = {"NORMAL"},
+ output = function(gate)
+ return 1 / FrameTime()
+ end
+}
diff --git a/lua/wire/fpga_gates/execution.lua b/lua/wire/fpga_gates/execution.lua
new file mode 100644
index 0000000000..5a8186f7d9
--- /dev/null
+++ b/lua/wire/fpga_gates/execution.lua
@@ -0,0 +1,297 @@
+FPGAGateActions("Execution")
+local i = 1
+
+i = i + 1
+FPGAGateActions["execution-delta"] = {
+ order = i,
+ name = "Execution Delta",
+ inputs = {},
+ outputs = {"Out"},
+ outputtypes = {"NORMAL"},
+ alwaysActive = true,
+ specialFunctions = true,
+ output = function(gate)
+ return gate:GetExecutionDelta()
+ end,
+}
+
+i = i + 1
+FPGAGateActions["execution-count"] = {
+ order = i,
+ name = "Execution Count",
+ inputs = {},
+ outputs = {"Out"},
+ outputtypes = {"NORMAL"},
+ alwaysActive = true,
+ specialFunctions = true,
+ output = function(gate)
+ return gate:GetExecutionCount()
+ end,
+}
+
+i = i + 1
+FPGAGateActions["execution-last-normal"] = {
+ order = i,
+ name = "Last Normal",
+ inputs = {"A"},
+ inputtypes = {"NORMAL"},
+ outputs = {"Out"},
+ outputtypes = {"NORMAL"},
+ neverActive = true,
+ output = function(gate, value)
+ gate.memory = value
+ return gate.value
+ end,
+ reset = function(gate)
+ gate.value = 0
+ gate.memory = 0
+ end,
+ postCycle = function(gate)
+ gate.value = gate.memory
+ end,
+}
+
+i = i + 1
+FPGAGateActions["execution-last-vector"] = {
+ order = i,
+ name = "Last Vector",
+ inputs = {"A"},
+ inputtypes = {"VECTOR"},
+ outputs = {"Out"},
+ outputtypes = {"VECTOR"},
+ neverActive = true,
+ output = function(gate, value)
+ gate.memory = value
+ return gate.value
+ end,
+ reset = function(gate)
+ gate.value = Vector(0, 0, 0)
+ gate.memory = Vector(0, 0, 0)
+ end,
+ postCycle = function(gate)
+ gate.value = gate.memory
+ end,
+}
+
+i = i + 1
+FPGAGateActions["execution-last-angle"] = {
+ order = i,
+ name = "Last Angle",
+ inputs = {"A"},
+ inputtypes = {"ANGLE"},
+ outputs = {"Out"},
+ outputtypes = {"ANGLE"},
+ neverActive = true,
+ output = function(gate, value)
+ gate.memory = value
+ return gate.value
+ end,
+ reset = function(gate)
+ gate.value = Angle(0, 0, 0)
+ gate.memory = Angle(0, 0, 0)
+ end,
+ postCycle = function(gate)
+ gate.value = gate.memory
+ end,
+}
+
+i = i + 1
+FPGAGateActions["execution-last-string"] = {
+ order = i,
+ name = "Last String",
+ inputs = {"A"},
+ inputtypes = {"STRING"},
+ outputs = {"Out"},
+ outputtypes = {"STRING"},
+ neverActive = true,
+ output = function(gate, value)
+ gate.memory = value
+ return gate.value
+ end,
+ reset = function(gate)
+ gate.value = ""
+ gate.memory = ""
+ end,
+ postCycle = function(gate)
+ gate.value = gate.memory
+ end,
+}
+
+i = i + 1
+FPGAGateActions["execution-timed-last-normal"] = {
+ order = i,
+ name = "Timed Last Normal",
+ inputs = {"A"},
+ inputtypes = {"NORMAL"},
+ outputs = {"Out"},
+ outputtypes = {"NORMAL"},
+ timed = true,
+ neverActive = true,
+ output = function(gate, value)
+ local oldValue = gate.value
+ gate.value = value
+ return oldValue
+ end,
+ reset = function(gate)
+ gate.value = 0
+ end
+}
+
+i = i + 1
+FPGAGateActions["execution-timed-last-vector"] = {
+ order = i,
+ name = "Timed Last Vector",
+ inputs = {"A"},
+ inputtypes = {"VECTOR"},
+ outputs = {"Out"},
+ outputtypes = {"VECTOR"},
+ timed = true,
+ neverActive = true,
+ output = function(gate, value)
+ local oldValue = gate.value
+ gate.value = value
+ return oldValue
+ end,
+ reset = function(gate)
+ gate.value = Vector(0, 0, 0)
+ gate.memory = Vector(0, 0, 0)
+ end
+}
+
+i = i + 1
+FPGAGateActions["execution-timed-last-angle"] = {
+ order = i,
+ name = "Timed Last Angle",
+ inputs = {"A"},
+ inputtypes = {"ANGLE"},
+ outputs = {"Out"},
+ outputtypes = {"ANGLE"},
+ timed = true,
+ neverActive = true,
+ output = function(gate, value)
+ local oldValue = gate.value
+ gate.value = value
+ return oldValue
+ end,
+ reset = function(gate)
+ gate.value = Angle(0, 0, 0)
+ gate.memory = Angle(0, 0, 0)
+ end
+}
+
+i = i + 1
+FPGAGateActions["execution-timed-last-string"] = {
+ order = i,
+ name = "Timed Last String",
+ inputs = {"A"},
+ inputtypes = {"STRING"},
+ outputs = {"Out"},
+ outputtypes = {"STRING"},
+ timed = true,
+ neverActive = true,
+ output = function(gate, value)
+ local oldValue = gate.value
+ gate.value = value
+ return oldValue
+ end,
+ reset = function(gate)
+ gate.value = ""
+ gate.memory = ""
+ end
+}
+
+i = i + 1
+FPGAGateActions["execution-previous-normal"] = {
+ order = i,
+ name = "Previous Normal",
+ inputs = {"A"},
+ inputtypes = {"NORMAL"},
+ outputs = {"Out"},
+ outputtypes = {"NORMAL"},
+ neverActive = true,
+ output = function(gate, value)
+ gate.memory = value
+ return gate.value
+ end,
+ reset = function(gate)
+ gate.value = 0
+ gate.memory = 0
+ end,
+ postExecution = function(gate)
+ local changed = gate.value != gate.memory
+ gate.value = gate.memory
+ return changed
+ end,
+}
+
+i = i + 1
+FPGAGateActions["execution-previous-vector"] = {
+ order = i,
+ name = "Previous Vector",
+ inputs = {"A"},
+ inputtypes = {"VECTOR"},
+ outputs = {"Out"},
+ outputtypes = {"VECTOR"},
+ neverActive = true,
+ output = function(gate, value)
+ gate.memory = value
+ return gate.value
+ end,
+ reset = function(gate)
+ gate.value = Vector(0, 0, 0)
+ gate.memory = Vector(0, 0, 0)
+ end,
+ postExecution = function(gate)
+ local changed = gate.value != gate.memory
+ gate.value = gate.memory
+ return changed
+ end,
+}
+
+i = i + 1
+FPGAGateActions["execution-previous-angle"] = {
+ order = i,
+ name = "Previous Angle",
+ inputs = {"A"},
+ inputtypes = {"ANGLE"},
+ outputs = {"Out"},
+ outputtypes = {"ANGLE"},
+ neverActive = true,
+ output = function(gate, value)
+ gate.memory = value
+ return gate.value
+ end,
+ reset = function(gate)
+ gate.value = Angle(0, 0, 0)
+ gate.memory = Angle(0, 0, 0)
+ end,
+ postExecution = function(gate)
+ local changed = gate.value != gate.memory
+ gate.value = gate.memory
+ return changed
+ end,
+}
+
+i = i + 1
+FPGAGateActions["execution-previous-string"] = {
+ order = i,
+ name = "Previous String",
+ inputs = {"A"},
+ inputtypes = {"STRING"},
+ outputs = {"Out"},
+ outputtypes = {"STRING"},
+ neverActive = true,
+ output = function(gate, value)
+ gate.memory = value
+ return gate.value
+ end,
+ reset = function(gate)
+ gate.value = ""
+ gate.memory = ""
+ end,
+ postExecution = function(gate)
+ local changed = gate.value != gate.memory
+ gate.value = gate.memory
+ return changed
+ end,
+}
\ No newline at end of file
diff --git a/lua/wire/fpga_gates/io.lua b/lua/wire/fpga_gates/io.lua
new file mode 100644
index 0000000000..fd9c17f22f
--- /dev/null
+++ b/lua/wire/fpga_gates/io.lua
@@ -0,0 +1,183 @@
+FPGAGateActions("Input & Output")
+local i = 1
+
+FPGAGateActions["normal-input"] = {
+ order = i,
+ name = "Normal Input",
+ inputs = {},
+ outputs = {"Out"},
+ outputtypes = {"NORMAL"},
+ isInput = true
+}
+
+i = i + 1
+FPGAGateActions["normal-output"] = {
+ order = i,
+ name = "Normal Output",
+ inputs = {"A"},
+ inputtypes = {"NORMAL"},
+ outputs = {},
+ isOutput = true
+}
+
+-- FPGAGateActions["vector2-input"] = {
+-- name = "2D Vector Input",
+-- inputs = {},
+-- outputs = {"Out"},
+-- outputtypes = {"VECTOR2"},
+-- isInput = true
+-- }
+
+-- FPGAGateActions["vector2-output"] = {
+-- name = "2D Vector Output",
+-- inputs = {"A"},
+-- inputtypes = {"VECTOR2"},
+-- outputs = {},
+-- isOutput = true
+-- }
+
+i = i + 1
+FPGAGateActions["vector-input"] = {
+ order = i,
+ name = "Vector Input",
+ inputs = {},
+ outputs = {"Out"},
+ outputtypes = {"VECTOR"},
+ isInput = true
+}
+
+i = i + 1
+FPGAGateActions["vector-output"] = {
+ order = i,
+ name = "Vector Output",
+ inputs = {"A"},
+ inputtypes = {"VECTOR"},
+ outputs = {},
+ isOutput = true
+}
+
+-- FPGAGateActions["vector4-input"] = {
+-- name = "4D Vector Input",
+-- inputs = {},
+-- outputs = {"Out"},
+-- outputtypes = {"VECTOR4"},
+-- isInput = true
+-- }
+
+-- FPGAGateActions["vector4-output"] = {
+-- name = "4D Vector Output",
+-- inputs = {"A"},
+-- inputtypes = {"VECTOR4"},
+-- outputs = {},
+-- isOutput = true
+-- }
+
+i = i + 1
+FPGAGateActions["angle-input"] = {
+ order = i,
+ name = "Angle Input",
+ inputs = {},
+ outputs = {"Out"},
+ outputtypes = {"ANGLE"},
+ isInput = true
+}
+
+i = i + 1
+FPGAGateActions["angle-output"] = {
+ order = i,
+ name = "Angle Output",
+ inputs = {"A"},
+ inputtypes = {"ANGLE"},
+ outputs = {},
+ isOutput = true
+}
+
+i = i + 1
+FPGAGateActions["string-input"] = {
+ order = i,
+ name = "String Input",
+ inputs = {},
+ outputs = {"Out"},
+ outputtypes = {"STRING"},
+ isInput = true
+}
+
+i = i + 1
+FPGAGateActions["string-output"] = {
+ order = i,
+ name = "String Output",
+ inputs = {"A"},
+ inputtypes = {"STRING"},
+ outputs = {},
+ isOutput = true
+}
+
+i = i + 1
+FPGAGateActions["entity-input"] = {
+ order = i,
+ name = "Entity Input",
+ inputs = {},
+ outputs = {"Out"},
+ outputtypes = {"ENTITY"},
+ isInput = true
+}
+
+i = i + 1
+FPGAGateActions["entity-output"] = {
+ order = i,
+ name = "Entity Output",
+ inputs = {"A"},
+ inputtypes = {"ENTITY"},
+ outputs = {},
+ isOutput = true
+}
+
+i = i + 1
+FPGAGateActions["array-input"] = {
+ order = i,
+ name = "Array Input",
+ inputs = {},
+ outputs = {"Out"},
+ outputtypes = {"ARRAY"},
+ isInput = true
+}
+
+i = i + 1
+FPGAGateActions["array-output"] = {
+ order = i,
+ name = "Array Output",
+ inputs = {"A"},
+ inputtypes = {"ARRAY"},
+ outputs = {},
+ isOutput = true
+}
+
+i = i + 1
+FPGAGateActions["ranger-input"] = {
+ order = i,
+ name = "Ranger Input",
+ inputs = {},
+ outputs = {"Out"},
+ outputtypes = {"RANGER"},
+ isInput = true
+}
+
+i = i + 1
+FPGAGateActions["ranger-output"] = {
+ order = i,
+ name = "Ranger Output",
+ inputs = {"A"},
+ inputtypes = {"RANGER"},
+ outputs = {},
+ isOutput = true
+}
+
+i = i + 1
+FPGAGateActions["wirelink-input"] = {
+ order = i,
+ name = "Wirelink Input",
+ inputs = {},
+ outputs = {"Wirelink"},
+ outputtypes = {"WIRELINK"},
+ isInput = true
+}
\ No newline at end of file
diff --git a/lua/wire/fpgagates.lua b/lua/wire/fpgagates.lua
new file mode 100644
index 0000000000..d3eb08ee92
--- /dev/null
+++ b/lua/wire/fpgagates.lua
@@ -0,0 +1,38 @@
+local gates = {}
+
+local gamt
+gamt = {
+ curcat = "DEFAULT",
+ __newindex = function(t,k,v)
+ if not v.group then
+ v.group = gamt.curcat
+ end
+ rawset(gates,k,v)
+ end,
+ __index = function(t,k)
+ return rawget(gates,k)
+ end,
+ __call = function(t,s) --call the table to set a default category
+ gamt.curcat = s or "DEFAULT"
+ end
+}
+
+function LoadFPGAGates()
+ FPGAGateActions = {}
+ setmetatable(FPGAGateActions,gamt)
+ local entries = file.Find( "wire/fpga_gates/*.lua", "LUA" )
+ for _,v in pairs(entries) do
+ include("fpga_gates/"..v)
+ if (SERVER) then AddCSLuaFile("fpga_gates/"..v) end
+ end
+ FPGAGateActions = gates
+
+ FPGAGatesSorted = {}
+ for name,gate in pairs(FPGAGateActions) do
+ if not FPGAGatesSorted[gate.group] then
+ FPGAGatesSorted[gate.group] = {}
+ end
+ FPGAGatesSorted[gate.group][name] = gate
+ end
+end
+LoadFPGAGates()
\ No newline at end of file
diff --git a/lua/wire/stools/fpga.lua b/lua/wire/stools/fpga.lua
new file mode 100644
index 0000000000..e70602ce1c
--- /dev/null
+++ b/lua/wire/stools/fpga.lua
@@ -0,0 +1,368 @@
+WireToolSetup.setCategory("Chips, Gates", "Advanced")
+WireToolSetup.open("fpga", "FPGA", "gmod_wire_fpga", nil, "FPGAs")
+
+if CLIENT then
+ language.Add("Tool.wire_fpga.name", "FPGA Tool (Wire)")
+ language.Add("Tool.wire_fpga.desc", "Spawns a field programmable gate array for use with the wire system.")
+ language.Add("ToolWirecpu_Model", "Model:" )
+ TOOL.Information = {
+ { name = "left", text = "Upload program to FPGA" },
+ { name = "right", text = "Open editor" },
+ { name = "reload", text = "Reset" }
+ }
+end
+WireToolSetup.BaseLang()
+WireToolSetup.SetupMax(40)
+
+TOOL.ClientConVar = {
+ model = "models/bull/gates/processor.mdl",
+ filename = "",
+}
+
+if CLIENT then
+ ------------------------------------------------------------------------------
+ -- Make sure firing animation is displayed clientside
+ ------------------------------------------------------------------------------
+ function TOOL:LeftClick() return true end
+ function TOOL:Reload() return true end
+ function TOOL:RightClick() return false end
+end
+
+if SERVER then
+ util.AddNetworkString("FPGA_Upload")
+ util.AddNetworkString("FPGA_Download")
+ util.AddNetworkString("FPGA_OpenEditor")
+
+ -- Reset
+ function TOOL:Reload(trace)
+ if trace.Entity:IsPlayer() then return false end
+ if CLIENT then return true end
+
+ if IsValid(trace.Entity) and trace.Entity:GetClass() == "gmod_wire_fpga" then
+ trace.Entity:Reset()
+ return true
+ else
+ return false
+ end
+ end
+
+ -- Spawn or upload
+ function TOOL:CheckHitOwnClass(trace)
+ return trace.Entity:IsValid() and (trace.Entity:GetClass() == "gmod_wire_fpga")
+ end
+ function TOOL:LeftClick_Update(trace)
+ self:Upload(trace.Entity)
+ end
+ function TOOL:MakeEnt(ply, model, Ang, trace)
+ local ent = WireLib.MakeWireEnt(ply, {Class = self.WireClass, Pos=trace.HitPos, Angle=Ang, Model=model})
+ return ent
+ end
+ function TOOL:PostMake(ent)
+ self:Upload(ent)
+ end
+
+ -- Open editor
+ function TOOL:RightClick(trace)
+ if trace.Entity:IsPlayer() then return false end
+
+ if IsValid(trace.Entity) and trace.Entity:GetClass() == "gmod_wire_fpga" then
+ self:Download(trace.Entity)
+ return true
+ end
+
+ net.Start("FPGA_OpenEditor") net.Send(self:GetOwner())
+ return false
+ end
+
+
+
+ ------------------------------------------------------------------------------
+ -- Uploading (Server -> Client -> Server)
+ ------------------------------------------------------------------------------
+ -- Send request to client for FPGA data
+ function TOOL:Upload(ent)
+ net.Start("FPGA_Upload")
+ net.WriteInt(ent:EntIndex(), 32)
+ net.Send(self:GetOwner())
+ end
+ ------------------------------------------------------------------------------
+ -- Downloading (Server -> Client)
+ ------------------------------------------------------------------------------
+ -- Send FPGA data to client
+ function TOOL:Download(ent)
+ local player = self:GetOwner()
+
+ if not WireLib.CanTool(player, ent, "wire_fpga") then
+ WireLib.AddNotify(player, "You're not allowed to download from this FPGA (ent index: " .. ent:EntIndex() .. ").", NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1)
+ return
+ end
+
+ local data = util.Compress(ent:GetOriginal())
+
+ net.Start("FPGA_Download")
+ net.WriteUInt(#data, 16)
+ net.WriteData(data, #data)
+ net.Send(player)
+ end
+end
+if CLIENT then
+ --------------------------------------------------------------
+ -- Clientside Send
+ --------------------------------------------------------------
+ function WireLib.FPGAUpload(targetEnt, data)
+ if type(targetEnt) == "number" then targetEnt = Entity(targetEnt) end
+ targetEnt = targetEnt or LocalPlayer():GetEyeTrace().Entity
+
+ if (not IsValid(targetEnt) or targetEnt:GetClass() ~= "gmod_wire_fpga") then
+ WireLib.AddNotify("FPGA: Invalid FPGA entity specified!", NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1)
+ return
+ end
+
+ if not data and not FPGA_Editor then
+ return
+ end
+ data = data or FPGA_Editor:GetData()
+
+ local data = util.Compress(data)
+
+ if #data > 65500 then
+ WireLib.AddNotify("FPGA: Code too large (exceeds 64kB)!", NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1)
+ return
+ end
+
+ net.Start("FPGA_Upload")
+ net.WriteEntity(targetEnt)
+ net.WriteUInt(#data, 16)
+ net.WriteData(data, #data)
+ net.SendToServer()
+ end
+
+ -- Received request to upload
+ net.Receive("FPGA_Upload", function(len, ply)
+ local entid = net.ReadInt(32)
+ timer.Create("FPGA_Upload_Delay", 0.03, 30, function()
+ if IsValid(Entity(entid)) then
+ WireLib.FPGAUpload(entid)
+ timer.Remove("FPGA_Upload_Delay")
+ timer.Remove("FPGA_Upload_Delay_Error")
+ end
+ end)
+ timer.Create("FPGA_Upload_Delay_Error", 0.03*31, 1, function() WireLib.AddNotify("FPGA: Invalid FPGA entity specified!", NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1) end)
+ end)
+
+ --------------------------------------------------------------
+ -- Clientside Receive
+ --------------------------------------------------------------
+ -- Received download data
+ net.Receive("FPGA_Download", function(len, ply)
+ if not FPGA_Editor then
+ FPGA_Editor = vgui.Create("FPGAEditorFrame")
+ FPGA_Editor:Setup("FPGA Editor", "fpgachip")
+ end
+
+ local dataLength = net.ReadUInt(16)
+ local data = net.ReadData(dataLength)
+ local ok, data = pcall(util.Decompress, data)
+
+ if ok then
+ FPGA_Editor:Open(nil, data, true)
+ end
+ end)
+end
+
+if SERVER then
+ --------------------------------------------------------------
+ -- Serverside Receive
+ --------------------------------------------------------------
+ -- Receive FPGA data from client
+ net.Receive("FPGA_Upload",function(len, ply)
+ local chip = net.ReadEntity()
+
+ if not IsValid(chip) or chip:GetClass() ~= "gmod_wire_fpga" then
+ WireLib.AddNotify(ply, "FPGA: Invalid FPGA chip specified. Upload aborted.", NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1)
+ return
+ end
+
+ if not WireLib.CanTool(ply, chip, "wire_fpga") then
+ WireLib.AddNotify(ply, "FPGA: You are not allowed to upload to the target FPGA chip. Upload aborted.", NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1)
+ return
+ end
+
+ local dataLength = net.ReadUInt(16)
+ local data = net.ReadData(dataLength)
+
+ local ok, data = pcall(util.Decompress, data)
+ if not ok then return end
+
+ ok, data = pcall(WireLib.von.deserialize, data)
+ if ok then
+ chip:Upload(data)
+ else
+ WireLib.AddNotify(ply, "FPGA: Upload failed! Error message:\n" .. data, NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1)
+ end
+ end)
+
+end
+
+
+
+if CLIENT then
+ ------------------------------------------------------------------------------
+ -- Open FPGA editor
+ ------------------------------------------------------------------------------
+ function FPGA_OpenEditor()
+ if not FPGA_Editor then
+ FPGA_Editor = vgui.Create("FPGAEditorFrame")
+ FPGA_Editor:Setup("FPGA Editor", "fpgachip")
+ end
+ FPGA_Editor:Open()
+ end
+ net.Receive("FPGA_OpenEditor", FPGA_OpenEditor)
+
+ ------------------------------------------------------------------------------
+ -- Build tool control panel
+ ------------------------------------------------------------------------------
+ function TOOL.BuildCPanel(panel)
+ local FileBrowser = vgui.Create("wire_expression2_browser" , panel)
+ panel:AddPanel(FileBrowser)
+ FileBrowser:Setup("fpgachip")
+ FileBrowser:SetSize(235,400)
+ function FileBrowser:OnFileOpen(filepath, newtab)
+ if not FPGA_Editor then
+ FPGA_Editor = vgui.Create("FPGAEditorFrame")
+ FPGA_Editor:Setup("FPGA Editor", "fpgachip")
+ end
+ FPGA_Editor:Open(filepath, nil, newtab)
+ end
+
+
+ ----------------------------------------------------------------------------
+ local New = vgui.Create("DButton" , panel)
+ panel:AddPanel(New)
+ New:SetText("New file")
+ New.DoClick = function(button)
+ FPGA_OpenEditor()
+ FPGA_Editor:AutoSave()
+ FPGA_Editor:NewChip(false)
+ end
+ panel:AddControl("Label", {Text = ""})
+
+ ----------------------------------------------------------------------------
+ local OpenEditor = vgui.Create("DButton", panel)
+ panel:AddPanel(OpenEditor)
+ OpenEditor:SetText("Open Editor")
+ OpenEditor.DoClick = FPGA_OpenEditor
+
+
+ ----------------------------------------------------------------------------
+ panel:AddControl("Label", {Text = ""})
+ panel:AddControl("Label", {Text = "FPGA settings:"})
+
+
+ ----------------------------------------------------------------------------
+ WireDermaExts.ModelSelect(panel, "wire_fpga_model", list.Get("Wire_gate_Models"), 5)
+ panel:AddControl("Label", {Text = ""})
+ end
+
+ ------------------------------------------------------------------------------
+ -- Tool screen
+ ------------------------------------------------------------------------------
+ tool_program_name = ""
+ tool_program_start = 0
+ tool_program_size = 0
+ tool_program_bytes = ""
+ function FPGASetToolInfo(name, size, last_bytes)
+ if #name > 18 then
+ tool_program_name = name:sub(1,15) .. "..."
+ else
+ tool_program_name = name
+ end
+ tool_program_start = math.max(size - 64, 0)
+ tool_program_start = tool_program_start - tool_program_start % 8 + 8
+ tool_program_size = size
+ tool_program_bytes = last_bytes
+ end
+
+ local fontTable = {
+ font = "Tahoma",
+ size = 20,
+ weight = 1000,
+ antialias = true,
+ additive = false,
+ }
+ surface.CreateFont("FPGAToolScreenAppFont", fontTable)
+ fontTable.size = 20
+ fontTable.font = "Courier New"
+ surface.CreateFont("FPGAToolScreenHexFont", fontTable)
+ fontTable.size = 14
+ surface.CreateFont("FPGAToolScreenSmallHexFont", fontTable)
+
+ local function drawButton(x, y)
+ surface.SetDrawColor(100, 100, 100, 255)
+ surface.DrawRect(x, y, 20, 20)
+ surface.SetDrawColor(200, 200, 200, 255)
+ surface.DrawRect(x, y, 18, 18)
+ surface.SetDrawColor(185, 180, 175, 255)
+ surface.DrawRect(x+2, y, 16, 18)
+ end
+
+ function TOOL:DrawToolScreen(width, height)
+ --Background
+ surface.SetDrawColor(185, 180, 175, 255)
+ surface.DrawRect(0, 0, 256, 256)
+
+ --Top bar
+ surface.SetDrawColor(156, 180, 225, 255)
+ surface.DrawRect(5, 5, 256-10, 30)
+ surface.SetTexture(surface.GetTextureID("gui/gradient"))
+ surface.SetDrawColor(31, 45, 130, 255)
+ surface.DrawTexturedRect(5, 5, 256-10, 30)
+
+ --App name
+ draw.SimpleText("FPGA Editor", "FPGAToolScreenAppFont", 13, 10, Color(255,255,255,255), 0, 0)
+
+ --Buttons
+ drawButton(184, 10)
+ draw.SimpleText("_", "FPGAToolScreenAppFont", 188, 6, Color(10,10,10,255), 0, 0)
+ drawButton(204, 10)
+ draw.SimpleText("☐", "FPGAToolScreenAppFont", 205, 8, Color(10,10,10,255), 0, 0)
+ drawButton(226, 10)
+ draw.SimpleText("x", "FPGAToolScreenAppFont", 231, 7, Color(10,10,10,255), 0, 0)
+
+ --Program name
+ draw.SimpleText(tool_program_name, "FPGAToolScreenHexFont", 10, 38, Color(10,10,10,255), 0, 0)
+ --Program size
+ if tool_program_size < 1024 then
+ draw.SimpleText(tool_program_size.."B", "FPGAToolScreenHexFont", 246, 38, Color(50,50,50,255), 2, 0)
+ else
+ draw.SimpleText(math.floor(tool_program_size/1024).."kB", "FPGAToolScreenHexFont", 246, 38, Color(50,50,50,255), 2, 0)
+ end
+
+
+ --Hex panel
+ surface.SetDrawColor(200, 200, 200, 255)
+ surface.DrawRect(5, 60, 256-10, 256-65)
+
+ --Hex address
+ draw.SimpleText("Offset", "FPGAToolScreenSmallHexFont", 15, 65, Color(0,0,191,255), 0, 0)
+ draw.SimpleText("00 01 02 03 04 05 06 07", "FPGAToolScreenSmallHexFont", 75, 65, Color(0,0,191,255), 0, 0)
+ local y = 0
+ for i=tool_program_start, tool_program_size, 8 do
+ draw.SimpleText(string.format(" %04X", i), "FPGAToolScreenSmallHexFont", 15, 82 + y * 20, Color(0,0,191,255), 0, 0)
+ y = y + 1
+ end
+
+ --Hex data
+ for line = 0, 7 do
+ local text = ""
+ for i=1, 8 do
+ local c = string.byte(tool_program_bytes, line * 8 + i)
+ if c then
+ text = text .. string.format("%02X", c) .. " "
+ end
+ end
+ draw.SimpleText(text, "FPGAToolScreenSmallHexFont", 75, 82 + line * 20, Color(0,0,0,255), 0, 0)
+ end
+
+ end
+end
\ No newline at end of file