-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added: net.RegisterStructure function. Added: net.WriteStructure function. Added: net.ReadStructure function.
- Loading branch information
1 parent
4466237
commit b1627bc
Showing
3 changed files
with
389 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,170 @@ | ||
# netStructures | ||
Garry's Mod library for networking tables. | ||
|
||
`net.ReadTable` and `net.WriteTable` are functions in the Garry's Mod net library tasked with networking a table of arbitrary data. Any Garry's Mod programmer worth their salt will know that these functions suck; they send more data than is really needed due to the fact that Lua and Garry's Mod cant know the structure of a table. | ||
|
||
Enter **netStructures**. **netStructures** introduces a way to send and receive tables of predefined structure in super friendly way. | ||
|
||
Why would you want to predefine your table structures for networking? Strict, consistent structure in code is important whether its for networking or not. It can also help immensely with debugging. If the structure of the table you are sending is known, it means the type information that `net.Read/Write` table use can be omitted, thus only sending the data that is needed and reducing network data. | ||
|
||
**netStructures** works by having the user "register" a table structure. This structure can then be used with the `net.WriteStructure` and `net.ReadStructure` functions. These 2 functions are almost a copy/paste replacement for net.Read/WriteTable. | ||
|
||
## Preamble: **netStructures** Type Enums | ||
In order for netStructures to know which keys in your table correspond to which values, **netStructures** defines 13 global integral values for primitive types commonly used in networking: | ||
|
||
``` | ||
STRUCTURE_STRING | ||
STRUCTURE_ANGLE | ||
STRUCTURE_VECTOR | ||
STRUCTURE_COLOR | ||
STRUCTURE_ENTITY | ||
STRUCTURE_BIT | ||
STRUCTURE_NUMBER -- Generic; floating point | ||
STRUCTURE_INT8 -- Integer values between -128 and 127 | ||
STRUCTURE_INT16 -- Integer values between -32768 and 32767 | ||
STRUCTURE_INT32 -- Integer values between -2147483648 and 2147483647 | ||
STRUCTURE_UINT8 -- Integer values between 0 and 255 | ||
STRUCTURE_UINT16 -- Integer values between 0 and 65535 | ||
STRUCTURE_UINT32 -- Integer values between 0 and 4294967295 | ||
``` | ||
|
||
## Step 1: Registration | ||
The first step to using netStructures is to register your table structure: | ||
``` | ||
net.RegisterStructure("my_struct_weps", { | ||
name = STRUCTURE_STRING, | ||
ammo = STRUCTURE_NUMBER | ||
}) | ||
``` | ||
|
||
The code above says to create a new structure called "my\_struct\_weps" with 2 fields: a name string and an ammo number. When this structure is used, **netStructures** will enforce these types on the values provided by the table. | ||
|
||
## Step 2: Write the structure | ||
Writing structures is almost identical to `net.WriteTable`: | ||
``` | ||
net.WriteStructure("my_struct_weps", { | ||
name = "pistol", | ||
ammo = 10 | ||
}) | ||
``` | ||
|
||
The first argument in `net.WriteStructure` tells **netStructs** which structure to use. In this example we are using the structure we created before. | ||
|
||
When `net.WriteStructure` is called, it will make sure that the name you have provided is an actual struct, then it will make sure that the table you have provided matches the structure. | ||
|
||
## Step 3: Reading the structure | ||
Like writing, reading structures is almost identical to `net.ReadTable`: | ||
``` | ||
local wep = net.ReadStructure("my_struct_weps") | ||
``` | ||
|
||
`wep` will be a table containing `name = "pistol", ammo = 10`. | ||
|
||
**netStructures** provides fixed-width integer types which are not wrapped. If you provide a value that is outside of the range specified in the structure, it will error. | ||
|
||
**netStructures** allows fields to reference other structures: | ||
``` | ||
net.RegisterStructure("my_struct", { | ||
name = STRUCTURE_STRING, | ||
weapon = "my_struct_weps" | ||
}) | ||
``` | ||
|
||
The above code creates a new structure where weapon refers to the structure we created earlier. This is known as a *Structure Reference*. This new structure can be networked like so: | ||
``` | ||
net.WriteStructure("my_struct", { | ||
name = "Gambit", | ||
weapon = { | ||
name = "pistol", | ||
ammo = 10 | ||
} | ||
}) | ||
``` | ||
|
||
**netStructures** also lets structures define sequential arrays. Arrays must be homogeneous, using the `STRUCTURE_*` enums or *Structure References*: | ||
``` | ||
net.RegisterStructure("my_struct", { | ||
name = STRUCTURE_STRING, | ||
weapons = {"my_struct_weps"} -- denotes that `items` is a sequential array of the "my_struct_weps" structure | ||
}) | ||
net.WriteStructure("my_struct", { | ||
name = "Gambit", | ||
weapons = { | ||
{ | ||
name = "pistol", | ||
ammo = 10 | ||
} | ||
} | ||
}) | ||
``` | ||
|
||
With the complete sample program, here are some results: | ||
``` | ||
net.RegisterStructure("my_struct_weps", { | ||
name = STRUCTURE_STRING, | ||
ammo = STRUCTURE_NUMBER | ||
}) | ||
net.RegisterStructure("my_struct", { | ||
name = STRUCTURE_STRING, | ||
items = {"my_struct_weps"} -- denotes that `items` is a sequential array of the "my_struct_weps" structure | ||
}) | ||
if (SERVER) then | ||
util.AddNetworkString("my_struct_nws") | ||
util.AddNetworkString("my_struct_nws_tbl") | ||
function netStructureTest(ply) | ||
net.Start("my_struct_nws") | ||
net.WriteStructure("my_struct", { | ||
name = "Gambit", | ||
items = { | ||
{ | ||
name = "pistol", | ||
ammo = 10 | ||
} | ||
} | ||
}) | ||
net.Send(ply) | ||
net.Start("my_struct_nws_tbl") | ||
net.WriteTable({ | ||
name = "Gambit", | ||
items = { | ||
{ | ||
name = "pistol", | ||
ammo = 10 | ||
} | ||
} | ||
}) | ||
net.Send(ply) | ||
end | ||
else | ||
net.Receive("my_struct_nws", function(l) | ||
print("net.ReadStructure", l) | ||
PrintTable(net.ReadStructure("my_struct")) | ||
end) | ||
net.Receive("my_struct_nws_tbl", function(l) | ||
print("net.ReadTable", l) | ||
PrintTable(net.ReadTable()) | ||
end) | ||
end | ||
``` | ||
``` | ||
net.ReadStructure 208 | ||
items: | ||
1: | ||
ammo = 10 | ||
name = pistol | ||
name = Gambit | ||
net.ReadTable 512 | ||
items: | ||
1: | ||
ammo = 10 | ||
name = pistol | ||
name = Gambit | ||
``` | ||
|
||
In this example, we can see an almost 2.5x improvement on size compared to `net.Read/WriteTable`. Other tables and structures I have tested have yielded upwards of 3.5x so your mileage may vary. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
"AddonInfo" | ||
{ | ||
"name" "netStructures" | ||
"version" "1.0" | ||
"author_name" "Gambit" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
STRUCTURE_STRING = 0 | ||
STRUCTURE_ANGLE = 1 | ||
STRUCTURE_VECTOR = 2 | ||
STRUCTURE_COLOR = 3 | ||
STRUCTURE_ENTITY = 4 | ||
STRUCTURE_BIT = 5 | ||
STRUCTURE_BOOL = 6 | ||
STRUCTURE_NUMBER = 7 | ||
STRUCTURE_INT8 = 8 | ||
STRUCTURE_INT16 = 9 | ||
STRUCTURE_INT32 = 10 | ||
STRUCTURE_UINT8 = 11 | ||
STRUCTURE_UINT16 = 12 | ||
STRUCTURE_UINT32 = 13 | ||
|
||
local structures = {} | ||
local structureType = { | ||
[STRUCTURE_STRING] = { | ||
name = "string", | ||
predicate = function(v) return isstring(v) end, | ||
write = net.WriteString, | ||
read = net.ReadString | ||
}, | ||
|
||
[STRUCTURE_ANGLE] = { | ||
name = "angle", | ||
predicate = function(v) return isangle(v) end, | ||
write = net.WriteAngle, | ||
read = net.ReadAngle | ||
}, | ||
|
||
[STRUCTURE_VECTOR] = { | ||
name = "vector", | ||
predicate = function(v) return isvector(v) end, | ||
write = net.WriteVector, | ||
read = net.ReadVector | ||
}, | ||
|
||
[STRUCTURE_COLOR] = { | ||
name = "color", | ||
predicate = function(v) return IsColor(v) end, | ||
write = net.WriteColor, | ||
read = net.ReadColor | ||
}, | ||
|
||
[STRUCTURE_ENTITY] = { | ||
name = "entity", | ||
predicate = function(v) return IsValid(v) end, | ||
write = net.WriteEntity, | ||
read = net.ReadEntity | ||
}, | ||
|
||
[STRUCTURE_BIT] = { | ||
name = "bit", | ||
predicate = function(v) return isbool(v) or v == 0 or v == 1 end, | ||
write = net.WriteBit, | ||
read = net.ReadBit | ||
}, | ||
|
||
[STRUCTURE_BOOL] = { | ||
name = "bool", | ||
predicate = function(v) return isbool(v) or v == 0 or v == 1 end, | ||
write = net.WriteBool, | ||
read = net.ReadBool | ||
}, | ||
|
||
[STRUCTURE_NUMBER] = { | ||
name = "number", | ||
predicate = function(v) return isnumber(v) end, | ||
write = net.WriteDouble, | ||
read = net.ReadDouble | ||
}, | ||
|
||
[STRUCTURE_INT8] = { | ||
name = "int8", | ||
predicate = function(v) return isnumber(v) and v >= -128 and v <= 127 end, | ||
write = function(v) net.WriteInt(v, 8) end, | ||
read = function() return net.ReadInt(8) end | ||
}, | ||
|
||
[STRUCTURE_INT16] = { | ||
name = "int16", | ||
predicate = function(v) return isnumber(v) and v >= -32768 and v <= 32767 end, | ||
write = function(v) net.WriteInt(v, 16) end, | ||
read = function() return net.ReadInt(16) end | ||
}, | ||
|
||
[STRUCTURE_INT32] = { | ||
name = "int32", | ||
predicate = function(v) return isnumber(v) and v >= -2147483648 and v <= 2147483647 end, | ||
write = function(v) net.WriteInt(v, 32) end, | ||
read = function() return net.ReadInt(32) end | ||
}, | ||
|
||
[STRUCTURE_UINT8] = { | ||
name = "uint8", | ||
predicate = function(v) return isnumber(v) and v >= 0 and v <= 255 end, | ||
write = function(v) net.WriteUInt(v, 8) end, | ||
read = function() return net.ReadUInt(8) end | ||
}, | ||
|
||
[STRUCTURE_UINT16] = { | ||
name = "uint16", | ||
predicate = function(v) return isnumber(v) and v >= 0 and v <= 65535 end, | ||
write = function(v) net.WriteUInt(v, 16) end, | ||
read = function() return net.ReadUInt(16) end | ||
}, | ||
|
||
[STRUCTURE_UINT32] = { | ||
name = "uint32", | ||
predicate = function(v) return isnumber(v) and v >= 0 and v <= 4294967295 end, | ||
write = function(v) net.WriteUInt(v, 32) end, | ||
read = function() return net.ReadUInt(32) end | ||
} | ||
} | ||
|
||
function net.RegisterStructure(name, structure) | ||
assert(isstring(name), "Structure name must be a string") | ||
assert(not istable(structures[name]), Format([[Structure "%s" already exists]], name)) | ||
assert(istable(structure), "Structure must be a table") | ||
|
||
for k, v in SortedPairs(structure) do | ||
assert(isstring(k), "structure keys must be strings") | ||
|
||
if (istable(v)) then | ||
assert(table.IsSequential(v), Format("Structure tables must be sequential (at index %s)", k)) | ||
assert(#v == 1, Format("Structure tables must contain only 1 element (at index %s)", k)) | ||
|
||
local element = v[1] | ||
|
||
if (isnumber(element)) then | ||
assert(element >= 0 and element <= 12, Format("Structure table element number value must be a STRUCTURE_* value (at index %s)", k)) | ||
elseif (isstring(element)) then | ||
assert(istable(structures[element]), Format("Structure table element string value must refer to a Structure Reference (at index %s)", k)) | ||
else | ||
assert(false, Format("Structure table elements must be STRUCTURE_* values or Structure References (at index %s)", k)) | ||
end | ||
elseif (isstring(v)) then | ||
assert(istable(structures[v]), "Structure string values must refer to a Structure") | ||
assert(v ~= name, "Structure references cannot refer to the Structure containing them") | ||
else | ||
assert(isnumber(v), Format("Structure values must be numbers (at index %s)", k)) | ||
assert(v >= 0 and v <= 12, Format("Structure values must be STRUCTURE_* values (at index %s)", k)) | ||
end | ||
end | ||
|
||
structures[name] = structure | ||
end | ||
|
||
function net.WriteStructure(name, structure) | ||
assert(isstring(name), "Structure name must me a string") | ||
assert(istable(structure), "Structure must be a table") | ||
assert(istable(structures[name]), Format([[Invalid structure "%s"]], name)) | ||
|
||
for k,v in SortedPairs(structures[name]) do | ||
local value = structure[k] | ||
|
||
assert(value ~= nil, Format("Structure table missing index for %s", k)) | ||
|
||
if (isnumber(v)) then | ||
local typeData = structureType[v] | ||
assert(typeData.predicate(value), Format("Structure value does not match predicate of %s (at index %s)", typeData.name, k)) | ||
typeData.write(value) | ||
elseif (istable(v)) then | ||
assert(table.IsSequential(value), "Structure tables must be sequential") | ||
|
||
local count = #value | ||
net.WriteUInt(count, 32) | ||
|
||
for index = 1, count do | ||
if (isnumber(v[1])) then | ||
local typeData = structureType[v[1]] | ||
assert(typeData.predicate(value[index]), Format("Structure table value does not match predicate of %s (at index %s:%i)", typeData.name, k, index)) | ||
typeData.write(value[index]) | ||
else -- if (isstring(v[1])) then | ||
net.WriteStructure(v[1], value[index]) | ||
end | ||
end | ||
else --if (isstring(v) and istable(structures[v])) then | ||
net.WriteStructure(v, structure[k]) | ||
end | ||
end | ||
end | ||
|
||
function net.ReadStructure(name) | ||
assert(isstring(name), "Structure name must me a string") | ||
assert(istable(structures[name]), Format([[Invalid structure "%s"]], name)) | ||
|
||
local ret = {} | ||
|
||
for k,v in SortedPairs(structures[name]) do | ||
if (isstring(v)) then | ||
ret[k] = net.ReadStructure(v) | ||
else | ||
if (istable(v)) then | ||
local count = net.ReadUInt(32) | ||
|
||
local array = {} | ||
for i = 1, count do | ||
if (isnumber(v[1])) then | ||
array[count] = typeData.read() | ||
else -- if (isstring(v[1])) then | ||
array[count] = net.ReadStructure(v[1]) | ||
end | ||
end | ||
|
||
ret[k] = array | ||
else --if (isstring(v) and istable(structures[v])) then | ||
ret[k] = structureType[v].read() | ||
end | ||
end | ||
end | ||
|
||
return ret | ||
end |