Graphical Remote Lua Debugger, a debugger for the lua programming language.
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.
 
 
 
 

879 lines
28 KiB

-- see copyright notice in wxLdb.lua
local grldc =
{
net = require( "grldc.net" ),
utilities = require( "grldc.utilities" )
}
local ui =
{
mainWindow = require( "ui.mainWindow" )
}
local lfs = require( "lfs" )
local wx = require( "wx" )
require( "coxpcall" )
local coxpcall = coxpcall
local copcall = copcall
local setmetatable = setmetatable
local ipairs = ipairs
local type = type
local assert = assert
local pairs = pairs
local next = next
local print = print
local tostring = tostring
local string = string
local coroutine = coroutine
local io = io
local debug = debug
local xpcall = xpcall
local table = table
local os = os
module( "wxLdbController" )
local meta = { __index = {} }
local complexValueManagerMeta = { __index = {} }
local function createClientConfig(name)
return {
name = name,
mappings = {},
breakpoints = {},
scrollPositions = {},
}
end
function new( engine, window )
local res =
{
engine = engine,
window = window,
clients = {},
configs = {},
activeClient = nil,
threadsDirty = true,
exiting = false,
requests_ = {},
toListen = {},
}
setmetatable( res, meta )
res.runCoroutine = coroutine.create( function() res:run_() end )
local ok, msg = coroutine.resume( res.runCoroutine )
assert( ok, debug.traceback( res.runCoroutine, msg ) )
return res
end
function meta.__index:ready_()
return self.sleeping and #self.requests_ == 0
end
function meta.__index:addListener( ip, port )
if self.engine.initialized() then
self.engine.listen( ip, port )
else
table.insert( self.toListen, { ip = ip, port = port } )
end
end
function meta.__index:update()
if self:ready_() then
self.sleeping = false
local ok, msg = coroutine.resume( self.runCoroutine )
assert( ok, debug.traceback( self.runCoroutine, msg ) )
elseif self.currentRequest_ ~= nil then
if coroutine.status( self.currentRequest_ ) ~= "suspended" then
print( "Asynchronous request has terminated" )
table.remove( self.requests_, 1 )
self.currentRequest_ = nil
end
elseif self.requests_[1] ~= nil then
print( "Starting asynchronous request..." )
local req = self.requests_[1]
local co = coroutine.create( req )
self.currentRequest_ = co
local ok, msg = coroutine.resume( co )
assert( ok, debug.traceback( co, msg ) )
end
end
function meta.__index:sleep_()
self.sleeping = true
coroutine.yield()
end
function meta.__index:queueRequest_( req )
table.insert( self.requests_, req )
end
function meta.__index:run_()
self:sleep_()
--@GRLD_PROTECTION@
self.engine.init()
for _, listener in ipairs( self.toListen ) do
self.engine.listen( listener.ip, listener.port )
end
self.toListen = {}
self.window:show( true )
for _, cbName in ipairs( { "onNewClient", "onClientBreak", "onClientLost" } ) do
self.engine.registerEvent( cbName, function( ... ) return self[cbName.."_"]( self, ... ) end )
end
local wrapCb = function( cb )
return function( ... )
if self:ready_() then
return cb( ... )
end
end
end
self.window:registerEvent( ui.mainWindow.ID_BREAK, wrapCb( function() self:onDebugCommand_( "breaknow", "running" ) end ) )
self.window:registerEvent( ui.mainWindow.ID_CONTINUE, wrapCb( function() self:onDebugCommand_( "run", "break" ) end ) )
self.window:registerEvent( ui.mainWindow.ID_STEP_OVER, wrapCb( function() self:onDebugCommand_( "stepover", "break" ) end ) )
self.window:registerEvent( ui.mainWindow.ID_STEP_INTO, wrapCb( function() self:onDebugCommand_( "stepin", "break" ) end ) )
self.window:registerEvent( ui.mainWindow.ID_STEP_OUT, wrapCb( function() self:onDebugCommand_( "stepout", "break" ) end ) )
self.window:registerEvent( ui.mainWindow.ID_TOGGLE_BREAKPOINT, wrapCb( function() self:onToggleBreakpoint_() end ) )
self.window:registerEvent( "onBreakPointChanged", wrapCb( function( ... ) self:onBreakPointChanged_( ... ) end ) )
self.window:registerEvent( "onFileOpen", wrapCb( function( ... ) self:onFileOpen_( ... ) end ) )
self.window:registerEvent( "onFileClosed", wrapCb( function( ... ) self:onFileClosed_( ... ) end ) )
self.window:registerEvent( "onScrollChanged", wrapCb(function( ... ) self:onScrollChanged_( ...) end ) )
self.window:registerEvent( "onApplicationExiting", wrapCb( function( ... ) self:onApplicationExiting_( ... ) end ) )
self.window.threads:registerEvent( "onThreadClicked", wrapCb( function( ... ) self:onThreadClicked_( ... ) end ) )
self.window.threads:registerEvent( "onBreakOnConnectionChanged", wrapCb( function( ... ) self:onBreakOnConnectionChanged_( ... ) end ) )
self.window.callstack:registerEvent( "onCallstackClicked", wrapCb( function( ... ) self:onCallstackClicked_( ... ) end ) )
self.window.watch.evaluateCallback = wrapCb( function( expr ) return self:evaluateExpression_( expr ) end )
self.configs.global = createClientConfig('global')
self:loadConfig_( "global" )
self:sleep_()
while not self.exiting do
for clientId, clientData in pairs( self.clients ) do
coxpcall( function()
if clientData.invalidateTimer ~= nil then
if os.time() >= clientData.invalidateTimer then
clientData.invalidateTimer = nil
assert( clientId == self.activeClient )
self:invalidateState_( true )
end
end
if clientData.config.dirty and os.time() >= (clientData.config.lastConfigSave or 0) + 10 then
self:saveConfig_( clientData.config.name )
end
if clientData.dirty then
self.window:setActive()
clientData.dirty = false
local client = self.engine.getClient( clientId )
if client == nil then
self.threadsDirty = true
elseif clientId == self.activeClient then
if client:status() == "break" then
client:setactivethread( clientData.activeThread )
self.threadsDirty = true
local callstack = client:callstack()
if type( callstack ) ~= "table" then
-- if we can not get the callstack, we try to switch to the current thread
clientData.activeThread = "current"
client:setactivethread( clientData.activeThread )
callstack = client:callstack()
end
self:refreshCallstack_( callstack )
self:refreshSourceFocus_( callstack, clientData.activeLevel )
end
self:refreshBreakPoints_()
end
elseif clientData.lastUpdate == nil or os.time() > clientData.lastUpdate + 2 then
clientData.lastUpdate = os.time()
self.engine.getClient( clientId ):checkConnection()
end
end, function( msg ) print( "Error refreshing client "..clientId ) print( msg ) print( debug.traceback() ) end )
end
if self.threadsDirty then
coxpcall( function()
self.threadsDirty = false
self:refreshThreads_()
end, function( msg ) print( "Error refreshing threads" ) print( msg ) print( debug.traceback() ) end )
end
self:sleep_()
end
end
function meta.__index:getActiveClientConfig_()
local activeClientId = self.activeClient
if activeClientId == nil then
return self.configs.global
end
local clientData = self.clients[activeClientId]
if clientData == nil then
return self.configs.global
end
return clientData.config
end
function meta.__index:evaluateExpression_( expr )
local clientId = self.activeClient
local client = self.engine.getClient( clientId )
if client == nil then return { { name = "<no such client>" } } end
if client:status() == "running" then return { { name = "<can't evaluate expression while client is running>" } } end
local clientData = assert( self.clients[clientId] )
local results = client:evaluate( expr, clientData.activeLevel )
complexValueManagerMeta.init( results, self.engine, clientId )
return results
end
function meta.__index:onCallstackClicked_( level )
local clientId = self.activeClient
local clientData = self.clients[clientId]
if clientData == nil then return end
clientData.activeLevel = level
clientData.dirty = true
end
function meta.__index:refreshSourcePageFocus_( remoteSource, line )
local clientId = self.activeClient
local clientData = assert( self.clients[clientId] )
local sourceType = string.sub( remoteSource, 1, 1 )
if sourceType == "@" then
self.window:raise()
print( "Setting focus to "..remoteSource.."("..line..")" )
remoteSource = grldc.utilities.normalizePath( string.sub( remoteSource, 2 ) )
local source, remotePath, remoteFile = self:getLocalSource_( clientId, remoteSource )
if source == nil then
print( "Prompting mount path..." )
local mount, path = self.window:promptMountPath( remotePath, nil, remoteFile )
print( mount, path )
if mount ~= nil then
mount = grldc.utilities.normalizePath( mount )
path = grldc.utilities.normalizePath( path, lfs.currentdir() )
print( mount, path )
local mountEnd = string.sub( mount, -1 )
if mountEnd ~= "/" and mountEnd ~= "\\" then
mount = mount.."/"
end
print( mount, path )
clientData.config.mappings[mount] = path
clientData.config.dirty = true
source = self:getLocalSource_( clientId, remoteSource )
assert( source ~= nil )
end
end
if source ~= nil then
print( source )
source = grldc.utilities.normalizePath( source )
self:setSourceFocus_( "@"..source, line )
end
--print( source )
return source
end
end
function meta.__index:refreshSourceFocus_( callstack, level )
local clientId = self.activeClient
local clientData = assert( self.clients[clientId] )
if type( callstack ) == "table" and callstack[level] ~= nil then
local remoteSource = callstack[level].source
local line = callstack[level].line
local source = self:refreshSourcePageFocus_( remoteSource, line )
self:setPointers_( level, source, line )
self:refreshPointers_()
self:refreshWatches_( level )
end
end
function meta.__index:setPointers_( level, source, line )
self.pointer = { level = level, source = source, line = line }
end
function meta.__index:refreshPointers_()
self.window:clearMarkers()
if self.pointer ~= nil then
local source = self.pointer.source
local level = self.pointer.level
local line = self.pointer.line
if source ~= nil then
if level == 1 then
self.window:setCurrentLine( "@"..source, line )
else
self.window:getSourcePage( "@"..source ):addOtherLine( line )
end
end
end
end
function meta.__index:refreshWatches_( level )
local clientId = self.activeClient
local client = self.engine.getClient( clientId )
if client == nil then return end
local autoVariables = {}
local locals = client:locals( level )
if type( locals ) == "string" then
self.window.auto:setData( { { name = "<error evaluating local variables>", value = locals } } )
else
for _, entry in ipairs( locals ) do
table.insert( autoVariables, { name = "[local] "..entry.name, value = entry.value } )
end
end
local upvalues = client:upvalues( level )
if type( upvalues ) == "string" then
self.window.auto:setData( { { name = "<error evaluating upvalues>", value = locals } } )
else
for _, entry in ipairs( upvalues ) do
table.insert( autoVariables, { name = "[upvalue] "..entry.name, value = entry.value } )
end
end
complexValueManagerMeta.init( autoVariables, self.engine, clientId )
self.window.auto:setData( autoVariables )
self.window.watch:refresh()
end
function meta.__index:onToggleBreakpoint_()
local source, line = self.window:findSourcePageFocus()
self:onBreakPointChanged_( source, line )
end
function meta.__index:onBreakPointChanged_( source, line )
local clientId = self.activeClient
local config = nil
if clientId == nil or self.clients[clientId] == nil then
config = self.configs.global
else
local clientData = self.clients[clientId]
if clientData == nil then return end
config = clientData.config
end
if config.breakpoints[source] == nil then
config.breakpoints[source] = {}
end
config.breakpoints[source][line] = not config.breakpoints[source][line]
newValue = config.breakpoints[source][line]
if not newValue then
config.breakpoints[source][line] = nil
end
print( "Setting breakpoint at "..source.."("..line..") to "..tostring(newValue) )
assert( string.sub( source, 1, 1 ) == "@" )
source = string.sub( source, 2 )
for clientId, clientData in pairs( self.clients ) do
if clientData.config == config then
local client = self.engine.getClient( clientId )
if client == nil then return end
local remoteSource, dir = self:getRemoteSource_( clientId, source )
self:queueRequest_( function()
if remoteSource == nil then
print( "Can't find remote source corresponding to "..source )
print( "Prompting mount path..." )
local mount, path = self.window:promptMountPath( nil, dir )
print( mount, path )
if mount ~= nil then
mount = grldc.utilities.normalizePath( mount )
path = grldc.utilities.normalizePath( path, lfs.currentdir() )
print( mount, path )
local mountEnd = string.sub( mount, -1 )
if mountEnd ~= "/" and mountEnd ~= "\\" then
mount = mount.."/"
end
print( mount, path )
clientData.config.mappings[mount] = path
remoteSource, dir = self:getRemoteSource_( clientId, source )
assert( remoteSource ~= nil )
end
end
if remoteSource ~= nil then
client:setbreakpoint( "@"..remoteSource, line, newValue )
clientData.config.dirty = true
end
self:refreshBreakPoints_()
end )
end
end
self:refreshBreakPoints_()
end
function meta.__index:onApplicationExiting_()
for id, clientData in pairs( self.clients ) do
if clientData.config.dirty then
self:saveConfig_( clientData.config.name )
end
end
self.window.threads:setData( nil )
end
function meta.__index:refreshScrollPosition_()
local config = self:getActiveClientConfig_()
for source, sp in pairs( config.scrollPositions ) do
local page = self.window:getSourcePage( source )
if page ~= nil then
page:SetScrollPos(sp)
end
end
end
function meta.__index:refreshBreakPoints_()
local clientId = self.activeClient
local config = nil
local client = nil
if self.activeClient == nil or self.clients[clientId] == nil then
config = self.configs.global
else
config = self.clients[clientId].config
client = self.engine.getClient( clientId )
end
local remoteBreakPoints = {}
if client ~= nil then
remoteBreakPoints = client:breakpoints()
end
self.window:clearBreakPoints()
local goodBreakpoints = {}
for remoteSource, lines in pairs( remoteBreakPoints ) do
if next( lines ) ~= nil then
local source = self:getLocalSource_( clientId, string.sub( remoteSource, 2 ) )
if source ~= nil then
local page = self.window:findSourcePage( "@"..source )
if page ~= nil then
if goodBreakpoints["@"..source] == nil then
goodBreakpoints["@"..source] = {}
end
for line, value in pairs( lines ) do
if value then
page:addBreakPoint( line )
goodBreakpoints["@"..source][line] = true
end
end
else
print( "Can't find source page for breakpoint in file "..source )
end
else
print( "Can't find source corresponding to remote source "..remoteSource )
end
end
end
for source, lines in pairs( config.breakpoints ) do
local page = self.window:findSourcePage( source )
if page ~= nil then
for line, value in pairs( lines ) do
if value then
if goodBreakpoints[source] == nil or not goodBreakpoints[source][line] then
page:addBreakPoint( line, true )
end
end
end
end
end
end
function meta.__index:onFileOpen_( path )
print( "onFileOpen: "..path )
source = "@"..grldc.utilities.normalizePath( path )
print( "normalized path: "..source )
self:setSourceFocus_( source, 1 )
self:refreshPointers_()
self:refreshBreakPoints_()
self:refreshScrollPosition_()
end
function meta.__index:onFileClosed_( source )
for id, clientData in pairs( self.clients ) do
clientData.config.dirty = true
end
end
function meta.__index:onScrollChanged_( source, position )
local clientConfig = self:getActiveClientConfig_()
clientConfig.scrollPositions[source] = position
clientConfig.dirty = true
end
function meta.__index:onThreadClicked_( clientId, threadId )
print( "Thread clicked: client="..clientId..", thread="..threadId )
local clientData = self.clients[clientId]
if clientData == nil then return end
clientData.activeThread = threadId
clientData.activeLevel = 1
self:setActiveClient_( clientId )
end
function meta.__index:onBreakOnConnectionChanged_( clientId, newValue )
local clientData = self.clients[clientId]
if clientData == nil then return end
clientData.config.breakOnConnection = newValue
clientData.config.dirty = true
end
function meta.__index:onDebugCommand_( command, neededState, targetClientId )
if targetClientId == nil then targetClientId = self.activeClient end
if targetClientId == nil then return end
local client = self.engine.getClient( targetClientId )
if client == nil then
print( "No client "..targetClientId )
return
end
if neededState ~= nil and client:status() ~= neededState then return end
self:invalidateState_( false )
local clientData = assert( self.clients[targetClientId] )
local ok, msg = xpcall( function() client[command]( client ) end, debug.traceback )
if not ok then
print( msg )
end
clientData.dirty = true
end
function meta.__index:invalidateState_( immediate )
local clientData = self.clients[self.activeClient]
if not clientData or immediate then
if clientData ~= nil then
clientData.invalidateTimer = nil
end
self.threadsDirty = true
self.pointer = nil
self.window:clearMarkers()
self.window.callstack:setData( nil )
self:refreshBreakPoints_()
local client = self.engine.getClient( self.activeClient )
if client == nil then
self.window.auto:clear()
self.window.watch:refresh()
else
self.window.auto:setData( nil )
self.window.watch:refresh()
end
else
clientData.invalidateTimer = os.time() + 1
end
end
function meta.__index:getLocalSource_( clientId, source )
--print( source )
local _, _, dir, file = string.find( source, "(.*[/\\])(.*)" )
assert( dir ~= nil and file ~= nil )
local clientData = assert( self.clients[clientId] )
for mount, path in pairs( clientData.config.mappings ) do
if string.sub( dir, 1, #mount ) == mount then
local s = string.sub( dir, #mount, #mount )
assert( s == "/" or s == "\\" )
local r = string.sub( dir, #mount + 1 )
if r ~= "" then r = r.."/" end
local localPath = grldc.utilities.normalizePath( path.."/"..r..file, lfs.currentdir() )
--print( localPath )
if lfs.attributes( localPath, "mode" ) == "file" then
return localPath, dir, file
end
end
end
return nil, dir, file
end
function meta.__index:getRemoteSource_( clientId, localSource )
print( "Searching remote source corresponding to local source "..localSource )
local _, _, dir, file = string.find( localSource, "(.*[/\\])(.*)" )
assert( dir ~= nil and file ~= nil )
local clientData = assert( self.clients[clientId] )
local bestScore = -1
local bestPath = nil
for mount, path in pairs( clientData.config.mappings ) do
if string.sub( dir, 1, #path ) == path then
local s = string.sub( dir, #path+1, #path+1 )
if s == "/" or s == "\\" then
local r = string.sub( dir, #path + 2 )
if r ~= "" then r = r.."/" end
local remotePath = grldc.utilities.normalizePath( mount..r..file )
local score = 0
string.gsub( mount, "[/\\]", function() score = score + 1 end )
print( "Candidate (score="..score..") : "..remotePath )
if score > bestScore then
bestScore = score
bestPath = remotePath
end
end
end
end
return bestPath, dir
end
function meta.__index:setSourceFocus_( source, line )
local exist = (self.window:findSourcePage( source ) ~= nil)
local page = self.window:getSourcePage( source )
page:setFocus( line )
self.window:setSourcePageFocus( source )
if not exist then
for id, clientData in pairs( self.clients ) do
clientData.config.dirty = true
end
end
end
function meta.__index:refreshCallstack_( callstack )
if type( callstack ) == "string" then
self.window.callstack:setData( { { name = callstack, type = "", source = "", line = "" } } )
elseif callstack[1] == nil then
self.window.callstack:setData( { { name = "empty callstack", type = "", source = "", line = "" } } )
else
local callstackData = {}
for level, data in ipairs( callstack ) do
local entry = {}
if data.namewhat ~= "" then
assert( data.name ~= nil )
entry.name = "["..data.namewhat.."] "..data.name
else
entry.name = "???"
end
entry.type = data.what
entry.source = data.source
if data.line < 0 then
entry.line = ""
else
entry.line = tostring( data.line )
end
callstackData[level] = entry
end
self.window.callstack:setData( callstackData )
end
end
function meta.__index:refreshThreads_()
local data = {}
for clientId, clientData in pairs( self.clients ) do
local client = self.engine.getClient( clientId )
if client == nil then
print( "Client does not exist anymore: "..clientId )
if self.activeClient == clientId then
self.activeClient = nil
self:invalidateState_( true )
end
self.clients[clientId] = nil
else
local cdata = {}
cdata.name = client:name()
cdata.ip = client:ip()
cdata.clientId = client.clientId
cdata.coroutines = {}
cdata.status = client:status()
cdata.active = (clientId == self.activeClient)
cdata.breakOnConnection = clientData.config.breakOnConnection
if cdata.status == "break" then
local current = client:getcurrentthread()
local active = client:getactivethread()
if active == "current" then
active = current
end
table.insert( cdata.coroutines, { id = "main", current = (current == "main"), active = (active=="main" and cdata.clientId == self.activeClient and client:getactivethread() ~= "current") } )
local coroutines = client:coroutines()
--print( coroutines )
for _, data in ipairs( coroutines ) do
local codata = {}
codata.id = data.id
codata.current = (current == codata.id)
codata.active = (active == codata.id and cdata.clientId == self.activeClient and client:getactivethread() ~= "current")
table.insert( cdata.coroutines, codata )
end
end
table.insert( data, cdata )
end
end
self.window.threads:setData( data )
end
function meta.__index:setActiveClient_( clientId )
self:invalidateState_( true )
self.activeClient = clientId
self.clients[clientId].dirty = true
self.threadsDirty = true
local clientConfig = self.clients[clientId].config
for source, lines in pairs( self.configs.global.breakpoints ) do
for line, _ in pairs( lines ) do
if clientConfig.breakpoints[source] == nil or not clientConfig.breakpoints[source][line] then
self:onBreakPointChanged_( source, line )
end
lines[line] = nil
end
end
self:refreshBreakPoints_()
end
function meta.__index:onNewClient_( clientId )
local client = self.engine.getClient( clientId )
local name = client:name()
if self.configs[name] == nil then
self.configs[name] = createClientConfig(name)
self:loadConfig_( name )
end
self.clients[clientId] = { dirty = true, activeThread = "current", activeLevel = 1, config = self.configs[name] }
for source, lines in pairs( self.configs[name].breakpoints ) do
assert( string.sub( source, 1, 1 ) == "@" )
source = string.sub( source, 2 )
local remoteSource, dir = self:getRemoteSource_( clientId, source )
if remoteSource ~= nil then
for line, value in pairs( lines ) do
if value then
client:setbreakpoint( "@"..remoteSource, line, true )
end
end
end
end
self.threadsDirty = true
if self.activeClient == nil then
self:setActiveClient_( clientId )
end
if not self.configs[name].breakOnConnection then
self.clients[clientId].ignoreNextBreak = true
end
end
function meta.__index:onClientBreak_( clientId )
local clientData = assert( self.clients[clientId] )
clientData.invalidateTimer = nil
clientData.dirty = true
clientData.activeLevel = 1
self.threadsDirty = true
if clientData.ignoreNextBreak then
clientData.ignoreNextBreak = false
self:onDebugCommand_( "run", "break", clientId )
end
end
function meta.__index:onClientLost_( clientId )
local clientData = assert( self.clients[clientId] )
if clientData.config.dirty then
self:saveConfig_( clientData.config.name )
end
self.threadsDirty = true
end
function meta.__index:saveConfig_( name )
local clientConfig = assert( self.configs[name] )
clientConfig.dirty = false
local name = clientConfig.name
local openFiles = {}
for source, page in pairs( self.window:getSourcePages() ) do
openFiles[page.pageIdx+1] = source
end
local breakpoints = clientConfig.breakpoints
local scrollPositions = clientConfig.scrollPositions
local path = "clients/"..name.."/config.lua"
lfs.mkdir( "clients" )
lfs.mkdir( "clients/"..name )
local file = assert( io.open( path, "w" ) )
file:write( grldc.net.serialize( {
mappings = clientConfig.mappings,
openFiles = openFiles,
breakpoints = breakpoints,
scrollPositions = scrollPositions,
breakOnConnection = clientConfig.breakOnConnection
} ) )
file:close()
print( "Saved config \""..name.."\"" )
clientConfig.lastConfigSave = os.time()
end
function meta.__index:loadConfig_( name )
local clientConfig = self.configs[name]
local path = "clients/"..name.."/config.lua"
local file = io.open( path, "r" )
if file ~= nil then
local config = grldc.net.deserialize( file:read( "*a" ) )
file:close()
clientConfig.mappings = config.mappings
clientConfig.breakOnConnection = config.breakOnConnection
if config.scrollPositions ~= nil then
for source, sp in pairs( config.scrollPositions ) do
clientConfig.scrollPositions[source] = sp
end
end
for _, file in ipairs( config.openFiles ) do
local page, newlyLoaded = self.window:getSourcePage( file )
if page ~= nil and newlyLoaded then
local sp = clientConfig.scrollPositions[file]
print("scroll to", sp)
if sp ~= nil then
page:SetScrollPos(sp)
end
end
end
if clientConfig.breakpoints ~= nil then
for source, bp in pairs( clientConfig.breakpoints ) do
if clientConfig.breakpoints[source] == nil then
clientConfig.breakpoints[source] = {}
end
for line, _ in pairs( bp ) do
clientConfig.breakpoints[source][line] = true
end
end
end
end
if clientConfig.breakOnConnection == nil then
clientConfig.breakOnConnection = true
end
clientConfig.dirty = false
end
function complexValueManagerMeta.init( variables, engine, clientId )
local manager = nil
for _, entry in pairs( variables ) do
if type( entry.value ) == "table" and entry.value.id ~= nil then
if manager == nil then
manager = { engine = engine, clientId = clientId }
end
assert( entry.value.manager == nil )
entry.value.manager = manager
setmetatable( entry.value, complexValueManagerMeta )
end
end
end
function complexValueManagerMeta.__index:release()
local client = self.manager.engine.getClient( self.manager.clientId )
if client == nil then return end
client:releaseValue( self.id )
end
function complexValueManagerMeta.__index:get()
local client = self.manager.engine.getClient( self.manager.clientId )
if client == nil then return { ERROR = "connection with client lost" } end
local value = client:getValue( self.id )
complexValueManagerMeta.init( value, self.manager.engine, self.manager.clientId )
return value
end