Merge remote-tracking branch 'mih/dev' into sdl-try-2

This commit is contained in:
Stepanov Igor 2025-10-17 22:36:34 +03:00
commit 8cd75ad790
106 changed files with 2838 additions and 1121 deletions

View file

@ -1,6 +1,8 @@
# Documentation
Documentation for 0.29.
Documentation for 0.30 (in development).
[Documentation for 0.29.](https://github.com/MihailRis/voxelcore/blob/release-0.29/doc/ru/main-page.md)
## Sections

View file

@ -89,6 +89,13 @@ player.set_loading_chunks(playerid: int, bool)
Getter and setter of the property that determines whether the player is loading chunks.
```lua
player.get_interaction_distance(playerid: int) -> float
player.set_interaction_distance(playerid: int, distance: float)
```
Getter and setter of the property for max interaction distance.
``` lua
player.set_spawnpoint(playerid: int, x: number, y: number, z: number)
player.get_spawnpoint(playerid: int) -> number, number, number
@ -135,3 +142,21 @@ player.get_entity(playerid: int) -> int
```
Returns unique identifier of the player entity
```lua
player.get_all_in_radius(center: vec3, radius: number) -> table<int>
```
Returns an array of player IDs within a sphere with center `center` and radius `radius`.
```lua
player.get_all() -> table<int>
```
Returns an array of all active player IDs.
```lua
player.get_nearest(position: vec3) -> int
```
Returns the ID of the player closest to the specified position, or nil if there are no players.

View file

@ -1,6 +1,8 @@
# Документация
Документация версии 0.29.
Документация версии 0.30 (в разработке).
[Документация 0.29.](https://github.com/MihailRis/voxelcore/blob/release-0.29/doc/ru/main-page.md)
## Разделы

View file

@ -0,0 +1,25 @@
# Библиотека *compression*
Библиотека функций для работы сжатия/разжатия массивов байт
```lua
-- Сжимает массив байт.
compression.encode(
-- Массив байт
data: array of integers,
-- Алгоритм сжатия (поддерживается только gzip)
[опционально] algorithm="gzip",
-- Вернуть результат в table?
[опционально] usetable=false
) -> array of integers
-- Разжимает массив байт.
compression.decode(
-- Массив байт
data: array of integers,
-- Алгоритм разжатия (поддерживается только gzip)
[опционально] algorithm="gzip",
-- Вернуть результат в table?
[опционально] usetable=false
) -> array of integers
```

View file

@ -205,4 +205,4 @@ file.open_named_pipe(имя: str, режим: str) -> io_stream
`/tmp/` или `\\\\.\\pipe\\` добавлять не нужно - движок делает это автоматически.
Доступные режимы такие же, как и в `file.open`, за исключением `+`
Доступные режимы такие же, как и в `file.open`, за исключением `+`

View file

@ -93,6 +93,12 @@ socket:is_connected() --> bool
-- Возвращает адрес и порт соединения.
socket:get_address() --> str, int
-- Возвращает состояние NoDelay
socket:is_nodelay() --> bool
-- Устанавливает состояние NoDelay
socket:set_nodelay(state: bool)
```
```lua

View file

@ -89,6 +89,13 @@ player.set_loading_chunks(playerid: int, bool)
Геттер и сеттер свойства, определяющего, прогружает ли игрок чанки вокруг.
```lua
player.get_interaction_distance(playerid: int) -> float
player.set_interaction_distance(playerid: int, distance: float)
```
Геттер и сеттер свойства, определяющего максимальную дистанцию взаимодействия.
```lua
player.set_spawnpoint(playerid: int, x: number, y: number, z: number)
player.get_spawnpoint(playerid: int) -> number, number, number
@ -135,3 +142,21 @@ player.get_entity(playerid: int) -> int
```
Возвращает уникальный идентификатор сущности игрока
```lua
player.get_all_in_radius(center: vec3, radius: number) -> table<int>
```
Возвращает массив id игроков в пределах сферы с центром `center` и радиусом `radius`.
```lua
player.get_all() -> table<int>
```
Возвращает массив id всех активных игроков.
```lua
player.get_nearest(position: vec3) -> int
```
Возвращает id ближайшего к указанной позиции игрока, либо nil если игроков нет.

View file

@ -42,6 +42,8 @@
- `from` - точка начала примитива. Пример: `from (0,0,0)`
- `to` - противоположная от начала точка. Пример: `to (1,1,1)`
- `origin` - точка, относительно которой будет применено вращение. По-умолчанию: центр примитива. Пример: `origin (0.5,0.5,0.5)`
- `rotate` - вращение вокруг осей (x,y,z) в градусах, или кватернион (x,y,z,w). Пример: `rotate (45,0,0)` или `rotate (0.3826834, 0, 0, 0.9238795)`
- `texture` - отображаемая текстура для всех сторон по-умолчанию.
- `shading` определяет возможность затенения на примитиве. Пример: `shading off`
- `delete` удаляет стороны по именам (top, bottom, east, west, north, south)

View file

@ -1,6 +1,6 @@
{
"id": "base",
"title": "Base",
"version": "0.29",
"version": "0.30",
"description": "basic content package"
}

View file

@ -53,10 +53,19 @@ function create_checkbox(id, name, tooltip)
))
end
function on_open()
create_setting("camera.fov", "FOV", 1, "°")
create_setting("display.framerate", "Framerate", 1, "", "", true)
create_checkbox("display.fullscreen", "Fullscreen")
document.root:add(string.format(
"<select context='settings' onselect='function(opt) core.set_setting(\"display.window-mode\", tonumber(opt)) end' selected='%s'>"..
"<option value='0'>@Windowed</option>"..
"<option value='1'>@Fullscreen</option>"..
"<option value='2'>@Borderless</option>"..
"</select>", core.get_setting("display.window-mode"))
)
create_checkbox("camera.shaking", "Camera Shaking")
create_checkbox("camera.inertia", "Camera Inertia")
create_checkbox("camera.fov-effects", "Camera FOV Effects")

View file

@ -44,6 +44,8 @@ local Socket = {__index={
is_alive=function(self) return network.__is_alive(self.id) end,
is_connected=function(self) return network.__is_connected(self.id) end,
get_address=function(self) return network.__get_address(self.id) end,
set_nodelay=function(self, nodelay) return network.__set_nodelay(self.id, nodelay or false) end,
is_nodelay=function(self) return network.__is_nodelay(self.id) end,
}}
local WriteableSocket = {__index={

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

@ -25,6 +25,7 @@ Grant %{0} pack modification permission?=Выдаць дазвол на мады
Error at line %{0}=Памылка ў радку %{0}
Run=Запусціць
Filter=Фільтр
Are you sure you want to open the link: =Ці вы ўпэўненыя, што хочаце адкрыць спасылку:
editor.info.tooltip=CTRL+S - Захаваць\nCTRL+R - Запусціць\nCTRL+Z - Скасаваць\nCTRL+Y - Паўтарыць
devtools.traceback=Стэк выклікаў (ад апошняга)

View file

@ -7,6 +7,7 @@ Back=Zurück
Continue=Weitermachen
Add=Hinzufügen
Converting world...=Weltkonvertierung im Gange...
Are you sure you want to open the link: =Sind Sie sicher, dass Sie den Link öffnen möchten:
error.pack-not-found=Paket konnte nicht gefunden werden
error.dependency-not-found=Die verwendete Abhängigkeit wurde nicht gefunden

View file

@ -19,6 +19,7 @@ Problems=Ongelmia
Monitor=Valvonta
Debug=Virheenkorjaus
File=Tiedosto
Are you sure you want to open the link: =Haluatko varmasti avata linkin:
devtools.traceback=Puhelupino (viimeisestä)
error.pack-not-found=Pakettia ei löytynyt!

View file

@ -7,6 +7,7 @@ Back=Powrót
Continue=Kontynuacja
Add=Dodać
Converting world...=Konwersja świata w toku...
Are you sure you want to open the link: =Czy na pewno chcesz otworzyć link:
error.pack-not-found=Nie udało się znaleźć pakietu

View file

@ -25,6 +25,7 @@ Grant %{0} pack modification permission?=Выдать разрешение на
Error at line %{0}=Ошибка на строке %{0}
Run=Запустить
Filter=Фильтр
Are you sure you want to open the link: =Вы уверены, что хотите открыть ссылку:
editor.info.tooltip=CTRL+S - Сохранить\nCTRL+R - Запустить\nCTRL+Z - Отменить\nCTRL+Y - Повторить
devtools.traceback=Стек вызовов (от последнего)
@ -102,6 +103,8 @@ settings.Limit Background FPS=Ограничить фоновую частоту
settings.Advanced render=Продвинутый рендер
settings.Shadows quality=Качество теней
settings.Conflict=Найдены возможные конфликты
settings.Windowed=Оконный
settings.Borderless=Безрамочный
# Управление
chunks.reload=Перезагрузить Чанки

View file

@ -23,6 +23,7 @@ devtools.traceback=Стек викликів (від останнього)
error.pack-not-found=Не вдалося знайти пакет
error.dependency-not-found=Використовувана залежність не знайдена
pack.remove-confirm=Видалити весь контент, що постачається паком/паками зі світу (безповоротно)?
Are you sure you want to open the link: =Ви впевнені, що хочете відкрити посилання:
# Меню
menu.Apply=Застосувати

View file

@ -24,6 +24,7 @@ Save=Saqlash
Grant %{0} pack modification permission?=%{0} to‘plamini o‘zgartirish ruxsatini berilsinmi?
Error at line %{0}=%{0}-qatorida xatolik
Run=Ishga tushirish
Are you sure you want to open the link: =Haqiqatan ham havolani ochmoqchimisiz:
editor.info.tooltip=CTRL+S - Saqlash\nCTRL+R - Ishga tushirish\nCTRL+Z - Bekor qilish\nCTRL+Y - Qayta bajarish
devtools.traceback=Chaqiruvlar steki (so`nggisidan boshlab)

View file

@ -91,7 +91,9 @@ target_compile_options(
/wd4245 # conversion from 'int' to 'const size_t', signed/unsigned
# mismatch
/wd4100 # unreferenced formal parameter
/wd4457 # declaration of 'var' hides function parameter
/wd4458 # declaration of 'var' hides class member
/wd4459 # declaration of 'var' hides global declaration
/wd4101 # 'var': unreferenced local variable
/wd4388 # 'token' : signed/unsigned mismatch
/wd4018 # '>': signed/unsigned mismatch
@ -101,6 +103,7 @@ target_compile_options(
-Wextra
# additional warnings
-Wformat-nonliteral
#-Wsign-conversion
-Wcast-align
-Wpointer-arith
-Wundef
@ -108,6 +111,10 @@ target_compile_options(
-Wno-unused-parameter
-Wno-sign-compare
-Wno-unknown-pragmas
>
$<$<CXX_COMPILER_ID:Clang>:
-Wduplicated-branches
-Wduplicated-cond
>)
target_link_options(

View file

@ -1,6 +1,8 @@
#include "vcm.hpp"
#include <iostream>
#include <algorithm>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "xml.hpp"
#include "util/stringutil.hpp"
@ -73,6 +75,29 @@ static void perform_box(const xmlelement& root, model::Model& model) {
auto from = root.attr("from").asVec3();
auto to = root.attr("to").asVec3();
glm::vec3 origin = (from + to) * 0.5f;
if (root.has("origin")) {
origin = root.attr("origin").asVec3();
}
glm::mat4 tsf(1.0f);
from -= origin;
to -= origin;
tsf = glm::translate(tsf, origin);
if (root.has("rotate")) {
auto text = root.attr("rotate").getText();
if (std::count(text.begin(), text.end(), ',') == 3) {
auto quat = root.attr("rotate").asVec4();
tsf *= glm::mat4_cast(glm::quat(quat.w, quat.x, quat.y, quat.z));
} else {
auto rot = root.attr("rotate").asVec3();
tsf = glm::rotate(tsf, glm::radians(rot.x), glm::vec3(1, 0, 0));
tsf = glm::rotate(tsf, glm::radians(rot.y), glm::vec3(0, 1, 0));
tsf = glm::rotate(tsf, glm::radians(rot.z), glm::vec3(0, 0, 1));
}
}
UVRegion regions[6] {};
regions[0].scale(to.x - from.x, to.y - from.y);
regions[1].scale(from.x - to.x, to.y - from.y);
@ -142,7 +167,7 @@ static void perform_box(const xmlelement& root, model::Model& model) {
bool enabled[6] {};
enabled[i] = true;
auto& mesh = model.addMesh(texfaces[i], shading);
mesh.addBox(center, halfsize, regions, enabled);
mesh.addBox(center, halfsize, regions, enabled, tsf);
}
}

View file

@ -6,7 +6,7 @@
#include <string>
inline constexpr int ENGINE_VERSION_MAJOR = 0;
inline constexpr int ENGINE_VERSION_MINOR = 29;
inline constexpr int ENGINE_VERSION_MINOR = 30;
#ifdef NDEBUG
inline constexpr bool ENGINE_DEBUG_BUILD = false;
@ -14,7 +14,7 @@ inline constexpr bool ENGINE_DEBUG_BUILD = false;
inline constexpr bool ENGINE_DEBUG_BUILD = true;
#endif // NDEBUG
inline const std::string ENGINE_VERSION_STRING = "0.29";
inline const std::string ENGINE_VERSION_STRING = "0.30";
/// @brief world regions format version
inline constexpr uint REGION_FORMAT_VERSION = 3;

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");
@ -135,10 +139,11 @@ void Engine::initializeClient() {
if (ENGINE_DEBUG_BUILD) {
menus::create_version_label(*gui);
}
keepAlive(settings.display.fullscreen.observe(
[this](bool value) {
if (value != this->window->isFullscreen()) {
this->window->toggleFullscreen();
keepAlive(settings.display.windowMode.observe(
[this](int value) {
WindowMode mode = static_cast<WindowMode>(value);
if (mode != this->window->getMode()) {
this->window->setMode(mode);
}
},
true
@ -172,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());
}
@ -237,7 +254,11 @@ void Engine::updateHotkeys() {
gui->toggleDebug();
}
if (input->jpressed(Keycode::F11)) {
settings.display.fullscreen.toggle();
if (settings.display.windowMode.get() != static_cast<int>(WindowMode::FULLSCREEN)) {
settings.display.windowMode.set(static_cast<int>(WindowMode::FULLSCREEN));
} else {
settings.display.windowMode.set(static_cast<int>(WindowMode::WINDOWED));
}
}
}
@ -261,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() {
@ -282,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());
@ -294,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()
);
}
}
}
@ -313,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,8 @@ struct CoreParameters {
std::filesystem::path userFolder = ".";
std::filesystem::path scriptFile;
std::filesystem::path projectFolder;
std::string debugServerString;
int tps = 20;
};
using OnWorldOpen = std::function<void(std::unique_ptr<Level>, int64_t)>;
@ -71,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;
@ -104,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,
@ -181,4 +186,10 @@ public:
const Project& getProject() {
return *project;
}
devtools::DebuggingServer* getDebuggingServer() {
return debuggingServer.get();
}
void detachDebugger();
};

View file

@ -15,8 +15,6 @@ using namespace std::chrono;
static debug::Logger logger("mainloop");
inline constexpr int TPS = 20;
ServerMainloop::ServerMainloop(Engine& engine) : engine(engine) {
}
@ -39,7 +37,7 @@ void ServerMainloop::run() {
"script:" + coreParams.scriptFile.filename().u8string()
);
double targetDelta = 1.0 / static_cast<double>(TPS);
double targetDelta = 1.0 / static_cast<double>(coreParams.tps);
double delta = targetDelta;
auto begin = system_clock::now();
auto startupTime = begin;

View file

@ -8,21 +8,6 @@ inline constexpr glm::vec3 X(1, 0, 0);
inline constexpr glm::vec3 Y(0, 1, 0);
inline constexpr glm::vec3 Z(0, 0, 1);
void Mesh::addPlane(
const glm::vec3& pos,
const glm::vec3& right,
const glm::vec3& up,
const glm::vec3& norm
) {
vertices.push_back({pos-right-up, {0,0}, norm});
vertices.push_back({pos+right-up, {1,0}, norm});
vertices.push_back({pos+right+up, {1,1}, norm});
vertices.push_back({pos-right-up, {0,0}, norm});
vertices.push_back({pos+right+up, {1,1}, norm});
vertices.push_back({pos-right+up, {0,1}, norm});
}
void Mesh::addPlane(
const glm::vec3& pos,
const glm::vec3& right,
@ -39,6 +24,23 @@ void Mesh::addPlane(
vertices.push_back({pos-right+up, {uv.u1, uv.v2}, norm});
}
void Mesh::addPlane(
const glm::vec3& pos,
const glm::vec3& right,
const glm::vec3& up,
const glm::vec3& norm,
const UVRegion& region,
const glm::mat4& transform
) {
addPlane(
glm::vec3(transform * glm::vec4(pos, 1.0f)),
glm::vec3(transform * glm::vec4(right, 0.0f)),
glm::vec3(transform * glm::vec4(up, 0.0f)),
glm::normalize(glm::vec3(transform * glm::vec4(norm, 0.0f))),
region
);
}
void Mesh::addRect(
const glm::vec3& pos,
const glm::vec3& right,
@ -56,14 +58,15 @@ void Mesh::addRect(
}
void Mesh::addBox(const glm::vec3& pos, const glm::vec3& size) {
addPlane(pos+Z*size, X*size, Y*size, Z);
addPlane(pos-Z*size, -X*size, Y*size, -Z);
UVRegion fullRegion (0, 0, 1, 1);
addPlane(pos+Z*size, X*size, Y*size, Z, fullRegion);
addPlane(pos-Z*size, -X*size, Y*size, -Z, fullRegion);
addPlane(pos+Y*size, X*size, -Z*size, Y);
addPlane(pos-Y*size, X*size, Z*size, -Y);
addPlane(pos+Y*size, X*size, -Z*size, Y, fullRegion);
addPlane(pos-Y*size, X*size, Z*size, -Y, fullRegion);
addPlane(pos+X*size, -Z*size, Y*size, X);
addPlane(pos-X*size, Z*size, Y*size, -X);
addPlane(pos+X*size, -Z*size, Y*size, X, fullRegion);
addPlane(pos-X*size, Z*size, Y*size, -X, fullRegion);
}
void Mesh::addBox(
@ -71,19 +74,29 @@ void Mesh::addBox(
const glm::vec3& size,
const UVRegion (&uvs)[6],
const bool enabledSides[6]
) {
addBox(pos, size, uvs, enabledSides, glm::mat4(1.0f));
}
void Mesh::addBox(
const glm::vec3& pos,
const glm::vec3& size,
const UVRegion (&uvs)[6],
const bool enabledSides[6],
const glm::mat4& transform
) {
if (enabledSides[0]) // north
addPlane(pos+Z*size, X*size, Y*size, Z, uvs[0]);
addPlane(pos+Z*size, X*size, Y*size, Z, uvs[0], transform);
if (enabledSides[1]) // south
addPlane(pos-Z*size, -X*size, Y*size, -Z, uvs[1]);
addPlane(pos-Z*size, -X*size, Y*size, -Z, uvs[1], transform);
if (enabledSides[2]) // top
addPlane(pos+Y*size, X*size, -Z*size, Y, uvs[2] * glm::vec2(-1));
addPlane(pos+Y*size, X*size, -Z*size, Y, uvs[2] * glm::vec2(-1), transform);
if (enabledSides[3]) // bottom
addPlane(pos-Y*size, X*size, Z*size, -Y, uvs[3] * glm::vec2(-1, 1));
addPlane(pos-Y*size, X*size, Z*size, -Y, uvs[3] * glm::vec2(-1, 1), transform);
if (enabledSides[4]) // west
addPlane(pos+X*size, -Z*size, Y*size, X, uvs[4]);
addPlane(pos+X*size, -Z*size, Y*size, X, uvs[4], transform);
if (enabledSides[5]) // east
addPlane(pos-X*size, Z*size, Y*size, -X, uvs[5] * glm::vec2(-1, 1));
addPlane(pos-X*size, Z*size, Y*size, -X, uvs[5] * glm::vec2(-1, 1), transform);
}
void Mesh::scale(const glm::vec3& size) {

View file

@ -22,14 +22,16 @@ namespace model {
const glm::vec3& pos,
const glm::vec3& right,
const glm::vec3& up,
const glm::vec3& norm
const glm::vec3& norm,
const UVRegion& region
);
void addPlane(
const glm::vec3& pos,
const glm::vec3& right,
const glm::vec3& up,
const glm::vec3& norm,
const UVRegion& region
const UVRegion& region,
const glm::mat4& transform
);
void addRect(
const glm::vec3& pos,
@ -45,6 +47,13 @@ namespace model {
const UVRegion (&texfaces)[6],
const bool enabledSides[6]
);
void addBox(
const glm::vec3& pos,
const glm::vec3& size,
const UVRegion (&texfaces)[6],
const bool enabledSides[6],
const glm::mat4& transform
);
void scale(const glm::vec3& size);
};

View file

@ -25,7 +25,7 @@ Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat)
0,
format,
GL_UNSIGNED_BYTE,
NULL
nullptr
);
}
}

View file

@ -1,8 +1,11 @@
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/norm.hpp>
#include <glm/gtc/constants.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "Shadows.hpp"
#include <GL/glew.h>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/norm.hpp>
#include "assets/Assets.hpp"
#include "graphics/core/DrawContext.hpp"
@ -23,7 +26,7 @@ public:
glGenTextures(1, &depthMap);
glBindTexture(GL_TEXTURE_2D, depthMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
resolution, resolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
resolution, resolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);

View file

@ -1,6 +1,8 @@
#include "Emitter.hpp"
#include <glm/gtc/random.hpp>
#include <glm/gtc/constants.hpp>
#include <glm/gtc/matrix_transform.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/norm.hpp>

View file

@ -103,9 +103,14 @@ void guiutil::confirm(
panel->setGravity(Gravity::center_center);
container->add(panel);
panel->setColor(glm::vec4(0.0f, 0.0f, 0.0f, 0.5f));
panel->add(std::make_shared<Label>(gui, text));
auto label = std::make_shared<Label>(gui, text);
label->setSize(glm::vec2(600, 50));
label->setMultiline(true);
label->setTextWrapping(true);
panel->add(label);
auto subpanel = std::make_shared<Panel>(gui, glm::vec2(600, 53));
subpanel->setColor(glm::vec4(0));

View file

@ -17,7 +17,7 @@ namespace guiutil {
/// @brief Create element from XML
/// @param source XML
std::shared_ptr<gui::UINode> create(
gui::GUI& gui, const std::string& source, scriptenv env = 0
gui::GUI& gui, const std::string& source, scriptenv env = nullptr
);
void alert(

View file

@ -6,6 +6,7 @@
#include <stack>
#include "typedefs.hpp"
#include "util/stringutil.hpp"
#include "util/platform.hpp"
#include <utility>
#include "io/devices/StdfsDevice.hpp"
@ -44,6 +45,7 @@ void EnginePaths::prepare() {
resourcesFolder.string() + " is not a directory"
);
}
logger.info() << "executable path: " << platform::get_executable_path().string();
logger.info() << "resources folder: " << fs::canonical(resourcesFolder).u8string();
logger.info() << "user files folder: " << fs::canonical(userFilesFolder).u8string();
logger.info() << "project folder: " << fs::canonical(projectFolder).u8string();

View file

@ -49,8 +49,8 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
builder.add("height", &settings.display.height);
builder.add("samples", &settings.display.samples);
builder.add("framerate", &settings.display.framerate);
builder.add("fullscreen", &settings.display.fullscreen);
builder.add("limit-fps-iconified", &settings.display.limitFpsIconified);
builder.add("window-mode", &settings.display.windowMode);
builder.section("camera");
builder.add("sensitivity", &settings.camera.sensitivity);

View file

@ -488,7 +488,10 @@ void PlayerController::updateInteraction(const Input& inputEvents, float delta)
}
const auto& bindings = inputEvents.getBindings();
bool xkey = bindings.active(BIND_PLAYER_FAST_INTERACTOIN);
float maxDistance = xkey ? 200.0f : 10.0f;
float maxDistance = player.getMaxInteractionDistance();
if (xkey) {
maxDistance *= 100.0;
}
bool longInteraction = interactionTimer <= 0 || xkey;
bool lclick = bindings.jactive(BIND_PLAYER_DESTROY) ||
(longInteraction && bindings.active(BIND_PLAYER_DESTROY));

View file

@ -43,6 +43,7 @@ extern const luaL_Reg playerlib[];
extern const luaL_Reg posteffectslib[]; // gfx.posteffects
extern const luaL_Reg quatlib[];
extern const luaL_Reg randomlib[];
extern const luaL_Reg compressionlib[];
extern const luaL_Reg text3dlib[]; // gfx.text3d
extern const luaL_Reg timelib[];
extern const luaL_Reg tomllib[];

View file

@ -169,4 +169,5 @@ const luaL_Reg rigidbodylib[] = {
{"set_crouching", lua::wrap<l_set_crouching>},
{"get_body_type", lua::wrap<l_get_body_type>},
{"set_body_type", lua::wrap<l_set_body_type>},
{NULL, NULL}};
{nullptr, nullptr}
};

View file

@ -169,5 +169,5 @@ const luaL_Reg skeletonlib[] = {
{"is_interpolated", lua::wrap<l_is_interpolated>},
{"set_interpolated", lua::wrap<l_set_interpolated>},
{"exists", lua::wrap<l_exists>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -51,4 +51,5 @@ const luaL_Reg transformlib[] = {
{"set_size", lua::wrap<l_set_size>},
{"get_rot", lua::wrap<l_get_rot>},
{"set_rot", lua::wrap<l_set_rot>},
{NULL, NULL}};
{nullptr, nullptr}
};

View file

@ -2,5 +2,5 @@
const luaL_Reg applib[] = {
// see libcore.cpp an stdlib.lua
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -67,5 +67,5 @@ static int l_parse_model(lua::State* L) {
const luaL_Reg assetslib[] = {
{"load_texture", lua::wrap<l_load_texture>},
{"parse_model", lua::wrap<l_parse_model>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -394,4 +394,5 @@ const luaL_Reg audiolib[] = {
{"get_velocity", lua::wrap<l_audio_get_velocity>},
{"count_speakers", lua::wrap<l_audio_count_speakers>},
{"count_streams", lua::wrap<l_audio_count_streams>},
{NULL, NULL}};
{nullptr, nullptr}
};

View file

@ -49,5 +49,5 @@ const luaL_Reg base64lib[] = {
{"decode", lua::wrap<l_decode<util::base64_decode>>},
{"encode_urlsafe", lua::wrap<l_encode<util::base64_urlsafe_encode>>},
{"decode_urlsafe", lua::wrap<l_decode<util::base64_urlsafe_decode>>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -35,5 +35,5 @@ static int l_frombytes(lua::State* L) {
const luaL_Reg bjsonlib[] = {
{"tobytes", lua::wrap<l_tobytes>},
{"frombytes", lua::wrap<l_frombytes>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -783,5 +783,5 @@ const luaL_Reg blocklib[] = {
{"has_tag", lua::wrap<l_has_tag>},
{"__get_tags", lua::wrap<l_get_tags>},
{"__pull_register_events", lua::wrap<l_pull_register_events>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -39,5 +39,5 @@ const luaL_Reg blockwrapslib[] = {
{"unwrap", lua::wrap<l_unwrap>},
{"set_pos", lua::wrap<l_set_pos>},
{"set_texture", lua::wrap<l_set_texture>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -208,5 +208,5 @@ const luaL_Reg byteutillib[] = {
{"tpack", lua::wrap<l_tpack>},
{"unpack", lua::wrap<l_unpack>},
{"get_size", lua::wrap<l_get_size>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -125,4 +125,5 @@ const luaL_Reg cameralib[] = {
{"get_right", lua::wrap<l_camera_getter<getter_right>>},
{"get_up", lua::wrap<l_camera_getter<getter_up>>},
{"look_at", lua::wrap<l_look_at>},
{NULL, NULL}};
{nullptr, nullptr}
};

View file

@ -0,0 +1,84 @@
#include "api_lua.hpp"
#include "coders/gzip.hpp"
#include "../lua_engine.hpp"
static int l_encode(lua::State* L) {
char argc = lua::gettop(L);
std::vector<unsigned char> compressedBytes;
std::string algo = "gzip";
if (argc >= 2) {
if (!lua::isstring(L, 2)) {
throw std::runtime_error("compression algorithm must be a string");
}
algo = lua::require_lstring(L, 2);
}
if (algo == "gzip") {
auto str = lua::bytearray_as_string(L, 1);
compressedBytes = gzip::compress(
reinterpret_cast<const ubyte*>(str.data()),
str.size()
);
} else {
throw std::runtime_error("unsupported compression algorithm");
}
if (argc < 3 || !lua::toboolean(L, 3)) {
lua::create_bytearray(L, std::move(compressedBytes));
} else {
size_t length = compressedBytes.size();
lua::createtable(L, length, 0);
int newTable = lua::gettop(L);
for (size_t i = 0; i < length; i++) {
lua::pushinteger(L, compressedBytes.data()[i]);
lua::rawseti(L, i + 1, newTable);
}
}
return 1;
}
static int l_decode(lua::State* L) {
char argc = lua::gettop(L);
std::vector<unsigned char> decompressedBytes;
std::string algo = "gzip";
if (argc >= 2) {
if (!lua::isstring(L, 2)) {
throw std::runtime_error("compression algorithm must be a string");
}
algo = lua::require_lstring(L, 2);
}
if (algo == "gzip") {
auto str = lua::bytearray_as_string(L, 1);
decompressedBytes = gzip::decompress(
reinterpret_cast<const ubyte*>(str.data()),
str.size()
);
} else {
throw std::runtime_error("unsupported compression algorithm");
}
if (argc < 3 || !lua::toboolean(L, 3)) {
lua::create_bytearray(L, std::move(decompressedBytes));
} else {
size_t length = decompressedBytes.size();
lua::createtable(L, length, 0);
int newTable = lua::gettop(L);
for (size_t i = 0; i < length; i++) {
lua::pushinteger(L, decompressedBytes.data()[i]);
lua::rawseti(L, i + 1, newTable);
}
}
return 1;
}
const luaL_Reg compressionlib[] = {
{"encode", lua::wrap<l_encode>},
{"decode", lua::wrap<l_decode>},
{nullptr, nullptr}
};

View file

@ -126,4 +126,5 @@ const luaL_Reg consolelib[] = {
{"set", lua::wrap<l_set>},
{"get_commands_list", lua::wrap<l_get_commands_list>},
{"get_command_info", lua::wrap<l_get_command_info>},
{NULL, NULL}};
{nullptr, nullptr}
};

View file

@ -20,6 +20,11 @@
#include "util/platform.hpp"
#include "world/Level.hpp"
#include "world/generator/WorldGenerator.hpp"
#include "util/platform.hpp"
#include "frontend/locale.hpp"
#include "graphics/ui/gui_util.hpp"
#include "graphics/ui/GUI.hpp"
#include "graphics/ui/elements/Menu.hpp"
using namespace scripting;
@ -229,6 +234,24 @@ static int l_open_folder(lua::State* L) {
return 0;
}
static int l_open_url(lua::State* L) {
auto url = lua::require_string(L, 1);
std::wstring msg = langs::get(L"Are you sure you want to open the link:") +
L"\n" + util::str2wstr_utf8(url) +
std::wstring(L"?");
auto menu = engine->getGUI().getMenu();
guiutil::confirm(*engine, msg, [url, menu]() {
platform::open_url(url);
if (!menu->back()) {
menu->reset();
}
});
return 0;
}
/// @brief Quit the game
static int l_quit(lua::State*) {
engine->quit();
@ -284,7 +307,8 @@ const luaL_Reg corelib[] = {
{"str_setting", lua::wrap<l_str_setting>},
{"get_setting_info", lua::wrap<l_get_setting_info>},
{"open_folder", lua::wrap<l_open_folder>},
{"open_url", lua::wrap<l_open_url>},
{"quit", lua::wrap<l_quit>},
{"capture_output", lua::wrap<l_capture_output>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -255,5 +255,5 @@ const luaL_Reg entitylib[] = {
{"get_all_in_radius", lua::wrap<l_get_all_in_radius>},
{"raycast", lua::wrap<l_raycast>},
{"reload_component", lua::wrap<l_reload_component>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -188,34 +188,6 @@ static int l_list(lua::State* L) {
return 1;
}
static int l_gzip_compress(lua::State* L) {
std::vector<ubyte> bytes;
lua::read_bytes_from_table(L, 1, bytes);
auto compressed_bytes = gzip::compress(bytes.data(), bytes.size());
int newTable = lua::gettop(L);
for (size_t i = 0; i < compressed_bytes.size(); i++) {
lua::pushinteger(L, compressed_bytes.data()[i]);
lua::rawseti(L, i + 1, newTable);
}
return 1;
}
static int l_gzip_decompress(lua::State* L) {
std::vector<ubyte> bytes;
lua::read_bytes_from_table(L, 1, bytes);
auto decompressed_bytes = gzip::decompress(bytes.data(), bytes.size());
int newTable = lua::gettop(L);
for (size_t i = 0; i < decompressed_bytes.size(); i++) {
lua::pushinteger(L, decompressed_bytes.data()[i]);
lua::rawseti(L, i + 1, newTable);
}
return 1;
}
static int l_read_combined_list(lua::State* L) {
std::string path = lua::require_string(L, 1);
if (path.find(':') != std::string::npos) {
@ -419,8 +391,6 @@ const luaL_Reg filelib[] = {
{"resolve", lua::wrap<l_resolve>},
{"write_bytes", lua::wrap<l_write_bytes>},
{"write", lua::wrap<l_write>},
{"gzip_compress", lua::wrap<l_gzip_compress>},
{"gzip_decompress", lua::wrap<l_gzip_decompress>},
{"read_combined_list", lua::wrap<l_read_combined_list>},
{"read_combined_object", lua::wrap<l_read_combined_object>},
{"is_writeable", lua::wrap<l_is_writeable>},
@ -434,5 +404,6 @@ const luaL_Reg filelib[] = {
{"__flush_descriptor", lua::wrap<l_flush_descriptor>},
{"__close_descriptor", lua::wrap<l_close_descriptor>},
{"__close_all_descriptors", lua::wrap<l_close_all_descriptors>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -104,5 +104,5 @@ const luaL_Reg generationlib[] = {
{"load_fragment", lua::wrap<l_load_fragment>},
{"get_generators", lua::wrap<l_get_generators>},
{"get_default_generator", lua::wrap<l_get_default_generator>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -1053,5 +1053,5 @@ const luaL_Reg guilib[] = {
{"alert", lua::wrap<l_gui_alert>},
{"load_document", lua::wrap<l_gui_load_document>},
{"__reindex", lua::wrap<l_gui_reindex>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -208,5 +208,5 @@ const luaL_Reg hudlib[] = {
{"_set_debug_cheats", wrap_hud<l_set_debug_cheats>},
{"set_allow_pause", wrap_hud<l_set_allow_pause>},
{"reload_script", wrap_hud<l_reload_script>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -177,5 +177,5 @@ const luaL_Reg inputlib[] = {
{"is_pressed", lua::wrap<l_is_pressed>},
{"reset_bindings", lua::wrap<l_reset_bindings>},
{"set_enabled", lua::wrap<l_set_enabled>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -256,5 +256,5 @@ const luaL_Reg inventorylib[] = {
{"create", lua::wrap<l_create>},
{"remove", lua::wrap<l_remove>},
{"clone", lua::wrap<l_clone>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -147,5 +147,5 @@ const luaL_Reg itemlib[] = {
{"reload_script", lua::wrap<l_reload_script>},
{"has_tag", lua::wrap<l_has_tag>},
{"__get_tags", lua::wrap<l_get_tags>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -19,5 +19,5 @@ static int l_json_parse(lua::State* L) {
const luaL_Reg jsonlib[] = {
{"tostring", lua::wrap<l_json_stringify>},
{"parse", lua::wrap<l_json_parse>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -282,4 +282,5 @@ const luaL_Reg mat4lib[] = {
{"look_at", lua::wrap<l_look_at>},
{"from_quat", lua::wrap<l_from_quat>},
{"tostring", lua::wrap<l_tostring>},
{NULL, NULL}};
{nullptr, nullptr}
};

View file

@ -155,7 +155,7 @@ static int l_post(lua::State* L, network::Network& network) {
static int l_close(lua::State* L, network::Network& network) {
u64id_t id = lua::tointeger(L, 1);
if (auto connection = network.getConnection(id)) {
if (auto connection = network.getConnection(id, false)) {
connection->close(true);
}
return 0;
@ -163,7 +163,7 @@ static int l_close(lua::State* L, network::Network& network) {
static int l_closeserver(lua::State* L, network::Network& network) {
u64id_t id = lua::tointeger(L, 1);
if (auto server = network.getServer(id)) {
if (auto server = network.getServer(id, false)) {
server->close();
}
return 0;
@ -171,7 +171,7 @@ static int l_closeserver(lua::State* L, network::Network& network) {
static int l_send(lua::State* L, network::Network& network) {
u64id_t id = lua::tointeger(L, 1);
auto connection = network.getConnection(id);
auto connection = network.getConnection(id, false);
if (connection == nullptr ||
connection->getState() == network::ConnectionState::CLOSED) {
return 0;
@ -201,7 +201,7 @@ static int l_send(lua::State* L, network::Network& network) {
static int l_udp_server_send_to(lua::State* L, network::Network& network) {
u64id_t id = lua::tointeger(L, 1);
if (auto server = network.getServer(id)) {
if (auto server = network.getServer(id, false)) {
if (server->getTransportType() != network::TransportType::UDP)
throw std::runtime_error("the server must work on UDP transport");
@ -238,7 +238,7 @@ static int l_recv(lua::State* L, network::Network& network) {
u64id_t id = lua::tointeger(L, 1);
int length = lua::tointeger(L, 2);
auto connection = engine->getNetwork().getConnection(id);
auto connection = engine->getNetwork().getConnection(id, false);
if (connection == nullptr || connection->getTransportType() != network::TransportType::TCP) {
return 0;
@ -268,7 +268,7 @@ static int l_recv(lua::State* L, network::Network& network) {
static int l_available(lua::State* L, network::Network& network) {
u64id_t id = lua::tointeger(L, 1);
if (auto connection = network.getConnection(id)) {
if (auto connection = network.getConnection(id, false)) {
return lua::pushinteger(L, dynamic_cast<network::TcpConnection*>(connection)->available());
}
@ -345,7 +345,7 @@ static int l_open_udp(lua::State* L, network::Network& network) {
static int l_is_alive(lua::State* L, network::Network& network) {
u64id_t id = lua::tointeger(L, 1);
if (auto connection = network.getConnection(id)) {
if (auto connection = network.getConnection(id, false)) {
return lua::pushboolean(
L,
connection->getState() != network::ConnectionState::CLOSED ||
@ -360,7 +360,7 @@ static int l_is_alive(lua::State* L, network::Network& network) {
static int l_is_connected(lua::State* L, network::Network& network) {
u64id_t id = lua::tointeger(L, 1);
if (auto connection = network.getConnection(id)) {
if (auto connection = network.getConnection(id, false)) {
return lua::pushboolean(
L, connection->getState() == network::ConnectionState::CONNECTED
);
@ -370,7 +370,7 @@ static int l_is_connected(lua::State* L, network::Network& network) {
static int l_get_address(lua::State* L, network::Network& network) {
u64id_t id = lua::tointeger(L, 1);
if (auto connection = network.getConnection(id)) {
if (auto connection = network.getConnection(id, false)) {
lua::pushstring(L, connection->getAddress());
lua::pushinteger(L, connection->getPort());
return 2;
@ -380,7 +380,7 @@ static int l_get_address(lua::State* L, network::Network& network) {
static int l_is_serveropen(lua::State* L, network::Network& network) {
u64id_t id = lua::tointeger(L, 1);
if (auto server = network.getServer(id)) {
if (auto server = network.getServer(id, false)) {
return lua::pushboolean(L, server->isOpen());
}
return lua::pushboolean(L, false);
@ -388,7 +388,7 @@ static int l_is_serveropen(lua::State* L, network::Network& network) {
static int l_get_serverport(lua::State* L, network::Network& network) {
u64id_t id = lua::tointeger(L, 1);
if (auto server = network.getServer(id)) {
if (auto server = network.getServer(id, false)) {
return lua::pushinteger(L, server->getPort());
}
return 0;
@ -402,6 +402,28 @@ static int l_get_total_download(lua::State* L, network::Network& network) {
return lua::pushinteger(L, network.getTotalDownload());
}
static int l_set_nodelay(lua::State* L, network::Network& network) {
u64id_t id = lua::tointeger(L, 1);
bool noDelay = lua::toboolean(L, 2);
if (auto connection = network.getConnection(id, false)) {
if (connection->getTransportType() == network::TransportType::TCP) {
dynamic_cast<network::TcpConnection*>(connection)->setNoDelay(noDelay);
}
}
return 0;
}
static int l_is_nodelay(lua::State* L, network::Network& network) {
u64id_t id = lua::tointeger(L, 1);
if (auto connection = network.getConnection(id, false)) {
if (connection->getTransportType() == network::TransportType::TCP) {
bool noDelay = dynamic_cast<network::TcpConnection*>(connection)->isNoDelay();
return lua::pushboolean(L, noDelay);
}
}
return lua::pushboolean(L, false);
}
static int l_pull_events(lua::State* L, network::Network& network) {
std::vector<NetworkEvent> local_queue;
{
@ -517,5 +539,7 @@ const luaL_Reg networklib[] = {
{"__get_address", wrap<l_get_address>},
{"__is_serveropen", wrap<l_is_serveropen>},
{"__get_serverport", wrap<l_get_serverport>},
{NULL, NULL}
{"__set_nodelay", wrap<l_set_nodelay>},
{"__is_nodelay", wrap<l_is_nodelay>},
{nullptr, nullptr}
};

View file

@ -268,5 +268,5 @@ const luaL_Reg packlib[] = {
{"get_base_packs", lua::wrap<l_pack_get_base_packs>},
{"assemble", lua::wrap<l_pack_assemble>},
{"request_writeable", lua::wrap<l_pack_request_writeable>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -82,5 +82,5 @@ const luaL_Reg particleslib[] = {
{"is_alive", wrap_hud<l_is_alive>},
{"get_origin", wrap_hud<l_get_origin>},
{"set_origin", wrap_hud<l_set_origin>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -126,5 +126,5 @@ const luaL_Reg pathfindinglib[] = {
{"set_max_visited", lua::wrap<l_set_max_visited_blocks>},
{"set_jump_height", lua::wrap<l_set_jump_height>},
{"avoid_tag", lua::wrap<l_avoid_tag>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -2,14 +2,14 @@
#include <glm/glm.hpp>
#include "items/Inventory.hpp"
#include "libentity.hpp"
#include "objects/Entities.hpp"
#include "objects/Entity.hpp"
#include "objects/Player.hpp"
#include "objects/Players.hpp"
#include "objects/Entity.hpp"
#include "physics/Hitbox.hpp"
#include "window/Camera.hpp"
#include "world/Level.hpp"
#include "libentity.hpp"
using namespace scripting;
@ -53,7 +53,7 @@ static int l_set_vel(lua::State* L) {
auto x = lua::tonumber(L, 2);
auto y = lua::tonumber(L, 3);
auto z = lua::tonumber(L, 4);
if (auto hitbox = player->getHitbox()) {
hitbox->velocity = glm::vec3(x, y, z);
}
@ -181,6 +181,21 @@ static int l_set_loading_chunks(lua::State* L) {
return 0;
}
static int l_get_interaction_distance(lua::State* L) {
if (auto player = get_player(L, 1)) {
return lua::pushnumber(L, player->getMaxInteractionDistance());
}
return 0;
}
static int l_set_interaction_distance(lua::State* L) {
if (auto player = get_player(L, 1)) {
player->setMaxInteractionDistance(
static_cast<float>(lua::tonumber(L, 2)));
}
return 0;
}
static int l_get_selected_block(lua::State* L) {
if (auto player = get_player(L, 1)) {
if (player->selection.vox.id == BLOCK_VOID) {
@ -307,6 +322,38 @@ static int l_set_suspended(lua::State* L) {
return 0;
}
static int l_get_all_in_radius(lua::State* L) {
auto center = lua::tovec3(L, 1);
auto radius = static_cast<float>(lua::tonumber(L, 2));
auto players = level->players->getAllInRadius(center, radius);
lua::createtable(L, players.size(), 0);
for (size_t i = 0; i < players.size(); i++) {
lua::pushinteger(L, players[i]->getId());
lua::rawseti(L, i + 1);
}
return 1;
}
static int l_get_all(lua::State* L) {
auto players = level->players->getAll();
lua::createtable(L, players.size(), 0);
for (size_t i = 0; i < players.size(); i++) {
lua::pushinteger(L, players[i]->getId());
lua::rawseti(L, i + 1);
}
return 1;
}
static int l_get_nearest(lua::State* L) {
auto position = lua::tovec3(L, 1);
if (auto player = level->players->getNearest(position)) {
lua::pushinteger(L, player->getId());
return 1;
}
return 0;
}
const luaL_Reg playerlib[] = {
{"get_pos", lua::wrap<l_get_pos>},
{"set_pos", lua::wrap<l_set_pos>},
@ -328,6 +375,8 @@ const luaL_Reg playerlib[] = {
{"set_instant_destruction", lua::wrap<l_set_instant_destruction>},
{"is_loading_chunks", lua::wrap<l_is_loading_chunks>},
{"set_loading_chunks", lua::wrap<l_set_loading_chunks>},
{"get_interaction_distance", lua::wrap<l_get_interaction_distance>},
{"set_interaction_distance", lua::wrap<l_set_interaction_distance>},
{"set_selected_slot", lua::wrap<l_set_selected_slot>},
{"get_selected_block", lua::wrap<l_get_selected_block>},
{"get_selected_entity", lua::wrap<l_get_selected_entity>},
@ -341,5 +390,8 @@ const luaL_Reg playerlib[] = {
{"set_name", lua::wrap<l_set_name>},
{"create", lua::wrap<l_create>},
{"delete", lua::wrap<l_delete>},
{NULL, NULL}
{"get_all_in_radius", lua::wrap<l_get_all_in_radius>},
{"get_all", lua::wrap<l_get_all>},
{"get_nearest", lua::wrap<l_get_nearest>},
{nullptr, nullptr}
};

View file

@ -84,5 +84,5 @@ const luaL_Reg posteffectslib[] = {
{"is_active", lua::wrap<l_is_active>},
{"set_params", lua::wrap<l_set_params>},
{"set_array", lua::wrap<l_set_array>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -48,4 +48,5 @@ const luaL_Reg quatlib[] = {
{"from_mat4", lua::wrap<l_from_mat4>},
{"slerp", lua::wrap<l_slerp>},
{"tostring", lua::wrap<l_tostring>},
{NULL, NULL}};
{nullptr, nullptr}
};

View file

@ -42,5 +42,5 @@ const luaL_Reg randomlib[] = {
{"random", lua::wrap<l_random>},
{"bytes", lua::wrap<l_bytes>},
{"uuid", lua::wrap<l_uuid>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -108,5 +108,5 @@ const luaL_Reg text3dlib[] = {
{"set_axis_y", wrap_hud<l_set_axis_y>},
{"set_rotation", wrap_hud<l_set_rotation>},
{"update_settings", wrap_hud<l_update_settings>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -68,5 +68,5 @@ const luaL_Reg timelib[] = {
{"utc_time", lua::wrap<l_utc_time>},
{"utc_offset", lua::wrap<l_utc_offset>},
{"local_time", lua::wrap<l_local_time>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -23,5 +23,5 @@ static int l_toml_parse(lua::State* L) {
const luaL_Reg tomllib[] = {
{"tostring", lua::wrap<l_toml_stringify>},
{"parse", lua::wrap<l_toml_parse>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -99,5 +99,5 @@ const luaL_Reg utf8lib[] = {
{"lower", lua::wrap<l_lower>},
{"encode", lua::wrap<l_encode>},
{"escape", lua::wrap<l_escape>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -228,7 +228,8 @@ const luaL_Reg vec2lib[] = {
{"angle", lua::wrap<l_vec2_angle>},
{"mix", lua::wrap<l_mix<2>>},
{"rotate", lua::wrap<l_vec2_rotate>},
{NULL, NULL}};
{nullptr, nullptr}
};
const luaL_Reg vec3lib[] = {
{"add", lua::wrap<l_binop<3, std::plus>>},
@ -246,7 +247,8 @@ const luaL_Reg vec3lib[] = {
{"dot", lua::wrap<l_dot<3>>},
{"spherical_rand", lua::wrap<l_spherical_rand>},
{"mix", lua::wrap<l_mix<3>>},
{NULL, NULL}};
{nullptr, nullptr}
};
const luaL_Reg vec4lib[] = {
{"add", lua::wrap<l_binop<4, std::plus>>},
@ -263,4 +265,5 @@ const luaL_Reg vec4lib[] = {
{"pow", lua::wrap<l_pow<4>>},
{"dot", lua::wrap<l_dot<4>>},
{"mix", lua::wrap<l_mix<4>>},
{NULL, NULL}};
{nullptr, nullptr}
};

View file

@ -64,5 +64,5 @@ const luaL_Reg weatherlib[] = {
{"get_current_data", lua::wrap<l_get_current_data>},
{"get_fall_intensity", lua::wrap<l_get_fall_intensity>},
{"is_transition", lua::wrap<l_is_transition>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -251,5 +251,5 @@ const luaL_Reg worldlib[] = {
{"save_chunk_data", lua::wrap<l_save_chunk_data>},
{"count_chunks", lua::wrap<l_count_chunks>},
{"reload_script", lua::wrap<l_reload_script>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -16,5 +16,5 @@ static int l_parse(lua::State* L) {
const luaL_Reg yamllib[] = {
{"tostring", lua::wrap<l_stringify>},
{"parse", lua::wrap<l_parse>},
{NULL, NULL}
{nullptr, nullptr}
};

View file

@ -52,6 +52,7 @@ static void create_libs(State* L, StateType stateType) {
openlib(L, "pack", packlib);
openlib(L, "quat", quatlib);
openlib(L, "random", randomlib);
openlib(L, "compression", compressionlib);
openlib(L, "toml", tomllib);
openlib(L, "utf8", utf8lib);
openlib(L, "vec2", vec2lib);

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

@ -14,7 +14,7 @@ int l_print(lua::State* L) {
lua::pushvalue(L, i); /* value to print */
lua::call(L, 1, 1);
const char* s = lua::tostring(L, -1); /* get result */
if (s == NULL)
if (s == nullptr)
return luaL_error(
L,
LUA_QL("tostring") " must return a string to " LUA_QL("print")

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

@ -23,7 +23,7 @@ namespace util {
PseudoRandom(unsigned short seed) : seed(seed) {}
PseudoRandom() {
seed = static_cast<unsigned short>(time(0));
seed = static_cast<unsigned short>(time(nullptr));
}
int rand() {

239
src/network/Curl.cpp Normal file
View file

@ -0,0 +1,239 @@
#include "commons.hpp"
#include "debug/Logger.hpp"
#define NOMINMAX
#include <curl/curl.h>
#include <queue>
using namespace network;
static debug::Logger logger("curl");
inline constexpr int HTTP_OK = 200;
inline constexpr int HTTP_BAD_GATEWAY = 502;
static size_t write_callback(
char* ptr, size_t size, size_t nmemb, void* userdata
) {
auto& buffer = *reinterpret_cast<std::vector<char>*>(userdata);
size_t psize = buffer.size();
buffer.resize(psize + size * nmemb);
std::memcpy(buffer.data() + psize, ptr, size * nmemb);
return size * nmemb;
}
enum class RequestType {
GET, POST
};
struct Request {
RequestType type;
std::string url;
OnResponse onResponse;
OnReject onReject;
long maxSize;
bool followLocation = false;
std::string data;
std::vector<std::string> headers;
};
class CurlRequests : public Requests {
CURLM* multiHandle;
CURL* curl;
size_t totalUpload = 0;
size_t totalDownload = 0;
OnResponse onResponse;
OnReject onReject;
std::vector<char> buffer;
std::string url;
std::queue<Request> requests;
public:
CurlRequests(CURLM* multiHandle, CURL* curl)
: multiHandle(multiHandle), curl(curl) {
}
virtual ~CurlRequests() {
curl_multi_remove_handle(multiHandle, curl);
curl_easy_cleanup(curl);
curl_multi_cleanup(multiHandle);
}
void get(
const std::string& url,
OnResponse onResponse,
OnReject onReject,
std::vector<std::string> headers,
long maxSize
) override {
Request request {
RequestType::GET,
url,
onResponse,
onReject,
maxSize,
true,
"",
std::move(headers)};
processRequest(std::move(request));
}
void post(
const std::string& url,
const std::string& data,
OnResponse onResponse,
OnReject onReject=nullptr,
std::vector<std::string> headers = {},
long maxSize=0
) override {
Request request {
RequestType::POST,
url,
onResponse,
onReject,
maxSize,
false,
"",
std::move(headers)};
request.data = data;
processRequest(std::move(request));
}
void processRequest(Request request) {
if (!url.empty()) {
requests.push(request);
return;
}
onResponse = request.onResponse;
onReject = request.onReject;
url = request.url;
buffer.clear();
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_POST, request.type == RequestType::POST);
curl_slist* hs = nullptr;
for (const auto& header : request.headers) {
hs = curl_slist_append(hs, header.c_str());
}
switch (request.type) {
case RequestType::GET:
break;
case RequestType::POST:
hs = curl_slist_append(hs, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request.data.length());
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, request.data.c_str());
break;
default:
throw std::runtime_error("not implemented");
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hs);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, request.followLocation);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/7.81.0");
if (request.maxSize == 0) {
curl_easy_setopt(
curl, CURLOPT_MAXFILESIZE, std::numeric_limits<long>::max()
);
} else {
curl_easy_setopt(curl, CURLOPT_MAXFILESIZE, request.maxSize);
}
curl_multi_add_handle(multiHandle, curl);
int running;
CURLMcode res = curl_multi_perform(multiHandle, &running);
if (res != CURLM_OK) {
auto message = curl_multi_strerror(res);
logger.error() << message << " (" << url << ")";
if (onReject) {
onReject(HTTP_BAD_GATEWAY, {});
}
url = "";
}
}
void update() override {
int messagesLeft;
int running;
CURLMsg* msg;
CURLMcode res = curl_multi_perform(multiHandle, &running);
if (res != CURLM_OK) {
auto message = curl_multi_strerror(res);
logger.error() << message << " (" << url << ")";
if (onReject) {
onReject(HTTP_BAD_GATEWAY, {});
}
curl_multi_remove_handle(multiHandle, curl);
url = "";
return;
}
if ((msg = curl_multi_info_read(multiHandle, &messagesLeft)) != nullptr) {
if(msg->msg == CURLMSG_DONE) {
curl_multi_remove_handle(multiHandle, curl);
}
int response;
curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response);
if (response == HTTP_OK) {
long size;
if (!curl_easy_getinfo(curl, CURLINFO_REQUEST_SIZE, &size)) {
totalUpload += size;
}
if (!curl_easy_getinfo(curl, CURLINFO_HEADER_SIZE, &size)) {
totalDownload += size;
}
totalDownload += buffer.size();
if (onResponse) {
onResponse(std::move(buffer));
}
} else {
logger.error()
<< "response code " << response << " (" << url << ")"
<< (buffer.empty()
? ""
: std::to_string(buffer.size()) + " byte(s)");
totalDownload += buffer.size();
if (onReject) {
onReject(response, std::move(buffer));
}
}
url = "";
}
if (url.empty() && !requests.empty()) {
auto request = std::move(requests.front());
requests.pop();
processRequest(std::move(request));
}
}
size_t getTotalUpload() const override {
return totalUpload;
}
size_t getTotalDownload() const override {
return totalDownload;
}
static std::unique_ptr<CurlRequests> create() {
auto curl = curl_easy_init();
if (curl == nullptr) {
throw std::runtime_error("could not initialzie cURL");
}
auto multiHandle = curl_multi_init();
if (multiHandle == nullptr) {
curl_easy_cleanup(curl);
throw std::runtime_error("could not initialzie cURL-multi");
}
return std::make_unique<CurlRequests>(multiHandle, curl);
}
};
namespace network {
std::unique_ptr<Requests> create_curl_requests() {
return CurlRequests::create();
}
}

View file

@ -1,853 +1,44 @@
#include "Network.hpp"
#pragma comment(lib, "Ws2_32.lib")
#define NOMINMAX
#include <curl/curl.h>
#include <stdexcept>
#include <limits>
#include <queue>
#include <thread>
#ifdef _WIN32
/// included in curl.h
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
using SOCKET = int;
#endif // _WIN32
#include "debug/Logger.hpp"
#include "util/stringutil.hpp"
using namespace network;
inline constexpr int HTTP_OK = 200;
inline constexpr int HTTP_BAD_GATEWAY = 502;
static debug::Logger logger("network");
static size_t write_callback(
char* ptr, size_t size, size_t nmemb, void* userdata
) {
auto& buffer = *reinterpret_cast<std::vector<char>*>(userdata);
size_t psize = buffer.size();
buffer.resize(psize + size * nmemb);
std::memcpy(buffer.data() + psize, ptr, size * nmemb);
return size * nmemb;
}
namespace network {
std::unique_ptr<Requests> create_curl_requests();
enum class RequestType {
GET, POST
};
struct Request {
RequestType type;
std::string url;
OnResponse onResponse;
OnReject onReject;
long maxSize;
bool followLocation = false;
std::string data;
std::vector<std::string> headers;
};
class CurlRequests : public Requests {
CURLM* multiHandle;
CURL* curl;
size_t totalUpload = 0;
size_t totalDownload = 0;
OnResponse onResponse;
OnReject onReject;
std::vector<char> buffer;
std::string url;
std::queue<Request> requests;
public:
CurlRequests(CURLM* multiHandle, CURL* curl)
: multiHandle(multiHandle), curl(curl) {
}
virtual ~CurlRequests() {
curl_multi_remove_handle(multiHandle, curl);
curl_easy_cleanup(curl);
curl_multi_cleanup(multiHandle);
}
void get(
const std::string& url,
OnResponse onResponse,
OnReject onReject,
std::vector<std::string> headers,
long maxSize
) override {
Request request {
RequestType::GET,
url,
onResponse,
onReject,
maxSize,
true,
"",
std::move(headers)};
processRequest(std::move(request));
}
void post(
const std::string& url,
const std::string& data,
OnResponse onResponse,
OnReject onReject=nullptr,
std::vector<std::string> headers = {},
long maxSize=0
) override {
Request request {
RequestType::POST,
url,
onResponse,
onReject,
maxSize,
false,
"",
std::move(headers)};
request.data = data;
processRequest(std::move(request));
}
void processRequest(Request request) {
if (!url.empty()) {
requests.push(request);
return;
}
onResponse = request.onResponse;
onReject = request.onReject;
url = request.url;
buffer.clear();
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_POST, request.type == RequestType::POST);
curl_slist* hs = NULL;
for (const auto& header : request.headers) {
hs = curl_slist_append(hs, header.c_str());
}
switch (request.type) {
case RequestType::GET:
break;
case RequestType::POST:
hs = curl_slist_append(hs, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request.data.length());
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, request.data.c_str());
break;
default:
throw std::runtime_error("not implemented");
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hs);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, request.followLocation);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/7.81.0");
if (request.maxSize == 0) {
curl_easy_setopt(
curl, CURLOPT_MAXFILESIZE, std::numeric_limits<long>::max()
);
} else {
curl_easy_setopt(curl, CURLOPT_MAXFILESIZE, request.maxSize);
}
curl_multi_add_handle(multiHandle, curl);
int running;
CURLMcode res = curl_multi_perform(multiHandle, &running);
if (res != CURLM_OK) {
auto message = curl_multi_strerror(res);
logger.error() << message << " (" << url << ")";
if (onReject) {
onReject(HTTP_BAD_GATEWAY, {});
}
url = "";
}
}
void update() override {
int messagesLeft;
int running;
CURLMsg* msg;
CURLMcode res = curl_multi_perform(multiHandle, &running);
if (res != CURLM_OK) {
auto message = curl_multi_strerror(res);
logger.error() << message << " (" << url << ")";
if (onReject) {
onReject(HTTP_BAD_GATEWAY, {});
}
curl_multi_remove_handle(multiHandle, curl);
url = "";
return;
}
if ((msg = curl_multi_info_read(multiHandle, &messagesLeft)) != NULL) {
if(msg->msg == CURLMSG_DONE) {
curl_multi_remove_handle(multiHandle, curl);
}
int response;
curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response);
if (response == HTTP_OK) {
long size;
if (!curl_easy_getinfo(curl, CURLINFO_REQUEST_SIZE, &size)) {
totalUpload += size;
}
if (!curl_easy_getinfo(curl, CURLINFO_HEADER_SIZE, &size)) {
totalDownload += size;
}
totalDownload += buffer.size();
if (onResponse) {
onResponse(std::move(buffer));
}
} else {
logger.error()
<< "response code " << response << " (" << url << ")"
<< (buffer.empty()
? ""
: std::to_string(buffer.size()) + " byte(s)");
totalDownload += buffer.size();
if (onReject) {
onReject(response, std::move(buffer));
}
}
url = "";
}
if (url.empty() && !requests.empty()) {
auto request = std::move(requests.front());
requests.pop();
processRequest(std::move(request));
}
}
size_t getTotalUpload() const override {
return totalUpload;
}
size_t getTotalDownload() const override {
return totalDownload;
}
static std::unique_ptr<CurlRequests> create() {
auto curl = curl_easy_init();
if (curl == nullptr) {
throw std::runtime_error("could not initialzie cURL");
}
auto multiHandle = curl_multi_init();
if (multiHandle == nullptr) {
curl_easy_cleanup(curl);
throw std::runtime_error("could not initialzie cURL-multi");
}
return std::make_unique<CurlRequests>(multiHandle, curl);
}
};
#ifndef _WIN32
static inline int closesocket(int descriptor) noexcept {
return close(descriptor);
}
static inline std::runtime_error handle_socket_error(const std::string& message) {
int err = errno;
return std::runtime_error(
message+" [errno=" + std::to_string(err) + "]: " +
std::string(strerror(err))
);
}
#else
static inline std::runtime_error handle_socket_error(const std::string& message) {
int errorCode = WSAGetLastError();
wchar_t* s = nullptr;
size_t size = FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
errorCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&s,
0,
nullptr
);
assert(s != nullptr);
while (size && isspace(s[size-1])) {
s[--size] = 0;
}
auto errorString = util::wstr2str_utf8(std::wstring(s));
LocalFree(s);
return std::runtime_error(message+" [WSA error=" +
std::to_string(errorCode) + "]: "+errorString);
}
#endif
static inline int connectsocket(
int descriptor, const sockaddr* addr, socklen_t len
) noexcept {
return connect(descriptor, addr, len);
}
static inline int recvsocket(
int descriptor, char* buf, size_t len
) noexcept {
return recv(descriptor, buf, len, 0);
}
static inline int sendsocket(
int descriptor, const char* buf, size_t len, int flags
) noexcept {
return send(descriptor, buf, len, flags);
}
static std::string to_string(const sockaddr_in& addr, bool port=true) {
char ip[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &(addr.sin_addr), ip, INET_ADDRSTRLEN)) {
return std::string(ip) +
(port ? (":" + std::to_string(htons(addr.sin_port))) : "");
}
return "";
}
class SocketTcpConnection : public TcpConnection {
SOCKET descriptor;
sockaddr_in addr;
size_t totalUpload = 0;
size_t totalDownload = 0;
ConnectionState state = ConnectionState::INITIAL;
std::unique_ptr<std::thread> thread = nullptr;
std::vector<char> readBatch;
util::Buffer<char> buffer;
std::mutex mutex;
void connectSocket() {
state = ConnectionState::CONNECTING;
logger.info() << "connecting to " << to_string(addr);
int res = connectsocket(descriptor, (const sockaddr*)&addr, sizeof(sockaddr_in));
if (res < 0) {
auto error = handle_socket_error("Connect failed");
closesocket(descriptor);
state = ConnectionState::CLOSED;
logger.error() << error.what();
return;
}
logger.info() << "connected to " << to_string(addr);
state = ConnectionState::CONNECTED;
}
public:
SocketTcpConnection(SOCKET descriptor, sockaddr_in addr)
: descriptor(descriptor), addr(std::move(addr)), buffer(16'384) {}
~SocketTcpConnection() {
if (state != ConnectionState::CLOSED) {
shutdown(descriptor, 2);
}
if (thread) {
thread->join();
}
}
void startListen() {
while (state == ConnectionState::CONNECTED) {
int size = recvsocket(descriptor, buffer.data(), buffer.size());
if (size == 0) {
logger.info() << "closed connection with " << to_string(addr);
closesocket(descriptor);
state = ConnectionState::CLOSED;
break;
} else if (size < 0) {
logger.warning() << "an error ocurred while receiving from "
<< to_string(addr);
auto error = handle_socket_error("recv(...) error");
closesocket(descriptor);
state = ConnectionState::CLOSED;
logger.error() << error.what();
break;
}
{
std::lock_guard lock(mutex);
for (size_t i = 0; i < size; i++) {
readBatch.emplace_back(buffer[i]);
}
totalDownload += size;
}
}
}
void startClient() {
state = ConnectionState::CONNECTED;
thread = std::make_unique<std::thread>([this]() { startListen();});
}
void connect(runnable callback) override {
thread = std::make_unique<std::thread>([this, callback]() {
connectSocket();
if (state == ConnectionState::CONNECTED) {
callback();
}
startListen();
});
}
int recv(char* buffer, size_t length) override {
std::lock_guard lock(mutex);
if (state != ConnectionState::CONNECTED && readBatch.empty()) {
return -1;
}
int size = std::min(readBatch.size(), length);
std::memcpy(buffer, readBatch.data(), size);
readBatch.erase(readBatch.begin(), readBatch.begin() + size);
return size;
}
int send(const char* buffer, size_t length) override {
if (state == ConnectionState::CLOSED) {
return 0;
}
int len = sendsocket(descriptor, buffer, length, 0);
if (len == -1) {
int err = errno;
close();
throw std::runtime_error(
"Send failed [errno=" + std::to_string(err) + "]: "
+ std::string(strerror(err))
);
}
totalUpload += len;
return len;
}
int available() override {
std::lock_guard lock(mutex);
return readBatch.size();
}
void close(bool discardAll=false) override {
{
std::lock_guard lock(mutex);
readBatch.clear();
if (state != ConnectionState::CLOSED) {
shutdown(descriptor, 2);
closesocket(descriptor);
}
}
if (thread) {
thread->join();
thread = nullptr;
}
}
size_t pullUpload() override {
size_t size = totalUpload;
totalUpload = 0;
return size;
}
size_t pullDownload() override {
size_t size = totalDownload;
totalDownload = 0;
return size;
}
int getPort() const override {
return htons(addr.sin_port);
}
std::string getAddress() const override {
return to_string(addr, false);
}
static std::shared_ptr<SocketTcpConnection> connect(
std::shared_ptr<TcpConnection> connect_tcp(
const std::string& address, int port, runnable callback
) {
addrinfo hints {};
);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
addrinfo* addrinfo = nullptr;
if (int res = getaddrinfo(
address.c_str(), nullptr, &hints, &addrinfo
)) {
throw std::runtime_error(gai_strerror(res));
}
sockaddr_in serverAddress;
std::memcpy(&serverAddress, addrinfo->ai_addr, sizeof(sockaddr_in));
serverAddress.sin_port = htons(port);
freeaddrinfo(addrinfo);
SOCKET descriptor = socket(AF_INET, SOCK_STREAM, 0);
if (descriptor == -1) {
throw std::runtime_error("Could not create socket");
}
auto socket = std::make_shared<SocketTcpConnection>(descriptor, std::move(serverAddress));
socket->connect(std::move(callback));
return socket;
}
ConnectionState getState() const override {
return state;
}
};
class SocketTcpServer : public TcpServer {
u64id_t id;
Network* network;
SOCKET descriptor;
std::vector<u64id_t> clients;
std::mutex clientsMutex;
bool open = true;
std::unique_ptr<std::thread> thread = nullptr;
int port;
public:
SocketTcpServer(u64id_t id, Network* network, SOCKET descriptor, int port)
: id(id), network(network), descriptor(descriptor), port(port) {}
~SocketTcpServer() {
closeSocket();
}
void startListen(ConnectCallback handler) override {
thread = std::make_unique<std::thread>([this, handler]() {
while (open) {
logger.info() << "listening for connections";
if (listen(descriptor, 2) < 0) {
close();
break;
}
socklen_t addrlen = sizeof(sockaddr_in);
SOCKET clientDescriptor;
sockaddr_in address;
logger.info() << "accepting clients";
if ((clientDescriptor = accept(descriptor, (sockaddr*)&address, &addrlen)) == -1) {
close();
break;
}
logger.info() << "client connected: " << to_string(address);
auto socket = std::make_shared<SocketTcpConnection>(
clientDescriptor, address
);
socket->startClient();
u64id_t id = network->addConnection(socket);
{
std::lock_guard lock(clientsMutex);
clients.push_back(id);
}
handler(this->id, id);
}
});
}
void closeSocket() {
if (!open) {
return;
}
logger.info() << "closing server";
open = false;
{
std::lock_guard lock(clientsMutex);
for (u64id_t clientid : clients) {
if (auto client = network->getConnection(clientid)) {
client->close();
}
}
}
clients.clear();
shutdown(descriptor, 2);
closesocket(descriptor);
thread->join();
}
void close() override {
closeSocket();
}
bool isOpen() override {
return open;
}
int getPort() const override {
return port;
}
static std::shared_ptr<SocketTcpServer> openServer(
std::shared_ptr<TcpServer> open_tcp_server(
u64id_t id, Network* network, int port, ConnectCallback handler
) {
SOCKET descriptor = socket(
AF_INET, SOCK_STREAM, 0
);
if (descriptor == -1) {
throw std::runtime_error("Could not create server socket");
}
int opt = 1;
int flags = SO_REUSEADDR;
# if !defined(_WIN32) && !defined(__APPLE__)
flags |= SO_REUSEPORT;
# endif
if (setsockopt(descriptor, SOL_SOCKET, flags, (const char*)&opt, sizeof(opt))) {
logger.error() << "setsockopt(SO_REUSEADDR) failed with errno: "
<< errno << "(" << std::strerror(errno) << ")";
closesocket(descriptor);
throw std::runtime_error("setsockopt");
}
sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
if (bind(descriptor, (sockaddr*)&address, sizeof(address)) < 0) {
closesocket(descriptor);
throw std::runtime_error("could not bind port "+std::to_string(port));
}
logger.info() << "opened server at port " << port;
auto server =
std::make_shared<SocketTcpServer>(id, network, descriptor, port);
server->startListen(std::move(handler));
return server;
}
};
);
static sockaddr_in resolve_address_dgram(const std::string& address, int port) {
sockaddr_in serverAddr{};
addrinfo hints {};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
addrinfo* addrinfo = nullptr;
if (int res = getaddrinfo(
address.c_str(), nullptr, &hints, &addrinfo
)) {
throw std::runtime_error(gai_strerror(res));
}
std::memcpy(&serverAddr, addrinfo->ai_addr, sizeof(sockaddr_in));
serverAddr.sin_port = htons(port);
freeaddrinfo(addrinfo);
return serverAddr;
}
class SocketUdpConnection : public UdpConnection {
u64id_t id;
SOCKET descriptor;
sockaddr_in addr{};
bool open = true;
std::unique_ptr<std::thread> thread;
ClientDatagramCallback callback;
size_t totalUpload = 0;
size_t totalDownload = 0;
ConnectionState state = ConnectionState::INITIAL;
public:
SocketUdpConnection(u64id_t id, SOCKET descriptor, sockaddr_in addr)
: id(id), descriptor(descriptor), addr(std::move(addr)) {}
~SocketUdpConnection() override {
SocketUdpConnection::close();
}
static std::shared_ptr<SocketUdpConnection> connect(
std::shared_ptr<UdpConnection> connect_udp(
u64id_t id,
const std::string& address,
int port,
ClientDatagramCallback handler,
runnable callback
) {
SOCKET descriptor = socket(AF_INET, SOCK_DGRAM, 0);
if (descriptor == -1) {
throw std::runtime_error("could not create udp socket");
}
);
sockaddr_in serverAddr = resolve_address_dgram(address, port);
std::shared_ptr<UdpServer> open_udp_server(
u64id_t id,
Network* network,
int port,
const ServerDatagramCallback& handler
);
}
if (::connect(descriptor, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
auto err = handle_socket_error("udp connect failed");
closesocket(descriptor);
throw err;
}
auto socket = std::make_shared<SocketUdpConnection>(id, descriptor, serverAddr);
socket->connect(std::move(handler));
callback();
return socket;
}
void connect(ClientDatagramCallback handler) override {
callback = std::move(handler);
state = ConnectionState::CONNECTED;
thread = std::make_unique<std::thread>([this]() {
util::Buffer<char> buffer(16'384);
while (open) {
int size = recv(descriptor, buffer.data(), buffer.size(), 0);
if (size <= 0) {
logger.error() << "udp connection " << id
<< handle_socket_error(" recv error").what();
if (!open) break;
closesocket(descriptor);
state = ConnectionState::CLOSED;
break;
}
totalDownload += size;
if (callback) {
callback(id, buffer.data(), size);
}
}
});
}
int send(const char* buffer, size_t length) override {
int len = ::send(descriptor, buffer, length, 0);
if (len < 0) {
auto err = handle_socket_error(" send failed");
closesocket(descriptor);
state = ConnectionState::CLOSED;
logger.error() << "udp connection " << id << err.what();
} else totalUpload += len;
return len;
}
void close(bool discardAll=false) override {
if (!open) return;
open = false;
logger.info() << "closing udp connection "<< id;
if (state != ConnectionState::CLOSED) {
shutdown(descriptor, 2);
closesocket(descriptor);
}
if (thread) {
thread->join();
thread.reset();
}
state = ConnectionState::CLOSED;
}
size_t pullUpload() override {
size_t s = totalUpload;
totalUpload = 0;
return s;
}
size_t pullDownload() override {
size_t s = totalDownload;
totalDownload = 0;
return s;
}
[[nodiscard]] int getPort() const override {
return ntohs(addr.sin_port);
}
[[nodiscard]] std::string getAddress() const override {
return to_string(addr, false);
}
[[nodiscard]] ConnectionState getState() const override {
return state;
}
};
class SocketUdpServer : public UdpServer {
u64id_t id;
SOCKET descriptor;
bool open = true;
std::unique_ptr<std::thread> thread = nullptr;
int port;
ServerDatagramCallback callback;
public:
SocketUdpServer(u64id_t id, Network* network, SOCKET descriptor, int port)
: id(id), descriptor(descriptor), port(port) {}
~SocketUdpServer() override {
SocketUdpServer::close();
}
void startListen(ServerDatagramCallback handler) override {
callback = std::move(handler);
thread = std::make_unique<std::thread>([this]() {
util::Buffer<char> buffer(16384);
sockaddr_in clientAddr{};
socklen_t addrlen = sizeof(clientAddr);
while (open) {
int size = recvfrom(descriptor, buffer.data(), buffer.size(), 0,
reinterpret_cast<sockaddr*>(&clientAddr), &addrlen);
if (size <= 0) {
if (!open) break;
continue;
}
std::string addrStr = to_string(clientAddr, false);
int port = ntohs(clientAddr.sin_port);
callback(id, addrStr, port, buffer.data(), size);
}
});
}
void sendTo(const std::string& addr, int port, const char* buffer, size_t length) override {
sockaddr_in client = resolve_address_dgram(addr, port);
if (sendto(descriptor, buffer, length, 0,
reinterpret_cast<sockaddr*>(&client), sizeof(client)) < 0) {
logger.error() << handle_socket_error("sendto").what();
}
}
void close() override {
if (!open) return;
open = false;
shutdown(descriptor, 2);
closesocket(descriptor);
if (thread) {
thread->join();
thread = nullptr;
}
}
bool isOpen() override { return open; }
int getPort() const override { return port; }
static std::shared_ptr<SocketUdpServer> openServer(
u64id_t id, Network* network, int port, const ServerDatagramCallback& handler
) {
SOCKET descriptor = socket(AF_INET, SOCK_DGRAM, 0);
if (descriptor == -1) throw std::runtime_error("could not create udp socket");
sockaddr_in address{};
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
if (bind(descriptor, (sockaddr*)&address, sizeof(address)) < 0) {
closesocket(descriptor);
throw std::runtime_error("could not bind udp port " + std::to_string(port));
}
auto server = std::make_shared<SocketUdpServer>(id, network, descriptor, port);
server->startListen(std::move(handler));
return server;
}
};
Network::Network(std::unique_ptr<Requests> requests)
: requests(std::move(requests)) {
@ -878,19 +69,19 @@ void Network::post(
);
}
Connection* Network::getConnection(u64id_t id) {
Connection* Network::getConnection(u64id_t id, bool includePrivate) {
std::lock_guard lock(connectionsMutex);
const auto& found = connections.find(id);
if (found == connections.end()) {
if (found == connections.end() || (!includePrivate && found->second->isPrivate())) {
return nullptr;
}
return found->second.get();
}
Server* Network::getServer(u64id_t id) const {
Server* Network::getServer(u64id_t id, bool includePrivate) const {
const auto& found = servers.find(id);
if (found == servers.end()) {
if (found == servers.end() || (!includePrivate && found->second->isPrivate())) {
return nullptr;
}
return found->second.get();
@ -900,7 +91,7 @@ u64id_t Network::connectTcp(const std::string& address, int port, consumer<u64id
std::lock_guard lock(connectionsMutex);
u64id_t id = nextConnection++;
auto socket = SocketTcpConnection::connect(address, port, [id, callback]() {
auto socket = connect_tcp(address, port, [id, callback]() {
callback(id);
});
connections[id] = std::move(socket);
@ -909,16 +100,21 @@ u64id_t Network::connectTcp(const std::string& address, int port, consumer<u64id
u64id_t Network::openTcpServer(int port, ConnectCallback handler) {
u64id_t id = nextServer++;
auto server = SocketTcpServer::openServer(id, this, port, handler);
auto server = open_tcp_server(id, this, port, handler);
servers[id] = std::move(server);
return id;
}
u64id_t Network::connectUdp(const std::string& address, int port, const consumer<u64id_t>& callback, ClientDatagramCallback handler) {
u64id_t Network::connectUdp(
const std::string& address,
int port,
const consumer<u64id_t>& callback,
ClientDatagramCallback handler
) {
std::lock_guard lock(connectionsMutex);
u64id_t id = nextConnection++;
auto socket = SocketUdpConnection::connect(id, address, port, std::move(handler), [id, callback]() {
auto socket = connect_udp(id, address, port, std::move(handler), [id, callback]() {
callback(id);
});
connections[id] = std::move(socket);
@ -927,7 +123,7 @@ u64id_t Network::connectUdp(const std::string& address, int port, const consumer
u64id_t Network::openUdpServer(int port, const ServerDatagramCallback& handler) {
u64id_t id = nextServer++;
auto server = SocketUdpServer::openServer(id, this, port, handler);
auto server = open_udp_server(id, this, port, handler);
servers[id] = std::move(server);
return id;
}
@ -968,19 +164,19 @@ 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;
}
}
std::unique_ptr<Network> Network::create(const NetworkSettings& settings) {
auto requests = CurlRequests::create();
return std::make_unique<Network>(std::move(requests));
return std::make_unique<Network>(network::create_curl_requests());
}

View file

@ -1,83 +1,16 @@
#pragma once
#include <memory>
#include <vector>
#include <mutex>
#include "typedefs.hpp"
#include "settings.hpp"
#include "util/Buffer.hpp"
#include "delegates.hpp"
#include "commons.hpp"
namespace network {
using OnResponse = std::function<void(std::vector<char>)>;
using OnReject = std::function<void(int, std::vector<char>)>;
using ConnectCallback = std::function<void(u64id_t, u64id_t)>;
using ServerDatagramCallback = std::function<void(u64id_t sid, const std::string& addr, int port, const char* buffer, size_t length)>;
using ClientDatagramCallback = std::function<void(u64id_t cid, const char* buffer, size_t length)>;
class Requests {
public:
virtual ~Requests() {}
virtual void get(
const std::string& url,
OnResponse onResponse,
OnReject onReject=nullptr,
std::vector<std::string> headers = {},
long maxSize=0
) = 0;
virtual void post(
const std::string& url,
const std::string& data,
OnResponse onResponse,
OnReject onReject=nullptr,
std::vector<std::string> headers = {},
long maxSize=0
) = 0;
[[nodiscard]] virtual size_t getTotalUpload() const = 0;
[[nodiscard]] virtual size_t getTotalDownload() const = 0;
virtual void update() = 0;
};
enum class ConnectionState {
INITIAL, CONNECTING, CONNECTED, CLOSED
};
enum class TransportType {
TCP, UDP
};
class Connection {
public:
virtual ~Connection() = default;
virtual void close(bool discardAll=false) = 0;
virtual int send(const char* buffer, size_t length) = 0;
virtual size_t pullUpload() = 0;
virtual size_t pullDownload() = 0;
[[nodiscard]] virtual int getPort() const = 0;
[[nodiscard]] virtual std::string getAddress() const = 0;
[[nodiscard]] virtual ConnectionState getState() const = 0;
[[nodiscard]] virtual TransportType getTransportType() const noexcept = 0;
};
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;
[[nodiscard]] TransportType getTransportType() const noexcept override {
return TransportType::TCP;
@ -95,15 +28,6 @@ namespace network {
}
};
class Server {
public:
virtual ~Server() = default;
virtual void close() = 0;
virtual bool isOpen() = 0;
[[nodiscard]] virtual TransportType getTransportType() const noexcept = 0;
[[nodiscard]] virtual int getPort() const = 0;
};
class TcpServer : public Server {
public:
~TcpServer() override {}
@ -112,6 +36,8 @@ namespace network {
[[nodiscard]] TransportType getTransportType() const noexcept override {
return TransportType::TCP;
}
virtual void setMaxClientsConnected(int count) = 0;
};
class UdpServer : public Server {
@ -159,8 +85,8 @@ namespace network {
long maxSize=0
);
[[nodiscard]] Connection* getConnection(u64id_t id);
[[nodiscard]] Server* getServer(u64id_t id) const;
[[nodiscard]] Connection* getConnection(u64id_t id, bool includePrivate);
[[nodiscard]] Server* getServer(u64id_t id, bool includePrivate) const;
u64id_t connectTcp(const std::string& address, int port, consumer<u64id_t> callback);
u64id_t connectUdp(const std::string& address, int port, const consumer<u64id_t>& callback, ClientDatagramCallback handler);

704
src/network/Sockets.cpp Normal file
View file

@ -0,0 +1,704 @@
#include "commons.hpp"
#pragma comment(lib, "Ws2_32.lib")
#define NOMINMAX
#include <stdexcept>
#include <limits>
#include <queue>
#include <thread>
#ifdef _WIN32
#include <curl/curl.h>
#define SHUT_RDWR SD_BOTH
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
using SOCKET = int;
#endif // _WIN32
#include "Network.hpp"
#include "util/stringutil.hpp"
#include "debug/Logger.hpp"
using namespace network;
static debug::Logger logger("sockets");
#ifndef _WIN32
static inline int closesocket(int descriptor) noexcept {
return close(descriptor);
}
static inline std::runtime_error handle_socket_error(const std::string& message) {
int err = errno;
return std::runtime_error(
message+" [errno=" + std::to_string(err) + "]: " +
std::string(strerror(err))
);
}
#else
static inline std::runtime_error handle_socket_error(const std::string& message) {
int errorCode = WSAGetLastError();
wchar_t* s = nullptr;
size_t size = FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
errorCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&s,
0,
nullptr
);
assert(s != nullptr);
while (size && isspace(s[size-1])) {
s[--size] = 0;
}
auto errorString = util::wstr2str_utf8(std::wstring(s));
LocalFree(s);
return std::runtime_error(message+" [WSA error=" +
std::to_string(errorCode) + "]: "+errorString);
}
#endif
static inline int connectsocket(
int descriptor, const sockaddr* addr, socklen_t len
) noexcept {
return connect(descriptor, addr, len);
}
static inline int recvsocket(
int descriptor, char* buf, size_t len
) noexcept {
return recv(descriptor, buf, len, 0);
}
static inline int sendsocket(
int descriptor, const char* buf, size_t len, int flags
) noexcept {
return send(descriptor, buf, len, flags);
}
static std::string to_string(const sockaddr_in& addr, bool port=true) {
char ip[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &(addr.sin_addr), ip, INET_ADDRSTRLEN)) {
return std::string(ip) +
(port ? (":" + std::to_string(htons(addr.sin_port))) : "");
}
return "";
}
class SocketTcpConnection : public TcpConnection {
SOCKET descriptor;
sockaddr_in addr;
size_t totalUpload = 0;
size_t totalDownload = 0;
ConnectionState state = ConnectionState::INITIAL;
std::unique_ptr<std::thread> thread = nullptr;
std::vector<char> readBatch;
util::Buffer<char> buffer;
std::mutex mutex;
void connectSocket() {
state = ConnectionState::CONNECTING;
logger.info() << "connecting to " << to_string(addr);
int res = connectsocket(descriptor, (const sockaddr*)&addr, sizeof(sockaddr_in));
if (res < 0) {
auto error = handle_socket_error("Connect failed");
closesocket(descriptor);
state = ConnectionState::CLOSED;
logger.error() << error.what();
return;
}
logger.info() << "connected to " << to_string(addr);
state = ConnectionState::CONNECTED;
}
public:
SocketTcpConnection(SOCKET descriptor, sockaddr_in addr)
: descriptor(descriptor), addr(std::move(addr)), buffer(16'384) {}
~SocketTcpConnection() {
if (state != ConnectionState::CLOSED) {
shutdown(descriptor, 2);
}
if (thread) {
thread->join();
}
}
void setNoDelay(bool noDelay) override {
int opt = noDelay ? 1 : 0;
if (setsockopt(descriptor, IPPROTO_TCP, TCP_NODELAY, (char*)&opt, sizeof(opt)) < 0) {
throw handle_socket_error("setsockopt(TCP_NODELAY) failed");
}
}
bool isNoDelay() const override {
int opt = 0;
socklen_t len = sizeof(opt);
if (getsockopt(descriptor, IPPROTO_TCP, TCP_NODELAY, (char*)&opt, &len) < 0) {
throw handle_socket_error("getsockopt(TCP_NODELAY) failed");
}
return opt != 0;
}
void startListen() {
while (state == ConnectionState::CONNECTED) {
int size = recvsocket(descriptor, buffer.data(), buffer.size());
if (size == 0) {
logger.info() << "closed connection with " << to_string(addr);
closesocket(descriptor);
state = ConnectionState::CLOSED;
break;
} else if (size < 0) {
logger.warning() << "an error ocurred while receiving from "
<< to_string(addr);
auto error = handle_socket_error("recv(...) error");
closesocket(descriptor);
state = ConnectionState::CLOSED;
logger.error() << error.what();
break;
}
{
std::lock_guard lock(mutex);
for (size_t i = 0; i < size; i++) {
readBatch.emplace_back(buffer[i]);
}
totalDownload += size;
}
}
}
void startClient() {
state = ConnectionState::CONNECTED;
thread = std::make_unique<std::thread>([this]() { startListen();});
}
void connect(runnable callback) override {
thread = std::make_unique<std::thread>([this, callback]() {
connectSocket();
if (state == ConnectionState::CONNECTED) {
callback();
}
startListen();
});
}
int recv(char* buffer, size_t length) override {
std::lock_guard lock(mutex);
if (state != ConnectionState::CONNECTED && readBatch.empty()) {
return -1;
}
int size = std::min(readBatch.size(), length);
std::memcpy(buffer, readBatch.data(), size);
readBatch.erase(readBatch.begin(), readBatch.begin() + size);
return size;
}
int send(const char* buffer, size_t length) override {
if (state == ConnectionState::CLOSED) {
return 0;
}
int len = sendsocket(descriptor, buffer, length, 0);
if (len == -1) {
int err = errno;
close();
throw std::runtime_error(
"Send failed [errno=" + std::to_string(err) + "]: "
+ std::string(strerror(err))
);
}
totalUpload += len;
return len;
}
int available() override {
std::lock_guard lock(mutex);
return readBatch.size();
}
void close(bool discardAll=false) override {
{
std::lock_guard lock(mutex);
readBatch.clear();
if (state != ConnectionState::CLOSED) {
shutdown(descriptor, SHUT_RDWR);
closesocket(descriptor);
}
}
if (thread) {
thread->join();
thread = nullptr;
}
}
size_t pullUpload() override {
size_t size = totalUpload;
totalUpload = 0;
return size;
}
size_t pullDownload() override {
size_t size = totalDownload;
totalDownload = 0;
return size;
}
int getPort() const override {
return htons(addr.sin_port);
}
std::string getAddress() const override {
return to_string(addr, false);
}
static std::shared_ptr<SocketTcpConnection> connect(
const std::string& address, int port, runnable callback
) {
addrinfo hints {};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
addrinfo* addrinfo = nullptr;
if (int res = getaddrinfo(
address.c_str(), nullptr, &hints, &addrinfo
)) {
throw std::runtime_error(gai_strerror(res));
}
sockaddr_in serverAddress;
std::memcpy(&serverAddress, addrinfo->ai_addr, sizeof(sockaddr_in));
serverAddress.sin_port = htons(port);
freeaddrinfo(addrinfo);
SOCKET descriptor = socket(AF_INET, SOCK_STREAM, 0);
if (descriptor == -1) {
throw std::runtime_error("Could not create socket");
}
auto socket = std::make_shared<SocketTcpConnection>(descriptor, std::move(serverAddress));
socket->connect(std::move(callback));
return socket;
}
ConnectionState getState() const override {
return state;
}
};
class SocketTcpServer : public TcpServer {
u64id_t id;
Network* network;
SOCKET descriptor;
std::vector<u64id_t> clients;
std::mutex clientsMutex;
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) {}
~SocketTcpServer() {
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) {
logger.info() << "listening for connections";
if (listen(descriptor, 2) < 0) {
close();
break;
}
socklen_t addrlen = sizeof(sockaddr_in);
SOCKET clientDescriptor;
sockaddr_in address;
logger.info() << "accepting clients";
if ((clientDescriptor = accept(descriptor, (sockaddr*)&address, &addrlen)) == -1) {
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
);
socket->startClient();
u64id_t id = network->addConnection(socket);
{
std::lock_guard lock(clientsMutex);
clients.push_back(id);
}
handler(this->id, id);
}
});
}
void closeSocket() {
if (!open) {
return;
}
logger.info() << "closing server";
open = false;
{
std::lock_guard lock(clientsMutex);
for (u64id_t clientid : clients) {
if (auto client = network->getConnection(clientid, true)) {
client->close();
}
}
}
clients.clear();
shutdown(descriptor, 2);
closesocket(descriptor);
thread->join();
}
void close() override {
closeSocket();
}
bool isOpen() override {
return open;
}
int getPort() const override {
return port;
}
static std::shared_ptr<SocketTcpServer> openServer(
u64id_t id, Network* network, int port, ConnectCallback handler
) {
SOCKET descriptor = socket(
AF_INET, SOCK_STREAM, 0
);
if (descriptor == -1) {
throw std::runtime_error("Could not create server socket");
}
int opt = 1;
int flags = SO_REUSEADDR;
# if !defined(_WIN32) && !defined(__APPLE__)
flags |= SO_REUSEPORT;
# endif
if (setsockopt(descriptor, SOL_SOCKET, flags, (const char*)&opt, sizeof(opt))) {
logger.error() << "setsockopt(SO_REUSEADDR) failed with errno: "
<< errno << "(" << std::strerror(errno) << ")";
closesocket(descriptor);
throw std::runtime_error("setsockopt");
}
sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
if (bind(descriptor, (sockaddr*)&address, sizeof(address)) < 0) {
closesocket(descriptor);
throw std::runtime_error("could not bind port "+std::to_string(port));
}
logger.info() << "opened server at port " << port;
auto server =
std::make_shared<SocketTcpServer>(id, network, descriptor, port);
server->startListen(std::move(handler));
return server;
}
};
static sockaddr_in resolve_address_dgram(const std::string& address, int port) {
sockaddr_in serverAddr{};
addrinfo hints {};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
addrinfo* addrinfo = nullptr;
if (int res = getaddrinfo(
address.c_str(), nullptr, &hints, &addrinfo
)) {
throw std::runtime_error(gai_strerror(res));
}
std::memcpy(&serverAddr, addrinfo->ai_addr, sizeof(sockaddr_in));
serverAddr.sin_port = htons(port);
freeaddrinfo(addrinfo);
return serverAddr;
}
class SocketUdpConnection : public UdpConnection {
u64id_t id;
SOCKET descriptor;
sockaddr_in addr{};
bool open = true;
std::unique_ptr<std::thread> thread;
ClientDatagramCallback callback;
size_t totalUpload = 0;
size_t totalDownload = 0;
ConnectionState state = ConnectionState::INITIAL;
public:
SocketUdpConnection(u64id_t id, SOCKET descriptor, sockaddr_in addr)
: id(id), descriptor(descriptor), addr(std::move(addr)) {}
~SocketUdpConnection() override {
SocketUdpConnection::close();
}
static std::shared_ptr<SocketUdpConnection> connect(
u64id_t id,
const std::string& address,
int port,
ClientDatagramCallback handler,
runnable callback
) {
SOCKET descriptor = socket(AF_INET, SOCK_DGRAM, 0);
if (descriptor == -1) {
throw std::runtime_error("could not create udp socket");
}
sockaddr_in serverAddr = resolve_address_dgram(address, port);
if (::connect(descriptor, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
auto err = handle_socket_error("udp connect failed");
closesocket(descriptor);
throw err;
}
auto socket = std::make_shared<SocketUdpConnection>(id, descriptor, serverAddr);
socket->connect(std::move(handler));
callback();
return socket;
}
void connect(ClientDatagramCallback handler) override {
callback = std::move(handler);
state = ConnectionState::CONNECTED;
thread = std::make_unique<std::thread>([this]() {
util::Buffer<char> buffer(16'384);
while (open) {
int size = recv(descriptor, buffer.data(), buffer.size(), 0);
if (size <= 0) {
logger.error() << "udp connection " << id
<< handle_socket_error(" recv error").what();
if (!open) break;
closesocket(descriptor);
state = ConnectionState::CLOSED;
break;
}
totalDownload += size;
if (callback) {
callback(id, buffer.data(), size);
}
}
});
}
int send(const char* buffer, size_t length) override {
int len = ::send(descriptor, buffer, length, 0);
if (len < 0) {
auto err = handle_socket_error(" send failed");
closesocket(descriptor);
state = ConnectionState::CLOSED;
logger.error() << "udp connection " << id << err.what();
} else totalUpload += len;
return len;
}
void close(bool discardAll=false) override {
if (!open) return;
open = false;
logger.info() << "closing udp connection "<< id;
if (state != ConnectionState::CLOSED) {
shutdown(descriptor, 2);
closesocket(descriptor);
}
if (thread) {
thread->join();
thread.reset();
}
state = ConnectionState::CLOSED;
}
size_t pullUpload() override {
size_t s = totalUpload;
totalUpload = 0;
return s;
}
size_t pullDownload() override {
size_t s = totalDownload;
totalDownload = 0;
return s;
}
[[nodiscard]] int getPort() const override {
return ntohs(addr.sin_port);
}
[[nodiscard]] std::string getAddress() const override {
return to_string(addr, false);
}
[[nodiscard]] ConnectionState getState() const override {
return state;
}
};
class SocketUdpServer : public UdpServer {
u64id_t id;
SOCKET descriptor;
bool open = true;
std::unique_ptr<std::thread> thread = nullptr;
int port;
ServerDatagramCallback callback;
public:
SocketUdpServer(u64id_t id, Network* network, SOCKET descriptor, int port)
: id(id), descriptor(descriptor), port(port) {}
~SocketUdpServer() override {
SocketUdpServer::close();
}
void update() override {}
void startListen(ServerDatagramCallback handler) override {
callback = std::move(handler);
thread = std::make_unique<std::thread>([this]() {
util::Buffer<char> buffer(16384);
sockaddr_in clientAddr{};
socklen_t addrlen = sizeof(clientAddr);
while (open) {
int size = recvfrom(descriptor, buffer.data(), buffer.size(), 0,
reinterpret_cast<sockaddr*>(&clientAddr), &addrlen);
if (size <= 0) {
if (!open) break;
continue;
}
std::string addrStr = to_string(clientAddr, false);
int port = ntohs(clientAddr.sin_port);
callback(id, addrStr, port, buffer.data(), size);
}
});
}
void sendTo(const std::string& addr, int port, const char* buffer, size_t length) override {
sockaddr_in client = resolve_address_dgram(addr, port);
if (sendto(descriptor, buffer, length, 0,
reinterpret_cast<sockaddr*>(&client), sizeof(client)) < 0) {
logger.error() << handle_socket_error("sendto").what();
}
}
void close() override {
if (!open) return;
open = false;
shutdown(descriptor, 2);
closesocket(descriptor);
if (thread) {
thread->join();
thread = nullptr;
}
}
bool isOpen() override { return open; }
int getPort() const override { return port; }
static std::shared_ptr<SocketUdpServer> openServer(
u64id_t id, Network* network, int port, const ServerDatagramCallback& handler
) {
SOCKET descriptor = socket(AF_INET, SOCK_DGRAM, 0);
if (descriptor == -1) throw std::runtime_error("could not create udp socket");
sockaddr_in address{};
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
if (bind(descriptor, (sockaddr*)&address, sizeof(address)) < 0) {
closesocket(descriptor);
throw std::runtime_error("could not bind udp port " + std::to_string(port));
}
auto server = std::make_shared<SocketUdpServer>(id, network, descriptor, port);
server->startListen(std::move(handler));
return server;
}
};
namespace network {
std::shared_ptr<TcpConnection> connect_tcp(
const std::string& address, int port, runnable callback
) {
return SocketTcpConnection::connect(address, port, std::move(callback));
}
std::shared_ptr<TcpServer> open_tcp_server(
u64id_t id, Network* network, int port, ConnectCallback handler
) {
return SocketTcpServer::openServer(id, network, port, std::move(handler));
}
std::shared_ptr<UdpConnection> connect_udp(
u64id_t id,
const std::string& address,
int port,
ClientDatagramCallback handler,
runnable callback
) {
return SocketUdpConnection::connect(
id, address, port, std::move(handler), std::move(callback)
);
}
std::shared_ptr<UdpServer> open_udp_server(
u64id_t id,
Network* network,
int port,
const ServerDatagramCallback& handler
) {
return SocketUdpServer::openServer(id, network, port, handler);
}
}

99
src/network/commons.hpp Normal file
View file

@ -0,0 +1,99 @@
#pragma once
#include "typedefs.hpp"
#include "settings.hpp"
#include "util/Buffer.hpp"
#include "delegates.hpp"
#include <memory>
#include <vector>
#include <mutex>
namespace network {
using OnResponse = std::function<void(std::vector<char>)>;
using OnReject = std::function<void(int, std::vector<char>)>;
using ConnectCallback = std::function<void(u64id_t, u64id_t)>;
using ServerDatagramCallback = std::function<void(u64id_t sid, const std::string& addr, int port, const char* buffer, size_t length)>;
using ClientDatagramCallback = std::function<void(u64id_t cid, const char* buffer, size_t length)>;
class Requests {
public:
virtual ~Requests() {}
virtual void get(
const std::string& url,
OnResponse onResponse,
OnReject onReject=nullptr,
std::vector<std::string> headers = {},
long maxSize=0
) = 0;
virtual void post(
const std::string& url,
const std::string& data,
OnResponse onResponse,
OnReject onReject=nullptr,
std::vector<std::string> headers = {},
long maxSize=0
) = 0;
[[nodiscard]] virtual size_t getTotalUpload() const = 0;
[[nodiscard]] virtual size_t getTotalDownload() const = 0;
virtual void update() = 0;
};
enum class ConnectionState {
INITIAL, CONNECTING, CONNECTED, CLOSED
};
enum class TransportType {
TCP, UDP
};
class Connection {
public:
virtual ~Connection() = default;
virtual void close(bool discardAll=false) = 0;
virtual int send(const char* buffer, size_t length) = 0;
virtual size_t pullUpload() = 0;
virtual size_t pullDownload() = 0;
bool isPrivate() const { return isprivate; }
void setPrivate(bool flag) {isprivate = flag;}
[[nodiscard]] virtual int getPort() const = 0;
[[nodiscard]] virtual std::string getAddress() const = 0;
[[nodiscard]] virtual ConnectionState getState() const = 0;
[[nodiscard]] virtual TransportType getTransportType() const noexcept = 0;
protected:
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;
[[nodiscard]] virtual int getPort() const = 0;
bool isPrivate() const { return isprivate; }
void setPrivate(bool flag) { isprivate = flag; }
protected:
bool isprivate = false;
};
}

View file

@ -196,6 +196,14 @@ void Player::setLoadingChunks(bool flag) {
loadingChunks = flag;
}
float Player::getMaxInteractionDistance() const {
return interactionDistance;
}
void Player::setMaxInteractionDistance(float distance) {
interactionDistance = std::max(1.0f, std::min(200.0f, distance));
}
entityid_t Player::getEntity() const {
return eid;
}
@ -250,6 +258,7 @@ dv::value Player::serialize() const {
root["rotation"] = dv::to_value(rotation);
root["spawnpoint"] = dv::to_value(spawnpoint);
root["interaction-distance"] = interactionDistance;
root["flight"] = flight;
root["noclip"] = noclip;
root["suspended"] = suspended;
@ -283,6 +292,8 @@ void Player::deserialize(const dv::value& src) {
const auto& sparr = src["spawnpoint"];
setSpawnPoint(glm::vec3(
sparr[0].asNumber(), sparr[1].asNumber(), sparr[2].asNumber()));
src.at("interaction-distance").get(interactionDistance);
flight = src["flight"].asBoolean();
noclip = src["noclip"].asBoolean();

View file

@ -46,6 +46,7 @@ class Player : public Serializable {
int64_t id;
std::string name;
float speed;
int chosenSlot;
glm::vec3 position;
glm::vec3 spawnpoint {};
@ -56,6 +57,8 @@ class Player : public Serializable {
bool infiniteItems = true;
bool instantDestruction = true;
bool loadingChunks = true;
float interactionDistance = 10.0f;
entityid_t eid = ENTITY_AUTO;
entityid_t selectedEid = 0;
@ -90,6 +93,7 @@ public:
void setChosenSlot(int index);
int getChosenSlot() const;
float getSpeed() const;
bool isSuspended() const;
@ -110,6 +114,9 @@ public:
bool isLoadingChunks() const;
void setLoadingChunks(bool flag);
float getMaxInteractionDistance() const;
void setMaxInteractionDistance(float distance);
entityid_t getEntity() const;
void setEntity(entityid_t eid);

View file

@ -1,5 +1,8 @@
#include "Players.hpp"
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/norm.hpp>
#include "Player.hpp"
#include "items/Inventories.hpp"
#include "world/Level.hpp"
@ -20,6 +23,48 @@ Player* Players::get(int64_t id) const {
return found->second.get();
}
std::vector<Player*> Players::getAllInRadius(
const glm::vec3& center, float radius
) const {
std::vector<Player*> foundPlayers;
for (const auto& pair : players) {
auto player = pair.second.get();
auto relativePos = player->getPosition() - center;
if (!player->isSuspended() && glm::length2(relativePos) <= radius) {
foundPlayers.emplace_back(player);
}
}
return foundPlayers;
}
std::vector<Player*> Players::getAll() const {
std::vector<Player*> allPlayers;
allPlayers.reserve(players.size());
for (const auto& pair : players) {
allPlayers.emplace_back(pair.second.get());
}
return allPlayers;
}
Player* Players::getNearest(const glm::vec3& position) const {
Player* nearest = nullptr;
float nearestDist2 = std::numeric_limits<float>::max();
for (const auto& pair : players) {
auto player = pair.second.get();
if (player->isSuspended()) {
continue;
}
auto relativePos = player->getPosition() - position;
auto dist2 = glm::length2(relativePos);
if (dist2 < nearestDist2) {
nearestDist2 = dist2;
nearest = player;
}
}
return nearest;
}
Player* Players::create(int64_t id) {
int64_t& nextPlayerID = level.getWorld()->getInfo().nextPlayerId;
if (id == NONE) {

View file

@ -1,7 +1,9 @@
#pragma once
#include <memory>
#include <vector>
#include <unordered_map>
#include <glm/vec3.hpp>
#include "typedefs.hpp"
#include "interfaces/Serializable.hpp"
@ -34,6 +36,10 @@ public:
void remove(int64_t id);
std::vector<Player*> getAllInRadius(const glm::vec3& center, float radius) const;
std::vector<Player*> getAll() const;
Player* getNearest(const glm::vec3& position) const;
dv::value serialize() const override;
void deserialize(const dv::value& src) override;

View file

@ -2,6 +2,8 @@
#include "data/dv_util.hpp"
#include <glm/gtc/matrix_transform.hpp>
void Transform::refresh() {
combined = glm::mat4(1.0f);
combined = glm::translate(combined, pos);

View file

@ -19,8 +19,8 @@ struct AudioSettings {
};
struct DisplaySettings {
/// @brief Is window in full screen mode
FlagSetting fullscreen {false};
/// @brief Window mode (windowed/fullscreen/borderless)
IntegerSetting windowMode {0, 0, 2};
/// @brief Window width (pixels)
IntegerSetting width {1280};
/// @brief Window height (pixels)

View file

@ -33,5 +33,14 @@ namespace util {
last = argv[pos];
return argv[pos++];
}
int nextInt() {
auto text = next();
try {
return std::stoi(text);
} catch (const std::exception& e) {
throw std::runtime_error(e.what());
}
}
};
}

View file

@ -4,6 +4,7 @@
#include <functional>
#include <vector>
#include <string>
#include <iomanip>
#include "io/engine_paths.hpp"
#include "util/ArgsReader.hpp"
@ -15,10 +16,17 @@ class ArgC {
public:
std::string keyword;
std::function<bool()> execute;
std::string args;
std::string help;
ArgC(const std::string& keyword, std::function<bool()> execute, const std::string& help) {
ArgC(
const std::string& keyword,
std::function<bool()> execute,
const std::string& args,
const std::string& help
) {
this->keyword = keyword;
this->execute = execute;
this->args = args;
this->help = help;
}
};
@ -31,42 +39,51 @@ static bool perform_keyword(
ArgC("--res", [&params, &reader]() -> bool {
params.resFolder = reader.next();
return true;
}, "<path> - set resources directory."),
}, "<path>", "set resources directory."),
ArgC("--dir", [&params, &reader]() -> bool {
params.userFolder = reader.next();
return true;
}, "<path> - set userfiles directory."),
}, "<path>", "set userfiles directory."),
ArgC("--project", [&params, &reader]() -> bool {
params.projectFolder = reader.next();
return true;
}, "<path> - set project directory."),
}, "<path>", "set project directory."),
ArgC("--test", [&params, &reader]() -> bool {
params.testMode = true;
params.scriptFile = reader.next();
return true;
}, "<path> - test script file."),
}, "<path>", "test script file."),
ArgC("--script", [&params, &reader]() -> bool {
params.testMode = false;
params.scriptFile = reader.next();
return true;
}, "<path> - main script file."),
}, "<path>", "main script file."),
ArgC("--headless", [&params]() -> bool {
params.headless = true;
return true;
}, "- run in headless mode."),
}, "", "run in headless mode."),
ArgC("--tps", [&params, &reader]() -> bool {
params.tps = reader.nextInt();
return true;
}, "<tps>", "headless mode tick-rate (default - 20)."),
ArgC("--version", []() -> bool {
std::cout << ENGINE_VERSION_STRING << std::endl;
return false;
}, "- display the engine version."),
}, "", "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 << a.keyword << " " << a.help << std::endl;
std::cout << std::setw(24) << std::left << (a.keyword + " " + a.args);
std::cout << "- " << a.help << std::endl;
}
std::cout << std::endl;
return false;
}, "- display this help.")
}, "", "display this help.")
};
for (auto& a : argumentsCommandline) {
if (a.keyword == keyword) {

Some files were not shown because too many files have changed in this diff Show more