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
|
__vc_named_coroutines[name] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
debug.pull_events()
|
||||||
network.__process_events()
|
network.__process_events()
|
||||||
block.__process_register_events()
|
block.__process_register_events()
|
||||||
block.__perform_ticks(time.delta())
|
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
|
-- Lua has no parallelizm, also _set_data does not call any lua functions so
|
||||||
-- may be reused one global ffi buffer per lua_State
|
-- may be reused one global ffi buffer per lua_State
|
||||||
local canvas_ffi_buffer
|
local canvas_ffi_buffer
|
||||||
@ -473,8 +614,6 @@ function file.readlines(path)
|
|||||||
return lines
|
return lines
|
||||||
end
|
end
|
||||||
|
|
||||||
local _debug_getinfo = debug.getinfo
|
|
||||||
|
|
||||||
function debug.count_frames()
|
function debug.count_frames()
|
||||||
local frames = 1
|
local frames = 1
|
||||||
while true do
|
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 "coders/commons.hpp"
|
||||||
#include "devtools/Editor.hpp"
|
#include "devtools/Editor.hpp"
|
||||||
#include "devtools/Project.hpp"
|
#include "devtools/Project.hpp"
|
||||||
|
#include "devtools/DebuggingServer.hpp"
|
||||||
#include "content/ContentControl.hpp"
|
#include "content/ContentControl.hpp"
|
||||||
#include "core_defs.hpp"
|
#include "core_defs.hpp"
|
||||||
#include "io/io.hpp"
|
#include "io/io.hpp"
|
||||||
@ -115,6 +116,9 @@ void Engine::initializeClient() {
|
|||||||
if (ENGINE_DEBUG_BUILD) {
|
if (ENGINE_DEBUG_BUILD) {
|
||||||
title += " [debug]";
|
title += " [debug]";
|
||||||
}
|
}
|
||||||
|
if (debuggingServer) {
|
||||||
|
title = "[debugging] " + title;
|
||||||
|
}
|
||||||
auto [window, input] = Window::initialize(&settings.display, title);
|
auto [window, input] = Window::initialize(&settings.display, title);
|
||||||
if (!window || !input){
|
if (!window || !input){
|
||||||
throw initialize_error("could not initialize window");
|
throw initialize_error("could not initialize window");
|
||||||
@ -173,6 +177,18 @@ void Engine::initialize(CoreParameters coreParameters) {
|
|||||||
cmd = std::make_unique<cmd::CommandsInterpreter>();
|
cmd = std::make_unique<cmd::CommandsInterpreter>();
|
||||||
network = network::Network::create(settings.network);
|
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()) {
|
if (!params.scriptFile.empty()) {
|
||||||
paths.setScriptFolder(params.scriptFile.parent_path());
|
paths.setScriptFolder(params.scriptFile.parent_path());
|
||||||
}
|
}
|
||||||
@ -266,6 +282,14 @@ void Engine::postUpdate() {
|
|||||||
network->update();
|
network->update();
|
||||||
postRunnables.run();
|
postRunnables.run();
|
||||||
scripting::process_post_runnables();
|
scripting::process_post_runnables();
|
||||||
|
|
||||||
|
if (debuggingServer) {
|
||||||
|
debuggingServer->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::detachDebugger() {
|
||||||
|
debuggingServer.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::updateFrontend() {
|
void Engine::updateFrontend() {
|
||||||
@ -287,6 +311,30 @@ void Engine::nextFrame() {
|
|||||||
input->pollEvents();
|
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() {
|
void Engine::renderFrame() {
|
||||||
screen->draw(time.getDelta());
|
screen->draw(time.getDelta());
|
||||||
|
|
||||||
@ -299,7 +347,11 @@ void Engine::saveSettings() {
|
|||||||
io::write_string(EnginePaths::SETTINGS_FILE, toml::stringify(*settingsHandler));
|
io::write_string(EnginePaths::SETTINGS_FILE, toml::stringify(*settingsHandler));
|
||||||
if (!params.headless) {
|
if (!params.headless) {
|
||||||
logger.info() << "saving bindings";
|
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";
|
logger.info() << "gui finished";
|
||||||
}
|
}
|
||||||
audio::close();
|
audio::close();
|
||||||
|
debuggingServer.reset();
|
||||||
network.reset();
|
network.reset();
|
||||||
clearKeepedObjects();
|
clearKeepedObjects();
|
||||||
project.reset();
|
project.reset();
|
||||||
|
|||||||
@ -36,6 +36,7 @@ namespace network {
|
|||||||
|
|
||||||
namespace devtools {
|
namespace devtools {
|
||||||
class Editor;
|
class Editor;
|
||||||
|
class DebuggingServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
class initialize_error : public std::runtime_error {
|
class initialize_error : public std::runtime_error {
|
||||||
@ -50,6 +51,7 @@ struct CoreParameters {
|
|||||||
std::filesystem::path userFolder = ".";
|
std::filesystem::path userFolder = ".";
|
||||||
std::filesystem::path scriptFile;
|
std::filesystem::path scriptFile;
|
||||||
std::filesystem::path projectFolder;
|
std::filesystem::path projectFolder;
|
||||||
|
std::string debugServerString = "tcp:9030";
|
||||||
int tps = 20;
|
int tps = 20;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -72,6 +74,7 @@ class Engine : public util::ObjectsKeeper {
|
|||||||
std::unique_ptr<Input> input;
|
std::unique_ptr<Input> input;
|
||||||
std::unique_ptr<gui::GUI> gui;
|
std::unique_ptr<gui::GUI> gui;
|
||||||
std::unique_ptr<devtools::Editor> editor;
|
std::unique_ptr<devtools::Editor> editor;
|
||||||
|
std::unique_ptr<devtools::DebuggingServer> debuggingServer;
|
||||||
PostRunnables postRunnables;
|
PostRunnables postRunnables;
|
||||||
Time time;
|
Time time;
|
||||||
OnWorldOpen levelConsumer;
|
OnWorldOpen levelConsumer;
|
||||||
@ -105,6 +108,7 @@ public:
|
|||||||
void updateFrontend();
|
void updateFrontend();
|
||||||
void renderFrame();
|
void renderFrame();
|
||||||
void nextFrame();
|
void nextFrame();
|
||||||
|
void startPauseLoop();
|
||||||
|
|
||||||
/// @brief Set screen (scene).
|
/// @brief Set screen (scene).
|
||||||
/// nullptr may be used to delete previous screen before creating new one,
|
/// nullptr may be used to delete previous screen before creating new one,
|
||||||
@ -182,4 +186,10 @@ public:
|
|||||||
const Project& getProject() {
|
const Project& getProject() {
|
||||||
return *project;
|
return *project;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
devtools::DebuggingServer* getDebuggingServer() {
|
||||||
|
return debuggingServer.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void detachDebugger();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#include "libs/api_lua.hpp"
|
#include "libs/api_lua.hpp"
|
||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
|
#include "engine/Engine.hpp"
|
||||||
|
#include "devtools/DebuggingServer.hpp"
|
||||||
#include "logic/scripting/scripting.hpp"
|
#include "logic/scripting/scripting.hpp"
|
||||||
|
|
||||||
|
using namespace devtools;
|
||||||
using namespace scripting;
|
using namespace scripting;
|
||||||
|
|
||||||
static debug::Logger logger("lua-debug");
|
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));
|
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) {
|
void initialize_libs_extends(lua::State* L) {
|
||||||
if (lua::getglobal(L, "debug")) {
|
if (lua::getglobal(L, "debug")) {
|
||||||
lua::pushcfunction(L, lua::wrap<l_debug_error>);
|
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::pushcfunction(L, lua::wrap<l_debug_print>);
|
||||||
lua::setfield(L, "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);
|
lua::pop(L);
|
||||||
}
|
}
|
||||||
if (lua::getglobal(L, "math")) {
|
if (lua::getglobal(L, "math")) {
|
||||||
|
|||||||
@ -249,7 +249,7 @@ namespace lua {
|
|||||||
inline lua::Number tonumber(lua::State* L, int idx) {
|
inline lua::Number tonumber(lua::State* L, int idx) {
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
if (lua_type(L, idx) != LUA_TNUMBER && !lua_isnoneornil(L, idx)) {
|
if (lua_type(L, idx) != LUA_TNUMBER && !lua_isnoneornil(L, idx)) {
|
||||||
throw std::runtime_error("integer expected");
|
throw std::runtime_error("number expected");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return lua_tonumber(L, idx);
|
return lua_tonumber(L, idx);
|
||||||
@ -617,7 +617,9 @@ namespace lua {
|
|||||||
void remove_environment(lua::State*, int id);
|
void remove_environment(lua::State*, int id);
|
||||||
|
|
||||||
inline void close(lua::State* L) {
|
inline void close(lua::State* L) {
|
||||||
lua_close(L);
|
if (L) {
|
||||||
|
lua_close(L);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void addfunc(
|
inline void addfunc(
|
||||||
|
|||||||
@ -50,8 +50,8 @@ namespace lua {
|
|||||||
inline int type(lua::State* L, int idx) {
|
inline int type(lua::State* L, int idx) {
|
||||||
return lua_type(L, idx);
|
return lua_type(L, idx);
|
||||||
}
|
}
|
||||||
inline const char* type_name(lua::State* L, int idx) {
|
inline const char* type_name(lua::State* L, int tp) {
|
||||||
return lua_typename(L, idx);
|
return lua_typename(L, tp);
|
||||||
}
|
}
|
||||||
inline int rawget(lua::State* L, int idx = -2) {
|
inline int rawget(lua::State* L, int idx = -2) {
|
||||||
lua_rawget(L, idx);
|
lua_rawget(L, idx);
|
||||||
|
|||||||
@ -25,7 +25,7 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
std::signal(SIGTERM, sigterm_handler);
|
std::signal(SIGTERM, sigterm_handler);
|
||||||
|
|
||||||
debug::Logger::init(coreParameters.userFolder.string()+"/latest.log");
|
debug::Logger::init(coreParameters.userFolder.string() + "/latest.log");
|
||||||
platform::configure_encoding();
|
platform::configure_encoding();
|
||||||
|
|
||||||
auto& engine = Engine::getInstance();
|
auto& engine = Engine::getInstance();
|
||||||
@ -33,7 +33,8 @@ int main(int argc, char** argv) {
|
|||||||
engine.initialize(std::move(coreParameters));
|
engine.initialize(std::move(coreParameters));
|
||||||
engine.run();
|
engine.run();
|
||||||
} catch (const initialize_error& err) {
|
} 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)
|
#if defined(NDEBUG) and defined(_WIN32)
|
||||||
catch (const std::exception& err) {
|
catch (const std::exception& err) {
|
||||||
|
|||||||
@ -164,15 +164,16 @@ void Network::update() {
|
|||||||
}
|
}
|
||||||
++socketiter;
|
++socketiter;
|
||||||
}
|
}
|
||||||
auto serveriter = servers.begin();
|
}
|
||||||
while (serveriter != servers.end()) {
|
auto serveriter = servers.begin();
|
||||||
auto server = serveriter->second.get();
|
while (serveriter != servers.end()) {
|
||||||
if (!server->isOpen()) {
|
auto server = serveriter->second.get();
|
||||||
serveriter = servers.erase(serveriter);
|
if (!server->isOpen()) {
|
||||||
continue;
|
serveriter = servers.erase(serveriter);
|
||||||
}
|
continue;
|
||||||
++serveriter;
|
|
||||||
}
|
}
|
||||||
|
server->update();
|
||||||
|
++serveriter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,13 +3,12 @@
|
|||||||
#include "commons.hpp"
|
#include "commons.hpp"
|
||||||
|
|
||||||
namespace network {
|
namespace network {
|
||||||
class TcpConnection : public Connection {
|
class TcpConnection : public ReadableConnection {
|
||||||
public:
|
public:
|
||||||
~TcpConnection() override = default;
|
~TcpConnection() override = default;
|
||||||
|
|
||||||
virtual void connect(runnable callback) = 0;
|
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;
|
virtual void setNoDelay(bool noDelay) = 0;
|
||||||
[[nodiscard]] virtual bool isNoDelay() const = 0;
|
[[nodiscard]] virtual bool isNoDelay() const = 0;
|
||||||
|
|
||||||
@ -37,6 +36,8 @@ namespace network {
|
|||||||
[[nodiscard]] TransportType getTransportType() const noexcept override {
|
[[nodiscard]] TransportType getTransportType() const noexcept override {
|
||||||
return TransportType::TCP;
|
return TransportType::TCP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void setMaxClientsConnected(int count) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class UdpServer : public Server {
|
class UdpServer : public Server {
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
#define SHUT_RDWR SD_BOTH
|
||||||
#else
|
#else
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
@ -231,7 +232,7 @@ public:
|
|||||||
readBatch.clear();
|
readBatch.clear();
|
||||||
|
|
||||||
if (state != ConnectionState::CLOSED) {
|
if (state != ConnectionState::CLOSED) {
|
||||||
shutdown(descriptor, 2);
|
shutdown(descriptor, SHUT_RDWR);
|
||||||
closesocket(descriptor);
|
closesocket(descriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,6 +305,7 @@ class SocketTcpServer : public TcpServer {
|
|||||||
bool open = true;
|
bool open = true;
|
||||||
std::unique_ptr<std::thread> thread = nullptr;
|
std::unique_ptr<std::thread> thread = nullptr;
|
||||||
int port;
|
int port;
|
||||||
|
int maxConnected = -1;
|
||||||
public:
|
public:
|
||||||
SocketTcpServer(u64id_t id, Network* network, SOCKET descriptor, int port)
|
SocketTcpServer(u64id_t id, Network* network, SOCKET descriptor, int port)
|
||||||
: id(id), network(network), descriptor(descriptor), port(port) {}
|
: id(id), network(network), descriptor(descriptor), port(port) {}
|
||||||
@ -312,6 +314,22 @@ public:
|
|||||||
closeSocket();
|
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 {
|
void startListen(ConnectCallback handler) override {
|
||||||
thread = std::make_unique<std::thread>([this, handler]() {
|
thread = std::make_unique<std::thread>([this, handler]() {
|
||||||
while (open) {
|
while (open) {
|
||||||
@ -328,6 +346,11 @@ public:
|
|||||||
close();
|
close();
|
||||||
break;
|
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);
|
logger.info() << "client connected: " << to_string(address);
|
||||||
auto socket = std::make_shared<SocketTcpConnection>(
|
auto socket = std::make_shared<SocketTcpConnection>(
|
||||||
clientDescriptor, address
|
clientDescriptor, address
|
||||||
@ -575,6 +598,8 @@ public:
|
|||||||
SocketUdpServer::close();
|
SocketUdpServer::close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void update() override {}
|
||||||
|
|
||||||
void startListen(ServerDatagramCallback handler) override {
|
void startListen(ServerDatagramCallback handler) override {
|
||||||
callback = std::move(handler);
|
callback = std::move(handler);
|
||||||
|
|
||||||
|
|||||||
@ -75,9 +75,17 @@ namespace network {
|
|||||||
bool isprivate = false;
|
bool isprivate = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ReadableConnection : public Connection {
|
||||||
|
public:
|
||||||
|
virtual int recv(char* buffer, size_t length) = 0;
|
||||||
|
virtual int available() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
public:
|
public:
|
||||||
virtual ~Server() = default;
|
virtual ~Server() = default;
|
||||||
|
|
||||||
|
virtual void update() = 0;
|
||||||
virtual void close() = 0;
|
virtual void close() = 0;
|
||||||
virtual bool isOpen() = 0;
|
virtual bool isOpen() = 0;
|
||||||
[[nodiscard]] virtual TransportType getTransportType() const noexcept = 0;
|
[[nodiscard]] virtual TransportType getTransportType() const noexcept = 0;
|
||||||
|
|||||||
@ -70,11 +70,15 @@ static bool perform_keyword(
|
|||||||
std::cout << ENGINE_VERSION_STRING << std::endl;
|
std::cout << ENGINE_VERSION_STRING << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}, "", "display the engine version."),
|
}, "", "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 {
|
ArgC("--help", []() -> bool {
|
||||||
std::cout << "VoxelCore v" << ENGINE_VERSION_STRING << "\n\n";
|
std::cout << "VoxelCore v" << ENGINE_VERSION_STRING << "\n\n";
|
||||||
std::cout << "Command-line arguments:\n";
|
std::cout << "Command-line arguments:\n";
|
||||||
for (auto& a : argumentsCommandline) {
|
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 << "- " << a.help << std::endl;
|
||||||
}
|
}
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user