├── .gitignore ├── LICENSE ├── README.md └── lua ├── autorun └── replication_init.lua └── replicate ├── example.lua ├── rep_property.lua ├── replicate.lua ├── replicate_assert.lua └── replication_template.lua /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Erlite 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Replicate 2 | A networking framework to read and write tables efficiently for Garry's Mod. 3 | 4 | ## What is it? 5 | Replicate has been made as a replacement for `net.ReadTable()` and `net.WriteTable()`. 6 | It requires a little bit more setting up for tables, but once done, will handle networking these efficiently and effortlessly. 7 | 8 | ## Why do I see `net.ReadTable()` and `net.WriteTable()` in the code then? 9 | 10 | This library does not make use of these, except for the following reasons: 11 | 12 | - You're networking a table that wasn't setup (i.e. your fault.) 13 | - You're networking a nil table (net.WriteTable() is fine since it'll just say it's nil, it isn't costly at all) 14 | 15 | I'm looking at you `gmodstore`, this library's usage of the functions shouldn't be "prohibited" as long as the developers respect the usage. 16 | 17 | ## Usage 18 | This assumes your tables are already metatables, if not, check this [wiki page](https://wiki.facepunch.com/gmod/Object_Oriented_Lua#method2metatables) out. 19 | 20 | First, you need to add the `GetReplicatedProperties(rt)` function to your metatable. 21 | This function is used to generate a "ReplicationTemplate" for your table, and will let Replicate handle networking automatically. 22 | 23 | You must add each property you wish to network, as they will be the only ones sent. 24 | ```lua 25 | function ReplicatedTable:GetReplicatedProperties(rt) 26 | rt:AddString("name") 27 | rt:AddUInt("money", 32) 28 | rt:AddBool("has_team") 29 | 30 | -- Properties can have replication conditions, and will be sent if the condition returns true. 31 | rt:AddColor("team_color") 32 | :SetReplicationCondition(function(tbl) return tbl.has_team end) 33 | 34 | -- They can also depend on other properties, and will only be sent if the dependency was replicated. 35 | rt:AddColor("secondary_color") 36 | :SetDependsOn("team_color") 37 | 38 | -- You can replicate lists, the values will be sent. 39 | -- The last argument is the number of bits that may represent the maximum amount of elements in the list. 40 | rt:AddOrderedList("inventory", ReplicationType.String, 8) 41 | end 42 | ``` 43 | You must then call `Replicate.SetupMetaTable()` at the end of your table. 44 | ```lua 45 | setmetatable(ReplicatedTable, {__call = ReplicatedTable.new}) 46 | -- The second argument is a debug/friendly name to give to your metatable. 47 | Replicate.SetupMetaTable(ReplicatedTable, "ReplicatedTable") 48 | ``` 49 | 50 | Replicate will generate everything needed to network your table (or throw an error if you've done something wrong!) 51 | You can check the example metatable [here](https://github.com/Erlite/Replicate/blob/master/lua/replicate/example.lua). 52 | 53 | ## Supported types 54 | Every type supported by the [net library](https://wiki.facepunch.com/gmod/net) is supported, as well as some custom helpers. You can find the complete list [here](https://github.com/Erlite/Replicate/blob/master/lua/replicate/rep_property.lua#L40). 55 | 56 | ## Sending the table 57 | Instead of using `net.WriteTable()`, you simply call `Replicate.WriteTable()` 58 | ```lua 59 | net.Start("MyNetMessage") 60 | Replicate.WriteTable(tbl) 61 | net.SendToServer() 62 | ``` 63 | Be careful: any table that isn't registered or doesn't have a metatable will default to `net.WriteTable()` 64 | 65 | ## Receiving the table. 66 | 67 | Same as above, but using `Replicate.ReadTable()`. You must supply the meta table to grab a template from. 68 | ```lua 69 | net.Receive("MyNetMessage", function(len, ply) 70 | local tbl = Replicate.ReadTable(MyMetaTable) 71 | end) 72 | ``` 73 | -------------------------------------------------------------------------------- /lua/autorun/replication_init.lua: -------------------------------------------------------------------------------- 1 | AddCSLuaFile() 2 | 3 | print() 4 | print("#############") 5 | print("# Replicate #") 6 | print("#############") 7 | print() 8 | 9 | include("replicate/replicate_assert.lua") 10 | include("replicate/replication_template.lua") 11 | include("replicate/rep_property.lua") 12 | include("replicate/replicate.lua") 13 | include("replicate/example.lua") -------------------------------------------------------------------------------- /lua/replicate/example.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Example table setup for Replicate. 3 | --]] 4 | 5 | AddCSLuaFile() 6 | 7 | ReplicatedTable = {} 8 | ReplicatedTable.__index = ReplicatedTable 9 | 10 | function ReplicatedTable.new() 11 | local tbl = 12 | { 13 | name = "", 14 | money = 0, 15 | has_team = false, 16 | team_color = Color(0, 0, 0), 17 | secondary_color = Color(0, 0, 0), 18 | inventory = {}, 19 | } 20 | 21 | setmetatable(tbl, ReplicatedTable) 22 | return tbl 23 | end 24 | 25 | function ReplicatedTable:GetReplicatedProperties(rt) 26 | rt:AddString("name") 27 | rt:AddUInt("money", 32) 28 | rt:AddBool("has_team") 29 | 30 | rt:AddColor("team_color") 31 | :SetReplicationCondition(function(tbl) return tbl.has_team end) 32 | rt:AddColor("secondary_color") 33 | :SetDependsOn("team_color") 34 | 35 | rt:AddOrderedList("inventory", ReplicationType.String) 36 | end 37 | 38 | setmetatable(ReplicatedTable, {__call = ReplicatedTable.new}) 39 | Replicate.SetupMetaTable(ReplicatedTable, "ReplicatedTable") 40 | 41 | --[[ 42 | Sending the table. 43 | --]] 44 | 45 | function MyAddon.SendDataToServer() 46 | local tbl = GetSomeTable() 47 | net.Start("Server.ReceiveData") 48 | Replicate.WriteTable(tbl) 49 | net.SendToServer() 50 | end 51 | 52 | --[[ 53 | Receiving the table 54 | --]] 55 | net.Receive("Server.ReceiveData", function(len, ply) 56 | local tbl = Replicate.ReadTable(ReplicatedTable) -- You must pass the metatable for it to be read correctly. 57 | end) 58 | -------------------------------------------------------------------------------- /lua/replicate/rep_property.lua: -------------------------------------------------------------------------------- 1 | AddCSLuaFile() 2 | 3 | RepProperty = {} 4 | RepProperty.__index = RepProperty 5 | 6 | local function FluentAccessorFunc(tab, varname, name, iForce) 7 | if ( !tab ) then debug.Trace() end 8 | 9 | tab[ "Get" .. name ] = function( self ) return self[ varname ] end 10 | 11 | if ( iForce == FORCE_STRING ) then 12 | tab[ "Set" .. name ] = function( self, v ) 13 | self[ varname ] = tostring( v ) 14 | return self 15 | end 16 | return end 17 | 18 | if ( iForce == FORCE_NUMBER ) then 19 | tab[ "Set" .. name ] = function( self, v ) 20 | self[ varname ] = tonumber( v ) 21 | return self 22 | end 23 | return end 24 | 25 | if ( iForce == FORCE_BOOL ) then 26 | tab[ "Set" .. name ] = 27 | function( self, v ) 28 | self[ varname ] = tobool( v ) 29 | return self 30 | end 31 | return end 32 | 33 | tab[ "Set" .. name ] = function( self, v ) 34 | self[ varname ] = v 35 | return self 36 | end 37 | end 38 | 39 | -- TODO: Add Key/Value type 40 | ReplicationType = 41 | { 42 | String = "String", -- A string property. 43 | Data = "Data", -- Binary data, 64kb max due to net message restrictions. 44 | Float = "Float", -- A float (decimal) value. 45 | Double = "Double", -- A double precision number. 46 | UInt = "UInt", -- An unsigned integer. Requires the amount of bits to be specified, or it will default to 32. 47 | Int = "Int", -- A signed integer. Requires the amount of bits to be specified, or it will default to 32. 48 | Bool = "Bool", -- A boolean, same as a bit value behind the scenes. 49 | Bit = "Bit", -- A single bit 50 | Color = "Color", -- A color (RGBA) 51 | Vector = "Vector", -- A Vector value (X Y Z) 52 | Normal = "Normal", -- A normalized vector (values must range from 0.0 to 1.0 in all components) 53 | Matrix = "VMatrix", -- A 4x4 matrix, 54 | Angle = "Angle", -- An angle. 55 | Entity = "Entity", -- An entity (internally, networks the EntIndex() using 16 bits) 56 | Table = "Table", -- Table properties must also be setup with Replicate, else it'll default to Read/WriteTable. You don't want that, do you? 57 | List = "List", -- A simple list, considered not ordered. Will be written as an ordinal list. 58 | OrderedList = "OrderedList", -- A simple list with ordered numerical keys (first key must be 1), 59 | ValueTable = "ValueTable", -- A key -> bool table, will only read and write keys that have true values. Keys are assumed to be strings. 60 | } 61 | 62 | function RepProperty:new(inName, inType) 63 | local tbl = 64 | { 65 | name = inName, 66 | type = inType, 67 | bits = nil, 68 | value_type = nil, 69 | condition = nil, 70 | depends_on = nil, 71 | default_value = nil, 72 | meta_table = nil, 73 | } 74 | 75 | setmetatable(tbl, RepProperty) 76 | return tbl 77 | end 78 | 79 | -- Key name of the property 80 | FluentAccessorFunc(RepProperty, "name", "Name") 81 | -- ReplicationType of the property. 82 | FluentAccessorFunc(RepProperty, "type", "Type") 83 | -- Amount of bits used for integers 84 | FluentAccessorFunc(RepProperty, "bits", "Bits") 85 | -- The ReplicationType of the value for lists. 86 | FluentAccessorFunc(RepProperty, "value_type", "ValueType") 87 | -- The condition in which this property will be written. A single bit will be written to determine whether or not the prop was replicated. 88 | -- function(tbl), return true if the property should be written. 89 | -- Only called when writing a table. 90 | FluentAccessorFunc(RepProperty, "condition", "ReplicationCondition") 91 | -- If not nil, the RepProperty name this property depends on. It will only be written/read if the dependency's condition is true. 92 | -- Only works if the dependency has a replication condition. 93 | FluentAccessorFunc(RepProperty, "depends_on", "DependsOn") 94 | -- The default value to assign to this property if it does not get replicated (useful for props with a condition or that depend on another) 95 | FluentAccessorFunc(RepProperty, "default_value", "DefaultValue") 96 | -- The meta table of a table property if any. 97 | -- Used when reading tables. If nil or unregistered, net.ReadTable() will be used. 98 | FluentAccessorFunc(RepProperty, "meta_table", "MetaTable") 99 | 100 | 101 | function RepProperty:AssertValid() 102 | local t = self:GetType() 103 | Replicate.Assert.NotNilOrEmptyString(self:GetName(), "Name") 104 | Replicate.Assert.IsValidReplicationType(t) 105 | 106 | if t == ReplicationType.Int or t == ReplicationType.UInt then 107 | Replicate.Assert.IsValidBitAmount(self:GetBits()) 108 | end 109 | 110 | if t == ReplicationType.List or t == ReplicationType.OrderedList then 111 | Replicate.Assert.IsValidReplicationType(self:GetValueType()) 112 | Replicate.Assert.IsValidBitAmount(self:GetBits()) 113 | end 114 | 115 | local cond = self:GetReplicationCondition() 116 | if cond and not isfunction(cond) then 117 | error("Replication Condition must be a function.") 118 | end 119 | end 120 | 121 | setmetatable(RepProperty, {__call = RepProperty.new}) -------------------------------------------------------------------------------- /lua/replicate/replicate.lua: -------------------------------------------------------------------------------- 1 | -- Licensed under MIT. 2 | -- Enjoy! 3 | AddCSLuaFile() 4 | 5 | Replicate = Replicate or {} 6 | Replicate.Funcs = Replicate.Funcs or {} 7 | Replicate.Templates = Replicate.Templates or {} 8 | 9 | function Replicate.SetupMetaTable(tbl, name) 10 | if not tbl or not istable(tbl) then 11 | error("Cannot setup a nil or invalid table.") 12 | end 13 | 14 | if not name or not isstring(name) or #name == 0 then 15 | error("You must provide a friendly name for the table!") 16 | end 17 | 18 | if not tbl.GetReplicatedProperties then 19 | error("Table is missing the GetReplicatedProperties() function.") 20 | end 21 | 22 | local template = ReplicationTemplate() 23 | tbl:GetReplicatedProperties(template) 24 | template:SetName(name) 25 | 26 | if template:IsEmpty() then 27 | MsgC(Color(255, 196, 0), string.format("Metatable '%s' has no replicated properties!", template:GetName()), "\n") 28 | return 29 | end 30 | 31 | template:AssertValid() 32 | 33 | Replicate.Templates[tbl] = template 34 | MsgC(Color(0, 255, 0), "Replicate: Registered metatable '", name, "'.", "\n") 35 | end 36 | 37 | function Replicate.WriteTable(tbl) 38 | -- Nil tables can just be written by WriteTable() 39 | if not tbl then 40 | net.WriteTable(tbl) 41 | return 42 | end 43 | 44 | -- Can't write anything other than a table. 45 | if tbl and not istable(tbl) then 46 | error("Expected to write a table, got: " .. type(tbl)) 47 | end 48 | 49 | -- If it's nil, write TYPE_NIL. 50 | -- The reason I don't write a bit, is that this will also work in case the receiving end uses net.ReadTable() for whatever reason. 51 | if not tbl then 52 | net.WriteType(TYPE_NIL) 53 | end 54 | 55 | -- No metatable, use WriteTable() 56 | local meta = getmetatable(tbl) 57 | if not meta then 58 | net.WriteTable(tbl) 59 | return 60 | end 61 | 62 | -- Unregistered metatables get the WriteTable() treatment. 63 | if not Replicate.Templates[meta] then 64 | net.WriteTable(tbl) 65 | return 66 | end 67 | 68 | net.WriteType(TYPE_TABLE) 69 | 70 | local template = Replicate.Templates[meta] 71 | 72 | local replicated_props = {} 73 | -- Write every registered property. 74 | for index, prop in ipairs(template:GetProperties()) do 75 | Replicate.WriteProperty(tbl, template, replicated_props, index, prop) 76 | end 77 | 78 | end 79 | 80 | --[[ 81 | Write a property to the net library. 82 | @param tbl The table that contains the property to write. 83 | @param template The replication template that holds the property. 84 | @param replicated_props An inversed table containing a list of properties that were replicated as keys. 85 | @param index The index of the property. 86 | @param prop The property to write. 87 | --]] 88 | function Replicate.WriteProperty(tbl, template, replicated_props, index, prop) 89 | local depends_on = prop:GetDependsOn() 90 | 91 | -- This property depends on another. Let's check that the other was replicated. 92 | if depends_on then 93 | local _, dependency = template:GetPropertyByName(depends_on) 94 | if not dependency then 95 | error(string.format("Property '%s' depends on unknown dependency '%s'. This should never happen!", prop:GetName(), depends_on)) 96 | end 97 | 98 | if not replicated_props[depends_on] then 99 | return 100 | end 101 | end 102 | 103 | -- Check if this property has a replication condition. 104 | local cond = prop:GetReplicationCondition() 105 | if cond then 106 | local shouldReplicate = cond(tbl) 107 | net.WriteBool(shouldReplicate) 108 | 109 | if not shouldReplicate then 110 | return 111 | end 112 | end 113 | 114 | Replicate.Funcs["Write" .. prop:GetType()](prop, tbl[prop:GetName()]) 115 | replicated_props[prop:GetName()] = true 116 | 117 | end 118 | 119 | function Replicate.ReadTable(meta) 120 | -- No metatable means we default to ReadTable(). 121 | if not meta then 122 | return net.ReadTable() 123 | end 124 | 125 | -- Don't pass in anything else than a table. 126 | if not istable(meta) then 127 | error("Expected a table, got: " .. type(tbl)) 128 | return 129 | end 130 | 131 | -- Unregistered metatable gets the ReadTable() treatment. 132 | if not Replicate.Templates[meta] then 133 | return net.ReadTable() 134 | end 135 | 136 | local template = Replicate.Templates[meta] 137 | local readType = net.ReadType() 138 | 139 | if readType == TYPE_NIL then 140 | return nil 141 | elseif readType ~= TYPE_TABLE then 142 | error("Expected TYPE_TABLE or TYPE_NIL when reading type for sanity check, got: " .. tostring(readType)) 143 | return nil 144 | end 145 | 146 | local tbl = {} 147 | local replicated_props = {} 148 | for index, prop in ipairs(template:GetProperties()) do 149 | Replicate.ReadProperty(tbl, template, replicated_props, index, prop) 150 | end 151 | 152 | setmetatable(tbl, meta) 153 | return tbl 154 | end 155 | 156 | --[[ 157 | Read a property to the net library. 158 | @param tbl The table to read the property to. 159 | @param template The replication template that holds the property to read. 160 | @param replicated_props An inversed table containing a list of properties that were replicated as keys. 161 | @param index The index of the property. 162 | @param prop The property to read. 163 | --]] 164 | function Replicate.ReadProperty(tbl, template, replicated_props, index, prop) 165 | local depends_on = prop:GetDependsOn() 166 | 167 | -- This property depends on another. Let's check that the other was replicated. 168 | if depends_on then 169 | local _, dependency = template:GetPropertyByName(depends_on) 170 | if not dependency then 171 | error(string.format("Property '%s' depends on unknown dependency '%s'. This should never happen!", prop:GetName(), depends_on)) 172 | end 173 | 174 | if not replicated_props[depends_on] then 175 | if prop:GetDefaultValue() then 176 | tbl[prop:GetName()] = prop:GetDefaultValue() 177 | end 178 | return 179 | end 180 | end 181 | 182 | -- Check if this property has a replication condition and if it passed it. 183 | local cond = prop:GetReplicationCondition() 184 | if cond then 185 | local passed_condition = net.ReadBool() 186 | 187 | if not passed_condition then 188 | if prop:GetDefaultValue() then 189 | tbl[prop:GetName()] = prop:GetDefaultValue() 190 | end 191 | return 192 | end 193 | end 194 | 195 | local value = Replicate.Funcs["Read" .. prop:GetType()](prop) 196 | tbl[prop:GetName()] = value 197 | replicated_props[prop:GetName()] = true 198 | 199 | end 200 | 201 | --[[ 202 | Write functions 203 | --]] 204 | 205 | function Replicate.Funcs.WriteString(prop, value) 206 | net.WriteString(value) 207 | end 208 | 209 | function Replicate.Funcs.WriteData(prop, value) 210 | net.WriteUInt(#value, prop:GetBits() or 16) 211 | net.WriteData(value, #value) 212 | end 213 | 214 | function Replicate.Funcs.WriteFloat(prop, value) 215 | net.WriteFloat(value) 216 | end 217 | 218 | function Replicate.Funcs.WriteDouble(prop, value) 219 | net.WriteDouble(value) 220 | end 221 | 222 | function Replicate.Funcs.WriteUInt(prop, value) 223 | Replicate.Assert.IsValidBitAmount(prop:GetBits()) 224 | net.WriteUInt(value, prop:GetBits()) 225 | end 226 | 227 | function Replicate.Funcs.WriteInt(prop, value) 228 | Replicate.Assert.IsValidBitAmount(prop:GetBits()) 229 | net.WriteInt(value, prop:GetBits()) 230 | end 231 | 232 | function Replicate.Funcs.WriteBool(prop, value) 233 | net.WriteBool(value) 234 | end 235 | 236 | function Replicate.Funcs.WriteBit(prop, value) 237 | net.WriteBit(value) 238 | end 239 | 240 | function Replicate.Funcs.WriteColor(prop, value) 241 | net.WriteColor(value) 242 | end 243 | 244 | function Replicate.Funcs.WriteVector(prop, value) 245 | net.WriteVector(value) 246 | end 247 | 248 | function Replicate.Funcs.WriteAngle(prop, value) 249 | net.WriteAngle(value) 250 | end 251 | 252 | function Replicate.Funcs.WriteEntity(prop, value) 253 | net.WriteEntity(value) 254 | end 255 | 256 | -- Haha recursion go brrrrt 257 | function Replicate.Funcs.WriteTable(prop, value) 258 | Replicate.WriteTable(value) 259 | end 260 | 261 | function Replicate.Funcs.WriteList(prop, value) 262 | Replicate.Assert.IsValidBitAmount(prop:GetBits()) 263 | net.WriteUInt(#value, prop:GetBits()) 264 | 265 | local writeFunc = Replicate.Funcs["Write" .. prop:GetValueType()] 266 | for _, v in pairs(value) do 267 | writeFunc(prop, v) 268 | end 269 | end 270 | 271 | function Replicate.Funcs.WriteOrderedList(prop, value) 272 | Replicate.Assert.IsValidBitAmount(prop:GetBits()) 273 | net.WriteUInt(#value, prop:GetBits()) 274 | 275 | local writeFunc = Replicate.Funcs["Write" .. prop:GetValueType()] 276 | for _, v in ipairs(value) do 277 | writeFunc(prop, v) 278 | end 279 | end 280 | 281 | function Replicate.Funcs.WriteValueTable(prop, value) 282 | local bits = prop:GetBits() or 32 283 | local keys = {} 284 | for k, v in pairs(value) do 285 | if v then 286 | values[#values + 1] = k 287 | end 288 | end 289 | 290 | net.WriteUInt(#keys, bits) 291 | for _, v in ipairs(keys) do 292 | net.WriteString(v) 293 | end 294 | end 295 | 296 | --[[ 297 | Read functions 298 | --]] 299 | 300 | function Replicate.Funcs.ReadString(prop) 301 | return net.ReadString() 302 | end 303 | 304 | function Replicate.Funcs.ReadData(prop) 305 | local len = net.ReadUInt(prop:GetBits() or 16) 306 | return net.ReadData(len) 307 | end 308 | 309 | function Replicate.Funcs.ReadFloat(prop) 310 | return net.ReadFloat() 311 | end 312 | 313 | function Replicate.Funcs.ReadDouble(prop) 314 | return net.ReadDouble() 315 | end 316 | 317 | function Replicate.Funcs.ReadUInt(prop) 318 | Replicate.Assert.IsValidBitAmount(prop:GetBits()) 319 | return net.ReadUInt(prop:GetBits()) 320 | end 321 | 322 | function Replicate.Funcs.ReadInt(prop) 323 | Replicate.Assert.IsValidBitAmount(prop:GetBits()) 324 | return net.ReadInt(prop:GetBits()) 325 | end 326 | 327 | function Replicate.Funcs.ReadBool(prop) 328 | return net.ReadBool() 329 | end 330 | 331 | function Replicate.Funcs.ReadBit(prop) 332 | return net.ReadBit() 333 | end 334 | 335 | function Replicate.Funcs.ReadColor(prop) 336 | return net.ReadColor() 337 | end 338 | 339 | function Replicate.Funcs.ReadVector(prop) 340 | return net.ReadVector() 341 | end 342 | 343 | function Replicate.Funcs.ReadAngle(prop) 344 | return net.ReadAngle() 345 | end 346 | 347 | function Replicate.Funcs.ReadEntity(prop) 348 | return net.ReadEntity() 349 | end 350 | 351 | -- Haha recursion go brrrrt 352 | function Replicate.Funcs.ReadTable(prop) 353 | return Replicate.ReadTable(prop:GetMetaTable()) 354 | end 355 | 356 | function Replicate.Funcs.ReadList(prop) 357 | Replicate.Assert.IsValidBitAmount(prop:GetBits()) 358 | local size = net.ReadUInt(prop:GetBits()) 359 | local tbl = {} 360 | 361 | local ReadFunc = Replicate.Funcs["Read" .. prop:GetValueType()] 362 | for i = 1, size do 363 | tbl[i] = ReadFunc(prop) 364 | end 365 | 366 | return tbl 367 | end 368 | 369 | function Replicate.Funcs.ReadOrderedList(prop) 370 | Replicate.Assert.IsValidBitAmount(prop:GetBits()) 371 | local size = net.ReadUInt(prop:GetBits()) 372 | local tbl = {} 373 | 374 | local ReadFunc = Replicate.Funcs["Read" .. prop:GetValueType()] 375 | for i = 1, size do 376 | tbl[i] = ReadFunc(prop) 377 | end 378 | 379 | return tbl 380 | end 381 | 382 | function Replicate.Funcs.WriteValueTable(prop) 383 | local bits = prop:GetBits() or 32 384 | 385 | local tbl = {} 386 | local count = net.ReadUInt(bits) 387 | for i = 1, count do 388 | tbl[net.ReadString()] = true 389 | end 390 | 391 | return tbl 392 | end -------------------------------------------------------------------------------- /lua/replicate/replicate_assert.lua: -------------------------------------------------------------------------------- 1 | AddCSLuaFile() 2 | 3 | Replicate = Replicate or {} 4 | Replicate.Assert = Replicate.Assert or {} 5 | 6 | function Replicate.Assert.NotNilOrEmptyString(name, argName) 7 | if not name or not isstring(name) or #name == 0 then 8 | error("Expected a valid, not empty value for argument name '" .. argName or "nil" .. "'") 9 | end 10 | end 11 | 12 | function Replicate.Assert.IsNotNil(tbl, name) 13 | if table == nil then 14 | error("Expected a non nil value for argument name '" .. name .. "'") 15 | end 16 | end 17 | 18 | function Replicate.Assert.IsValidReplicationType(type) 19 | if not type or not table.HasValue(ReplicationType, type) then 20 | error("Invalid ReplicationType value '" .. tostring(type) or "nil" .. "'") 21 | end 22 | end 23 | 24 | function Replicate.Assert.IsValidBitAmount(num) 25 | if not isnumber(num) or num < 1 or num > 32 then 26 | error("Invalid amount of bits '" .. tostring(num) or "nil" .. "'"); 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lua/replicate/replication_template.lua: -------------------------------------------------------------------------------- 1 | AddCSLuaFile() 2 | 3 | ReplicationTemplate = {} 4 | ReplicationTemplate.__index = ReplicationTemplate 5 | 6 | function ReplicationTemplate:new() 7 | local tbl = 8 | { 9 | properties = {}, 10 | name = nil, 11 | } 12 | 13 | setmetatable(tbl, ReplicationTemplate) 14 | return tbl 15 | end 16 | 17 | AccessorFunc(ReplicationTemplate, "properties", "Properties") 18 | AccessorFunc(ReplicationTemplate, "name", "Name", FORCE_STRING) 19 | 20 | function ReplicationTemplate:IsEmpty() 21 | return #self.properties == 0 22 | end 23 | 24 | function ReplicationTemplate:AddProperty(prop) 25 | Replicate.Assert.IsNotNil(prop, "RepProperty") 26 | 27 | if getmetatable(prop) ~= RepProperty then 28 | error("Expected a RepProperty, got: " .. type(prop)) 29 | end 30 | 31 | prop:AssertValid() 32 | 33 | -- Overwrite an existing one in case of a hot reload. 34 | for k, v in ipairs(self.properties) do 35 | if v:GetName() == prop:GetName() then 36 | self.properties[k] = prop 37 | return prop 38 | end 39 | end 40 | 41 | MsgC(Color(255, 145, 0), "Registered property '", prop:GetName(), "' of type '", prop:GetType(), "'", "\n") 42 | 43 | self.properties[#self.properties + 1] = prop 44 | return prop 45 | end 46 | 47 | function ReplicationTemplate:AddString(name) 48 | local prop = RepProperty(name, ReplicationType.String) 49 | return self:AddProperty(prop) 50 | end 51 | 52 | function ReplicationTemplate:AddData(name) 53 | local prop = RepProperty(name, ReplicationType.Data) 54 | return self:AddProperty(prop) 55 | end 56 | 57 | function ReplicationTemplate:AddFloat(name) 58 | local prop = RepProperty(name, ReplicationType.Float) 59 | return self:AddProperty(prop) 60 | end 61 | 62 | function ReplicationTemplate:AddDouble(name) 63 | local prop = RepProperty(name, ReplicationType.Double) 64 | return self:AddProperty(prop) 65 | end 66 | 67 | function ReplicationTemplate:AddInt(name, bits) 68 | local b = bits or 32 69 | 70 | local prop = RepProperty(name, ReplicationType.Int) 71 | prop:SetBits(b) 72 | 73 | return self:AddProperty(prop) 74 | end 75 | 76 | function ReplicationTemplate:AddUInt(name, bits) 77 | local b = bits or 32 78 | 79 | local prop = RepProperty(name, ReplicationType.UInt) 80 | prop:SetBits(b) 81 | 82 | return self:AddProperty(prop) 83 | end 84 | 85 | function ReplicationTemplate:AddBool(name) 86 | local prop = RepProperty(name, ReplicationType.Bool) 87 | return self:AddProperty(prop) 88 | end 89 | 90 | function ReplicationTemplate:AddBit(name) 91 | local prop = RepProperty(name, ReplicationType.Bit) 92 | return self:AddProperty(prop) 93 | end 94 | 95 | function ReplicationTemplate:AddColor(name) 96 | local prop = RepProperty(name, ReplicationType.Color) 97 | return self:AddProperty(prop) 98 | end 99 | 100 | function ReplicationTemplate:AddVector(name) 101 | local prop = RepProperty(name, ReplicationType.Vector) 102 | return self:AddProperty(prop) 103 | end 104 | 105 | function ReplicationTemplate:AddNormal(name) 106 | local prop = RepProperty(name, ReplicationType.Normal) 107 | return self:AddProperty(prop) 108 | end 109 | 110 | function ReplicationTemplate:AddMatrix(name) 111 | local prop = RepProperty(name, ReplicationType.Matrix) 112 | return self:AddProperty(prop) 113 | end 114 | 115 | function ReplicationTemplate:AddAngle(name) 116 | local prop = RepProperty(name, ReplicationType.Angle) 117 | return self:AddProperty(prop) 118 | end 119 | 120 | function ReplicationTemplate:AddEntity(name) 121 | local prop = RepProperty(name, ReplicationType.Entity) 122 | return self:AddProperty(prop) 123 | end 124 | 125 | function ReplicationTemplate:AddTable(name, metatable) 126 | local prop = RepProperty(name, ReplicationType.Table) 127 | prop:SetMetaTable(metatable) 128 | return self:AddProperty(prop) 129 | end 130 | 131 | -- Value Type: the type of the list's values. 132 | -- Bits: the amount of bits that represents the max amount of values. Defaults to 16 133 | function ReplicationTemplate:AddList(name, valueType, bits) 134 | bits = (bits and isnumber(bits)) and bits or 16 135 | 136 | local prop = RepProperty(name, ReplicationType.List) 137 | prop:SetValueType(valueType) 138 | prop:SetBits(bits) 139 | 140 | return self:AddProperty(prop) 141 | end 142 | 143 | function ReplicationTemplate:AddOrderedList(name, valueType, bits) 144 | bits = (bits and isnumber(bits)) and bits or 16 145 | local prop = RepProperty(name, ReplicationType.OrderedList) 146 | prop:SetValueType(valueType) 147 | prop:SetBits(bits) 148 | 149 | return self:AddProperty(prop) 150 | end 151 | 152 | function ReplicationTemplate:AddValueTable(name, bits) 153 | bits = (bits and isnumber(bits)) and bits or 16 154 | local prop = RepProperty(name, ReplicationType.ValueTable) 155 | prop:SetBits(bits) 156 | 157 | return self:AddProperty(prop) 158 | end 159 | 160 | -- Validates the template. 161 | function ReplicationTemplate:AssertValid() 162 | for key, prop in ipairs(self:GetProperties()) do 163 | prop:AssertValid() 164 | 165 | -- Make sure this property doesn't depend on a property that doesn't exist, or is registered after this one. 166 | local depends_on = prop:GetDependsOn() 167 | if depends_on then 168 | local idx, depend_prop = self:GetPropertyByName(depends_on) 169 | if not depend_prop then 170 | error(string.format("Property '%s' cannot depend on unknown property '%s'", prop:GetName(), depends_on)) 171 | end 172 | 173 | if idx == key then 174 | error(string.format("Property '%s' cannot depend on itself."), prop:GetName()) 175 | end 176 | 177 | if idx > key then 178 | error(string.format("Property '%s' cannot depend on '%s' as it is registered after it.", prop:GetName(), depends_on)) 179 | end 180 | end 181 | end 182 | end 183 | 184 | -- Returns the index and property it finds from the given name. 185 | -- If none are found, returns -1, nil 186 | function ReplicationTemplate:GetPropertyByName(name) 187 | Replicate.Assert.NotNilOrEmptyString(name) 188 | 189 | for key, prop in ipairs(self:GetProperties()) do 190 | if prop:GetName() == name then 191 | return key, prop 192 | end 193 | end 194 | 195 | return -1, nil 196 | end 197 | 198 | setmetatable(ReplicationTemplate, {__call = ReplicationTemplate.new}) --------------------------------------------------------------------------------