NetStream: A simple net wrapper.

I wanted something to make the net library easier to use because adding a networked string for each different message wasn’t efficient.
This wrapper uses vON for serialization and uses the net library to send and recieve network messages.
I’ve not done any speed tests or much optimization, this was originally coded for Clockwork but I figured others could make use of it too.

vON is required to use NetStream!

Code

[lua]
–[[
NetStream
http://www.revotech.org

Copyright (c) 2012 Alexander Grist-Hucker

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:


The above copyright notice and this permission notice shall be included in all copies or substantial portions 
of the Software.


THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
DEALINGS IN THE SOFTWARE.

Credits to:
	Alexandru-Mihai Maftei aka Vercas for vON.
	https://dl.dropbox.com/u/1217587/GMod/Lua/von%20for%20GMOD.lua

–]]

local type, error, pcall, pairs, AddCSLuaFile, require, _player = type, error, pcall, pairs, AddCSLuaFile, require, player

AddCSLuaFile(“includes/modules/netstream.lua”);
AddCSLuaFile(“includes/modules/von.lua”);
require(“von”);

if (!von) then
error(“NetStream: Unable to find vON!”);
end;

netstream = {};
netstream.stored = {};

– A function to hook a data stream.
function netstream.Hook(name, Callback)
self.stored[name] = Callback;
end;

if (SERVER) then
util.AddNetworkString(“NetStreamDS”);

-- A function to start a net stream.
function netstream.Start(player, name, data)
	local recipients = {};
	local bShouldSend = false;

	if (type(player) != "table") then
		if (!player) then
			player = _player.GetAll();
		else
			player = {player};
		end;
	end;
	
	for k, v in pairs(player) do
		if (type(v) == "Player") then
			recipients[#recipients + 1] = v;
			
			bShouldSend = true;
		elseif (type(k) == "Player") then
			recipients[#recipients + 1] = k;
		
			bShouldSend = true;
		end;
	end;
	
	local dataTable = {data = data};
	local vonData = von.serialize(dataTable);
	local encodedData = util.Compress(vonData);
		
	if (encodedData and #encodedData > 0 and bShouldSend) then
		net.Start("NetStreamDS");
			net.WriteString(name);
			net.WriteUInt(#encodedData, 32);
			net.WriteData(encodedData, #encodedData);
		net.Send(recipients);
	end;
end;

net.Receive("NetStreamDS", function(length, player)
	local NS_DS_NAME = net.ReadString();
	local NS_DS_LENGTH = net.ReadUInt(32);
	local NS_DS_DATA = net.ReadData(NS_DS_LENGTH);
	
	if (NS_DS_NAME and NS_DS_DATA and NS_DS_LENGTH) then
		NS_DS_DATA = util.Decompress(NS_DS_DATA);
		
		if (!NS_DS_DATA) then
			error("NetStream: The data failed to decompress!");
			
			return;
		end;
		
		player.nsDataStreamName = NS_DS_NAME;
		player.nsDataStreamData = "";
		
		if (player.nsDataStreamName and player.nsDataStreamData) then
			player.nsDataStreamData = NS_DS_DATA;
							
			if (netstream.stored[player.nsDataStreamName]) then
				local bStatus, value = pcall(von.deserialize, player.nsDataStreamData);
				
				if (bStatus) then
					netstream.stored[player.nsDataStreamName](player, value.data);
				else
					error("NetStream: "..value);
				end;
			end;
			
			player.nsDataStreamName = nil;
			player.nsDataStreamData = nil;
		end;
	end;
	
	NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH = nil, nil, nil;
end);

else
– A function to start a net stream.
function netstream.Start(name, data)
local dataTable = {data = data};
local vonData = von.serialize(dataTable);
local encodedData = util.Compress(vonData);

	if (encodedData and #encodedData > 0) then
		net.Start("NetStreamDS");
			net.WriteString(name);
			net.WriteUInt(#encodedData, 32);
			net.WriteData(encodedData, #encodedData);
		net.SendToServer();
	end;
end;

net.Receive("NetStreamDS", function(length)
	NS_DS_NAME = net.ReadString();
	NS_DS_LENGTH = net.ReadUInt(32);
	NS_DS_DATA = net.ReadData(NS_DS_LENGTH);
	
	if (NS_DS_NAME and NS_DS_DATA and NS_DS_LENGTH) then
		NS_DS_DATA = util.Decompress(NS_DS_DATA);


		if (!NS_DS_DATA) then
			error("NetStream: The data failed to decompress!");
			
			return;
		end;
					
		if (netstream.stored[NS_DS_NAME]) then
			local bStatus, value = pcall(von.deserialize, NS_DS_DATA);
		
			if (bStatus) then
				netstream.stored[NS_DS_NAME](value.data);
			else
				error("NetStream: "..value);
			end;
		end;
	end;
	
	NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH = nil, nil, nil;
end);

end;
[/lua]

Usage

Client hooking
[lua]
if (CLIENT) then
require(“netstream”);
netstream.Hook(“ClientExample”, function(data)
print(data);
end);
else
require(“netstream”);
netstream.Start(player, “ClientExample”, “Example message!”);
end;
[/lua]

Server hooking
[lua]
if (CLIENT) then
require(“netstream”);
netstream.Start(“ServerExample”, {
message = “Server message!”
});
else
require(“netstream”);
netstream.Hook(“ServerExample”, function(player, data)
print(player:Name(), data.message);
end);
end;
[/lua]

Why are you using vOn instead of net.WriteTable?

net.WriteTable has some serious issues with precision loss on small numbers though. Anything with more than one decimal gets rounded and I don’t know if that’s intended behavior or not.

When I wrote this, it was for Clockwork and it wasn’t always sending for sending a table. I chose a string out of personal preference and the util JSON functions are terrible for keeping table integrity so I decided to use vON. This works and I’ve not had any issues with it, especially when I needed to keep integrity of the data.

Using json would bloat the output, but you could send any type with table regardless. (net.WriteTable({var})) I don’t know if it’s actually more efficient or not and there’s the issue with precision on decimals.

Can you change the style from netstream:Something() to netstream.Something() for consistency (with gmod) purposes?

Done, it’s now netstream.* and I’m not really sure if it would make a difference at this point, it’s pretty fast and reliable as it is.

I could see this being very useful. Good job.

I wrote something like this as well for sending large strings (> 64KB) both ways

[lua]
–[[
=== HSP Plugin Module ===
sh_Netburst, v1.0
Sending of >64KB strings, both ways!

Created by:
	Henry00		- http://00laboratories.com
	HeX			- http://gmod.game-host.org
For [United|Hosts]

SHARED:
	netburst.Send(stream_id, str)
	netburst.Hook(stream_id, callback)
		callback(len, str, stream_id, CRC, NewCRC, Splits, ply)

]]

netburst = {
CommonID = “Netburst”,
Done = “Done”,
Max = 62000,
}

if (SERVER) then
AddCSLuaFile()
util.AddNetworkString(netburst.CommonID)
end

function netburst.Split(str)
local Temp = “”
local result = {}
local index = 1

for i=0,str:len() do
	Temp = Temp..str:sub(i,i)
	
	if (#Temp == netburst.Max) then
		result[index] = Temp
		index = index +1
		Temp = ""
	end
end

result[index] = Temp
return result

end

function netburst.UnSplit(tab)
local Temp = “”
for k,v in ipairs(tab) do
Temp = Temp…v
end
return Temp
end

local Hooks = {}
function netburst.Hook(sID, callback)
Hooks[sID] = callback
end

local Buff = {}
function netburst.InternalReceive(len,ply) --Shared
local sID = net.ReadString()
local Cont = net.ReadString()

if not Hooks[sID] then
	if IsValid(ply) then
		Error("Unhandled netburst: '"..sID.."' from "..tostring(ply).."

“)
else
Error(“Unhandled netburst: '”…sID…”’
")
end
return
end

if (Cont == netburst.Done) then
	local CRC = net.ReadString()
	
	if not Buff[sID] then
		return Error("Buff fuckup: "..sID)
	end
	
	local Tab = Buff[sID]
	Buff[sID] = nil --Clear
	
	local str = netburst.UnSplit(Tab)
	local NewCRC = tostring( util.CRC(str) )
	
	if (NewCRC != CRC) then
		ErrorNoHalt("Bad CRC for incoming netburst, expected '"..CRC.."', got '"..NewCRC.."'

")
end

	Hooks[sID](str,len,sID,CRC,NewCRC,#Tab,ply)
else
	if not Buff[sID] then
		Buff[sID] = {}
	end
	
	table.insert(Buff[sID], Cont)
end

end
net.Receive(netburst.CommonID, netburst.InternalReceive)

function netburst.Send(sID,str,ply)
local CRC = tostring( util.CRC(str) )

for k,v in ipairs( netburst.Split(str) ) do
	net.Start(netburst.CommonID)
	net.WriteString(sID)
	net.WriteString(v)
	
	if CLIENT then
		net.SendToServer()
	else
		if ply then
			net.Send(ply)
		else
			net.Broadcast()
		end
	end
end

net.Start(netburst.CommonID)
net.WriteString(sID)
net.WriteString(netburst.Done)
net.WriteString(CRC)


if CLIENT then
	net.SendToServer()
else
	if ply then
		net.Send(ply)
	else
		net.Broadcast()
	end
end

end
[/lua]

Test script (shared):

[lua]
local huge = string.rep(“H”, 128000)

if CLIENT then
netburst.Hook(“Cake”, function(str,len,sID,CRC,NewCRC,Splits)
print("! got from SERVER: “, len, str == huge, Splits…” splits")
end)

concommand.Add("shit", function()
	netburst.Send("Tits", huge)
	
	print("! sent from CLIENT")
end)

end

if SERVER then
concommand.Add(“fuck”, function()
netburst.Send(“Cake”, huge)

	print("! sent from SERVER")
end)


netburst.Hook("Tits", function(str,len,sID,CRC,NewCRC,Splits,ply)
	print("! got from CLIENT: ", str:len(), str == huge, Splits.." splits", ply)
end)

end
[/lua]

Named after one of Intel’s older CPU architectures?

I find it funny that the open source thingo notice contains approximately 1/4 of all the characters in the script

I love how it’s more like 1/6 on both in reality.

Ontopic:

Thanks, using this in one of my projects

Yes :slight_smile:

I wish you wouldn’t use VON for something like this. Also is this for sending massive tables? That seems kinda counter intuitive to me, tbh

Well I wanted to use encoding rather than just sending a table since I can compress it also. JSON has so many problems that make it impossible to use for something like this… Plus I like vON.

>implying I meant JSON.
Um I promise you if you send using BYTES instead of using serialized strings it’ll go much farther.
The compress function works on assuming that it’s plaintext i.e. common characters. You will probably find the compressed version is larger than the decompressed version

vON doesn’t encode into uncommon characters…