Merge pull request #643 from MihailRis/debugging-server

Debugging-server
This commit is contained in:
MihailRis 2025-10-09 12:10:32 +03:00 committed by GitHub
commit f16da17991
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 921 additions and 22 deletions

View File

@ -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())

View File

@ -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

View 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;
}

View 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
);
};
}

View File

@ -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();

View File

@ -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();
};

View File

@ -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")) {

View File

@ -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(

View File

@ -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);

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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);

View File

@ -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;

View File

@ -70,11 +70,15 @@ static bool perform_keyword(
std::cout << ENGINE_VERSION_STRING << std::endl;
return false;
}, "", "display the engine version."),
ArgC("--dbg-server", [&params, &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;