• Useful networking wrappers
    34 replies, posted
[QUOTE=Lau;19573400]It doesn't pool smsg names for the specific reason that pooling doesn't even work right in source <-> lua interface, if you notice: umsg.PoolString does nothing, and it pools after a string has been sent 5 times.[/QUOTE] I did a little bit of research into this a while ago: umsg.PoolString works, it puts the string in the LuaStringTable (which the server and client use for the umsg. library as well as other parts) code: [server] umsg.PoolString("Test String") [client] dumpstringtables_new LuaStringTable The handling of pooled strings is transparent, when you use umsg.Start it will look up the index of the string in the table or add it otherwise. As for pooled strings with umsg.String, it writes to the buffer a bit for pooled, and either the index or the string. Here's what I get (see this thread [url]http://www.facepunch.com/showthread.php?t=705037[/url]): [server] lua_run umsg.Start("a") umsg.String("a") umsg.End() lua_run umsg.Start("a") umsg.String("Test String") umsg.End() lua_run umsg.Start("a") umsg.String("Test Stringaaa") umsg.End() [client] umsg incoming: a size: 5 Warning: Unhandled usermessage 'a' umsg incoming: a size: 5 Warning: Unhandled usermessage 'a' umsg incoming: a size: 18 Warning: Unhandled usermessage 'a' So yes, pooling works, unless you're expecting it to magically pool the inside of a larger string. But you should also be aware that string table synchronization isn't instant, it still has to be networked.
why rely on gmod's string tables? you could just make your own string tables and at least send smsg IDs instead of string names. and yeah, I could have read the source but 1. I couldn't be asked 2. I'm sure there's a lot of voodoo in a library like that and the interesting parts are hard to find 3. asking made AzuiSleet post his research :) an interesting thought about pooling: if client-side console command names are pooled as well, wouldn't that make it easy to overflow the server's string table? (provided you find a way to make it execute those commands on you)
I might have been wrong about the size of the lua usermessage, because I took a look at gm_transmittools [code] int umsgStringTableOffset table->AddString(true, "N") << 1 | 1 bf_write *bf_write = engine->UserMessageBegin(&filter, 28); // LuaUserMessage bf_write->WriteShort(umsgStringTableOffset); bf_write->WriteOneBit(0); // important bit! [/code] Either way, the first part of the umsgs (before any actual data) is still 3 bytes. And I honestly don't remember why it's like that with the header, I wonder if it's supposed to be bit + short.
[QUOTE=AzuiSleet;19593832]I might have been wrong about the size of the lua usermessage, because I took a look at gm_transmittools [code] int umsgStringTableOffset table->AddString(true, "N") << 1 | 1 bf_write *bf_write = engine->UserMessageBegin(&filter, 28); // LuaUserMessage bf_write->WriteShort(umsgStringTableOffset); bf_write->WriteOneBit(0); // important bit! [/code] Either way, the first part of the umsgs (before any actual data) is still 3 bytes. And I honestly don't remember why it's like that with the header, I wonder if it's supposed to be bit + short.[/QUOTE] Yeah, I just assumed its it had a short, but the extra byte is also odd: could it be a 0 | 1 byte to signify pooling?
Unfortunately, i get encoding logical errors when using server to client interface. [lua] if SERVER then include('server/servermessage.lua') end local NAME = "stream" module('stream',package.seeall) local Hooks = {} local Streams = { IN = SERVER and {}, OUT = {} } --if SERVER then Streams.IN = {} end local hex2bin = { ["0"] = "0000", ["1"] = "0001", ["2"] = "0010", ["3"] = "0011", ["4"] = "0100", ["5"] = "0101", ["6"] = "0110", ["7"] = "0111", ["8"] = "1000", ["9"] = "1001", ["a"] = "1010", ["b"] = "1011", ["c"] = "1100", ["d"] = "1101", ["e"] = "1110", ["f"] = "1111" } local function NearestPower(n, pMax) for i=0, pMax or 256 do if 2^i >= n then return i end end return 256 end local function string_split(str,d) --Thanks to Deco and Janorkie's datastream local t = {} local len = str:len() local i = 0 while i*d < len do t[i+1] = str:sub(i*d+1,(i+1)*d) i=i+1 end return t end local function Hex2Bin(s) local ret = '' local i = 0 for i in string.gmatch(s, ".") do i = string.lower(i) ret = ret..hex2bin[i] end return ret end _G.Dec2Bin = function (s, num) local n if (num == nil) then n = 0 elseif num > 2048 then ErrorNoHalt("Dec2Bin #2 argument is insanely high\n") return "" else n = num end s = Hex2Bin(string.format("%x", tostring(s))) while string.len(s) < n do s = '0'..s end return s end local function EncodeBinaryToString(tblBits) local str = '' local byte for i=1, #tblBits, 8 do byte = 0 for bit=0, 7 do byte = byte + ((tblBits[i+bit] and 2^bit) or 0) end str = str..string.char(byte) end return str end local function DecodeStringToBinary(str) local bin = {} local t for i=1, #str do t = Dec2Bin(tostring(string.byte(string.sub(str,i,i))),8) for bit=8, 1, -1 do --normally 8 to 1 table.insert(bin,string.sub(t,bit,bit)=='1') end end return bin end local function AppendBinary(bitsBuffer,strBin) for i=string.len(strBin), 1, -1 do table.insert(bitsBuffer,string.sub(strBin,i,i)=='1') end end local function ReadBits(tblBits,index,numBits) local sBits = "" for i=index+numBits-1, index, -1 do if type(tblBits[i])~="boolean" then error('fucking non-boolean index in bitstream :tblBits['..tostring(i)..'] dim is '..tostring(#tblBits)) end sBits=sBits..(tblBits[i] and "1" or "0") end return sBits end --TODO: Add table recursion support and key sending too! local tblSpecificType = { --[[['Short'] = function (bitStream,n) AppendBinary(bitStream,Dec2Bin(math.Clamp(tonumber(n) or 0,-(2^15),2^15 - 1)+2^15,16)) end, ['UShort'] = function (bitStream,n) AppendBinary(bitStream,Dec2Bin(math.Clamp(tonumber(n) or 0,0,2^15),16)) end, ['ULong'] = function (bitStream,n) AppendBinary(bitStream,Dec2Bin(math.Clamp(tonumber(n) or 0,0,2^31),32)) end,]] ['number'] = { encode=function (bitStream,n) --signed long integer AppendBinary(bitStream,Dec2Bin(math.Clamp(tonumber(n) or 0,-(2^31),2^31 - 1)+2^31,32)) end, decode=function (bitStream,index) return tonumber(ReadBits(bitStream,index,32),2)-2^31, index+32 end}, ['Char'] = { encode=function (bitStream,ch) AppendBinary(bitStream,Dec2Bin(tostring(string.byte(ch)),8)) end, decode=function (bitStream,index) return string.char(tonumber(ReadBits(bitStream,index,8),2)), index+8 end}, ['string'] = { encode=function (bitStream,str) AppendBinary(bitStream,Dec2Bin(tostring(string.len(str)),8)) for i=1, string.len(str) do AppendBinary(bitStream,Dec2Bin(tostring(string.byte(string.sub(str,i,i))),8)) end end, decode=function (bitStream,index) local s='' for i=1, tonumber(ReadBits(bitStream,index,8),2) do index=index+8 s=s..string.char(tonumber(ReadBits(bitStream,index,8),2)) end return s, index+8 end}, ['boolean'] = { encode=function (bitStream,b) table.insert(bitStream,b) end, decode=function (bitStream,index) return bitStream[index], index+1 end}, ['Entity'] = { encode=function (bitStream,ent) AppendBinary(bitStream,Dec2Bin(tostring(ent:EntIndex()),16)) end, decode=function (bitStream,index) return ents.GetByIndex(tonumber(ReadBits(bitStream,index,16),2)), index+16 end}, ['table'] = true, ['EOS']=true, } local tblSpecificTypeID = {} local numMaxID = 0 for k,v in pairs(tblSpecificType) do numMaxID=numMaxID+1 tblSpecificTypeID[k] = numMaxID tblSpecificTypeID[numMaxID] = k end local tblTypeAlias = { --Unique assignments such as player object ['Player'] = 'Entity' } local TBL_DIM_BITSIZE = 32 local IDENT_BITS = NearestPower(table.Count(tblSpecificType)) --derpa print("IDENTBITS = "..IDENT_BITS) _G.TYPTEST = function (typ,data) local tblBin = {} AppendBinary(tblBin,Dec2Bin(tostring(tblSpecificTypeID[typ]),IDENT_BITS)) tblSpecificType[typ].encode(tblBin,data) PrintTable(tblBin) print("typ converted back to : "..tostring(tblSpecificTypeID[tonumber(ReadBits(tblBin,1,IDENT_BITS),2)]).." id="..tonumber(ReadBits(tblBin,1,IDENT_BITS),2)) local dat, index = tblSpecificType[typ].decode(tblBin,IDENT_BITS+1) print('data converted back to : '..tostring(dat)..' new index:'..tostring(index)) end --print("DERPA IS: maxid=",numMaxID,"tablecount=",table.Count(tblSpecificTypeID),"nearestpwr=",NearestPower(table.Count(tblSpecificTypeID))) local function table_count_nonint(t, bAllowFloat) local n = 0 for k, v in pairs(t) do if type(k) ~= 'number' or (bAllowFloat and math.floor(k)~=k) then n=n+1 end end return n end local function ParseToStringX(tblBin,typ,tblX) local nonint = table_count_nonint(tblX,false) --AppendBinary(tblBin,Dec2Bin(tostring(#tblX),TBL_DIM_BITSIZE)) --number of integer keys for k, v in ipairs(tblX) do typ = type(v) if typ == 'table' then AppendBinary(tblBin,Dec2Bin(tostring(tblSpecificTypeID[typ]),IDENT_BITS)) --identify it as a table and then do recursion ParseToStringX(tblBin,typ,tblX[k]) --elseif typ == 'number' then --Work on float and precision checking (try dynamic identification maybe? elseif typ~='nil' then typ = tblTypeAlias[typ] or typ if tblSpecificType[typ] then AppendBinary(tblBin,Dec2Bin(tostring(tblSpecificTypeID[typ]),IDENT_BITS)) tblSpecificType[typ].encode(tblBin,v) end end end AppendBinary(tblBin,Dec2Bin(tostring(tblSpecificTypeID['EOS']),IDENT_BITS)) --a flag to end the stream (little endian?) instead of a huge 32 bit table dimension encoded --TODO: add non-int key sending --[[table.insert(tblBin,nonint>0) --bit to tell if non-integer keys exist if nonint>0 then AppendBinary(tblBin,Dec2Bin(table_count_nonint(v,true)),2^32) end]] --[[ --todo non-integer indexed sending for k, v in pairs(tbl) do if type(k) ~= 'number' then --is NaN elseif math.floor(k)~=k then --# that has a decimal value end end ]] end local function ParseToString(tbl, bAllowCompress) --DATA ENCODING PROCESS local tblBin = {} local typ ParseToStringX(tblBin,typ,tbl) --allows for recursion (some heavy duty shit bro) --STRING ENCODING then COMPRESSION PROCESS local str = EncodeBinaryToString(tblBin) local bCompressed = false if bAllowCompress then str, bCompressed = LibCompress.CompressLZW(str) --decode from table of bools to an encoded string then try to compress it end return str, bCompressed end local function ParseFromStringX(tbl,bitIndex,tblBin) local typ local tblIndex = 1 while true do print("Reading a value...") typ = tblSpecificTypeID[tonumber(ReadBits(tblBin,bitIndex,IDENT_BITS),2)] if not typ then print("ERR With ID:"..tostring(tonumber(ReadBits(tblBin,bitIndex,IDENT_BITS),2))) end bitIndex = bitIndex + IDENT_BITS if typ then if typ == 'EOS' then break --end of table parsing else if typ == 'table' then tbl[i] = {} --[[if tonumber(ReadBits(tblBin,bitIndex,TBL_DIM_BITSIZE),2) > 512 then error("shit is so high "..tostring(tonumber(ReadBits(tblBin,bitIndex,TBL_DIM_BITSIZE),2))) end]] bitIndex = ParseFromStringX(tbl[i],bitIndex,tblBin) else --other object local dat, newBitIndex = tblSpecificType[typ].decode(tblBin,bitIndex) tbl[i] = dat bitIndex = newBitIndex end tblIndex = tblIndex + 1
Sorry, you need to Log In to post a reply to this thread.