Skip to content

Commit

Permalink
Added: STRUCTURE_* enums.
Browse files Browse the repository at this point in the history
Added: net.RegisterStructure function.
Added: net.WriteStructure function.
Added: net.ReadStructure function.
  • Loading branch information
GamerGambit committed Feb 21, 2018
1 parent 4466237 commit b1627bc
Show file tree
Hide file tree
Showing 3 changed files with 389 additions and 0 deletions.
168 changes: 168 additions & 0 deletions README.md
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.
6 changes: 6 additions & 0 deletions addoninfo.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"AddonInfo"
{
"name" "netStructures"
"version" "1.0"
"author_name" "Gambit"
}
215 changes: 215 additions & 0 deletions lua/autorun/netstructures.lua
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

0 comments on commit b1627bc

Please sign in to comment.