WriteTable Replacements

post where you’re using writetable and give me enough context (what you’re sending) and I’ll write you a replacement for free.
you guys are too lazy to stop using it, well fuck you, now you have no excuse

What is exactly wrong with it?

Network overhead. Read/WriteTable sends the type of every key and value in a table alongside their values.

Frankly I don’t see the big deal as long as you use it conservatively.

writetable:


for each item in the table:
	write 8 bits for the type of the key
	write the key (bad for non-associative arrays)
	write 8 bits for the type of the value
	write the value
write another 8 bits

it’s way unnecessary, and there’s a better way to do it 100% of the time

it’s lazy coding, it adds up, and it just slows down servers

Thanks for doing this, but can you show us an example on how to avoid using WriteTable and then we could try it out for ourselves?

I have tons of addons using net.WriteTable and I would like to rewrite all of them.

Isn’t creating a replacement in your addons as simple as sending the table size then looping thorough the table networking the values with their respective net.Write’s and reading it on the client? Would that be the way to do it or am I missing somthing?

it really depends on the type of table you’re trying to send. if a table is all the same type, all it really takes is sending the length, and then each value. for more advanced stuff, it gets more complicated. here’s a method of sending arguments for chat.AddText that I recently wrote:
[lua]nchat = {
writetype = {
[TYPE_NIL] = function() return 0 end,
–[[ [TYPE_BOOL] = function(val)
return 1, {val and “true” or “false”}
end, ]]
[TYPE_NUMBER] = function(val)
return 1, {val}
end,
[TYPE_STRING] = function(val)
return 1, {val}
end,
[TYPE_TABLE] = function(val)
assert(IsColor(val), string.format(“unsupported object type ‘%s’ (%s)”, type(arg), tostring(arg)))

		return 1, {val}
	end,
	[TYPE_ENTITY] = function(val, vals)
		if(val:GetClass() == "player") then
			return 3, {team.GetColor(val:Team()), val:Nick(), nchat.lastcol}
		else
			return 1, {val:GetClass()}
		end
	end,
	--[[ [TYPE_VECTOR] = function(val)
		return 1, {tostring(val)}
	end,
	[TYPE_ANGLE] = function(val)
		return 1, {tostring(val)}
	end ]]
},
pack = function(col)
	local num = col.b

	num = bit.bor(num, bit.lshift(col.g, 8))
	num = bit.bor(num, bit.lshift(col.r, 16))

	return num
end,
unpack = function(num)
	local b = bit.band(num, 255)
	local g = bit.band(bit.rshift(num, 8), 255)
	local r = bit.band(bit.rshift(num, 16), 255)

	return Color(r, g, b)
end

}

if(SERVER) then
AddCSLuaFile()

nchat.writechat = function(...)
	local args = {...}
	local len = 0
	local vals = {}

	nchat.lastcol = Color(255, 255, 255)

	for k, arg in pairs(args) do
		local func = nchat.writetype[TypeID(arg)]

		--assert(func, string.format("unsupported object type '%s' (%s)", type(arg), tostring(arg)))

		if(func) then
			local num, sep = func(arg)

			len = len + num

			for k, val in pairs(sep) do
				vals[#vals + 1] = val
			end
		end
	end

	if(len < 1) then return end

	net.WriteUInt(len - 1, 5) --32 things

	for k, val in pairs(vals) do
		local color = TypeID(val) == TYPE_TABLE --it's either a string or a color

		if not(color and nchat.lastcol == color) then
			net.WriteBit(color)

			if(color) then
				net.WriteUInt(nchat.pack(val), 24) --packed colors are 24 bits

				nchat.lastcol = val
			else
				net.WriteString(val)
			end
		end
	end
end

else
nchat.readchat = function()
local tab = {}

	local len = net.ReadUInt(5) + 1

	for i = 1, len do
		local kind = net.ReadBit() == 1

		if(kind) then
			tab[#tab + 1] = nchat.unpack(net.ReadUInt(24))
		else
			tab[#tab + 1] = net.ReadString()
		end
	end

	chat.AddText(unpack(tab))
end

end[/lua]

it works by converting all arguments to either strings or colors, then sending those

Another one that writes to chat:

[lua]
local function WriteChat(…)
local args = {…}
local sends = {}
for i = 1, #args do – flatten players into team col and nick; can’t think of way to get correct len without this
local o = args*
if type(o) == “Player” then
sends[#sends + 1] = team.GetColor(o:Team())
sends[#sends + 1] = o:Nick()
else
sends[#sends + 1] = o
end
end
net.WriteUInt(#sends, 4)
for i = 1, #sends do
local o = sends*
local typ = type(o)
if typ == “string” or typ == “number” or typ == “boolean” then – technically you could put whatever here
net.WriteUInt(0, 1)
net.WriteString(tostring(o))
elseif IsColor(o) then
net.WriteUInt(1, 1)
net.WriteColor(o)
end
end
end

local function ReadChat()
local len = net.ReadUInt(4)
local ret = {}
for i = 1, len do
local typ = net.ReadUInt(1)
local func = (typ == 0) and net.ReadString or net.ReadColor
ret[#ret + 1] = func()
end
return ret
end
[/lua]

Here’s a thing I made some time ago:

It allows you to define a data structure and generates code to send and receive exactly what you defined.
Usage is like this:



--- CLIENT
local ds = adv.NetStruct "i32 {i8 s} ${someKey:fs otherKey:v}"
ds:InitReader()
net.Receive("someMsg", function()
    local a, b, c = ds:ReadStruct()
    -- do something with data
end)

--- SERVER
local ds = adv.NetStruct "i32 {i8 s} ${someKey:fs otherKey:v}"
ds:InitWriter()
net.Start("someMsg")
ds:WriteStruct(1234567, {1, "one", 2, "two"}, {someKey = 2.718, otherKey = Vector(5,5,5)})
net.Send()


What data structure in above example actually means:
i32 - 32 bit signed integer
{i8 s} - list of alternating 8 bit signed ints and strings
(Lists have length sent as 16 bit unsigned integer, so limit is 65535 entries)
${someKey:fs otherKey:v} - table with two keys: fs - single precision float, v - vector

Generated code for that data structure (this is then CompileString’d):



--- WRITER
local _val0 = {...}
net.WriteInt(_val0[1],32)
local _val1 = _val0[2]
net.WriteUInt(#_val1,16)
for _i1 = 1, #_val1, 2 do
net.WriteInt(_val1[_i1+0],8)
net.WriteString(_val1[_i1+1])
end
local _val1 = _val0[3]
net.WriteFloat(_val1["someKey"])
net.WriteFloat(_val1["otherKey"].x) net.WriteFloat(_val1["otherKey"].y) net.WriteFloat(_val1["otherKey"].z)

--- READER
local _val0 = {}
_val0[1] = net.ReadInt(32)
local _val1 = {}
_val0[2] = _val1
local _len1 = net.ReadUInt(16)
for _i1 = 1, _len1, 2 do
_val1[_i1+0] = net.ReadInt(8)
_val1[_i1+1] = net.ReadString()
end
local _val1 = {}
_val0[3] = _val1
_val1["someKey"] = net.ReadFloat()
_val1["otherKey"] = Vector(net.ReadFloat(), net.ReadFloat(), net.ReadFloat())
return _val0


This is cool. Lulpeg is a pretty big dependency though. I feel like this could be done using Lua pattern matching in less than 50 lines.

22 matches across 11 files

God help me.

I used LPeg there because I was using it for other stuff as well, mainly for Moonscript compiler I ported to gmod. But yea, it can be pretty heavy just for something like this.