Merge pull request #643 from MihailRis/debugging-server
Debugging-server
This commit is contained in:
commit
f16da17991
@ -587,6 +587,7 @@ function __process_post_runnables()
|
||||
__vc_named_coroutines[name] = nil
|
||||
end
|
||||
|
||||
debug.pull_events()
|
||||
network.__process_events()
|
||||
block.__process_register_events()
|
||||
block.__perform_ticks(time.delta())
|
||||
|
||||
@ -1,3 +1,144 @@
|
||||
local breakpoints = {}
|
||||
local dbg_steps_mode = false
|
||||
local dbg_step_into_func = false
|
||||
local hook_lock = false
|
||||
local current_func
|
||||
local current_func_stack_size
|
||||
|
||||
local _debug_getinfo = debug.getinfo
|
||||
local _debug_getlocal = debug.getlocal
|
||||
local __pause = debug.pause
|
||||
local __error = error
|
||||
local __sethook = debug.sethook
|
||||
|
||||
-- 'return' hook not called for some functions
|
||||
-- todo: speedup
|
||||
local function calc_stack_size()
|
||||
local s = debug.traceback("", 2)
|
||||
local count = 0
|
||||
for i in s:gmatch("\n") do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
local is_debugging = debug.is_debugging()
|
||||
if is_debugging then
|
||||
__sethook(function (e, line)
|
||||
if e == "return" then
|
||||
local info = _debug_getinfo(2)
|
||||
if info.func == current_func then
|
||||
current_func = nil
|
||||
end
|
||||
end
|
||||
if dbg_steps_mode and not hook_lock then
|
||||
hook_lock = true
|
||||
|
||||
if not dbg_step_into_func then
|
||||
local func = _debug_getinfo(2).func
|
||||
if func ~= current_func then
|
||||
return
|
||||
end
|
||||
if current_func_stack_size ~= calc_stack_size() then
|
||||
return
|
||||
end
|
||||
end
|
||||
current_func = func
|
||||
__pause("step")
|
||||
debug.pull_events()
|
||||
end
|
||||
hook_lock = false
|
||||
local bps = breakpoints[line]
|
||||
if not bps then
|
||||
return
|
||||
end
|
||||
local source = _debug_getinfo(2).source
|
||||
if not bps[source] then
|
||||
return
|
||||
end
|
||||
current_func = _debug_getinfo(2).func
|
||||
current_func_stack_size = calc_stack_size()
|
||||
__pause("breakpoint")
|
||||
debug.pull_events()
|
||||
end, "lr")
|
||||
end
|
||||
|
||||
local DBG_EVENT_SET_BREAKPOINT = 1
|
||||
local DBG_EVENT_RM_BREAKPOINT = 2
|
||||
local DBG_EVENT_STEP = 3
|
||||
local DBG_EVENT_STEP_INTO_FUNCTION = 4
|
||||
local DBG_EVENT_RESUME = 5
|
||||
local DBG_EVENT_GET_VALUE = 6
|
||||
local __pull_events = debug.__pull_events
|
||||
local __sendvalue = debug.__sendvalue
|
||||
debug.__pull_events = nil
|
||||
debug.__sendvalue = nil
|
||||
|
||||
function debug.pull_events()
|
||||
if not is_debugging then
|
||||
return
|
||||
end
|
||||
if not debug.is_debugging() then
|
||||
is_debugging = false
|
||||
__sethook()
|
||||
end
|
||||
local events = __pull_events()
|
||||
if not events then
|
||||
return
|
||||
end
|
||||
for i, event in ipairs(events) do
|
||||
if event[1] == DBG_EVENT_SET_BREAKPOINT then
|
||||
debug.set_breakpoint(event[2], event[3])
|
||||
elseif event[1] == DBG_EVENT_RM_BREAKPOINT then
|
||||
debug.remove_breakpoint(event[2], event[3])
|
||||
elseif event[1] == DBG_EVENT_STEP then
|
||||
dbg_steps_mode = true
|
||||
dbg_step_into_func = false
|
||||
elseif event[1] == DBG_EVENT_STEP_INTO_FUNCTION then
|
||||
dbg_steps_mode = true
|
||||
dbg_step_into_func = true
|
||||
elseif event[1] == DBG_EVENT_RESUME then
|
||||
dbg_steps_mode = false
|
||||
dbg_step_into_func = false
|
||||
elseif event[1] == DBG_EVENT_GET_VALUE then
|
||||
local _, value = _debug_getlocal(event[2] + 3, event[3])
|
||||
for _, key in ipairs(event[4]) do
|
||||
if value == nil then
|
||||
value = "error: index nil value"
|
||||
break
|
||||
end
|
||||
value = value[key]
|
||||
end
|
||||
__sendvalue(value, event[2], event[3], event[4])
|
||||
__pause()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function debug.set_breakpoint(source, line)
|
||||
local bps = breakpoints[line]
|
||||
if not bps then
|
||||
bps = {}
|
||||
breakpoints[line] = bps
|
||||
end
|
||||
bps[source] = true
|
||||
end
|
||||
|
||||
function debug.remove_breakpoint(source, line)
|
||||
local bps = breakpoints[line]
|
||||
if not bps then
|
||||
return
|
||||
end
|
||||
bps[source] = nil
|
||||
end
|
||||
|
||||
function error(message, level)
|
||||
if is_debugging then
|
||||
__pause("exception", message)
|
||||
end
|
||||
__error(message, level)
|
||||
end
|
||||
|
||||
-- Lua has no parallelizm, also _set_data does not call any lua functions so
|
||||
-- may be reused one global ffi buffer per lua_State
|
||||
local canvas_ffi_buffer
|
||||
@ -473,8 +614,6 @@ function file.readlines(path)
|
||||
return lines
|
||||
end
|
||||
|
||||
local _debug_getinfo = debug.getinfo
|
||||
|
||||
function debug.count_frames()
|
||||
local frames = 1
|
||||
while true do
|
||||
|
||||
315
src/devtools/DebuggingServer.cpp
Normal file
315
src/devtools/DebuggingServer.cpp
Normal file
@ -0,0 +1,315 @@
|
||||
#include "DebuggingServer.hpp"
|
||||
|
||||
#include "engine/Engine.hpp"
|
||||
#include "network/Network.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "coders/json.hpp"
|
||||
|
||||
using namespace devtools;
|
||||
|
||||
static debug::Logger logger("debug-server");
|
||||
|
||||
ClientConnection::~ClientConnection() {
|
||||
if (auto connection = dynamic_cast<network::ReadableConnection*>(
|
||||
network.getConnection(this->connection, true)
|
||||
)) {
|
||||
connection->close();
|
||||
}
|
||||
}
|
||||
|
||||
bool ClientConnection::initiate(network::ReadableConnection* connection) {
|
||||
if (connection->available() < 8) {
|
||||
return false;
|
||||
}
|
||||
char buffer[8] {};
|
||||
char expected[8] {};
|
||||
std::memcpy(expected, VCDBG_MAGIC, sizeof(VCDBG_MAGIC));
|
||||
expected[6] = VCDBG_VERSION >> 8;
|
||||
expected[7] = VCDBG_VERSION & 0xFF;
|
||||
connection->recv(buffer, sizeof(VCDBG_MAGIC));
|
||||
|
||||
connection->send(expected, sizeof(VCDBG_MAGIC));
|
||||
if (std::memcmp(expected, buffer, sizeof(VCDBG_MAGIC)) == 0) {
|
||||
initiated = true;
|
||||
return false;
|
||||
} else {
|
||||
connection->close(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string ClientConnection::read() {
|
||||
auto connection = dynamic_cast<network::ReadableConnection*>(
|
||||
network.getConnection(this->connection, true)
|
||||
);
|
||||
if (connection == nullptr) {
|
||||
return "";
|
||||
}
|
||||
if (!initiated) {
|
||||
if (initiate(connection)) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
if (messageLength == 0) {
|
||||
if (connection->available() >= sizeof(int32_t)) {
|
||||
int32_t length = 0;
|
||||
connection->recv(reinterpret_cast<char*>(&length), sizeof(int32_t));
|
||||
if (length <= 0) {
|
||||
logger.error() << "invalid message length " << length;
|
||||
} else {
|
||||
logger.info() << "message length " << length;
|
||||
messageLength = length;
|
||||
}
|
||||
}
|
||||
} else if (connection->available() >= messageLength) {
|
||||
std::string string(messageLength, 0);
|
||||
connection->recv(string.data(), messageLength);
|
||||
messageLength = 0;
|
||||
return string;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void ClientConnection::send(const dv::value& object) {
|
||||
auto connection = dynamic_cast<network::ReadableConnection*>(
|
||||
network.getConnection(this->connection, true)
|
||||
);
|
||||
if (connection == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto message = json::stringify(object, false);
|
||||
int32_t length = message.length();
|
||||
connection->send(reinterpret_cast<char*>(&length), sizeof(int32_t));
|
||||
connection->send(message.data(), length);
|
||||
}
|
||||
|
||||
void ClientConnection::sendResponse(const std::string& type) {
|
||||
send(dv::object({{"type", type}}));
|
||||
}
|
||||
|
||||
bool ClientConnection::alive() const {
|
||||
return network.getConnection(this->connection, true) != nullptr;
|
||||
}
|
||||
|
||||
static network::Server& create_tcp_server(
|
||||
DebuggingServer& dbgServer, Engine& engine, int port
|
||||
) {
|
||||
auto& network = engine.getNetwork();
|
||||
u64id_t serverId = network.openTcpServer(
|
||||
port,
|
||||
[&network, &dbgServer](u64id_t sid, u64id_t id) {
|
||||
auto& connection = dynamic_cast<network::ReadableConnection&>(
|
||||
*network.getConnection(id, true)
|
||||
);
|
||||
connection.setPrivate(true);
|
||||
logger.info() << "connected client " << id << ": "
|
||||
<< connection.getAddress() << ":"
|
||||
<< connection.getPort();
|
||||
dbgServer.setClient(id);
|
||||
}
|
||||
);
|
||||
auto& server = *network.getServer(serverId, true);
|
||||
server.setPrivate(true);
|
||||
|
||||
auto& tcpServer = dynamic_cast<network::TcpServer&>(server);
|
||||
tcpServer.setMaxClientsConnected(1);
|
||||
|
||||
logger.info() << "tcp debugging server open at port " << server.getPort();
|
||||
|
||||
return tcpServer;
|
||||
}
|
||||
|
||||
static network::Server& create_server(
|
||||
DebuggingServer& dbgServer, Engine& engine, const std::string& serverString
|
||||
) {
|
||||
logger.info() << "starting debugging server";
|
||||
|
||||
size_t sepPos = serverString.find(':');
|
||||
if (sepPos == std::string::npos) {
|
||||
throw std::runtime_error("invalid debugging server configuration string");
|
||||
}
|
||||
auto transport = serverString.substr(0, sepPos);
|
||||
if (transport == "tcp") {
|
||||
int port;
|
||||
try {
|
||||
port = std::stoi(serverString.substr(sepPos + 1));
|
||||
} catch (const std::exception& err) {
|
||||
throw std::runtime_error("invalid tcp port");
|
||||
}
|
||||
return create_tcp_server(dbgServer, engine, port);
|
||||
} else {
|
||||
throw std::runtime_error(
|
||||
"unsupported debugging server transport '" + transport + "'"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DebuggingServer::DebuggingServer(
|
||||
Engine& engine, const std::string& serverString
|
||||
)
|
||||
: engine(engine),
|
||||
server(create_server(*this, engine, serverString)),
|
||||
connection(nullptr) {
|
||||
}
|
||||
|
||||
DebuggingServer::~DebuggingServer() {
|
||||
logger.info() << "stopping debugging server";
|
||||
server.close();
|
||||
}
|
||||
|
||||
|
||||
bool DebuggingServer::update() {
|
||||
if (connection == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::string message = connection->read();
|
||||
if (message.empty()) {
|
||||
if (!connection->alive()) {
|
||||
bool status = performCommand(disconnectAction, dv::object());
|
||||
connection.reset();
|
||||
return status;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
logger.debug() << "received: " << message;
|
||||
try {
|
||||
auto obj = json::parse(message);
|
||||
if (!obj.has("type")) {
|
||||
logger.error() << "missing message type";
|
||||
return false;
|
||||
}
|
||||
const auto& type = obj["type"].asString();
|
||||
if (performCommand(type, obj)) {
|
||||
connection->sendResponse("resumed");
|
||||
return true;
|
||||
}
|
||||
} catch (const std::runtime_error& err) {
|
||||
logger.error() << "could not to parse message: " << err.what();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DebuggingServer::performCommand(
|
||||
const std::string& type, const dv::value& map
|
||||
) {
|
||||
if (!connectionEstablished && type == "connect") {
|
||||
map.at("disconnect-action").get(disconnectAction);
|
||||
connectionEstablished = true;
|
||||
logger.info() << "client connection established";
|
||||
connection->sendResponse("success");
|
||||
}
|
||||
if (!connectionEstablished) {
|
||||
return false;
|
||||
}
|
||||
if (type == "terminate") {
|
||||
engine.quit();
|
||||
connection->sendResponse("success");
|
||||
} else if (type == "detach") {
|
||||
connection->sendResponse("success");
|
||||
connection.reset();
|
||||
engine.detachDebugger();
|
||||
return false;
|
||||
} else if (type == "set-breakpoint" || type == "remove-breakpoint") {
|
||||
if (!map.has("source") || !map.has("line"))
|
||||
return false;
|
||||
breakpointEvents.push_back(DebuggingEvent {
|
||||
type[0] == 's'
|
||||
? DebuggingEventType::SET_BREAKPOINT
|
||||
: DebuggingEventType::REMOVE_BREAKPOINT,
|
||||
BreakpointEventDto {
|
||||
map["source"].asString(),
|
||||
static_cast<int>(map["line"].asInteger()),
|
||||
}
|
||||
});
|
||||
} else if (type == "step" || type == "step-into-function") {
|
||||
breakpointEvents.push_back(DebuggingEvent {
|
||||
type == "step"
|
||||
? DebuggingEventType::STEP
|
||||
: DebuggingEventType::STEP_INTO_FUNCTION,
|
||||
SignalEventDto {}
|
||||
});
|
||||
return true;
|
||||
} else if (type == "resume") {
|
||||
breakpointEvents.push_back(DebuggingEvent {
|
||||
DebuggingEventType::RESUME, SignalEventDto {}});
|
||||
return true;
|
||||
} else if (type == "get-value") {
|
||||
if (!map.has("frame") || !map.has("local") || !map.has("path"))
|
||||
return false;
|
||||
|
||||
int frame = map["frame"].asInteger();
|
||||
int localIndex = map["local"].asInteger();
|
||||
|
||||
ValuePath path;
|
||||
for (const auto& segment : map["path"]) {
|
||||
if (segment.isString()) {
|
||||
path.emplace_back(segment.asString());
|
||||
} else {
|
||||
path.emplace_back(static_cast<int>(segment.asInteger()));
|
||||
}
|
||||
}
|
||||
breakpointEvents.push_back(DebuggingEvent {
|
||||
DebuggingEventType::GET_VALUE, GetValueEventDto {
|
||||
frame, localIndex, std::move(path)
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
logger.error() << "unsupported command '" << type << "'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DebuggingServer::pause(
|
||||
std::string&& reason, std::string&& message, dv::value&& stackTrace
|
||||
) {
|
||||
if (connection == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto response = dv::object({{"type", std::string("paused")}});
|
||||
if (!reason.empty()) {
|
||||
response["reason"] = std::move(reason);
|
||||
}
|
||||
if (!message.empty()) {
|
||||
response["message"] = std::move(message);
|
||||
}
|
||||
if (stackTrace != nullptr) {
|
||||
response["stack"] = std::move(stackTrace);
|
||||
}
|
||||
connection->send(std::move(response));
|
||||
engine.startPauseLoop();
|
||||
}
|
||||
|
||||
void DebuggingServer::sendValue(
|
||||
dv::value&& value, int frame, int local, ValuePath&& path
|
||||
) {
|
||||
auto pathValue = dv::list();
|
||||
for (const auto& segment : path) {
|
||||
if (auto string = std::get_if<std::string>(&segment)) {
|
||||
pathValue.add(*string);
|
||||
} else {
|
||||
pathValue.add(std::get<int>(segment));
|
||||
}
|
||||
}
|
||||
connection->send(dv::object({
|
||||
{"type", std::string("value")},
|
||||
{"frame", frame},
|
||||
{"local", local},
|
||||
{"path", std::move(pathValue)},
|
||||
{"value", std::move(value)},
|
||||
}));
|
||||
}
|
||||
|
||||
void DebuggingServer::setClient(u64id_t client) {
|
||||
connection =
|
||||
std::make_unique<ClientConnection>(engine.getNetwork(), client);
|
||||
connectionEstablished = false;
|
||||
}
|
||||
|
||||
std::vector<DebuggingEvent> DebuggingServer::pullEvents() {
|
||||
return std::move(breakpointEvents);
|
||||
}
|
||||
|
||||
void DebuggingServer::setDisconnectAction(const std::string& action) {
|
||||
disconnectAction = action;
|
||||
}
|
||||
106
src/devtools/DebuggingServer.hpp
Normal file
106
src/devtools/DebuggingServer.hpp
Normal file
@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
|
||||
#include "typedefs.hpp"
|
||||
|
||||
namespace network {
|
||||
class Server;
|
||||
class Connection;
|
||||
class ReadableConnection;
|
||||
class Network;
|
||||
}
|
||||
|
||||
namespace dv {
|
||||
class value;
|
||||
}
|
||||
|
||||
class Engine;
|
||||
|
||||
namespace devtools {
|
||||
inline constexpr const char VCDBG_MAGIC[8] = "vc-dbg\0";
|
||||
inline constexpr int VCDBG_VERSION = 1;
|
||||
|
||||
class ClientConnection {
|
||||
public:
|
||||
ClientConnection(network::Network& network, u64id_t connection)
|
||||
: network(network), connection(connection) {
|
||||
}
|
||||
~ClientConnection();
|
||||
|
||||
std::string read();
|
||||
void send(const dv::value& message);
|
||||
void sendResponse(const std::string& type);
|
||||
|
||||
bool alive() const;
|
||||
private:
|
||||
network::Network& network;
|
||||
size_t messageLength = 0;
|
||||
u64id_t connection;
|
||||
bool initiated = false;
|
||||
|
||||
bool initiate(network::ReadableConnection* connection);
|
||||
};
|
||||
|
||||
enum class DebuggingEventType {
|
||||
SET_BREAKPOINT = 1,
|
||||
REMOVE_BREAKPOINT,
|
||||
STEP,
|
||||
STEP_INTO_FUNCTION,
|
||||
RESUME,
|
||||
GET_VALUE,
|
||||
};
|
||||
|
||||
struct BreakpointEventDto {
|
||||
std::string source;
|
||||
int line;
|
||||
};
|
||||
|
||||
struct SignalEventDto {
|
||||
};
|
||||
|
||||
using ValuePath = std::vector<std::variant<std::string, int>>;
|
||||
|
||||
struct GetValueEventDto {
|
||||
int frame;
|
||||
int localIndex;
|
||||
ValuePath path;
|
||||
};
|
||||
|
||||
struct DebuggingEvent {
|
||||
DebuggingEventType type;
|
||||
std::variant<BreakpointEventDto, SignalEventDto, GetValueEventDto> data;
|
||||
};
|
||||
|
||||
class DebuggingServer {
|
||||
public:
|
||||
DebuggingServer(Engine& engine, const std::string& serverString);
|
||||
~DebuggingServer();
|
||||
|
||||
bool update();
|
||||
void pause(
|
||||
std::string&& reason, std::string&& message, dv::value&& stackTrace
|
||||
);
|
||||
|
||||
void sendValue(dv::value&& value, int frame, int local, ValuePath&& path);
|
||||
|
||||
void setClient(u64id_t client);
|
||||
std::vector<DebuggingEvent> pullEvents();
|
||||
|
||||
void setDisconnectAction(const std::string& action);
|
||||
private:
|
||||
Engine& engine;
|
||||
network::Server& server;
|
||||
std::unique_ptr<ClientConnection> connection;
|
||||
bool connectionEstablished = false;
|
||||
std::vector<DebuggingEvent> breakpointEvents;
|
||||
std::string disconnectAction = "resume";
|
||||
|
||||
bool performCommand(
|
||||
const std::string& type, const dv::value& map
|
||||
);
|
||||
};
|
||||
}
|
||||
@ -14,6 +14,7 @@
|
||||
#include "coders/commons.hpp"
|
||||
#include "devtools/Editor.hpp"
|
||||
#include "devtools/Project.hpp"
|
||||
#include "devtools/DebuggingServer.hpp"
|
||||
#include "content/ContentControl.hpp"
|
||||
#include "core_defs.hpp"
|
||||
#include "io/io.hpp"
|
||||
@ -115,6 +116,9 @@ void Engine::initializeClient() {
|
||||
if (ENGINE_DEBUG_BUILD) {
|
||||
title += " [debug]";
|
||||
}
|
||||
if (debuggingServer) {
|
||||
title = "[debugging] " + title;
|
||||
}
|
||||
auto [window, input] = Window::initialize(&settings.display, title);
|
||||
if (!window || !input){
|
||||
throw initialize_error("could not initialize window");
|
||||
@ -173,6 +177,18 @@ void Engine::initialize(CoreParameters coreParameters) {
|
||||
cmd = std::make_unique<cmd::CommandsInterpreter>();
|
||||
network = network::Network::create(settings.network);
|
||||
|
||||
if (!params.debugServerString.empty()) {
|
||||
try {
|
||||
debuggingServer = std::make_unique<devtools::DebuggingServer>(
|
||||
*this, params.debugServerString
|
||||
);
|
||||
} catch (const std::runtime_error& err) {
|
||||
throw initialize_error(
|
||||
"debugging server error: " + std::string(err.what())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!params.scriptFile.empty()) {
|
||||
paths.setScriptFolder(params.scriptFile.parent_path());
|
||||
}
|
||||
@ -266,6 +282,14 @@ void Engine::postUpdate() {
|
||||
network->update();
|
||||
postRunnables.run();
|
||||
scripting::process_post_runnables();
|
||||
|
||||
if (debuggingServer) {
|
||||
debuggingServer->update();
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::detachDebugger() {
|
||||
debuggingServer.reset();
|
||||
}
|
||||
|
||||
void Engine::updateFrontend() {
|
||||
@ -287,6 +311,30 @@ void Engine::nextFrame() {
|
||||
input->pollEvents();
|
||||
}
|
||||
|
||||
void Engine::startPauseLoop() {
|
||||
bool initialCursorLocked = false;
|
||||
if (!isHeadless()) {
|
||||
initialCursorLocked = input->isCursorLocked();
|
||||
if (initialCursorLocked) {
|
||||
input->toggleCursor();
|
||||
}
|
||||
}
|
||||
while (!isQuitSignal() && debuggingServer) {
|
||||
network->update();
|
||||
if (debuggingServer->update()) {
|
||||
break;
|
||||
}
|
||||
if (isHeadless()) {
|
||||
platform::sleep(1.0 / params.tps * 1000);
|
||||
} else {
|
||||
nextFrame();
|
||||
}
|
||||
}
|
||||
if (initialCursorLocked) {
|
||||
input->toggleCursor();
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::renderFrame() {
|
||||
screen->draw(time.getDelta());
|
||||
|
||||
@ -299,7 +347,11 @@ void Engine::saveSettings() {
|
||||
io::write_string(EnginePaths::SETTINGS_FILE, toml::stringify(*settingsHandler));
|
||||
if (!params.headless) {
|
||||
logger.info() << "saving bindings";
|
||||
io::write_string(EnginePaths::CONTROLS_FILE, input->getBindings().write());
|
||||
if (input) {
|
||||
io::write_string(
|
||||
EnginePaths::CONTROLS_FILE, input->getBindings().write()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -318,6 +370,7 @@ void Engine::close() {
|
||||
logger.info() << "gui finished";
|
||||
}
|
||||
audio::close();
|
||||
debuggingServer.reset();
|
||||
network.reset();
|
||||
clearKeepedObjects();
|
||||
project.reset();
|
||||
|
||||
@ -36,6 +36,7 @@ namespace network {
|
||||
|
||||
namespace devtools {
|
||||
class Editor;
|
||||
class DebuggingServer;
|
||||
}
|
||||
|
||||
class initialize_error : public std::runtime_error {
|
||||
@ -50,6 +51,7 @@ struct CoreParameters {
|
||||
std::filesystem::path userFolder = ".";
|
||||
std::filesystem::path scriptFile;
|
||||
std::filesystem::path projectFolder;
|
||||
std::string debugServerString = "tcp:9030";
|
||||
int tps = 20;
|
||||
};
|
||||
|
||||
@ -72,6 +74,7 @@ class Engine : public util::ObjectsKeeper {
|
||||
std::unique_ptr<Input> input;
|
||||
std::unique_ptr<gui::GUI> gui;
|
||||
std::unique_ptr<devtools::Editor> editor;
|
||||
std::unique_ptr<devtools::DebuggingServer> debuggingServer;
|
||||
PostRunnables postRunnables;
|
||||
Time time;
|
||||
OnWorldOpen levelConsumer;
|
||||
@ -105,6 +108,7 @@ public:
|
||||
void updateFrontend();
|
||||
void renderFrame();
|
||||
void nextFrame();
|
||||
void startPauseLoop();
|
||||
|
||||
/// @brief Set screen (scene).
|
||||
/// nullptr may be used to delete previous screen before creating new one,
|
||||
@ -182,4 +186,10 @@ public:
|
||||
const Project& getProject() {
|
||||
return *project;
|
||||
}
|
||||
|
||||
devtools::DebuggingServer* getDebuggingServer() {
|
||||
return debuggingServer.get();
|
||||
}
|
||||
|
||||
void detachDebugger();
|
||||
};
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
|
||||
#include "libs/api_lua.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
#include "devtools/DebuggingServer.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
|
||||
using namespace devtools;
|
||||
using namespace scripting;
|
||||
|
||||
static debug::Logger logger("lua-debug");
|
||||
@ -159,6 +163,223 @@ static int l_math_normal_random(lua::State* L) {
|
||||
return lua::pushnumber(L, randomFloats(generator));
|
||||
}
|
||||
|
||||
constexpr inline int MAX_SHORT_STRING_LEN = 50;
|
||||
|
||||
static std::string get_short_value(lua::State* L, int idx, int type) {
|
||||
switch (type) {
|
||||
case LUA_TNIL:
|
||||
return "nil";
|
||||
case LUA_TBOOLEAN:
|
||||
return lua::toboolean(L, idx) ? "true" : "false";
|
||||
case LUA_TNUMBER: {
|
||||
std::stringstream ss;
|
||||
ss << lua::tonumber(L, idx);
|
||||
return ss.str();
|
||||
}
|
||||
case LUA_TSTRING: {
|
||||
const char* str = lua::tostring(L, idx);
|
||||
if (strlen(str) > MAX_SHORT_STRING_LEN) {
|
||||
return std::string(str, MAX_SHORT_STRING_LEN);
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
case LUA_TTABLE:
|
||||
return "{...}";
|
||||
case LUA_TFUNCTION: {
|
||||
std::stringstream ss;
|
||||
ss << "function: 0x" << std::hex
|
||||
<< reinterpret_cast<ptrdiff_t>(lua::topointer(L, idx));
|
||||
return ss.str();
|
||||
}
|
||||
case LUA_TUSERDATA: {
|
||||
std::stringstream ss;
|
||||
ss << "userdata: 0x" << std::hex
|
||||
<< reinterpret_cast<ptrdiff_t>(lua::topointer(L, idx));
|
||||
return ss.str();
|
||||
}
|
||||
case LUA_TTHREAD: {
|
||||
std::stringstream ss;
|
||||
ss << "thread: 0x" << std::hex
|
||||
<< reinterpret_cast<ptrdiff_t>(lua::topointer(L, idx));
|
||||
return ss.str();
|
||||
}
|
||||
default: {
|
||||
std::stringstream ss;
|
||||
ss << "cdata: 0x" << std::hex
|
||||
<< reinterpret_cast<ptrdiff_t>(lua::topointer(L, idx));
|
||||
return ss.str();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static dv::value collect_locals(lua::State* L, lua_Debug& frame) {
|
||||
auto locals = dv::list();
|
||||
|
||||
int localIndex = 1;
|
||||
const char* name;
|
||||
while ((name = lua_getlocal(L, &frame, localIndex++))) {
|
||||
if (name[0] == '(') {
|
||||
lua::pop(L);
|
||||
continue;
|
||||
}
|
||||
auto local = dv::object();
|
||||
local["name"] = name;
|
||||
local["index"] = localIndex - 1;
|
||||
|
||||
int type = lua::type(L, -1);
|
||||
local["type"] = lua::type_name(L, type);
|
||||
local["short"] = get_short_value(L, -1, type);
|
||||
locals.add(std::move(local));
|
||||
lua::pop(L);
|
||||
}
|
||||
return locals;
|
||||
}
|
||||
|
||||
static dv::value create_stack_trace(lua::State* L, int initFrame = 2) {
|
||||
auto entriesList = dv::list();
|
||||
|
||||
lua_Debug frame;
|
||||
int level = initFrame;
|
||||
|
||||
while (lua_getstack(L, level, &frame)) {
|
||||
auto entry = dv::object();
|
||||
if (lua_getinfo(L, "nSlf", &frame) == 0) {
|
||||
level++;
|
||||
entriesList.add(std::move(entry));
|
||||
continue;
|
||||
}
|
||||
if (frame.name) {
|
||||
entry["function"] = frame.name;
|
||||
}
|
||||
if (frame.source) {
|
||||
const char* src =
|
||||
(frame.source[0] == '@') ? frame.source + 1 : frame.source;
|
||||
entry["source"] = src;
|
||||
entry["line"] = frame.currentline;
|
||||
}
|
||||
entry["what"] = frame.what;
|
||||
entry["locals"] = collect_locals(L, frame);
|
||||
entriesList.add(std::move(entry));
|
||||
level++;
|
||||
}
|
||||
return entriesList;
|
||||
}
|
||||
|
||||
static int l_debug_pause(lua::State* L) {
|
||||
if (auto server = engine->getDebuggingServer()) {
|
||||
std::string reason;
|
||||
std::string message;
|
||||
if (lua::isstring(L, 1)) {
|
||||
reason = lua::tolstring(L, 1);
|
||||
}
|
||||
if (lua::isstring(L, 2)) {
|
||||
message = lua::tolstring(L, 2);
|
||||
}
|
||||
server->pause(
|
||||
std::move(reason), std::move(message), create_stack_trace(L)
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_debug_sendvalue(lua::State* L) {
|
||||
auto server = engine->getDebuggingServer();
|
||||
if (!server) {
|
||||
return 0;
|
||||
}
|
||||
int frame = lua::tointeger(L, 2);
|
||||
int local = lua::tointeger(L, 3);
|
||||
|
||||
ValuePath path;
|
||||
int pathSectors = lua::objlen(L, 4);
|
||||
for (int i = 0; i < pathSectors; i++) {
|
||||
lua::rawgeti(L, i + 1, 4);
|
||||
if (lua::isstring(L, -1)) {
|
||||
path.emplace_back(lua::tostring(L, -1));
|
||||
} else {
|
||||
path.emplace_back(static_cast<int>(lua::tointeger(L, -1)));
|
||||
}
|
||||
lua::pop(L);
|
||||
}
|
||||
|
||||
dv::value value = nullptr;
|
||||
if (lua::istable(L, 1)) {
|
||||
auto table = dv::object();
|
||||
|
||||
lua::pushnil(L);
|
||||
while (lua::next(L, 1)) {
|
||||
auto key = lua::tolstring(L, -2);
|
||||
|
||||
int type = lua::type(L, -1);
|
||||
table[std::string(key)] = dv::object({
|
||||
{"type", std::string(lua::type_name(L, type))},
|
||||
{"short", get_short_value(L, -1, type)},
|
||||
});
|
||||
lua::pop(L);
|
||||
}
|
||||
lua::pop(L);
|
||||
value = std::move(table);
|
||||
} else {
|
||||
value = lua::tovalue(L, 1);
|
||||
}
|
||||
|
||||
server->sendValue(std::move(value), frame, local, std::move(path));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_debug_pull_events(lua::State* L) {
|
||||
auto server = engine->getDebuggingServer();
|
||||
if (!server) {
|
||||
return 0;
|
||||
}
|
||||
auto events = server->pullEvents();
|
||||
if (events.empty()) {
|
||||
return 0;
|
||||
}
|
||||
lua::createtable(L, events.size(), 0);
|
||||
for (int i = 0; i < events.size(); i++) {
|
||||
const auto& event = events[i];
|
||||
lua::createtable(L, 3, 0);
|
||||
|
||||
lua::pushinteger(L, static_cast<int>(event.type));
|
||||
lua::rawseti(L, 1);
|
||||
|
||||
if (auto dto = std::get_if<BreakpointEventDto>(&event.data)) {
|
||||
lua::pushstring(L, dto->source);
|
||||
lua::rawseti(L, 2);
|
||||
|
||||
lua::pushinteger(L, dto->line);
|
||||
lua::rawseti(L, 3);
|
||||
} else if (auto dto = std::get_if<GetValueEventDto>(&event.data)) {
|
||||
lua::pushinteger(L, dto->frame);
|
||||
lua::rawseti(L, 2);
|
||||
|
||||
lua::pushinteger(L, dto->localIndex);
|
||||
lua::rawseti(L, 3);
|
||||
|
||||
lua::createtable(L, dto->path.size(), 0);
|
||||
for (int i = 0; i < dto->path.size(); i++) {
|
||||
const auto& segment = dto->path[i];
|
||||
if (auto string = std::get_if<std::string>(&segment)) {
|
||||
lua::pushstring(L, *string);
|
||||
} else {
|
||||
lua::pushinteger(L, std::get<int>(segment));
|
||||
}
|
||||
lua::rawseti(L, i + 1);
|
||||
}
|
||||
lua::rawseti(L, 4);
|
||||
}
|
||||
|
||||
lua::rawseti(L, i + 1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_debug_is_debugging(lua::State* L) {
|
||||
return lua::pushboolean(L, engine->getDebuggingServer() != nullptr);
|
||||
}
|
||||
|
||||
void initialize_libs_extends(lua::State* L) {
|
||||
if (lua::getglobal(L, "debug")) {
|
||||
lua::pushcfunction(L, lua::wrap<l_debug_error>);
|
||||
@ -173,6 +394,18 @@ void initialize_libs_extends(lua::State* L) {
|
||||
lua::pushcfunction(L, lua::wrap<l_debug_print>);
|
||||
lua::setfield(L, "print");
|
||||
|
||||
lua::pushcfunction(L, lua::wrap<l_debug_pause>);
|
||||
lua::setfield(L, "pause");
|
||||
|
||||
lua::pushcfunction(L, lua::wrap<l_debug_pull_events>);
|
||||
lua::setfield(L, "__pull_events");
|
||||
|
||||
lua::pushcfunction(L, lua::wrap<l_debug_sendvalue>);
|
||||
lua::setfield(L, "__sendvalue");
|
||||
|
||||
lua::pushcfunction(L, lua::wrap<l_debug_is_debugging>);
|
||||
lua::setfield(L, "is_debugging");
|
||||
|
||||
lua::pop(L);
|
||||
}
|
||||
if (lua::getglobal(L, "math")) {
|
||||
|
||||
@ -249,7 +249,7 @@ namespace lua {
|
||||
inline lua::Number tonumber(lua::State* L, int idx) {
|
||||
#ifndef NDEBUG
|
||||
if (lua_type(L, idx) != LUA_TNUMBER && !lua_isnoneornil(L, idx)) {
|
||||
throw std::runtime_error("integer expected");
|
||||
throw std::runtime_error("number expected");
|
||||
}
|
||||
#endif
|
||||
return lua_tonumber(L, idx);
|
||||
@ -617,7 +617,9 @@ namespace lua {
|
||||
void remove_environment(lua::State*, int id);
|
||||
|
||||
inline void close(lua::State* L) {
|
||||
lua_close(L);
|
||||
if (L) {
|
||||
lua_close(L);
|
||||
}
|
||||
}
|
||||
|
||||
inline void addfunc(
|
||||
|
||||
@ -50,8 +50,8 @@ namespace lua {
|
||||
inline int type(lua::State* L, int idx) {
|
||||
return lua_type(L, idx);
|
||||
}
|
||||
inline const char* type_name(lua::State* L, int idx) {
|
||||
return lua_typename(L, idx);
|
||||
inline const char* type_name(lua::State* L, int tp) {
|
||||
return lua_typename(L, tp);
|
||||
}
|
||||
inline int rawget(lua::State* L, int idx = -2) {
|
||||
lua_rawget(L, idx);
|
||||
|
||||
@ -25,7 +25,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
std::signal(SIGTERM, sigterm_handler);
|
||||
|
||||
debug::Logger::init(coreParameters.userFolder.string()+"/latest.log");
|
||||
debug::Logger::init(coreParameters.userFolder.string() + "/latest.log");
|
||||
platform::configure_encoding();
|
||||
|
||||
auto& engine = Engine::getInstance();
|
||||
@ -33,7 +33,8 @@ int main(int argc, char** argv) {
|
||||
engine.initialize(std::move(coreParameters));
|
||||
engine.run();
|
||||
} catch (const initialize_error& err) {
|
||||
logger.error() << "could not to initialize engine\n" << err.what();
|
||||
logger.error() << err.what();
|
||||
logger.error() << "could not to initialize engine";
|
||||
}
|
||||
#if defined(NDEBUG) and defined(_WIN32)
|
||||
catch (const std::exception& err) {
|
||||
|
||||
@ -164,15 +164,16 @@ void Network::update() {
|
||||
}
|
||||
++socketiter;
|
||||
}
|
||||
auto serveriter = servers.begin();
|
||||
while (serveriter != servers.end()) {
|
||||
auto server = serveriter->second.get();
|
||||
if (!server->isOpen()) {
|
||||
serveriter = servers.erase(serveriter);
|
||||
continue;
|
||||
}
|
||||
++serveriter;
|
||||
}
|
||||
auto serveriter = servers.begin();
|
||||
while (serveriter != servers.end()) {
|
||||
auto server = serveriter->second.get();
|
||||
if (!server->isOpen()) {
|
||||
serveriter = servers.erase(serveriter);
|
||||
continue;
|
||||
}
|
||||
server->update();
|
||||
++serveriter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,13 +3,12 @@
|
||||
#include "commons.hpp"
|
||||
|
||||
namespace network {
|
||||
class TcpConnection : public Connection {
|
||||
class TcpConnection : public ReadableConnection {
|
||||
public:
|
||||
~TcpConnection() override = default;
|
||||
|
||||
virtual void connect(runnable callback) = 0;
|
||||
virtual int recv(char* buffer, size_t length) = 0;
|
||||
virtual int available() = 0;
|
||||
|
||||
virtual void setNoDelay(bool noDelay) = 0;
|
||||
[[nodiscard]] virtual bool isNoDelay() const = 0;
|
||||
|
||||
@ -37,6 +36,8 @@ namespace network {
|
||||
[[nodiscard]] TransportType getTransportType() const noexcept override {
|
||||
return TransportType::TCP;
|
||||
}
|
||||
|
||||
virtual void setMaxClientsConnected(int count) = 0;
|
||||
};
|
||||
|
||||
class UdpServer : public Server {
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <curl/curl.h>
|
||||
#define SHUT_RDWR SD_BOTH
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
@ -231,7 +232,7 @@ public:
|
||||
readBatch.clear();
|
||||
|
||||
if (state != ConnectionState::CLOSED) {
|
||||
shutdown(descriptor, 2);
|
||||
shutdown(descriptor, SHUT_RDWR);
|
||||
closesocket(descriptor);
|
||||
}
|
||||
}
|
||||
@ -304,6 +305,7 @@ class SocketTcpServer : public TcpServer {
|
||||
bool open = true;
|
||||
std::unique_ptr<std::thread> thread = nullptr;
|
||||
int port;
|
||||
int maxConnected = -1;
|
||||
public:
|
||||
SocketTcpServer(u64id_t id, Network* network, SOCKET descriptor, int port)
|
||||
: id(id), network(network), descriptor(descriptor), port(port) {}
|
||||
@ -312,6 +314,22 @@ public:
|
||||
closeSocket();
|
||||
}
|
||||
|
||||
void setMaxClientsConnected(int count) override {
|
||||
maxConnected = count;
|
||||
}
|
||||
|
||||
void update() override {
|
||||
std::vector<u64id_t> clients;
|
||||
for (u64id_t cid : this->clients) {
|
||||
if (auto client = network->getConnection(cid, true)) {
|
||||
if (client->getState() != ConnectionState::CLOSED) {
|
||||
clients.emplace_back(cid);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::swap(clients, this->clients);
|
||||
}
|
||||
|
||||
void startListen(ConnectCallback handler) override {
|
||||
thread = std::make_unique<std::thread>([this, handler]() {
|
||||
while (open) {
|
||||
@ -328,6 +346,11 @@ public:
|
||||
close();
|
||||
break;
|
||||
}
|
||||
if (maxConnected >= 0 && clients.size() >= maxConnected) {
|
||||
logger.info() << "refused connection attempt from " << to_string(address);
|
||||
closesocket(clientDescriptor);
|
||||
continue;
|
||||
}
|
||||
logger.info() << "client connected: " << to_string(address);
|
||||
auto socket = std::make_shared<SocketTcpConnection>(
|
||||
clientDescriptor, address
|
||||
@ -575,6 +598,8 @@ public:
|
||||
SocketUdpServer::close();
|
||||
}
|
||||
|
||||
void update() override {}
|
||||
|
||||
void startListen(ServerDatagramCallback handler) override {
|
||||
callback = std::move(handler);
|
||||
|
||||
|
||||
@ -75,9 +75,17 @@ namespace network {
|
||||
bool isprivate = false;
|
||||
};
|
||||
|
||||
class ReadableConnection : public Connection {
|
||||
public:
|
||||
virtual int recv(char* buffer, size_t length) = 0;
|
||||
virtual int available() = 0;
|
||||
};
|
||||
|
||||
class Server {
|
||||
public:
|
||||
virtual ~Server() = default;
|
||||
|
||||
virtual void update() = 0;
|
||||
virtual void close() = 0;
|
||||
virtual bool isOpen() = 0;
|
||||
[[nodiscard]] virtual TransportType getTransportType() const noexcept = 0;
|
||||
|
||||
@ -70,11 +70,15 @@ static bool perform_keyword(
|
||||
std::cout << ENGINE_VERSION_STRING << std::endl;
|
||||
return false;
|
||||
}, "", "display the engine version."),
|
||||
ArgC("--dbg-server", [¶ms, &reader]() -> bool {
|
||||
params.debugServerString = reader.next();
|
||||
return true;
|
||||
}, "<serv>", "open debugging server where <serv> is {transport}:{port}"),
|
||||
ArgC("--help", []() -> bool {
|
||||
std::cout << "VoxelCore v" << ENGINE_VERSION_STRING << "\n\n";
|
||||
std::cout << "Command-line arguments:\n";
|
||||
for (auto& a : argumentsCommandline) {
|
||||
std::cout << std::setw(20) << std::left << (a.keyword + " " + a.args);
|
||||
std::cout << std::setw(24) << std::left << (a.keyword + " " + a.args);
|
||||
std::cout << "- " << a.help << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user