You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
219 lines
5.7 KiB
219 lines
5.7 KiB
-- 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
|
|
|