From d81fdc86f39ae445d44c3f52a712d24c68c260fb Mon Sep 17 00:00:00 2001 From: Denneisk <20892685+Denneisk@users.noreply.github.com> Date: Mon, 1 Sep 2025 09:48:32 -0400 Subject: [PATCH 01/10] Create E2Table class --- .../gmod_wire_expression2/core/e2lib.lua | 179 +++++++++++++++++- 1 file changed, 174 insertions(+), 5 deletions(-) diff --git a/lua/entities/gmod_wire_expression2/core/e2lib.lua b/lua/entities/gmod_wire_expression2/core/e2lib.lua index 11ae13fc2b..20a0b7efb9 100644 --- a/lua/entities/gmod_wire_expression2/core/e2lib.lua +++ b/lua/entities/gmod_wire_expression2/core/e2lib.lua @@ -221,11 +221,6 @@ function E2Lib.setSubMaterial(ent, index, material) duplicator.StoreEntityModifier(ent, "submaterial", { ["SubMaterialOverride_"..index] = material }) end --- Returns a default e2 table instance. -function E2Lib.newE2Table() - return { n = {}, ntypes = {}, s = {}, stypes = {}, size = 0 } -end - ---@class E2Lambda ---@field fn fun(args: any[]): any ---@field arg_sig string @@ -303,6 +298,180 @@ function Function:Unwrap(arg_sig, ctx) end end +--- A wrapper for Lua tables for use in E2. +--- When called as a function, will create a new `E2Table` instance. If `data` is specified, then automatically initializes the E2Table with that data.
+--- Otherwise, returns an empty E2Table.
+--- The `typeids` argument is optional. It will be used for typeids instead of type inferral. Useful for table-based types.
+---@return E2Table +---@class E2Table +---@field n table A table containing only numeric keys +---@field ntypes table A table with typeids corresponding to numeric keys +---@field s table A table containing only string keys +---@field stypes table A table with typeids corresponding to string keys +---@field size number The size of the table. This is equivalent to `table.Count(self.n) + table.Count(self.s)` +---@overload fun(data:{ [string|number]:any }?, typeids:{ [string|number]:any }?):E2Table +local E2Table = {} +E2Table.__index = E2Table + +local _e2table_infer_type_lut = { + TYPE_NUMBER = "n", + TYPE_STRING = "s", + TYPE_ENTITY = "e", + TYPE_VECTOR = "v", + TYPE_ANGLE = "a", + TYPE_PHYSOBJ = "b" +} +---Does a simple attempt at inferring the type of an object. +local function _e2table_infer_type(v) + local tp = TypeID(v) + local easy_type = _e2table_infer_type_lut[tp] + if easy_type then return easy_type end + if istable(v) then + -- lame workaround that'll get fixed in 5 years or something + local mt = getmetatable(v) + if mt then + if mt == E2Table then + return "t" + elseif mt == Function then + return "f" + end + end + end +end + +---Sets the key to the value and assigns it the typeid. Special types will require `typeid` to be set. +---@param key string|number The key. Must be a string or number. +---@param value any The value to be stored. +---@param typeid string? The typeid of the value. Will be inferred if nil. +function E2Table:Set(key, value, typeid) + if not typeid then + typeid = _e2table_infer_type(value) or error(string.format("Unknown type for value [%s] at key [%s]", tostring(value), tostring(key))) + end + + if key then + local dest, dest_type + if isnumber(key) then + dest, dest_type = self.n, self.ntypes + else + dest, dest_type = self.s, self.stypes + end + if not dest_type[key] then self.size = self.size + 1 end + dest[key] = value + dest_type[key] = typeid + end +end + +---Returns the typeid and value of the key, if it exists. +---@param key string|number The key. Must be a string or number. +---@return string? typeid The typeid of the key or nil if it doesn't exist. +---@return any? value The value at the key if it exists. +function E2Table:Get(key) + local dest, dest_type + if isnumber(key) then + dest, dest_type = self.n, self.ntypes + else + dest, dest_type = self.s, self.stypes + end + return dest_type[key], dest[key] +end + +---Returns the typeid, value, source typeid table, source table +local function _e2table_get_ext(self, key) + local dest, dest_type + if isnumber(key) then + dest, dest_type = self.n, self.ntypes + else + dest, dest_type = self.s, self.stypes + end + return dest_type[key], dest[key], dest_type, dest +end + +---Returns the typeid and value of the key and removes it from the table. +---This will not shift elements down the sequential part of the array. +---@param key string|number The key. Must be a string or number. +---@return string? typeid The typeid of the key or nil if it doesn't exist. +---@return any? value The value at the key if it exists. +function E2Table:Unset(key) + local typeid, value, dest_type, dest = _e2table_get_ext(self, key) + + if typeid then + dest[key] = nil + dest_type[key] = nil + self.size = self.size - 1 + end + return typeid, value +end + +---Returns the typeid and value of the key and removes it from the table. +---This *will* shift elements down the sequential part of the array. +---@param key string|number The key. Must be a string or number. +---@return string? typeid The typeid of the key or nil if it doesn't exist. +---@return any? value The value at the key if it exists. +function E2Table:Remove(key) + local typeid, value, dest_type, dest = _e2table_get_ext(self, key) + + if typeid then + if isnumber(key) and table.remove(dest_type, key --[[@as number]]) then + -- table.remove will return nil if it fails, meaning we need to unset + ---@cast key number + table.remove(dest, key) + else + dest[key] = nil + dest_type[key] = nil + end + self.size = self.size - 1 + end + return typeid, value +end + +--- Returns an `E2Table` instance. If `data` is specified, then automatically initializes the E2Table with that data. +--- Otherwise, returns an empty E2Table. +---@param data { [number|string]:any }? The data to store in the E2Table. +---@param typeids { [number|string]:any }? Optional typeids. Does not need to include every typeid. Useful for table-based types. +---@return E2Table +local function newE2Table(data, typeids) + ---@cast E2Table -function + if data then + local n, ntypes, s, stypes, size = {}, {}, {}, {}, 0 + for key, value in pairs(data) do + local dest, dest_types + if isnumber(key) then + dest = n + dest_types = ntypes + elseif isstring(key) then + dest = s + dest_types = stypes + else + error(string.format("Tried to create E2 table with invalid key type: %s, type: %s", tostring(key), type(key))) + end + local tp = typeids and typeids[key] or _e2table_infer_type(value) + if not tp then error(string.format("Unknown type for value [%s] at key [%s]", tostring(value), tostring(key))) end + dest[key] = value + dest_types[key] = tp + size = size + 1 + end + return setmetatable({ n = n, ntypes = ntypes, s = s, stypes = stypes, size = size }, E2Table) + else + return setmetatable({ n = {}, ntypes = {}, s = {}, stypes = {}, size = 0 }, E2Table) + end +end +E2Table.New = newE2Table + +function E2Table:__call(data, typeids) + return newE2Table(data, typeids) +end + +E2Lib.E2Table = E2Table + +--- Deprecated. Creates an empty `E2Table`. Use `E2Lib.E2Table` or `E2Lib.E2Table.New` instead. +---@see E2Table +---@deprecated +local function newE2Table_compat() + return newE2Table() +end + +E2Lib.newE2Table = newE2Table_compat -- Deprecated, backwards compat + -- Returns a cloned table of the variable given if it is a table. -- TODO: Ditch this system for instead having users provide a function that returns the default value. -- Would be much more efficient and avoid type checks. From 52b76a48d6a896d98c1fd991f17d5e4d470d9034 Mon Sep 17 00:00:00 2001 From: Denneisk <20892685+Denneisk@users.noreply.github.com> Date: Mon, 1 Sep 2025 10:21:10 -0400 Subject: [PATCH 02/10] Update prop to new API --- .../core/custom/prop.lua | 56 ++++++++----------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/lua/entities/gmod_wire_expression2/core/custom/prop.lua b/lua/entities/gmod_wire_expression2/core/custom/prop.lua index 86ee6ca7e3..3a2a3fc017 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/prop.lua @@ -21,6 +21,8 @@ local setAng = WireLib.setAng local typeIDToString = WireLib.typeIDToString local castE2ValueToLuaValue = E2Lib.castE2ValueToLuaValue +local newE2Table = E2Lib.E2Table.New + local E2totalspawnedprops = 0 local playerMeta = FindMetaTable("Player") @@ -506,7 +508,7 @@ end __e2setcost(30) [nodiscard] e2function table sentGetData(string class) - local res = E2Lib.newE2Table() + local res = newE2Table() local sent = list.Get("wire_spawnable_ents_registry")[class] if not sent then self:throw("No class '"..class.."' found in sent registry", res) end @@ -515,16 +517,13 @@ e2function table sentGetData(string class) for key, tbl in pairs( sent ) do if key=="_preFactory" or key=="_postFactory" then continue end - res.s[key] = E2Lib.newE2Table() - res.s[key].size = 2 - res.s[key].s["type"] = typeIDToString(tbl[1]) - res.s[key].s["default_value"] = sentDataFormatDefaultVal(tbl[2]) - res.s[key].s["description"] = tbl[3] or "" - res.stypes[key] = "t" - - size = size + 1 + local subt = newE2Table({ + type = typeIDToString(tbl[1]), + default_value = sentDataFormatDefaultVal(tbl[2]), + description = tbl[3] or "" + }) + res:Set(key, subt) end - res.size = size return res end @@ -537,11 +536,11 @@ e2function table sentGetData(string class, string key) if not sent[key] then self:throw("Class '"..class.."' does not have any value at key '"..key.."'", "") end if key=="_preFactory" or key=="_postFactory" then self:throw("Prohibited key '"..key.."'", "") end - local res = E2Lib.newE2Table() - res.s["type"] = typeIDToString(sent[key][1]) - res.s["default_value"] = sentDataFormatDefaultVal(sent[key][2]) - res.s["description"] = sent[key][3] or "" - res.size = 3 + local res = newE2Table({ + type = typeIDToString(tbl[1]), + default_value = sentDataFormatDefaultVal(tbl[2]), + description = tbl[3] or "" + }) return res end @@ -551,19 +550,16 @@ end __e2setcost(25) [nodiscard] e2function table sentGetDataTypes(string class) - local res = E2Lib.newE2Table() + local res = newE2Table() local sent = list.Get("wire_spawnable_ents_registry")[class] if not sent then self:throw("No class '"..class.."' found in sent registry", res) end - local size = 0 for key, tbl in pairs( sent ) do if key=="_preFactory" or key=="_postFactory" then continue end - res.s[key] = typeIDToString(tbl[1]) - size = size + 1 + res:Set(key, typeIDToString(tbl[1])) end - res.size = size return res end @@ -584,19 +580,16 @@ end __e2setcost(25) [nodiscard] e2function table sentGetDataDefaultValues(string class) - local res = E2Lib.newE2Table() + local res = newE2Table() local sent = list.Get("wire_spawnable_ents_registry")[class] if not sent then self:throw("No class '"..class.."' found in sent registry", res) end - local size = 0 for key, tbl in pairs( sent ) do if key=="_preFactory" or key=="_postFactory" then continue end - res.s[key] = sentDataFormatDefaultVal(tbl[2]) - size = size + 1 + res:Set(key, sentDataFormatDefaultVal(tbl[2])) end - res.size = size return res end @@ -617,19 +610,16 @@ end __e2setcost(25) [nodiscard] e2function table sentGetDataDescriptions(string class) - local res = E2Lib.newE2Table() + local res = newE2Table() local sent = list.Get("wire_spawnable_ents_registry")[class] if not sent then self:throw("No class '"..class.."' found in sent registry", res) end - local size = 0 for key, tbl in pairs( sent ) do if key=="_preFactory" or key=="_postFactory" then continue end - res.s[key] = tbl[3] or "" - size = size + 1 + res:Set(key, tbl[3] or "") end - res.size = size return res end @@ -1255,13 +1245,13 @@ end e2function table entity:ragdollGetPose() if not ValidAction(self, this) then return end - local pose = E2Lib.newE2Table() + local pose = newE2Table() local bones = GetBones(this) local originPos, originAng = bones[0]:GetPos(), bones[0]:GetAngles() local size = 0 for k, bone in pairs(bones) do - local value = E2Lib.newE2Table() + local value = newE2Table() local pos, ang = WorldToLocal(bone:GetPos(), bone:GetAngles(), originPos, originAng) value.n[1] = pos @@ -1495,8 +1485,6 @@ local typefilter = { number = "n", } -local newE2Table = E2Lib.newE2Table - __e2setcost(20) e2function table collision:toTable() From 2f9685f8db720785807ae03fa10c63a916859495 Mon Sep 17 00:00:00 2001 From: Denneisk <20892685+Denneisk@users.noreply.github.com> Date: Mon, 1 Sep 2025 10:21:15 -0400 Subject: [PATCH 03/10] Update table to new API --- .../gmod_wire_expression2/core/table.lua | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/lua/entities/gmod_wire_expression2/core/table.lua b/lua/entities/gmod_wire_expression2/core/table.lua index 76dd69514c..19f244e4bf 100644 --- a/lua/entities/gmod_wire_expression2/core/table.lua +++ b/lua/entities/gmod_wire_expression2/core/table.lua @@ -8,6 +8,8 @@ local tostring = tostring local table = table local type = type +local newE2Table = E2Lib.E2Table.New + local opcost = 1/3 -- cost of looping through table multiplier -- All different table types in E2 @@ -33,8 +35,6 @@ end -- Type defining -------------------------------------------------------------------------------- -local newE2Table = E2Lib.newE2Table - registerType("table", "t", newE2Table(), function(self, input) if input.size == 0 then @@ -524,28 +524,20 @@ end -- Removes the specified entry from the array-part and returns 1 if removed e2function number table:remove( number index ) if (#this.n == 0) then return 0 end - if (not this.n[index]) then return 0 end - if index < 1 then -- table.remove doesn't work if the index is below 1 - this.n[index] = nil - this.ntypes[index] = nil - else - table.remove( this.n, index ) - table.remove( this.ntypes, index ) + if this:Remove(index) then + self.GlobalScope.vclk[this] = true + return 1 end - this.size = this.size - 1 - self.GlobalScope.vclk[this] = true - return 1 + return 0 end --- Force removes the specified entry from the table-part, without moving subsequent entries down and returns 1 if removed e2function number table:remove( string index ) if (IsEmpty(this.s)) then return 0 end - if (not this.s[index]) then return 0 end - this.s[index] = nil - this.stypes[index] = nil - this.size = this.size - 1 - self.GlobalScope.vclk[this] = true - return 1 + if this:Unset(index) then + self.GlobalScope.vclk[this] = true + return 1 + end + return 0 end -------------------------------------------------------------------------------- @@ -554,12 +546,11 @@ end -- Does not shift larger indexes down to fill the hole -------------------------------------------------------------------------------- e2function number table:unset( index ) - if this.n[index] == nil then return 0 end - this.n[index] = nil - this.ntypes[index] = nil - this.size = this.size - 1 - self.GlobalScope.vclk[this] = true - return 1 + if this:Unset(index) then + self.GlobalScope.vclk[this] = exists + return 1 + end + return 0 end -- Force remove for strings is an alias to table:remove(string) From 35ab7e3977de22225bfde11d9bcea3786325ee9b Mon Sep 17 00:00:00 2001 From: Denneisk <20892685+Denneisk@users.noreply.github.com> Date: Mon, 1 Sep 2025 10:33:10 -0400 Subject: [PATCH 04/10] dumb --- lua/entities/gmod_wire_expression2/core/e2lib.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lua/entities/gmod_wire_expression2/core/e2lib.lua b/lua/entities/gmod_wire_expression2/core/e2lib.lua index 20a0b7efb9..e42638bdd6 100644 --- a/lua/entities/gmod_wire_expression2/core/e2lib.lua +++ b/lua/entities/gmod_wire_expression2/core/e2lib.lua @@ -314,12 +314,12 @@ local E2Table = {} E2Table.__index = E2Table local _e2table_infer_type_lut = { - TYPE_NUMBER = "n", - TYPE_STRING = "s", - TYPE_ENTITY = "e", - TYPE_VECTOR = "v", - TYPE_ANGLE = "a", - TYPE_PHYSOBJ = "b" + [TYPE_NUMBER] = "n", + [TYPE_STRING] = "s", + [TYPE_ENTITY] = "e", + [TYPE_VECTOR] = "v", + [TYPE_ANGLE] = "a", + [TYPE_PHYSOBJ] = "b" } ---Does a simple attempt at inferring the type of an object. local function _e2table_infer_type(v) From dea24f8652172bb0af031d3d376ed1c23cfce080 Mon Sep 17 00:00:00 2001 From: Denneisk <20892685+Denneisk@users.noreply.github.com> Date: Mon, 1 Sep 2025 10:38:38 -0400 Subject: [PATCH 05/10] Remove useless comment --- lua/entities/gmod_wire_expression2/core/e2lib.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/entities/gmod_wire_expression2/core/e2lib.lua b/lua/entities/gmod_wire_expression2/core/e2lib.lua index e42638bdd6..7caf54b3e1 100644 --- a/lua/entities/gmod_wire_expression2/core/e2lib.lua +++ b/lua/entities/gmod_wire_expression2/core/e2lib.lua @@ -302,7 +302,6 @@ end --- When called as a function, will create a new `E2Table` instance. If `data` is specified, then automatically initializes the E2Table with that data.
--- Otherwise, returns an empty E2Table.
--- The `typeids` argument is optional. It will be used for typeids instead of type inferral. Useful for table-based types.
----@return E2Table ---@class E2Table ---@field n table A table containing only numeric keys ---@field ntypes table A table with typeids corresponding to numeric keys From f42e969620a012f989c5f818d018cc942ff1d024 Mon Sep 17 00:00:00 2001 From: Denneisk <20892685+Denneisk@users.noreply.github.com> Date: Mon, 1 Sep 2025 22:20:41 -0400 Subject: [PATCH 06/10] Move E2Table to WireLib --- .../gmod_wire_expression2/core/e2lib.lua | 166 +---------------- lua/wire/server/wirelib.lua | 168 +++++++++++++++++- 2 files changed, 169 insertions(+), 165 deletions(-) diff --git a/lua/entities/gmod_wire_expression2/core/e2lib.lua b/lua/entities/gmod_wire_expression2/core/e2lib.lua index 7caf54b3e1..b1fef2096b 100644 --- a/lua/entities/gmod_wire_expression2/core/e2lib.lua +++ b/lua/entities/gmod_wire_expression2/core/e2lib.lua @@ -298,171 +298,9 @@ function Function:Unwrap(arg_sig, ctx) end end ---- A wrapper for Lua tables for use in E2. ---- When called as a function, will create a new `E2Table` instance. If `data` is specified, then automatically initializes the E2Table with that data.
---- Otherwise, returns an empty E2Table.
---- The `typeids` argument is optional. It will be used for typeids instead of type inferral. Useful for table-based types.
----@class E2Table ----@field n table A table containing only numeric keys ----@field ntypes table A table with typeids corresponding to numeric keys ----@field s table A table containing only string keys ----@field stypes table A table with typeids corresponding to string keys ----@field size number The size of the table. This is equivalent to `table.Count(self.n) + table.Count(self.s)` ----@overload fun(data:{ [string|number]:any }?, typeids:{ [string|number]:any }?):E2Table -local E2Table = {} -E2Table.__index = E2Table - -local _e2table_infer_type_lut = { - [TYPE_NUMBER] = "n", - [TYPE_STRING] = "s", - [TYPE_ENTITY] = "e", - [TYPE_VECTOR] = "v", - [TYPE_ANGLE] = "a", - [TYPE_PHYSOBJ] = "b" -} ----Does a simple attempt at inferring the type of an object. -local function _e2table_infer_type(v) - local tp = TypeID(v) - local easy_type = _e2table_infer_type_lut[tp] - if easy_type then return easy_type end - if istable(v) then - -- lame workaround that'll get fixed in 5 years or something - local mt = getmetatable(v) - if mt then - if mt == E2Table then - return "t" - elseif mt == Function then - return "f" - end - end - end -end - ----Sets the key to the value and assigns it the typeid. Special types will require `typeid` to be set. ----@param key string|number The key. Must be a string or number. ----@param value any The value to be stored. ----@param typeid string? The typeid of the value. Will be inferred if nil. -function E2Table:Set(key, value, typeid) - if not typeid then - typeid = _e2table_infer_type(value) or error(string.format("Unknown type for value [%s] at key [%s]", tostring(value), tostring(key))) - end - - if key then - local dest, dest_type - if isnumber(key) then - dest, dest_type = self.n, self.ntypes - else - dest, dest_type = self.s, self.stypes - end - if not dest_type[key] then self.size = self.size + 1 end - dest[key] = value - dest_type[key] = typeid - end -end - ----Returns the typeid and value of the key, if it exists. ----@param key string|number The key. Must be a string or number. ----@return string? typeid The typeid of the key or nil if it doesn't exist. ----@return any? value The value at the key if it exists. -function E2Table:Get(key) - local dest, dest_type - if isnumber(key) then - dest, dest_type = self.n, self.ntypes - else - dest, dest_type = self.s, self.stypes - end - return dest_type[key], dest[key] -end - ----Returns the typeid, value, source typeid table, source table -local function _e2table_get_ext(self, key) - local dest, dest_type - if isnumber(key) then - dest, dest_type = self.n, self.ntypes - else - dest, dest_type = self.s, self.stypes - end - return dest_type[key], dest[key], dest_type, dest -end - ----Returns the typeid and value of the key and removes it from the table. ----This will not shift elements down the sequential part of the array. ----@param key string|number The key. Must be a string or number. ----@return string? typeid The typeid of the key or nil if it doesn't exist. ----@return any? value The value at the key if it exists. -function E2Table:Unset(key) - local typeid, value, dest_type, dest = _e2table_get_ext(self, key) - - if typeid then - dest[key] = nil - dest_type[key] = nil - self.size = self.size - 1 - end - return typeid, value -end - ----Returns the typeid and value of the key and removes it from the table. ----This *will* shift elements down the sequential part of the array. ----@param key string|number The key. Must be a string or number. ----@return string? typeid The typeid of the key or nil if it doesn't exist. ----@return any? value The value at the key if it exists. -function E2Table:Remove(key) - local typeid, value, dest_type, dest = _e2table_get_ext(self, key) - - if typeid then - if isnumber(key) and table.remove(dest_type, key --[[@as number]]) then - -- table.remove will return nil if it fails, meaning we need to unset - ---@cast key number - table.remove(dest, key) - else - dest[key] = nil - dest_type[key] = nil - end - self.size = self.size - 1 - end - return typeid, value -end - ---- Returns an `E2Table` instance. If `data` is specified, then automatically initializes the E2Table with that data. ---- Otherwise, returns an empty E2Table. ----@param data { [number|string]:any }? The data to store in the E2Table. ----@param typeids { [number|string]:any }? Optional typeids. Does not need to include every typeid. Useful for table-based types. ----@return E2Table -local function newE2Table(data, typeids) - ---@cast E2Table -function - if data then - local n, ntypes, s, stypes, size = {}, {}, {}, {}, 0 - for key, value in pairs(data) do - local dest, dest_types - if isnumber(key) then - dest = n - dest_types = ntypes - elseif isstring(key) then - dest = s - dest_types = stypes - else - error(string.format("Tried to create E2 table with invalid key type: %s, type: %s", tostring(key), type(key))) - end - local tp = typeids and typeids[key] or _e2table_infer_type(value) - if not tp then error(string.format("Unknown type for value [%s] at key [%s]", tostring(value), tostring(key))) end - dest[key] = value - dest_types[key] = tp - size = size + 1 - end - return setmetatable({ n = n, ntypes = ntypes, s = s, stypes = stypes, size = size }, E2Table) - else - return setmetatable({ n = {}, ntypes = {}, s = {}, stypes = {}, size = 0 }, E2Table) - end -end -E2Table.New = newE2Table - -function E2Table:__call(data, typeids) - return newE2Table(data, typeids) -end - -E2Lib.E2Table = E2Table +local newE2Table = WireLib.E2Table.New ---- Deprecated. Creates an empty `E2Table`. Use `E2Lib.E2Table` or `E2Lib.E2Table.New` instead. +--- Deprecated. Creates an empty `E2Table`. Use `WireLib.E2Table` or `WireLib.E2Table.New` instead. ---@see E2Table ---@deprecated local function newE2Table_compat() diff --git a/lua/wire/server/wirelib.lua b/lua/wire/server/wirelib.lua index 367acb275c..63eb16cc61 100644 --- a/lua/wire/server/wirelib.lua +++ b/lua/wire/server/wirelib.lua @@ -95,6 +95,172 @@ function WireLib.TriggerInput(ent, name, value, ...) end end +local newE2Table +do + --- A wrapper for Lua tables for use in E2. + --- When called as a function, will create a new `E2Table` instance. If `data` is specified, then automatically initializes the E2Table with that data.
+ --- Otherwise, returns an empty E2Table.
+ --- The `typeids` argument is optional. It will be used for typeids instead of type inferral. Useful for table-based types.
+ ---@class E2Table + ---@field n table A table containing only numeric keys + ---@field ntypes table A table with typeids corresponding to numeric keys + ---@field s table A table containing only string keys + ---@field stypes table A table with typeids corresponding to string keys + ---@field size number The size of the table. This is equivalent to `table.Count(self.n) + table.Count(self.s)` + ---@overload fun(data:{ [string|number]:any }?, typeids:{ [string|number]:any }?):E2Table + local E2Table = {} + E2Table.__index = E2Table + + local e2t_tp_lut = { + [TYPE_NUMBER] = "n", + [TYPE_STRING] = "s", + [TYPE_ENTITY] = "e", + [TYPE_VECTOR] = "v", + [TYPE_ANGLE] = "a", + [TYPE_PHYSOBJ] = "b" + } + ---Does a simple attempt at inferring the type of an object. + local function e2t_infer_tp(v) + local tp = TypeID(v) + local easy_tp = e2t_tp_lut[tp] + if easy_tp then return easy_tp end + if istable(v) then + local mt = getmetatable(v) + if mt then + if mt == E2Table then + return "t" + elseif E2Lib and mt == E2Lib.Function then + return "f" + end + end + end + end + + ---Sets the key to the value and assigns it the typeid. Special types will require `typeid` to be set. + ---@param key string|number The key. Must be a string or number. + ---@param value any The value to be stored. + ---@param typeid string? The typeid of the value. Will be inferred if nil. + function E2Table:Set(key, value, typeid) + if not typeid then + typeid = e2t_infer_tp(value) or error(string.format("Unknown type for value [%s] at key [%s]", tostring(value), tostring(key))) + end + + if key then + local dest, dest_type + if isnumber(key) then + dest, dest_type = self.n, self.ntypes + else + dest, dest_type = self.s, self.stypes + end + if not dest_type[key] then self.size = self.size + 1 end + dest[key] = value + dest_type[key] = typeid + end + end + + ---Returns the typeid and value of the key, if it exists. + ---@param key string|number The key. Must be a string or number. + ---@return string? typeid The typeid of the key or nil if it doesn't exist. + ---@return any? value The value at the key if it exists. + function E2Table:Get(key) + local dest, dest_type + if isnumber(key) then + dest, dest_type = self.n, self.ntypes + else + dest, dest_type = self.s, self.stypes + end + return dest_type[key], dest[key] + end + + ---Returns the typeid, value, source typeid table, source table + local function e2t_get_ext(self, key) + local dest, dest_type + if isnumber(key) then + dest, dest_type = self.n, self.ntypes + else + dest, dest_type = self.s, self.stypes + end + return dest_type[key], dest[key], dest_type, dest + end + + ---Returns the typeid and value of the key and removes it from the table. + ---This will not shift elements down the sequential part of the array. + ---@param key string|number The key. Must be a string or number. + ---@return string? typeid The typeid of the key or nil if it doesn't exist. + ---@return any? value The value at the key if it exists. + function E2Table:Unset(key) + local typeid, value, dest_type, dest = e2t_get_ext(self, key) + + if typeid then + dest[key] = nil + dest_type[key] = nil + self.size = self.size - 1 + end + return typeid, value + end + + ---Returns the typeid and value of the key and removes it from the table. + ---This *will* shift elements down the sequential part of the array. + ---@param key string|number The key. Must be a string or number. + ---@return string? typeid The typeid of the key or nil if it doesn't exist. + ---@return any? value The value at the key if it exists. + function E2Table:Remove(key) + local typeid, value, dest_type, dest = e2t_get_ext(self, key) + + if typeid then + if isnumber(key) and table.remove(dest_type, key --[[@as number]]) then + -- table.remove will return nil if it fails, meaning we need to unset + ---@cast key number + table.remove(dest, key) + else + dest[key] = nil + dest_type[key] = nil + end + self.size = self.size - 1 + end + return typeid, value + end + + --- Returns an `E2Table` instance. If `data` is specified, then automatically initializes the E2Table with that data. + --- Otherwise, returns an empty E2Table. + ---@param data { [number|string]:any }? The data to store in the E2Table. + ---@param typeids { [number|string]:any }? Optional typeids. Does not need to include every typeid. Useful for table-based types. + ---@return E2Table + newE2Table = function(data, typeids) + ---@cast E2Table -function + if data then + local n, ntypes, s, stypes, size = {}, {}, {}, {}, 0 + for key, value in pairs(data) do + local dest, dest_types + if isnumber(key) then + dest = n + dest_types = ntypes + elseif isstring(key) then + dest = s + dest_types = stypes + else + error(string.format("Tried to create E2 table with invalid key type: %s, type: %s", tostring(key), type(key))) + end + local tp = typeids and typeids[key] or e2t_infer_tp(value) + if not tp then error(string.format("Unknown type for value [%s] at key [%s]", tostring(value), tostring(key))) end + dest[key] = value + dest_types[key] = tp + size = size + 1 + end + return setmetatable({ n = n, ntypes = ntypes, s = s, stypes = stypes, size = size }, E2Table) + else + return setmetatable({ n = {}, ntypes = {}, s = {}, stypes = {}, size = 0 }, E2Table) + end + end + E2Table.New = newE2Table + + function E2Table:__call(data, typeids) + return newE2Table(data, typeids) + end + + WireLib.E2Table = E2Table +end + --- Array of data types for Wiremod. ---@type table WireLib.DT = { @@ -154,7 +320,7 @@ WireLib.DT = { }, TABLE = { Zero = function() - return { n = {}, ntypes = {}, s = {}, stypes = {}, size = 0 } + return newE2Table() end, Validator = function(t) return istable(t) From 3129aeb352e83980e2b9844bcfe63b8b38fa39b5 Mon Sep 17 00:00:00 2001 From: Denneisk <20892685+Denneisk@users.noreply.github.com> Date: Mon, 1 Sep 2025 22:28:39 -0400 Subject: [PATCH 07/10] Fix compat in damage detector --- lua/entities/gmod_wire_damage_detector.lua | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lua/entities/gmod_wire_damage_detector.lua b/lua/entities/gmod_wire_damage_detector.lua index dbfe11abfd..aa243886c3 100644 --- a/lua/entities/gmod_wire_damage_detector.lua +++ b/lua/entities/gmod_wire_damage_detector.lua @@ -6,7 +6,7 @@ ENT.WireDebugName = "Damage Detector" if CLIENT then return end -- No more client -local DEFAULT = {n={},ntypes={},s={},stypes={},size=0} +local newE2Table = WireLib.E2Table.New -- Global table to keep track of all detectors local Wire_Damage_Detectors = {} @@ -67,7 +67,7 @@ function ENT:Initialize() self.key_ents = {} -- Store output damage info - self.victims = table.Copy(DEFAULT) + self.victims = newE2Table() WireLib.TriggerOutput( self, "Victims", self.victims ) self.damage = 0 @@ -178,7 +178,7 @@ function ENT:TriggerInput( iname, value ) if value then self.count = 0 self.firsthit_dmginfo = {} - self.victims = table.Copy(DEFAULT) + self.victims = newE2Table() self.damage = 0 self:TriggerOutput() end @@ -193,7 +193,7 @@ function ENT:TriggerOutput() -- Entity outputs won't trigger again until they ch WireLib.TriggerOutput( self, "Victim", IsValid(victim) and victim or NULL) self.victims.size = table.Count(self.victims.s) - WireLib.TriggerOutput( self, "Victims", self.victims or table.Copy(DEFAULT) ) + WireLib.TriggerOutput( self, "Victims", self.victims or newE2Table() ) WireLib.TriggerOutput( self, "Position", self.firsthit_dmginfo[3] or Vector(0,0,0) ) WireLib.TriggerOutput( self, "Force", self.firsthit_dmginfo[4] or Vector(0,0,0) ) WireLib.TriggerOutput( self, "Type", self.firsthit_dmginfo[5] or "" ) @@ -256,6 +256,8 @@ local damageTypes = { function ENT:UpdateDamage( dmginfo, ent ) -- Update damage table local damage = dmginfo:GetDamage() + local victims = self.victims + if not self.hit then -- Only register the first target's damage info self.firsthit_dmginfo = { dmginfo:GetAttacker(), @@ -268,8 +270,8 @@ function ENT:UpdateDamage( dmginfo, ent ) -- Update damage table self.dmgtype = damageTypes[dmginfo:GetDamageType()] or "Other" - - self.victims = table.Copy(DEFAULT) + victims = newE2Table() + self.victims = victims self.firsthit_dmginfo[5] = self.dmgtype self.hit = true @@ -286,8 +288,8 @@ function ENT:UpdateDamage( dmginfo, ent ) -- Update damage table -- Update victims table (ent, damage) local entID = tostring(ent:EntIndex()) - self.victims.s[entID] = ( self.victims[entID] or 0 ) + damage - self.victims.stypes[entID] = "n" + local old_dmg = select(2, victims:Get(entID)) or 0 + victims:Set(entID, old_dmg + damage) self.count = self.count + 1 if self.count == math.huge then self.count = 0 end -- This shouldn't ever happen... unless you're really REALLY bored From 4070645039d2334fb198cd7b4d57db230bdc6b47 Mon Sep 17 00:00:00 2001 From: Denneisk <20892685+Denneisk@users.noreply.github.com> Date: Tue, 2 Sep 2025 16:16:17 -0400 Subject: [PATCH 08/10] Fix bad ref and clientside errors --- .../core/custom/prop.lua | 2 +- .../gmod_wire_expression2/core/table.lua | 2 +- lua/wire/wireshared.lua | 165 ++++++++++++++++++ 3 files changed, 167 insertions(+), 2 deletions(-) diff --git a/lua/entities/gmod_wire_expression2/core/custom/prop.lua b/lua/entities/gmod_wire_expression2/core/custom/prop.lua index 3a2a3fc017..c0a8deb6b6 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/prop.lua @@ -21,7 +21,7 @@ local setAng = WireLib.setAng local typeIDToString = WireLib.typeIDToString local castE2ValueToLuaValue = E2Lib.castE2ValueToLuaValue -local newE2Table = E2Lib.E2Table.New +local newE2Table = WireLib.E2Table.New local E2totalspawnedprops = 0 local playerMeta = FindMetaTable("Player") diff --git a/lua/entities/gmod_wire_expression2/core/table.lua b/lua/entities/gmod_wire_expression2/core/table.lua index 19f244e4bf..fa3186f5a7 100644 --- a/lua/entities/gmod_wire_expression2/core/table.lua +++ b/lua/entities/gmod_wire_expression2/core/table.lua @@ -8,7 +8,7 @@ local tostring = tostring local table = table local type = type -local newE2Table = E2Lib.E2Table.New +local newE2Table = WireLib.E2Table.New local opcost = 1/3 -- cost of looping through table multiplier diff --git a/lua/wire/wireshared.lua b/lua/wire/wireshared.lua index 69eb47b78b..b5d795897e 100644 --- a/lua/wire/wireshared.lua +++ b/lua/wire/wireshared.lua @@ -1366,3 +1366,168 @@ local typeIDToStringTable = { function WireLib.typeIDToString(typeID) return typeIDToStringTable[typeID] or "unregistered type" end + +do + --- A wrapper for Lua tables for use in E2. + --- When called as a function, will create a new `E2Table` instance. If `data` is specified, then automatically initializes the E2Table with that data.
+ --- Otherwise, returns an empty E2Table.
+ --- The `typeids` argument is optional. It will be used for typeids instead of type inferral. Useful for table-based types.
+ ---@class E2Table + ---@field n table A table containing only numeric keys + ---@field ntypes table A table with typeids corresponding to numeric keys + ---@field s table A table containing only string keys + ---@field stypes table A table with typeids corresponding to string keys + ---@field size number The size of the table. This is equivalent to `table.Count(self.n) + table.Count(self.s)` + ---@overload fun(data:{ [string|number]:any }?, typeids:{ [string|number]:any }?):E2Table + local E2Table = {} + E2Table.__index = E2Table + + local e2t_tp_lut = { + [TYPE_NUMBER] = "n", + [TYPE_STRING] = "s", + [TYPE_ENTITY] = "e", + [TYPE_VECTOR] = "v", + [TYPE_ANGLE] = "a", + [TYPE_PHYSOBJ] = "b" + } + ---Does a simple attempt at inferring the type of an object. + local function e2t_infer_tp(v) + local tp = TypeID(v) + local easy_tp = e2t_tp_lut[tp] + if easy_tp then return easy_tp end + if istable(v) then + local mt = getmetatable(v) + if mt then + if mt == E2Table then + return "t" + elseif E2Lib and mt == E2Lib.Function then + return "f" + end + end + end + end + + ---Sets the key to the value and assigns it the typeid. Special types will require `typeid` to be set. + ---@param key string|number The key. Must be a string or number. + ---@param value any The value to be stored. + ---@param typeid string? The typeid of the value. Will be inferred if nil. + function E2Table:Set(key, value, typeid) + if not typeid then + typeid = e2t_infer_tp(value) or error(string.format("Unknown type for value [%s] at key [%s]", tostring(value), tostring(key))) + end + + if key then + local dest, dest_type + if isnumber(key) then + dest, dest_type = self.n, self.ntypes + else + dest, dest_type = self.s, self.stypes + end + if not dest_type[key] then self.size = self.size + 1 end + dest[key] = value + dest_type[key] = typeid + end + end + + ---Returns the typeid and value of the key, if it exists. + ---@param key string|number The key. Must be a string or number. + ---@return string? typeid The typeid of the key or nil if it doesn't exist. + ---@return any? value The value at the key if it exists. + function E2Table:Get(key) + local dest, dest_type + if isnumber(key) then + dest, dest_type = self.n, self.ntypes + else + dest, dest_type = self.s, self.stypes + end + return dest_type[key], dest[key] + end + + ---Returns the typeid, value, source typeid table, source table + local function e2t_get_ext(self, key) + local dest, dest_type + if isnumber(key) then + dest, dest_type = self.n, self.ntypes + else + dest, dest_type = self.s, self.stypes + end + return dest_type[key], dest[key], dest_type, dest + end + + ---Returns the typeid and value of the key and removes it from the table. + ---This will not shift elements down the sequential part of the array. + ---@param key string|number The key. Must be a string or number. + ---@return string? typeid The typeid of the key or nil if it doesn't exist. + ---@return any? value The value at the key if it exists. + function E2Table:Unset(key) + local typeid, value, dest_type, dest = e2t_get_ext(self, key) + + if typeid then + dest[key] = nil + dest_type[key] = nil + self.size = self.size - 1 + end + return typeid, value + end + + ---Returns the typeid and value of the key and removes it from the table. + ---This *will* shift elements down the sequential part of the array. + ---@param key string|number The key. Must be a string or number. + ---@return string? typeid The typeid of the key or nil if it doesn't exist. + ---@return any? value The value at the key if it exists. + function E2Table:Remove(key) + local typeid, value, dest_type, dest = e2t_get_ext(self, key) + + if typeid then + if isnumber(key) and table.remove(dest_type, key --[[@as number]]) then + -- table.remove will return nil if it fails, meaning we need to unset + ---@cast key number + table.remove(dest, key) + else + dest[key] = nil + dest_type[key] = nil + end + self.size = self.size - 1 + end + return typeid, value + end + + --- Returns an `E2Table` instance. If `data` is specified, then automatically initializes the E2Table with that data. + --- Otherwise, returns an empty E2Table. + ---@param data { [number|string]:any }? The data to store in the E2Table. + ---@param typeids { [number|string]:any }? Optional typeids. Does not need to include every typeid. Useful for table-based types. + ---@return E2Table + local newE2Table = function(data, typeids) + ---@cast E2Table -function + if data then + local n, ntypes, s, stypes, size = {}, {}, {}, {}, 0 + for key, value in pairs(data) do + local dest, dest_types + if isnumber(key) then + dest = n + dest_types = ntypes + elseif isstring(key) then + dest = s + dest_types = stypes + else + error(string.format("Tried to create E2 table with invalid key type: %s, type: %s", tostring(key), type(key))) + end + local tp = typeids and typeids[key] or e2t_infer_tp(value) + if not tp then error(string.format("Unknown type for value [%s] at key [%s]", tostring(value), tostring(key))) end + dest[key] = value + dest_types[key] = tp + size = size + 1 + end + return setmetatable({ n = n, ntypes = ntypes, s = s, stypes = stypes, size = size }, E2Table) + else + return setmetatable({ n = {}, ntypes = {}, s = {}, stypes = {}, size = 0 }, E2Table) + end + end + E2Table.New = newE2Table + + function E2Table:__call(data, typeids) + return newE2Table(data, typeids) + end + + WireLib.E2Table = E2Table +end \ No newline at end of file From 9cc04d745cc453d1659f1088b226c852bb9d05e2 Mon Sep 17 00:00:00 2001 From: Denneisk <20892685+Denneisk@users.noreply.github.com> Date: Fri, 12 Sep 2025 09:26:42 -0400 Subject: [PATCH 09/10] Remove duplicated code --- lua/wire/server/wirelib.lua | 166 +----------------------------------- 1 file changed, 1 insertion(+), 165 deletions(-) diff --git a/lua/wire/server/wirelib.lua b/lua/wire/server/wirelib.lua index 63eb16cc61..32cf5bd3f3 100644 --- a/lua/wire/server/wirelib.lua +++ b/lua/wire/server/wirelib.lua @@ -95,171 +95,7 @@ function WireLib.TriggerInput(ent, name, value, ...) end end -local newE2Table -do - --- A wrapper for Lua tables for use in E2. - --- When called as a function, will create a new `E2Table` instance. If `data` is specified, then automatically initializes the E2Table with that data.
- --- Otherwise, returns an empty E2Table.
- --- The `typeids` argument is optional. It will be used for typeids instead of type inferral. Useful for table-based types.
- ---@class E2Table - ---@field n table A table containing only numeric keys - ---@field ntypes table A table with typeids corresponding to numeric keys - ---@field s table A table containing only string keys - ---@field stypes table A table with typeids corresponding to string keys - ---@field size number The size of the table. This is equivalent to `table.Count(self.n) + table.Count(self.s)` - ---@overload fun(data:{ [string|number]:any }?, typeids:{ [string|number]:any }?):E2Table - local E2Table = {} - E2Table.__index = E2Table - - local e2t_tp_lut = { - [TYPE_NUMBER] = "n", - [TYPE_STRING] = "s", - [TYPE_ENTITY] = "e", - [TYPE_VECTOR] = "v", - [TYPE_ANGLE] = "a", - [TYPE_PHYSOBJ] = "b" - } - ---Does a simple attempt at inferring the type of an object. - local function e2t_infer_tp(v) - local tp = TypeID(v) - local easy_tp = e2t_tp_lut[tp] - if easy_tp then return easy_tp end - if istable(v) then - local mt = getmetatable(v) - if mt then - if mt == E2Table then - return "t" - elseif E2Lib and mt == E2Lib.Function then - return "f" - end - end - end - end - - ---Sets the key to the value and assigns it the typeid. Special types will require `typeid` to be set. - ---@param key string|number The key. Must be a string or number. - ---@param value any The value to be stored. - ---@param typeid string? The typeid of the value. Will be inferred if nil. - function E2Table:Set(key, value, typeid) - if not typeid then - typeid = e2t_infer_tp(value) or error(string.format("Unknown type for value [%s] at key [%s]", tostring(value), tostring(key))) - end - - if key then - local dest, dest_type - if isnumber(key) then - dest, dest_type = self.n, self.ntypes - else - dest, dest_type = self.s, self.stypes - end - if not dest_type[key] then self.size = self.size + 1 end - dest[key] = value - dest_type[key] = typeid - end - end - - ---Returns the typeid and value of the key, if it exists. - ---@param key string|number The key. Must be a string or number. - ---@return string? typeid The typeid of the key or nil if it doesn't exist. - ---@return any? value The value at the key if it exists. - function E2Table:Get(key) - local dest, dest_type - if isnumber(key) then - dest, dest_type = self.n, self.ntypes - else - dest, dest_type = self.s, self.stypes - end - return dest_type[key], dest[key] - end - - ---Returns the typeid, value, source typeid table, source table - local function e2t_get_ext(self, key) - local dest, dest_type - if isnumber(key) then - dest, dest_type = self.n, self.ntypes - else - dest, dest_type = self.s, self.stypes - end - return dest_type[key], dest[key], dest_type, dest - end - - ---Returns the typeid and value of the key and removes it from the table. - ---This will not shift elements down the sequential part of the array. - ---@param key string|number The key. Must be a string or number. - ---@return string? typeid The typeid of the key or nil if it doesn't exist. - ---@return any? value The value at the key if it exists. - function E2Table:Unset(key) - local typeid, value, dest_type, dest = e2t_get_ext(self, key) - - if typeid then - dest[key] = nil - dest_type[key] = nil - self.size = self.size - 1 - end - return typeid, value - end - - ---Returns the typeid and value of the key and removes it from the table. - ---This *will* shift elements down the sequential part of the array. - ---@param key string|number The key. Must be a string or number. - ---@return string? typeid The typeid of the key or nil if it doesn't exist. - ---@return any? value The value at the key if it exists. - function E2Table:Remove(key) - local typeid, value, dest_type, dest = e2t_get_ext(self, key) - - if typeid then - if isnumber(key) and table.remove(dest_type, key --[[@as number]]) then - -- table.remove will return nil if it fails, meaning we need to unset - ---@cast key number - table.remove(dest, key) - else - dest[key] = nil - dest_type[key] = nil - end - self.size = self.size - 1 - end - return typeid, value - end - - --- Returns an `E2Table` instance. If `data` is specified, then automatically initializes the E2Table with that data. - --- Otherwise, returns an empty E2Table. - ---@param data { [number|string]:any }? The data to store in the E2Table. - ---@param typeids { [number|string]:any }? Optional typeids. Does not need to include every typeid. Useful for table-based types. - ---@return E2Table - newE2Table = function(data, typeids) - ---@cast E2Table -function - if data then - local n, ntypes, s, stypes, size = {}, {}, {}, {}, 0 - for key, value in pairs(data) do - local dest, dest_types - if isnumber(key) then - dest = n - dest_types = ntypes - elseif isstring(key) then - dest = s - dest_types = stypes - else - error(string.format("Tried to create E2 table with invalid key type: %s, type: %s", tostring(key), type(key))) - end - local tp = typeids and typeids[key] or e2t_infer_tp(value) - if not tp then error(string.format("Unknown type for value [%s] at key [%s]", tostring(value), tostring(key))) end - dest[key] = value - dest_types[key] = tp - size = size + 1 - end - return setmetatable({ n = n, ntypes = ntypes, s = s, stypes = stypes, size = size }, E2Table) - else - return setmetatable({ n = {}, ntypes = {}, s = {}, stypes = {}, size = 0 }, E2Table) - end - end - E2Table.New = newE2Table - - function E2Table:__call(data, typeids) - return newE2Table(data, typeids) - end - - WireLib.E2Table = E2Table -end +local newE2Table = WireLib.E2Table.New --- Array of data types for Wiremod. ---@type table From eaba08b1c654c1c88c44ca7471d10d5d4a20fb1a Mon Sep 17 00:00:00 2001 From: Denneisk <20892685+Denneisk@users.noreply.github.com> Date: Fri, 12 Sep 2025 09:28:55 -0400 Subject: [PATCH 10/10] Remove unused size var --- lua/entities/gmod_wire_expression2/core/custom/prop.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/entities/gmod_wire_expression2/core/custom/prop.lua b/lua/entities/gmod_wire_expression2/core/custom/prop.lua index c0a8deb6b6..84c4eac71d 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/prop.lua @@ -513,7 +513,6 @@ e2function table sentGetData(string class) local sent = list.Get("wire_spawnable_ents_registry")[class] if not sent then self:throw("No class '"..class.."' found in sent registry", res) end - local size = 0 for key, tbl in pairs( sent ) do if key=="_preFactory" or key=="_postFactory" then continue end