-- see copyright notice in grldc.h local socket = require( "grldc.socket" ) local assert = assert local setmetatable = setmetatable local print = print local error = error local type = type local tonumber = tonumber local tostring = tostring local loadstring = loadstring local string = string local table = table local pairs = pairs local globals = _G module( "grldc.net" ) local listenerMeta = { __index = {} } local connectionMeta = { __index = {} } local function debugPrint( ... ) --print( ... ) end function serialize( value ) local t = type( value ) if t == "number" then local res = tostring( value ) if string.find( value, "[^%.,%-0-9]" ) ~= nil then res = "function() return 0/0 end" end return res elseif t == "boolean" then if value then return "true" end return "false" elseif t == "string" then return string.format( "%q", value ) elseif t == "table" then local res = "{ " for k, v in pairs( value ) do res = res.."["..serialize( k ).."] = "..serialize( v )..", " end res = res.." }" return res else error( "Can't serialize a value of type "..t ) end end local function fixUp( value ) local t = type( value ) if t == "table" then local res = {} for k, v in pairs( value ) do res[fixUp(k)] = fixUp(v) end return res elseif t == "function" then return value() else return value end end function deserialize( str ) local f = loadstring( "return "..str ) if f then local res = f() return fixUp( res ) else error( "Unable to parse serialized value: "..tostring(str) ) end end function bind( address, port ) local self = { listener = socket.bind( address, port ) } setmetatable( self, listenerMeta ) return self end function listenerMeta.__index:accept() local res = self.listener:accept() if res ~= nil then res = { connection = res } setmetatable( res, connectionMeta ) res:init_() end return res end function connect( address, port ) local self = { connection = socket.connect( address, port ) } print( "Connected to "..address..":"..port ) setmetatable( self, connectionMeta ) self:init_() return self end function connectionMeta.__index:init_() self.received_ = {} end function connectionMeta.__index:getpeername() return self.connection:getpeername() end function connectionMeta.__index:send( data, channel ) channel = channel or "default" debugPrint( "Sending "..tostring( data ).." on channel "..channel ) local sdata = serialize( data ) local packet = channel.."\n"..(#sdata).."\n"..sdata local res, msg = self.connection:send( packet ) if res == nil then error( msg, 0 ) else return res end end function connectionMeta.__index:waitData() local needResumeHook = false if globals.grldc.suspendHook ~= nil then -- the grldc module is loaded, we need to avoid the debug hook to be called from the receive function (because the hook internally uses receive too) globals.grldc.suspendHook() needResumeHook = true end local hasData = false for _, received in pairs( self.received_ ) do if #received > 0 then hasData = true break end end if hasData then if needResumeHook then globals.grldc.resumeHook() end return end debugPrint( "updating channels..." ) self:updateChannels_( nil ) if needResumeHook then globals.grldc.resumeHook() end end function connectionMeta.__index:receive( channel ) channel = channel or "default" debugPrint( "Blocking receive on channel "..channel ) local data = self:popReceived_( channel ) while data == nil do self:updateChannels_( nil ) data = self:popReceived_( channel ) end debugPrint( "received "..tostring(data).." on channel "..channel ) return data end function connectionMeta.__index:tryReceive( channel ) channel = channel or "default" local data = self:popReceived_( channel ) if data == nil then self:updateChannels_( 0 ) data = self:popReceived_( channel ) end if data ~= nil then debugPrint( "received "..tostring(data).." on channel "..channel ) end return data end function connectionMeta.__index:updateChannels_( timeout ) local needResumeHook = false if globals.grldc.suspendHook ~= nil then -- the grldc module is loaded, we need to avoid the debug hook to be called from the receive function (because the hook internally uses receive too) globals.grldc.suspendHook() needResumeHook = true end self.connection:settimeout( timeout ) local channel, msg = self.connection:receive() self.connection:settimeout( nil ) if channel == nil and msg == "timeout" then if needResumeHook then globals.grldc.resumeHook() end return end local size if channel ~= nil then debugPrint( "receiving..." ) size, msg = self.connection:receive() end local data if size ~= nil then size = assert( tonumber( size ) ) data, msg = self.connection:receive( size ) end if data == nil then if needResumeHook then globals.grldc.resumeHook() end error( msg, 0 ) end if channel == "ka" then -- special channel keepalive, we simply ignore data received on this channel else local received = self.received_[channel] if received == nil then received = {} self.received_[channel] = received end table.insert( received, deserialize( data ) ) debugPrint( "queued "..tostring(data).." on channel "..channel ) end if needResumeHook then globals.grldc.resumeHook() end end function connectionMeta.__index:popReceived_( channel ) local received = self.received_[channel] if received == nil then return nil end local res = received[1] if res == nil then return nil end table.remove( received, 1 ) return res end