diff --git a/doc/en/main-page.md b/doc/en/main-page.md index a5e866bf3..5eb767f94 100644 --- a/doc/en/main-page.md +++ b/doc/en/main-page.md @@ -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 diff --git a/doc/en/scripting/builtins/libplayer.md b/doc/en/scripting/builtins/libplayer.md index fa799198a..4bc814242 100644 --- a/doc/en/scripting/builtins/libplayer.md +++ b/doc/en/scripting/builtins/libplayer.md @@ -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 +``` + +Returns an array of player IDs within a sphere with center `center` and radius `radius`. + +```lua +player.get_all() -> table +``` + +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. diff --git a/doc/ru/main-page.md b/doc/ru/main-page.md index c98d893dc..60d0794d7 100644 --- a/doc/ru/main-page.md +++ b/doc/ru/main-page.md @@ -1,6 +1,8 @@ # Документация -Документация версии 0.29. +Документация версии 0.30 (в разработке). + +[Документация 0.29.](https://github.com/MihailRis/voxelcore/blob/release-0.29/doc/ru/main-page.md) ## Разделы diff --git a/doc/ru/scripting/builtins/libcompression.md b/doc/ru/scripting/builtins/libcompression.md new file mode 100644 index 000000000..276fbd29c --- /dev/null +++ b/doc/ru/scripting/builtins/libcompression.md @@ -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 +``` \ No newline at end of file diff --git a/doc/ru/scripting/builtins/libfile.md b/doc/ru/scripting/builtins/libfile.md index e06e420ca..4afbe23c9 100644 --- a/doc/ru/scripting/builtins/libfile.md +++ b/doc/ru/scripting/builtins/libfile.md @@ -205,4 +205,4 @@ file.open_named_pipe(имя: str, режим: str) -> io_stream `/tmp/` или `\\\\.\\pipe\\` добавлять не нужно - движок делает это автоматически. -Доступные режимы такие же, как и в `file.open`, за исключением `+` \ No newline at end of file +Доступные режимы такие же, как и в `file.open`, за исключением `+` diff --git a/doc/ru/scripting/builtins/libnetwork.md b/doc/ru/scripting/builtins/libnetwork.md index c880240c5..bef8967c5 100644 --- a/doc/ru/scripting/builtins/libnetwork.md +++ b/doc/ru/scripting/builtins/libnetwork.md @@ -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 diff --git a/doc/ru/scripting/builtins/libplayer.md b/doc/ru/scripting/builtins/libplayer.md index 2228f42c3..c038ecae4 100644 --- a/doc/ru/scripting/builtins/libplayer.md +++ b/doc/ru/scripting/builtins/libplayer.md @@ -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 +``` + +Возвращает массив id игроков в пределах сферы с центром `center` и радиусом `radius`. + +```lua +player.get_all() -> table +``` + +Возвращает массив id всех активных игроков. + +```lua +player.get_nearest(position: vec3) -> int +``` + +Возвращает id ближайшего к указанной позиции игрока, либо nil если игроков нет. diff --git a/doc/ru/vcm.md b/doc/ru/vcm.md index 0eb555b42..379e0328d 100644 --- a/doc/ru/vcm.md +++ b/doc/ru/vcm.md @@ -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) diff --git a/res/content/base/package.json b/res/content/base/package.json index 4aeca4d9e..f270a6d52 100644 --- a/res/content/base/package.json +++ b/res/content/base/package.json @@ -1,6 +1,6 @@ { "id": "base", "title": "Base", - "version": "0.29", + "version": "0.30", "description": "basic content package" } diff --git a/res/layouts/pages/settings_display.xml.lua b/res/layouts/pages/settings_display.xml.lua index f8b24d0b1..6934cef6a 100644 --- a/res/layouts/pages/settings_display.xml.lua +++ b/res/layouts/pages/settings_display.xml.lua @@ -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( + "", 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") diff --git a/res/scripts/classes.lua b/res/scripts/classes.lua index 49d112685..27a73c975 100644 --- a/res/scripts/classes.lua +++ b/res/scripts/classes.lua @@ -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={ diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index b08e8c023..3f732e07c 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -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()) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 26d5d813c..df5c7ce22 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -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 diff --git a/res/texts/be_BY.txt b/res/texts/be_BY.txt index 879e5a355..1fb2a9931 100644 --- a/res/texts/be_BY.txt +++ b/res/texts/be_BY.txt @@ -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=Стэк выклікаў (ад апошняга) diff --git a/res/texts/de_DE.txt b/res/texts/de_DE.txt index 8e1251591..81c395716 100644 --- a/res/texts/de_DE.txt +++ b/res/texts/de_DE.txt @@ -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 diff --git a/res/texts/fi_FI.txt b/res/texts/fi_FI.txt index 52f4b3e09..6a21f73fc 100644 --- a/res/texts/fi_FI.txt +++ b/res/texts/fi_FI.txt @@ -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! diff --git a/res/texts/pl_PL.txt b/res/texts/pl_PL.txt index caae775b4..3e0398243 100644 --- a/res/texts/pl_PL.txt +++ b/res/texts/pl_PL.txt @@ -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 diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index 084e11b38..03ee8322c 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -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=Перезагрузить Чанки diff --git a/res/texts/uk_UA.txt b/res/texts/uk_UA.txt index 7ce046ea3..a288f5163 100644 --- a/res/texts/uk_UA.txt +++ b/res/texts/uk_UA.txt @@ -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=Застосувати diff --git a/res/texts/uz_UZ.txt b/res/texts/uz_UZ.txt index 845c0cf5b..edae1c023 100644 --- a/res/texts/uz_UZ.txt +++ b/res/texts/uz_UZ.txt @@ -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) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 38d00b63c..ff4b2ca9a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 + > + $<$: + -Wduplicated-branches + -Wduplicated-cond >) target_link_options( diff --git a/src/coders/vcm.cpp b/src/coders/vcm.cpp index 337d721f1..b7d93b578 100644 --- a/src/coders/vcm.cpp +++ b/src/coders/vcm.cpp @@ -1,6 +1,8 @@ #include "vcm.hpp" -#include +#include +#include +#include #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); } } diff --git a/src/constants.hpp b/src/constants.hpp index 0a6df9e51..6544316e0 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -6,7 +6,7 @@ #include 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; diff --git a/src/devtools/DebuggingServer.cpp b/src/devtools/DebuggingServer.cpp new file mode 100644 index 000000000..09a2b368c --- /dev/null +++ b/src/devtools/DebuggingServer.cpp @@ -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.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.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(&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.getConnection(this->connection, true) + ); + if (connection == nullptr) { + return; + } + auto message = json::stringify(object, false); + int32_t length = message.length(); + connection->send(reinterpret_cast(&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.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(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(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(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(&segment)) { + pathValue.add(*string); + } else { + pathValue.add(std::get(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(engine.getNetwork(), client); + connectionEstablished = false; +} + +std::vector DebuggingServer::pullEvents() { + return std::move(breakpointEvents); +} + +void DebuggingServer::setDisconnectAction(const std::string& action) { + disconnectAction = action; +} diff --git a/src/devtools/DebuggingServer.hpp b/src/devtools/DebuggingServer.hpp new file mode 100644 index 000000000..b02229c65 --- /dev/null +++ b/src/devtools/DebuggingServer.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include +#include + +#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>; + + struct GetValueEventDto { + int frame; + int localIndex; + ValuePath path; + }; + + struct DebuggingEvent { + DebuggingEventType type; + std::variant 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 pullEvents(); + + void setDisconnectAction(const std::string& action); + private: + Engine& engine; + network::Server& server; + std::unique_ptr connection; + bool connectionEstablished = false; + std::vector breakpointEvents; + std::string disconnectAction = "resume"; + + bool performCommand( + const std::string& type, const dv::value& map + ); + }; +} diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 212765f30..a977f354b 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -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(value); + if (mode != this->window->getMode()) { + this->window->setMode(mode); } }, true @@ -172,6 +177,18 @@ void Engine::initialize(CoreParameters coreParameters) { cmd = std::make_unique(); network = network::Network::create(settings.network); + if (!params.debugServerString.empty()) { + try { + debuggingServer = std::make_unique( + *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(WindowMode::FULLSCREEN)) { + settings.display.windowMode.set(static_cast(WindowMode::FULLSCREEN)); + } else { + settings.display.windowMode.set(static_cast(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(); diff --git a/src/engine/Engine.hpp b/src/engine/Engine.hpp index 98bcd497c..98a2c3efb 100644 --- a/src/engine/Engine.hpp +++ b/src/engine/Engine.hpp @@ -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, int64_t)>; @@ -71,6 +74,7 @@ class Engine : public util::ObjectsKeeper { std::unique_ptr input; std::unique_ptr gui; std::unique_ptr editor; + std::unique_ptr 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(); }; diff --git a/src/engine/ServerMainloop.cpp b/src/engine/ServerMainloop.cpp index 27c35bddc..2f4465334 100644 --- a/src/engine/ServerMainloop.cpp +++ b/src/engine/ServerMainloop.cpp @@ -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(TPS); + double targetDelta = 1.0 / static_cast(coreParams.tps); double delta = targetDelta; auto begin = system_clock::now(); auto startupTime = begin; diff --git a/src/graphics/commons/Model.cpp b/src/graphics/commons/Model.cpp index 20d90eb44..386ae1cce 100644 --- a/src/graphics/commons/Model.cpp +++ b/src/graphics/commons/Model.cpp @@ -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) { diff --git a/src/graphics/commons/Model.hpp b/src/graphics/commons/Model.hpp index 73bee3594..bbb0d398f 100644 --- a/src/graphics/commons/Model.hpp +++ b/src/graphics/commons/Model.hpp @@ -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); }; diff --git a/src/graphics/core/Cubemap.cpp b/src/graphics/core/Cubemap.cpp index 13bba31bc..631a8d7e4 100644 --- a/src/graphics/core/Cubemap.cpp +++ b/src/graphics/core/Cubemap.cpp @@ -25,7 +25,7 @@ Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat) 0, format, GL_UNSIGNED_BYTE, - NULL + nullptr ); } } diff --git a/src/graphics/core/Shadows.cpp b/src/graphics/core/Shadows.cpp index c47b8a6a3..010ad06f6 100644 --- a/src/graphics/core/Shadows.cpp +++ b/src/graphics/core/Shadows.cpp @@ -1,8 +1,11 @@ +#define GLM_ENABLE_EXPERIMENTAL +#include +#include +#include + #include "Shadows.hpp" #include -#define GLM_ENABLE_EXPERIMENTAL -#include #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); diff --git a/src/graphics/render/Emitter.cpp b/src/graphics/render/Emitter.cpp index 483a6339b..7e10daae9 100644 --- a/src/graphics/render/Emitter.cpp +++ b/src/graphics/render/Emitter.cpp @@ -1,6 +1,8 @@ #include "Emitter.hpp" #include +#include +#include #define GLM_ENABLE_EXPERIMENTAL #include diff --git a/src/graphics/ui/gui_util.cpp b/src/graphics/ui/gui_util.cpp index 6d28b990a..4e5217153 100644 --- a/src/graphics/ui/gui_util.cpp +++ b/src/graphics/ui/gui_util.cpp @@ -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