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.
302 lines
8.2 KiB
302 lines
8.2 KiB
-- see copyright notice in wxLdb.lua |
|
|
|
local net = require( "grldc.net" ) |
|
local socket = require( "grldc.socket" ) |
|
|
|
local print = print |
|
local assert = assert |
|
local pairs = pairs |
|
local ipairs = ipairs |
|
local type = type |
|
local debug = debug |
|
local xpcall = xpcall |
|
local setmetatable = setmetatable |
|
local error = error |
|
local table = table |
|
local tonumber = tonumber |
|
|
|
module( "grld.engine" ) |
|
|
|
local clients |
|
local activeClient |
|
local nextClient = 1 |
|
local listeners = {} |
|
local nextListener = 1 |
|
local events = |
|
{ |
|
onNewClient = {}, |
|
onClientBreak = {}, |
|
onClientLost = {}, |
|
} |
|
|
|
local clientMeta = { __index = {} } |
|
|
|
local function addClient( listenerIdx, connection, name ) |
|
print( "New client connected on listener "..listenerIdx.." : assigned client ID "..nextClient ) |
|
local client = { clientId = nextClient, listenerIdx = listenerIdx, connection = connection, status_ = "running", activeThread_ = "current", name_ = name, ip_ = connection:getpeername(), breakPoints_ = {} } |
|
setmetatable( client, clientMeta ) |
|
clients[nextClient] = client |
|
if activeClient == nil or clients[activeClient] == nil then |
|
setactiveclient( nextClient ) |
|
end |
|
runEvents_( "onNewClient", nextClient ) |
|
nextClient = nextClient + 1 |
|
end |
|
|
|
function init() |
|
print( "Initializing grld.engine..." ) |
|
assert( clients == nil, "Trying to initialize grld.engine twice" ) |
|
clients = {} |
|
end |
|
|
|
function initialized() |
|
return clients ~= nil |
|
end |
|
|
|
function listen( address, port ) |
|
print( "Listening for new clients on "..address..":"..port..", listener ID "..nextListener ) |
|
local listener = net.bind( address, port ) |
|
listeners[nextListener] = listener |
|
nextListener = nextListener + 1 |
|
end |
|
|
|
function registerEvent( eventName, callback ) |
|
assert( events[eventName] ~= nil, "Unknown event name "..eventName ) |
|
table.insert( events[eventName], callback ) |
|
end |
|
|
|
function runEvents_( eventName, ... ) |
|
for _, callback in pairs( events[eventName] ) do |
|
callback( ... ) |
|
end |
|
end |
|
|
|
function update( timeout ) |
|
local active = false |
|
|
|
for idx, listener in pairs( listeners ) do |
|
local connection = listener:accept() |
|
if connection ~= nil then |
|
local name = connection:receive() |
|
addClient( idx, connection, name ) |
|
active = true |
|
end |
|
end |
|
|
|
local sockets = {} |
|
for idx, client in pairs( clients ) do |
|
table.insert( sockets, client.connection ) |
|
end |
|
socket.select( sockets, timeout ) |
|
|
|
for idx, client in pairs( clients ) do |
|
local ok, msg = xpcall( function() active = active or client:update() end, client.errorHandler ) |
|
if not ok then |
|
active = true |
|
if msg == "closed" then |
|
print( "Connection with client "..idx.." closed" ) |
|
runEvents_( "onClientLost", idx ) |
|
clients[idx] = nil |
|
else |
|
error( msg ) |
|
end |
|
end |
|
end |
|
|
|
return active |
|
end |
|
|
|
function clientMeta.__index:checkConnection() |
|
self.connection:send( "", "ka" ) |
|
end |
|
|
|
function clientMeta.__index:update() |
|
local command = self.connection:tryReceive() |
|
if command ~= nil then |
|
self["cmd_"..command]( self ) |
|
return true |
|
end |
|
return false |
|
end |
|
|
|
function clientMeta.__index:setactivethread( thread ) |
|
self.activeThread_ = thread |
|
end |
|
|
|
function clientMeta.__index:getactivethread() |
|
return self.activeThread_ |
|
end |
|
|
|
function clientMeta.__index:getcurrentthread() |
|
assert( self.status_ == "break", "Bad status: "..self.status_ ) |
|
self.connection:send( "currentthread" ) |
|
return self.connection:receive() |
|
end |
|
|
|
function clientMeta.__index:name() |
|
return self.name_ |
|
end |
|
|
|
function clientMeta.__index:ip() |
|
return self.ip_ |
|
end |
|
|
|
function clientMeta.__index:status() |
|
return self.status_ |
|
end |
|
|
|
function clientMeta.__index:source() |
|
assert( self.status_ == "break", "Bad status: "..self.status_ ) |
|
return self.source_ |
|
end |
|
|
|
function clientMeta.__index:line() |
|
assert( self.status_ == "break", "Bad status: "..self.status_ ) |
|
return self.line_ |
|
end |
|
|
|
function clientMeta.__index:setbreakpoint( source, line, value ) |
|
local setValue = value or nil |
|
local sourceBp = self.breakPoints_[source] |
|
if sourceBp == nil then |
|
sourceBp = {} |
|
self.breakPoints_[source] = sourceBp |
|
end |
|
sourceBp[line] = setValue |
|
self.connection:send( "setbreakpoint", "running" ) |
|
self.connection:send( { source = source, line = line, value = value }, "running" ) |
|
end |
|
|
|
function clientMeta.__index:callstack() |
|
assert( self.status_ == "break", "Bad status: "..self.status_ ) |
|
self.connection:send( "callstack" ) |
|
self.connection:send( self.activeThread_ ) |
|
return self.connection:receive() |
|
end |
|
|
|
function clientMeta.__index:breakpoints() |
|
--if self.status_ == "break" then |
|
-- self.connection:send( "breakpoints" ) |
|
-- self.breakPoints_ = self.connection:receive() |
|
--end |
|
return self.breakPoints_ |
|
end |
|
|
|
function clientMeta.__index:locals( level ) |
|
assert( self.status_ == "break", "Bad status: "..self.status_ ) |
|
self.connection:send( "locals" ) |
|
self.connection:send( self.activeThread_ ) |
|
self.connection:send( level ) |
|
return self.connection:receive() |
|
end |
|
|
|
function clientMeta.__index:upvalues( level ) |
|
assert( self.status_ == "break", "Bad status: "..self.status_ ) |
|
self.connection:send( "upvalues" ) |
|
self.connection:send( self.activeThread_ ) |
|
self.connection:send( level ) |
|
return self.connection:receive() |
|
end |
|
|
|
function clientMeta.__index:evaluate( expr, level ) |
|
assert( self.status_ == "break", "Bad status: "..self.status_ ) |
|
self.connection:send( "evaluate" ) |
|
self.connection:send( expr ) |
|
self.connection:send( self.activeThread_ ) |
|
self.connection:send( level ) |
|
return self.connection:receive() |
|
end |
|
|
|
function clientMeta.__index:releaseValue( id ) |
|
self.connection:send( "releaseValue", "running" ) |
|
self.connection:send( id, "running" ) |
|
end |
|
|
|
function clientMeta.__index:getValue( id ) |
|
assert( self.status_ == "break", "Bad status: "..self.status_ ) |
|
self.connection:send( "getValue" ) |
|
self.connection:send( id ) |
|
return self.connection:receive() |
|
end |
|
|
|
function clientMeta.__index:coroutines() |
|
assert( self.status_ == "break", "Bad status: "..self.status_ ) |
|
self.connection:send( "coroutines" ) |
|
return self.connection:receive() |
|
end |
|
|
|
for _, command in ipairs( { "run", "stepover", "stepin", "stepout" } ) do |
|
clientMeta.__index[command] = function( self ) |
|
assert( self.status_ == "break", "Bad status: "..self.status_ ) |
|
self.connection:send( command ) |
|
--assert( self.connection:receive() == "ack_"..command ) |
|
self.status_ = "running" |
|
end |
|
end |
|
|
|
function clientMeta.__index:breaknow() |
|
assert( self.status_ == "running", "Bad status: "..self.status_ ) |
|
self.connection:send( "break", "running" ) |
|
end |
|
|
|
function clientMeta.__index:cmd_synchronize() |
|
self.connection:send( 0 ) |
|
self.connection:send( true ) |
|
end |
|
|
|
function clientMeta.__index:cmd_break() |
|
assert( self.status_ == "running" ) |
|
--self.connection:send( "ack_break" ) |
|
self.source_ = self.connection:receive() |
|
self.line_ = self.connection:receive() |
|
self.status_ = "break" |
|
assert( type( self.source_ ) == "string" ) |
|
assert( type( self.line_ ) == "number" ) |
|
print( "Client "..self.clientId.." break at "..self.source_.."("..self.line_..")" ) |
|
runEvents_( "onClientBreak", self.clientId ) |
|
end |
|
|
|
function clientMeta.__index.errorHandler( msg ) |
|
if msg == "closed" then return msg end |
|
return debug.traceback( msg ) |
|
end |
|
|
|
function listclients() |
|
local res = {} |
|
for idx, client in pairs( clients ) do |
|
table.insert( res, { clientId = idx } ) |
|
end |
|
return res |
|
end |
|
|
|
function getactiveclient() |
|
return activeClient or 0 |
|
end |
|
|
|
function setactiveclient( idx ) |
|
idx = assert( tonumber( idx ) ) |
|
print( "Setting active client: "..idx ) |
|
activeClient = idx |
|
if clients[idx] == nil then |
|
print( "Warning: no such client" ) |
|
end |
|
end |
|
|
|
function getClient( clientId ) |
|
return clients[clientId] |
|
end |
|
|
|
for _, func in ipairs( { "name", "ip", "status", "source", "line", "callstack", "locals", "coroutines", "run", "breaknow", "stepover", "stepin", "stepout", "setactivethread", "getactivethread", "getcurrentthread" } ) do |
|
_M[func] = function( ... ) |
|
local idx = getactiveclient() |
|
local client = clients[idx] |
|
if client == nil then return "no such client" end |
|
return client[func]( client, ... ) |
|
end |
|
end |
|
|
|
function shutdown() |
|
print( "Shuting down grld.engine..." ) |
|
clients = nil |
|
listeners = {} |
|
end
|
|
|