From 805cde8dcae02226aac53503c1b9e8513f50dba4 Mon Sep 17 00:00:00 2001 From: UtoECat Date: Wed, 21 Feb 2024 00:12:28 +0300 Subject: [PATCH] AAAAAAAA METAINFO DONEgit status! AAAAAAAAAA --- .clang | 1 - .clangd | 3 + base/base.hpp | 6 +- base/{event.hpp => defer.hpp} | 48 +- base/profiler.cpp | 15 +- base/profiler.hpp | 16 +- base/readme.md | 2 +- base/resource.hpp | 2 +- base/scopeguard.hpp | 66 --- base/sharedobj.hpp | 2 - base/spinlock.hpp | 1 - base/string.hpp | 8 +- base/strtod.cpp | 2 - base/tests/network.cpp | 88 +++ compile_commands.json | 212 +++++--- engine/config.hpp | 1 - engine/core.hpp | 7 +- engine/database.cpp | 161 ------ engine/database.hpp | 184 ------- engine/meta.cpp | 507 +++++++++++++++++- engine/meta.hpp | 132 +++-- engine/network.cpp | 56 +- engine/network.hpp | 297 ++-------- engine/readme.md | 15 - engine/tests/meta.cpp | 137 +++++ external/raiisqlite.cpp | 348 ++++++++++++ external/raiisqlite.hpp | 983 ++++++++++++++++++++++++++++++++++ game/server.hpp | 6 +- game/world_view.hpp | 8 +- imgui.ini | 2 +- launch.json | 15 - readme.md | 9 +- 32 files changed, 2447 insertions(+), 893 deletions(-) delete mode 100644 .clang create mode 100644 .clangd rename base/{event.hpp => defer.hpp} (52%) delete mode 100644 base/scopeguard.hpp delete mode 100644 engine/database.cpp delete mode 100644 engine/database.hpp delete mode 100644 engine/readme.md create mode 100644 engine/tests/meta.cpp create mode 100644 external/raiisqlite.cpp create mode 100644 external/raiisqlite.hpp delete mode 100644 launch.json diff --git a/.clang b/.clang deleted file mode 100644 index e46131a..0000000 --- a/.clang +++ /dev/null @@ -1 +0,0 @@ --I. -fanalyze \ No newline at end of file diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..c29ae33 --- /dev/null +++ b/.clangd @@ -0,0 +1,3 @@ +--- +CompileFlags: + Add: [-I. -fanalyze] diff --git a/base/base.hpp b/base/base.hpp index 970f609..61f0120 100644 --- a/base/base.hpp +++ b/base/base.hpp @@ -22,8 +22,6 @@ * Please use compiler with C++20 concepts and general C++20 support to compile pixelbox! */ -#include -#include #if !((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L || PB_NO_STDVER_CHECK) #error C++17 support is required! #endif @@ -37,7 +35,6 @@ #include #include -#include namespace pb { @@ -76,7 +73,6 @@ namespace pb { }; /** @deprecated */ - class Default : public Static {}; /** Abstract base class */ class Abstract { @@ -97,4 +93,4 @@ namespace pb { #define ANONYMOUS(x) CONCAT(x, __COUNTER__) #else // __COUNTER__ #define ANONYMOUS(x) CONCAT(x, __LINE__) -#endif // __COUNTER__ \ No newline at end of file +#endif // __COUNTER__ diff --git a/base/event.hpp b/base/defer.hpp similarity index 52% rename from base/event.hpp rename to base/defer.hpp index 0e6c361..0e9e72e 100644 --- a/base/event.hpp +++ b/base/defer.hpp @@ -1,6 +1,6 @@ /* * This file is a part of Pixelbox - Infinite 2D sandbox game - * Evenst for objservers/subscribers patterns + * Guaranteed execution of destructor, for RAII * Copyright (C) 2023-2024 UtoECat * * This program is free software: you can redistribute it and/or modify @@ -18,33 +18,35 @@ */ #pragma once -#include +#include #include +#include "base/base.hpp" namespace pb { - // subject for "Observers" - // TODO : This thing is horrible... - template - class Subject { - protected: - using function_type = std::function; - std::vector handlers; - public: - Subject() {}; - Subject(const Subject&) = default; - Subject(Subject&&) = default; - Subject& operator=(const Subject&) = default; - Subject& operator=(Subject&&) = default; - void subscribe(const function_type& f) { handlers.emplace_back(f); } - void subscribe(function_type&& f) { handlers.emplace_back(std::move(f)); } - void notify_all(Args&&... args) { - for (auto& f : handlers) { - f(std::forward(args)...); - } - } - }; +namespace impl { +template +class ScopeGuard : Moveable { + private: + F f; + bool invoke = true; + public: + explicit ScopeGuard(const F& ff) : f(ff) { } + explicit ScopeGuard(F&& ff) : f(std::move(ff)) { } + ~ScopeGuard() { if (invoke) f(); } + ScopeGuard(ScopeGuard&& other) noexcept : f(std::move(other.f)) { + other.invoke = false; + } + ScopeGuard(const ScopeGuard&&) = delete; }; +}; + +// defer operator :D yay +template +[[nodiscard]] auto defer(F&& f) noexcept { + return impl::ScopeGuard>{std::forward(f)}; +} +}; diff --git a/base/profiler.cpp b/base/profiler.cpp index fe61533..c11449e 100644 --- a/base/profiler.cpp +++ b/base/profiler.cpp @@ -18,18 +18,14 @@ */ #include "profiler.hpp" -#include "base/base.hpp" #include "clock.hpp" #include "spinlock.hpp" #include "base/resource.hpp" -#include #include #include -#include -#include #include #include #include @@ -338,15 +334,6 @@ void ThreadData::begin(const HString& name) { void ThreadData::end() {return data.end();} void ThreadData::step() {return data.step();} -/** syntax sugar and safer wrapper of begin()/end() functions above - * Calls begin() immediatly, and calls end when returned object is destructed - */ -ScopeGuard ThreadData::make_zone(const HString& name) { - begin(name); - auto &master = *this; - return ScopeGuard([&master]{master.end();}); -} - /** * API for retrieving profiling results */ @@ -414,4 +401,4 @@ size_t get_current_position(ThreadID id) { }; -}; \ No newline at end of file +}; diff --git a/base/profiler.hpp b/base/profiler.hpp index 2bc502c..0fe9c2d 100644 --- a/base/profiler.hpp +++ b/base/profiler.hpp @@ -20,7 +20,7 @@ #pragma once #include "base/base.hpp" #include "base/string.hpp" -#include "base/scopeguard.hpp" +#include "base/defer.hpp" #include // insertion speed and reference stablility matters here #include // for ThreadID @@ -86,12 +86,16 @@ class ThreadData { * done.~~ This requirement is removed in current version : you can safely * tick in the middle of deep zone stack :) */ - void step(); + void step(); - /** syntax sugar and safer wrapper of begin()/end() functions above + /** syntax sugar and safer wrapper of begin()/end() functions above * Calls begin() immediatly, and calls end when returned object is destructed */ - pb::ScopeGuard make_zone(const HString& name); + auto make_zone(const HString& name) { + begin(name); + auto &master = *this; + return pb::defer([&master]{master.end();}); + } }; /** @@ -108,9 +112,9 @@ void free_thread_data(ThreadData); ThreadData get_thread_data(); /** convinient wrapper around initialization things above */ -inline pb::ScopeGuard make_thread_data() { +inline auto make_thread_data() { ThreadData data = init_thread_data(); - return pb::ScopeGuard ([data]() { + return pb::defer([data]() { free_thread_data(data); }); } diff --git a/base/readme.md b/base/readme.md index 5de0126..a836b9a 100644 --- a/base/readme.md +++ b/base/readme.md @@ -22,4 +22,4 @@ functions/libraries/systems all over the place : - locale-independent strtod() # todo -- ~~Locale and implementation-independent vsnprintf() :p~~(done) \ No newline at end of file +- ~~Locale and implementation-independent vsnprintf() :p~~(done) diff --git a/base/resource.hpp b/base/resource.hpp index 5d557f4..1e11670 100644 --- a/base/resource.hpp +++ b/base/resource.hpp @@ -61,4 +61,4 @@ class Resource : public T{ ResUsage use() { return {static_cast(*this)}; } -}; \ No newline at end of file +}; diff --git a/base/scopeguard.hpp b/base/scopeguard.hpp deleted file mode 100644 index 9f91b29..0000000 --- a/base/scopeguard.hpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - * This file is a part of Pixelbox - Infinite 2D sandbox game - * Guaranteed execution of costructor and destructor, for RAII - * Copyright (C) 2023-2024 UtoECat - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#pragma once -#include -#include - - -namespace pb { - -template -class ScopeGuard { - private: - std::function call; - public: - ScopeGuard() = delete; - ScopeGuard(const ScopeGuard &) = default; - ScopeGuard(ScopeGuard &&) = default; - ScopeGuard &operator=(const ScopeGuard &) = default; - ScopeGuard &operator=(ScopeGuard &&) = default; - public: - void invoke() { - if (call) call(); - dismiss(); - } - void dismiss() { - call = std::function(); - } - operator bool() {return call;} - public: - ScopeGuard(const std::function &src) noexcept(std::is_nothrow_copy_constructible::value) : call(src) {} - ~ScopeGuard() {invoke();} -}; - -template <> -class ScopeGuard { - public: - ScopeGuard(const ScopeGuard &) = default; - ScopeGuard(ScopeGuard &&) = default; - ScopeGuard &operator=(const ScopeGuard &) = default; - ScopeGuard &operator=(ScopeGuard &&) = default; - public: - void invoke() {} - void dismiss() {} - operator bool() {return false;} - public: - ScopeGuard() noexcept {} -}; - -}; \ No newline at end of file diff --git a/base/sharedobj.hpp b/base/sharedobj.hpp index 41524b1..6d660a2 100644 --- a/base/sharedobj.hpp +++ b/base/sharedobj.hpp @@ -18,8 +18,6 @@ */ #pragma once -#include "base.hpp" -#include "spinlock.hpp" #include namespace pb { diff --git a/base/spinlock.hpp b/base/spinlock.hpp index 1c0da83..095c614 100644 --- a/base/spinlock.hpp +++ b/base/spinlock.hpp @@ -20,7 +20,6 @@ #pragma once #include #include -#include namespace pb { diff --git a/base/string.hpp b/base/string.hpp index ace021a..1cf658b 100644 --- a/base/string.hpp +++ b/base/string.hpp @@ -20,6 +20,7 @@ #pragma once #include #include +#include #include "external/printf.h" namespace pb { @@ -108,6 +109,7 @@ namespace pb { auto size = static_cast(size_s + 1); // Extra space for '\0' result.resize(size); auto vv = snprintf_( &result[0], size, format.c_str(), args... ); + result.pop_back(); // remove '\0!' return (vv > 0); } @@ -131,6 +133,10 @@ namespace pb { */ double strtod(const char *str, char **end); + + using String = std::string; + using StringView = std::string_view; + }; // here is the hash function @@ -142,4 +148,4 @@ struct std::hash { std::size_t operator()(const pb::HString& s) const noexcept { return s.get_hash(); } -}; \ No newline at end of file +}; diff --git a/base/strtod.cpp b/base/strtod.cpp index 0f6f826..51a0568 100644 --- a/base/strtod.cpp +++ b/base/strtod.cpp @@ -6,8 +6,6 @@ * modified a bit by utoecat. still public domain. */ -#include "base/base.hpp" -#include #include #include diff --git a/base/tests/network.cpp b/base/tests/network.cpp index 09aea03..dc2051d 100644 --- a/base/tests/network.cpp +++ b/base/tests/network.cpp @@ -28,8 +28,96 @@ #include "base/base.hpp" #include "base/doctest.h" +#include "base/string.hpp" #include "external/enet.h" +namespace pb { + +class NetTestCli : public ENetHandler { + public: + public: + int i = 0; + virtual void net_connect(ENetConnection& con) { + con.send(1, std::string_view("gimme your name")); + } + virtual void net_switch_out(ENetConnection& con) { + + } + virtual void net_recieve(ENetConnection& con, uint8_t channel, std::string_view data) { + std::cerr << "client recieved : " << data << std::endl; + con.send(1, std::string_view("abobus")); + if (i++) con.disconnect_later(); + } + virtual void net_update(ENetConnection& con) { + + } + virtual void net_disconnect(ENetConnection& con, bool is_timeout) { + + } +}; + +class NetTestSrv : public ENetHandler { + public: + public: + virtual void net_connect(ENetConnection& con) { + + } + virtual void net_switch_out(ENetConnection& con) { + + } + virtual void net_recieve(ENetConnection& con, uint8_t channel, std::string_view data) { + std::string v = format_r("amogus %i", rand()); + con.send(1, std::string_view(v)); + } + virtual void net_update(ENetConnection& con) { + + } + virtual void net_disconnect(ENetConnection& con, bool is_timeout) { + std::cerr << "server : client disconnected!" << std::endl; + } +}; + + TEST_CASE("network_test") { + + ENetServer server([]() { + return std::make_shared(); + }); + + std::vector threads; + + ProtocolInfo info; + info.nconnections = 10; + + server.set_address(info); + server.create("127.0.0.1"); + + for (int i = 0; i < 4; i++) threads.push_back(std::thread([](){ + ENetClient client([](){ + return std::make_shared(); + }); + + client.connect("127.0.0.1"); + + while (client.service(5000)) {}; + + client.disconnect(); + + })); + + while (server.service(5000)) { + server.keep_working = false; // stop when no connections left + } + + server.destroy(); + + for (auto &th : threads) { + th.join(); + } + + } + +}; + #if 0 class NetTestData : public pb::Abstract { public: diff --git a/compile_commands.json b/compile_commands.json index a6b437d..f0168f4 100644 --- a/compile_commands.json +++ b/compile_commands.json @@ -1,164 +1,206 @@ [ { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBase.dir/base/base.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/base/base.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/base/base.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBase.dir/base/base.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/base/base.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base/base.cpp", "output": "CMakeFiles/libBase.dir/base/base.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBase.dir/base/clock.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/base/clock.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/base/clock.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBase.dir/base/clock.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/base/clock.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base/clock.cpp", "output": "CMakeFiles/libBase.dir/base/clock.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBase.dir/base/doctest.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/base/doctest.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/base/doctest.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBase.dir/base/doctest.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/base/doctest.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base/doctest.cpp", "output": "CMakeFiles/libBase.dir/base/doctest.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBase.dir/base/profiler.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/base/profiler.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/base/profiler.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBase.dir/base/profiler.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/base/profiler.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base/profiler.cpp", "output": "CMakeFiles/libBase.dir/base/profiler.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBase.dir/base/random.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/base/random.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/base/random.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBase.dir/base/random.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/base/random.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base/random.cpp", "output": "CMakeFiles/libBase.dir/base/random.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBase.dir/base/tests/profiler.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/base/tests/profiler.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/base/tests/profiler.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBase.dir/base/strtod.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/base/strtod.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base/strtod.cpp", + "output": "CMakeFiles/libBase.dir/base/strtod.cpp.o" +}, +{ + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBase.dir/base/tests/network.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/base/tests/network.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base/tests/network.cpp", + "output": "CMakeFiles/libBase.dir/base/tests/network.cpp.o" +}, +{ + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBase.dir/base/tests/profiler.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/base/tests/profiler.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base/tests/profiler.cpp", "output": "CMakeFiles/libBase.dir/base/tests/profiler.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/cc -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -g -Wall -Wextra -o CMakeFiles/libExternal.dir/external/enet.c.o -c /home/utoecat/PIXELBOX_PROJECT/external/enet.c", - "file": "/home/utoecat/PIXELBOX_PROJECT/external/enet.c", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/cc -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -Wall -Wextra -o CMakeFiles/libExternal.dir/external/enet.c.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/enet.c", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/enet.c", "output": "CMakeFiles/libExternal.dir/external/enet.c.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/cc -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -g -Wall -Wextra -o CMakeFiles/libExternal.dir/external/galogen.c.o -c /home/utoecat/PIXELBOX_PROJECT/external/galogen.c", - "file": "/home/utoecat/PIXELBOX_PROJECT/external/galogen.c", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/cc -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -Wall -Wextra -o CMakeFiles/libExternal.dir/external/galogen.c.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/galogen.c", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/galogen.c", "output": "CMakeFiles/libExternal.dir/external/galogen.c.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libExternal.dir/external/imgui.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/external/imgui.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/external/imgui.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libExternal.dir/external/imgui.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/imgui.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/imgui.cpp", "output": "CMakeFiles/libExternal.dir/external/imgui.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libExternal.dir/external/imgui_demo.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/external/imgui_demo.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/external/imgui_demo.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libExternal.dir/external/imgui_demo.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/imgui_demo.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/imgui_demo.cpp", "output": "CMakeFiles/libExternal.dir/external/imgui_demo.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libExternal.dir/external/imgui_draw.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/external/imgui_draw.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/external/imgui_draw.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libExternal.dir/external/imgui_draw.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/imgui_draw.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/imgui_draw.cpp", "output": "CMakeFiles/libExternal.dir/external/imgui_draw.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libExternal.dir/external/imgui_tables.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/external/imgui_tables.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/external/imgui_tables.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libExternal.dir/external/imgui_tables.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/imgui_tables.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/imgui_tables.cpp", "output": "CMakeFiles/libExternal.dir/external/imgui_tables.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libExternal.dir/external/imgui_widgets.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/external/imgui_widgets.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/external/imgui_widgets.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libExternal.dir/external/imgui_widgets.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/imgui_widgets.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/imgui_widgets.cpp", "output": "CMakeFiles/libExternal.dir/external/imgui_widgets.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/cc -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -g -Wall -Wextra -o CMakeFiles/libExternal.dir/external/sqlite3.c.o -c /home/utoecat/PIXELBOX_PROJECT/external/sqlite3.c", - "file": "/home/utoecat/PIXELBOX_PROJECT/external/sqlite3.c", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libExternal.dir/external/luau.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/luau.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/luau.cpp", + "output": "CMakeFiles/libExternal.dir/external/luau.cpp.o" +}, +{ + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/cc -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -Wall -Wextra -o CMakeFiles/libExternal.dir/external/printf.c.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/printf.c", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/printf.c", + "output": "CMakeFiles/libExternal.dir/external/printf.c.o" +}, +{ + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libExternal.dir/external/printf_impl.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/printf_impl.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/printf_impl.cpp", + "output": "CMakeFiles/libExternal.dir/external/printf_impl.cpp.o" +}, +{ + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libExternal.dir/external/raiisqlite.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/raiisqlite.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/raiisqlite.cpp", + "output": "CMakeFiles/libExternal.dir/external/raiisqlite.cpp.o" +}, +{ + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/cc -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -g -Wall -Wextra -o CMakeFiles/libExternal.dir/external/sqlite3.c.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/sqlite3.c", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external/sqlite3.c", "output": "CMakeFiles/libExternal.dir/external/sqlite3.c.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/base -I/home/utoecat/PIXELBOX_PROJECT/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libEngine.dir/engine/database.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/engine/database.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/engine/database.cpp", - "output": "CMakeFiles/libEngine.dir/engine/database.cpp.o" + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libEngine.dir/engine/meta.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/engine/meta.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/engine/meta.cpp", + "output": "CMakeFiles/libEngine.dir/engine/meta.cpp.o" +}, +{ + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libEngine.dir/engine/network.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/engine/network.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/engine/network.cpp", + "output": "CMakeFiles/libEngine.dir/engine/network.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/base -I/home/utoecat/PIXELBOX_PROJECT/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libEngine.dir/engine/vfs.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/engine/vfs.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/engine/vfs.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libEngine.dir/engine/vfs.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/engine/vfs.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/engine/vfs.cpp", "output": "CMakeFiles/libEngine.dir/engine/vfs.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/base -I/home/utoecat/PIXELBOX_PROJECT/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBackend.dir/back_sdl/graphics.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/back_sdl/graphics.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/back_sdl/graphics.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBackend.dir/back_sdl/graphics.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/back_sdl/graphics.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/back_sdl/graphics.cpp", "output": "CMakeFiles/libBackend.dir/back_sdl/graphics.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/base -I/home/utoecat/PIXELBOX_PROJECT/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBackend.dir/back_sdl/imgui_impl_opengl3.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/back_sdl/imgui_impl_opengl3.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/back_sdl/imgui_impl_opengl3.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBackend.dir/back_sdl/imgui_impl_opengl3.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/back_sdl/imgui_impl_opengl3.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/back_sdl/imgui_impl_opengl3.cpp", "output": "CMakeFiles/libBackend.dir/back_sdl/imgui_impl_opengl3.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/base -I/home/utoecat/PIXELBOX_PROJECT/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBackend.dir/back_sdl/imgui_impl_sdl2.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/back_sdl/imgui_impl_sdl2.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/back_sdl/imgui_impl_sdl2.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBackend.dir/back_sdl/imgui_impl_sdl2.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/back_sdl/imgui_impl_sdl2.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/back_sdl/imgui_impl_sdl2.cpp", "output": "CMakeFiles/libBackend.dir/back_sdl/imgui_impl_sdl2.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/base -I/home/utoecat/PIXELBOX_PROJECT/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libBackend.dir/back_sdl/noop.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/back_sdl/noop.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/back_sdl/noop.cpp", - "output": "CMakeFiles/libBackend.dir/back_sdl/noop.cpp.o" -}, -{ - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/base -I/home/utoecat/PIXELBOX_PROJECT/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libGame.dir/game/main.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/game/main.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/game/main.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libGame.dir/game/main.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/game/main.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/game/main.cpp", "output": "CMakeFiles/libGame.dir/game/main.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/base -I/home/utoecat/PIXELBOX_PROJECT/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libGame.dir/game/main_client.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/game/main_client.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/game/main_client.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libGame.dir/game/main_client.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/game/main_client.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/game/main_client.cpp", "output": "CMakeFiles/libGame.dir/game/main_client.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/base -I/home/utoecat/PIXELBOX_PROJECT/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libGame.dir/game/main_server.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/game/main_server.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/game/main_server.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libGame.dir/game/main_server.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/game/main_server.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/game/main_server.cpp", "output": "CMakeFiles/libGame.dir/game/main_server.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/base -I/home/utoecat/PIXELBOX_PROJECT/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libGame.dir/game/noop.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/game/noop.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/game/noop.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libGame.dir/game/noop.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/game/noop.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/game/noop.cpp", "output": "CMakeFiles/libGame.dir/game/noop.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/base -I/home/utoecat/PIXELBOX_PROJECT/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libGame.dir/game/tools/imgui_wrap.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/game/tools/imgui_wrap.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/game/tools/imgui_wrap.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libGame.dir/game/tools/imgui_wrap.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/game/tools/imgui_wrap.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/game/tools/imgui_wrap.cpp", "output": "CMakeFiles/libGame.dir/game/tools/imgui_wrap.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/base -I/home/utoecat/PIXELBOX_PROJECT/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libGame.dir/game/tools/master.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/game/tools/master.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/game/tools/master.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libGame.dir/game/tools/master.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/game/tools/master.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/game/tools/master.cpp", "output": "CMakeFiles/libGame.dir/game/tools/master.cpp.o" }, { - "directory": "/home/utoecat/PIXELBOX_PROJECT/build", - "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT -I/home/utoecat/PIXELBOX_PROJECT/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/base -I/home/utoecat/PIXELBOX_PROJECT/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libGame.dir/game/tools/profiler.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/game/tools/profiler.cpp", - "file": "/home/utoecat/PIXELBOX_PROJECT/game/tools/profiler.cpp", + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libGame.dir/game/tools/profiler.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/game/tools/profiler.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/game/tools/profiler.cpp", "output": "CMakeFiles/libGame.dir/game/tools/profiler.cpp.o" +}, +{ + "directory": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/build", + "command": "/usr/bin/c++ -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/libSDL -I/usr/include/SDL2 -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/base -I/home/utoecat/PIXELBOX_PROJECT/CPP_VER/external -g -std=gnu++20 -Wall -Wextra -o CMakeFiles/libGame.dir/game/world_view.cpp.o -c /home/utoecat/PIXELBOX_PROJECT/CPP_VER/game/world_view.cpp", + "file": "/home/utoecat/PIXELBOX_PROJECT/CPP_VER/game/world_view.cpp", + "output": "CMakeFiles/libGame.dir/game/world_view.cpp.o" } ] \ No newline at end of file diff --git a/engine/config.hpp b/engine/config.hpp index 3320dfa..7a815c4 100644 --- a/engine/config.hpp +++ b/engine/config.hpp @@ -18,4 +18,3 @@ */ #pragma once - #include "engine/database.hpp" \ No newline at end of file diff --git a/engine/core.hpp b/engine/core.hpp index 5b12b61..d7a02c9 100644 --- a/engine/core.hpp +++ b/engine/core.hpp @@ -19,11 +19,8 @@ #pragma once #include - #include #include - -#include #include namespace pb { @@ -49,8 +46,8 @@ namespace pb { static constexpr int16_t CHUNK_WIDTH = 16; static constexpr int16_t CHUNK_BYTES = CHUNK_WIDTH*CHUNK_WIDTH; - /** Minimal Pixel container. For rendering and clients. - Also used as storage for per-pixel metadata. + /** Minimal Pixel container. + Also used as storage for per-pixel serverosideonly metadata. */ struct Pixels : public Copyable { uint8_t data[CHUNK_BYTES]; // pixel type diff --git a/engine/database.cpp b/engine/database.cpp deleted file mode 100644 index bc642ab..0000000 --- a/engine/database.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/* - * This file is a part of Pixelbox - Infinite 2D sandbox game - * Database classes :p - * Copyright (C) 2023-2024 UtoECat - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include "database.hpp" - -namespace pb { - - bool Statement::iterator() { // return true while has data - int attemts = 0, res = 0; - retry: - res = sqlite3_step(ptr); - if (res == SQLITE_BUSY) { - attemts++; - if (attemts < 100) goto retry; - } - - bool reset = false; - if (res == SQLITE_DONE) - reset = true; - else if (res == SQLITE_ROW) - reset = false; - else { - sqlite3_reset(ptr); - throw StatementError(sqlite3_errmsg(sqlite3_db_handle(ptr))); - } - - if (reset) { - sqlite3_reset(ptr); - } - - return !reset; - } - - bool Statement::column_blob(int index, void* rdst, size_t max) { - unsigned char* dst = (unsigned char*) rdst; - const char* src = (const char*)sqlite3_column_blob(ptr, index); - size_t slen = column_bytes(index); - if (!src || !max) return false; - size_t len = slen > max ? max : slen; - memcpy(dst, src, len); - return true; - } - - bool Statement::column_text(int index, std::string& v) { - const char* src = (const char*)sqlite3_column_text(ptr, index); - size_t slen = column_bytes(index); - v.clear(); - if (!src || !slen) return false; - v.reserve(slen); - v.append(src, slen); - return true; - } - - bool Statement::column_text(int index, char* dst, size_t max) { - const char* src = (const char*)sqlite3_column_text(ptr, index); - size_t slen = column_bytes(index); - if (!src || !max || !(max-1)) return false; - max--; - - size_t len = slen > max ? max : slen; - memcpy(dst, src, len); - dst[len] = '\0'; // end of line - return true; - }; - - - namespace db { - - void make_safe_string(std::string& s) { - std::replace(s.begin(), s.end(), '"', '_'); - std::replace(s.begin(), s.end(), '\'', '_'); - std::replace(s.begin(), s.end(), '(', '_'); - std::replace(s.begin(), s.end(), ')', '_'); - } - - /** Create sql table if not exists */ - void create_properties_table(Database &db, const char* _name) { - std::string s("CREATE TABLE IF NOT EXISTS "); - std::string name = _name; - make_safe_string(name); - - s += "\""; s += name; s += "\" "; - s += "(key STRING PRIMARY KEY, value);"; - db.exec(s.c_str()); - } - - /** default database settings */ - void world_settings(Database &db) { - static const char* init_sql = - "PRAGMA cache_size = -16000;" - "PRAGMA journal_mode = MEMORY;" - "PRAGMA synchronous = 0;" // TODO : may be 1 ok? - "PRAGMA auto_vacuum = 0;" - "PRAGMA secure_delete = 0;" - "PRAGMA temp_store = MEMORY;" - "PRAGMA page_size = 32768;" - "PRAGMA integrity_check;"; - // TODO: "CREATE TABLE IF NOT EXISTS PROPERTIES (key STRING PRIMARY KEY, "value);" - // FIXME: "CREATE TABLE IF NOT EXISTS WCHUNKS (id INTEGER PRIMARY KEY, value BLOB);" - db.exec(init_sql); - } - - /** default database settings */ - void config_settings(Database &db) { - static const char* init_sql = - "PRAGMA cache_size = -16000;" - "PRAGMA journal_mode = WAL;" - "PRAGMA synchronous = 0;" // TODO : may be 1 ok? - "PRAGMA auto_vacuum = 0;" - "PRAGMA secure_delete = 0;" - "PRAGMA integrity_check;"; - db.exec(init_sql); - } - - void set_property(Database &db, const char* _table, const char* name, const std::string& value) { - std::string table = _table; - make_safe_string(table); - - std::string q = "INSERT OR REPLACE INTO "; - q += "\""; q += table ; q += "\" VALUES (?1, ?2);"; - auto s = db.query(q.c_str()); - - s.bind(1, name); - s.bind(2, value.c_str(), value.size()); - while (s.iterator()) {} - } - - void get_property(Database &db, const char* _table, const char* name, std::string& value) { - std::string table = _table; - make_safe_string(table); - value.clear(); - - std::string q = "SELECT value FROM "; - q += "\""; q += table ; q += "\" WHERE key = ?1;"; - auto s = db.query(q.c_str()); - - s.bind(1, name); - while (s.iterator()) { - value = s.column(0); - } - } - - }; - -}; diff --git a/engine/database.hpp b/engine/database.hpp deleted file mode 100644 index 02149d5..0000000 --- a/engine/database.hpp +++ /dev/null @@ -1,184 +0,0 @@ -/* - * This file is a part of Pixelbox - Infinite 2D sandbox game - * Database classes :p - * Copyright (C) 2023-2024 UtoECat - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#pragma once -#include -#include -#include -#include -#include - -#include - -namespace pb { - -class StatementError : public std::runtime_error { - public: - using re = std::runtime_error; - using re::re; -}; - -class Statement : public Default { - sqlite3_stmt* ptr; - public: - Statement(sqlite3* db, const char* sql) { - int err = sqlite3_prepare_v3(db, sql, -1, 0, &ptr, (const char**)0); - if (err != SQLITE_OK) { - throw StatementError(sqlite3_errmsg(db)); - } - } - Statement(Statement&& src) { - ptr = src.ptr; - src.ptr = nullptr; - } - ~Statement() { - if (ptr) sqlite3_finalize(ptr); - } - public: - bool iterator(); - void reset() { - if (ptr) sqlite3_reset(ptr); - } - void clear() { - if (ptr) sqlite3_clear_bindings(ptr); - } - int bind_index(const char* name) { - return sqlite3_bind_parameter_index(ptr, name); - } - - bool bind(int index, int64_t value) { - return sqlite3_bind_int64(ptr, index, value); - } - - bool bind(int index, uint64_t value) { - return sqlite3_bind_int64(ptr, index, (int64_t)value); - } - - bool bind(int index, double value) { - return sqlite3_bind_double(ptr, index, value); - } - - bool bind(int index, const char* value) { - return sqlite3_bind_text(ptr, index, value, -1, SQLITE_STATIC); - } - - bool bind(int index, const char* value, size_t size) { - return sqlite3_bind_blob(ptr, index, - value, size, SQLITE_TRANSIENT); - } - - bool unbind(int index) { - return sqlite3_bind_null(ptr, index); - } - - // get values - - int column_count() { - return sqlite3_data_count(ptr); - } - - template - inline T column(int) { - static_assert("Type is not supported"); - } - - size_t column_bytes(int index) { - return sqlite3_column_bytes(ptr, index); - } - - bool column_blob(int index, void* rdst, size_t max); - bool column_text(int index, char* dst, size_t max); - bool column_text(int index, std::string& v); - -}; - - template<> - inline int64_t Statement::column(int index) { - return sqlite3_column_int64(ptr, index); - } - - template<> - inline uint64_t Statement::column(int index) { - return (uint64_t)sqlite3_column_int64(ptr, index); - } - - template<> - inline double Statement::column(int index) { - return sqlite3_column_double(ptr, index); - } - - template<> - inline std::string Statement::column(int index) { - return std::string( - (const char*)sqlite3_column_text(ptr, index), - column_bytes(index) - ); - } - - -class Database : public Default { - sqlite3* db = nullptr; - public: - Database() = default; - Database(const Database&) = delete; - Database(Database&& src) { - db = src.db; - src.db = nullptr; - } - public: - void close() { - if (db) sqlite3_close_v2(db); - db = nullptr; - } - bool open(const char* path, bool readonly = false) { - int flags = readonly ? SQLITE_OPEN_READONLY : - SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; - return sqlite3_open_v2(path, &db, flags, NULL) == SQLITE_OK; - } - ~Database() { - close(); - } - Database(const char* path, bool ro = false) { - open(path, ro); - } - public: - void exec(const char* str) { - char* msg; - int ok = sqlite3_exec(db, str, NULL, NULL, &msg); - if (ok != SQLITE_OK) - throw StatementError(msg); - } - Statement query(const char* sql) { - return Statement(db, sql); - } -}; - -namespace db { - /* HIGH LEVEL API */ - void make_safe_string(std::string& s); - void world_settings(Database& db); - void config_settings(Database &db); - void create_properties_table(Database &db, const char* _name); - void set_property(Database &db, const char* _table, const char* name, const std::string& value); - void get_property(Database &db, const char* _table, const char* name, std::string& value); -}; - - -}; - diff --git a/engine/meta.cpp b/engine/meta.cpp index 47035d4..4979185 100644 --- a/engine/meta.cpp +++ b/engine/meta.cpp @@ -18,8 +18,513 @@ */ #include "core.hpp" +#include "meta.hpp" +#include +#include +#include +#include + +#include "base/defer.hpp" + +//implementation +#include "external/robin_map.h" +namespace pb { + + using MetaValue = std::variant; + // types of MetaValue + enum MetaTypes { + NUM, + STR, + OBJ + }; + +namespace impl { + + // Value for associative metadata map (see below) + struct MetaMap : public tsl::robin_map{ + using Base = tsl::robin_map; + using Base::Base; + + // assumes key is validated already. May return NULLPTR + const MetaValue* get_variant(MetaKey k) const { + auto v = find(k); + if (v != end()) { + return &v->second; + }; + return nullptr; + }; + }; + + const MetaValue NIL = std::string(); + const MetaKey NILK = HString(); + + bool is_nil(const MetaValue &src) { + return src.index() == 1 && std::get<1>(src).size() == 0; + } + + bool is_nil(const MetaKey &src) { + return src.index() == 1 && std::get<1>(src).size() == 0; + } + + bool is_str_number(const std::string& ref, double& res) { + res = 0; + if (isdigit(ref[0])) { + const char* a = ref.c_str(); + char* b = nullptr; + double v = pb::strtod(a, &b); + res = v; + + while (isspace(*b)) b++; + if (*b == '\0' && (std::isnormal(v) || v == 0.0)) { + return true; + }; // valid number + } + return false; // also in case of Nan, Inf + } + + // utility : values to valid to MetaKey + bool validateKey(MetaKey& src) { + // check number + if (src.index() == 0) { + double& ref = std::get<0>(src); + if (std::isnormal(ref) || ref == 0.0) return true; // number + return false; // bad numeric value + } + + // check if empty string + HString& ref = std::get<1>(src); + if (ref.size() == 0) return false; // empty string + + // check if string is a valid number + double res; + if (is_str_number(ref, res)) { + src = MetaKey(res); + return true; // successful cast + } + + // string is OK + return true; + }; + + std::string num2str(double d) { + double v = std::trunc(d); + if (v == d) + return pb::format_r("%li", (int32_t)v); + return pb::format_r("%f", d); + } + + std::string to_str(const MetaValue& v) { + try { + if (v.index() == 0) { + return num2str(std::get<0>(v)); + } + return std::get<1>(v); + } catch(...) { + return std::string(); // oom + } + } + + std::string to_str(const MetaKey& v) { + try { + if (v.index() == 0) { + return num2str(std::get<0>(v)); + } + return std::get<1>(v); + } catch(...) { + return std::string(); // oom + } + } + + MetaValue to_val(const std::string& src) { + if (src.size() == 0) return NIL; // empty + + double res; + if (is_str_number(src, res)) { // cast to number if string is a number! + return MetaValue(res); // to number + } + + return MetaValue(src); // keep original string + } + + // for numbers + MetaValue to_val(const double src) { + if (std::isnormal(src) || src == 0.0) return MetaValue(src); + return NIL; // NULL + } + + +}; + +Metadata::Metadata() noexcept { + map = nullptr; +} + +Metadata::Metadata(Metadata&& src) noexcept { + map = src.map; + src.map = nullptr; +} + +Metadata& Metadata::operator=(Metadata&& src) noexcept { + if (this != &src) { + if (map) delete map; + map = src.map; + src.map = nullptr; + } + return *this; +} + +Metadata::~Metadata() { + if (map) delete map; +} + +size_t Metadata::size() const noexcept { + return map ? map->size() : 0; +} + +// we check +bool Metadata::has_value(const MetaKey& key) const { + MetaKey k = key; + return get_type(k) >= 0; +} + +// type of a VALUE +int Metadata::get_type(MetaKey& k) const { + if (map && impl::validateKey(k)) { + auto v = map->find(k); + if (v != map->end()) { + size_t t = v->second.index(); // VALUE + return (int)t; // both can be converted back to string + }; + }; + return -1; // bad key or empty map +} +/** check for type + has */ +bool Metadata::is_string (const MetaKey& key) const { + MetaKey k = key; + return get_type(k) >= 0; // number and string are convertable to string! +} + +/** may attempt casting :) */ +bool Metadata::is_number(const MetaKey& key) const { + MetaKey k = key; + return get_type(k) == 0; // number +} + +/** return string value or emplaces it (and casts). Returns empty string on OOM or empty! */ +std::string Metadata::get_string(const MetaKey& key) const { + MetaKey k = key; + if (map && impl::validateKey(k)) { + const MetaValue* v = map->get_variant(k); + if (v) { + return impl::to_str(*v); + } + } + return std::string(); +} + +int32_t Metadata::get_integer(const MetaKey& key) const { + MetaKey k = key; + if (map && impl::validateKey(k)) { + const MetaValue* v = map->get_variant(k); + if (v && v->index() == 0) { + return std::get<0>(*v); + } + } + return 0; // default +} + +double Metadata::get_number(const MetaKey& key) const { + MetaKey k = key; + if (map && impl::validateKey(k)) { + const MetaValue* v = map->get_variant(k); + if (v && v->index() == 0) { + return std::get<0>(*v); + } + } + return 0.0; // default +} + +/** removes value from the map */ +void Metadata::unset(const MetaKey& key) { + MetaKey k = key; + if (map && impl::validateKey(k)) { + map->erase(k); + } +} + +/** sets value */ +void Metadata::set(const MetaKey& key, const std::string& value) { + MetaKey k = key; + if (impl::validateKey(k)) { + if (!map) map = new impl::MetaMap(); + MetaValue v = impl::to_val(value); + if (!impl::is_nil(v)) // ignore emty strings + map->insert_or_assign(k, v); + else map->erase(k); // else remove key + } +} + +void Metadata::set(const MetaKey& key, const int32_t value) { + MetaKey k = key; + if (impl::validateKey(k)) { + if (!map) map = new impl::MetaMap(); + MetaValue v = impl::to_val(value); + if (!impl::is_nil(v)) // ignore invalid numbers + map->insert_or_assign(k, v); + else map->erase(k); // else remove key + } +} + +void Metadata::set(const MetaKey& key, const double value) { + MetaKey k = key; + if (impl::validateKey(k)) { + if (!map) map = new impl::MetaMap(); + MetaValue v = impl::to_val(value); + if (!impl::is_nil(v)) // ignore invalid numbers + map->insert_or_assign(k, v); + else map->erase(k); // else remove key + } +} + +Metadata Metadata::copy() { + Metadata dst; + if (!map) return dst; // empty map + + // copy stuff + dst.map = new impl::MetaMap; + dst.map->insert(map->begin(), map->end()); + return dst; // done +} + +void Metadata::clear() { + // don't delete, because may be refilled + if (map) map->clear(); +} + +bool Metadata::next(MetaKey& key) { + if (!map) { // nothing + key = impl::NILK; + return false; + } + + // get next key + { + MetaKey k = key; + if (impl::validateKey(k)) { + auto v = map->find(k); + if (v != map->end()) v++; // next + if (v != map->end()) { // found + key = v->first; + return true; // another one + }; + // unlikely to hit there, so we can let us do extra checks for is_nil! + }; + } + // key does not exist + + // is nil -> start of the iteration? + if (impl::is_nil(key)) { + auto i = map->begin(); + if (i != map->end()) { + key = i->first; // get "next" key + return true; + } + } + + // not found or end of iteration or empty map! + key = impl::NILK; + return false; +}; +}; +/** + * THE BEAST + * + * JSON + * + * I hate "modern c++ json porsers" because of importance of object representation. + * We can do that on the fly. Also, we don't use deep ass distionary-in-dictionary thing at all. + */ + +#include +#include +#include +#include + namespace pb { +namespace impl { + + // uTF8 string out + void jsonify_string(std::ostream& dst, const std::string& src) { + dst << '"' ; + for (char c : src) { + if (c == '"' || c == '\\') { // escape + dst << '\\' << c; + } else if(iscntrl(c)) { // special + dst << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast(c); + dst << std::dec << std::setw(0); + } else dst << c; + } + dst << '"'; + } + + struct Parser { + std::istream& src; + int c = ' '; // current char; + public: + Parser(std::istream& src) : src(src) {} + + char next() { + c = src.get(); + if (c == EOF) std::runtime_error("unexpected EOF"); + return c; + } + + void skip_space() { + if (!isspace(c)) return; // we already jave good char + while (isspace(next())) {} // skip :) + } + + char escape() { + switch (next()) { + case '\\' : return '\\'; + case '\n' : return '\n'; + case 'u' : { + char v[5] = {0}; + for (int i = 0; i < 4; i++) { + if (!isdigit(next())) throw std::runtime_error("Invalid UTF8 escape!"); + } + int v2 = std::strtod(v, nullptr); + if (v2 > '\x1F') throw std::runtime_error("UTF escapes not support values > than 255! :D. I am lazy"); + return char(v2); + }; + default: throw std::runtime_error("Invalid escape"); + } + } + + // we found " char! + std::string parse_str() { + std::string v; + while (next() != '"') { + if (c == '\\') { + v += escape(); + } else { + v += c; + } + } + next(); // after string + return v; // done + } + + double parse_num() { + char tmp[64] = {0}; + int i = 0; + // put curr digit + tmp[i++] = c; + + while (next(), (isdigit(c) || c == 'e' || c == 'E' || c == '.') && i < 63) { + tmp[i++] = c; + } + + char* p = nullptr; + double v = pb::strtod(tmp, &p); + if (*p != '\0') throw std::runtime_error("Bad number parsed!"); + return v; + } + + void execute(Metadata& meta) { + while ((c = src.get()) != EOF && isspace(c)) {} + if (c == EOF) return; // no data + if (c != '{') throw std::runtime_error("only json dictionary is supported and only once!"); + next(); + if (c == '}') return; // end early + + while (true) { + skip_space(); + + // parse key + if (c != '"') // required to be a string! + throw std::runtime_error("only string may be a key!"); + MetaKey k = HString(parse_str()); + skip_space(); + if (c != ':') std::runtime_error(": separator betwwen key and value required!"); + next(); + skip_space(); + + // parse value and set pair into metamap + if (isdigit(c) || c == '.' || c == 'e' || c == 'E') meta.set(k, parse_num()); + else if (c == '"') meta.set(k, parse_str()); + else std::runtime_error("invalid value!"); + + // check for end or separator + skip_space(); + if (c == ',') next(); // skip separator + else if (c == '}') return; + else throw std::runtime_error("separator , or end of dictionary } required!"); + } + // unreachable + }; + + // end + + }; + + }; + + +bool Metadata::deserialize(std::istream& src) { + impl::Parser p(src); + try { + p.execute(*this); + return true; + } catch (std::exception& e) { + std::cerr << "Metadata::deserialize() : " << e.what() << std::endl; + return false; + } +} + +// hard one... :skull: +void Metadata::serialize(std::ostream& dst) { + dst << '{'; + + if (!map) { + dst << '}'; + return; // end + } + + std::ios_base::fmtflags f(dst.flags() ); + auto _ = pb::defer([&dst, f]() { + dst.flags(f); + }); + + std::string tmp; + + for (auto i = map->begin(); i != map->end();) { + + const MetaValue& v = i->second; + if (v.index() == 0 && std::get<0>(v) == 0.0) { // skip to save disk space + i++; + continue; + } + + // key + impl::jsonify_string(dst, impl::to_str(i->first)); // to string all keys + dst << ':'; + + if (v.index() == 0) { // number + format_v(tmp, "%f", std::get<0>(v)); + dst << tmp; + } else { + impl::jsonify_string(dst, impl::to_str(v)); + } + + // next + i++; + if (i != map->end()) dst << ','; + } + + dst << '}'; +} -}; \ No newline at end of file +}; diff --git a/engine/meta.hpp b/engine/meta.hpp index 4b2d134..11fe1d9 100644 --- a/engine/meta.hpp +++ b/engine/meta.hpp @@ -18,40 +18,49 @@ */ #pragma once -#include "base.hpp" -#include "core.hpp" - -//implementation -#include -#include +#include "base/base.hpp" #include "base/string.hpp" +#include "engine/core.hpp" +#include namespace pb { - // Value for associative metadata map (see below) - // can keep direct references to values like entities and chunks. - // But will be serialized back into UUID/Chunk position when saved - // TODO FIXME NAN KEY STORAGING PROBLEM MUST BE FIXED!!!1 - using MetaKey = std::variant; - - // types of MetaValue - enum MetaTypes { - NIL, - NUM, - STR, - ARR, - OBJ - }; - - using MetaValue = std::variant, weak_ptr>; - // Associative array for metadata. see below. - using MetapropMap = tsl::robin_map; - - // Chunk Associative array - using ChunkMap = tsl::robin_map>; +namespace impl { +/* + * PRIVATE implementation of METADATA! + * Metadata methods are just a proxies to the methods of this object! + */ + struct MetaMap; /* + * Basic associative array for Chunks! + */ + struct ChunkMap; +}; + +struct MetaKey : public std::variant { + using Base = std::variant; + using Base::Base; + + MetaKey(int i) : Base((double)i) {} + MetaKey(uint32_t i) : Base((double)i) {} + MetaKey(const char* c) : Base(HString(c)) {} +}; + +}; + +template<> +struct std::hash { + size_t operator()(const pb::MetaKey& key) const noexcept { + std::hash hasher; + return hasher(pb::MetaKey::Base(key)); + } +}; + +namespace pb { + +/** * Every Object that relates to the game itself : Chunk, Entyty, * Player, etc. Always has special storage for any amount of key=value * paired data - METADATA. @@ -61,27 +70,70 @@ namespace pb { * dynamic and less depended on exact object structures! * * All mods are primary use ONLY metadata. - * We don't make this shared to prevent recursion + * We don't make this shared to prevent recursion. + * @warning USED AS COMPOSITION! metadata filed MUST be public! + * + * @warning string keys that can be represented as numbers will be casted to a number! + * @warning empty string keys will return default/empty values! + * @warning values with empty key will not be set! + * @warning NAN, INF and other invalid key/values will not be set! + * default values are : "", 0 and 0.0 + * default values are excluded from a dump */ class Metadata : public Moveable { + private: + impl::MetaMap *map = nullptr; // dynamicly allocated! + int get_type(MetaKey& k) const; public: - MetapropMap map; - public: - MetaValue& operator[](const MetaKey& key) { - auto v = map.find(key); - if (v == map.end()) { - v = map.emplace_hint(v, nullptr); - } - return v.value(); - } + Metadata() noexcept; + Metadata(Metadata&&) noexcept; + Metadata& operator=(Metadata&&) noexcept; + ~Metadata(); + public: + /** returns count of items in map */ + size_t size() const noexcept; + + /** check if map has this key */ + bool has_value(const MetaKey& key) const; + + /** check for type + has */ + bool is_string (const MetaKey& key) const; + /** may attempt casting :) */ + bool is_number (const MetaKey& key) const; + + /** return string value or emplaces it (and casts). Returns empty string on OOM or empty! */ + std::string get_string(const MetaKey& key) const; + int32_t get_integer(const MetaKey& key) const; + double get_number(const MetaKey& key) const; + + /** removes value from the map */ + void unset(const MetaKey& key); + + /** sets value */ + void set(const MetaKey& key, const std::string& value); + void set(const MetaKey& key, const int32_t value); + void set(const MetaKey& key, const double value); + + Metadata copy(); // explicit copy + void clear(); // clear all content + + // iterate over map + // overwrites key on return. Returns false and ampty key at end + // pass empty string as first value + bool next(MetaKey& key); + + public: // extra + void serialize(std::ostream&); // save map content into stringstream (JSON-LIKE) + bool deserialize(std::istream&); // load map content from stringstream (JSON-LIKE) }; /** * To make difference between Game-related Objects and just a metadata, every Game-related * must inherit from MetaClass, not Metadata! */ - class MetaClass : public Metadata, public Shared { - - }; + class MetaClass : public Shared { + public: + Metadata metadata; + }; }; diff --git a/engine/network.cpp b/engine/network.cpp index c0f4bd7..32b636b 100644 --- a/engine/network.cpp +++ b/engine/network.cpp @@ -209,6 +209,60 @@ bool ENetClient::connect(const char* ip, unsigned short port) { return false; } +bool ENetClient::service(int timeout) { + ENetEvent event; + + if (!get_server() && attempt_reconnect > 0 && attempt_reconnect < 10) { + force_destroy(); + connect(info.ip, info.port); + attempt_reconnect++; + return true; // whatever + } + + if (!host || !peers_count) return false; + + if (service_events(&event, timeout)) { + handle_event(event); // do init + + if (event.type == ENET_EVENT_TYPE_DISCONNECT || event.type == ENET_EVENT_TYPE_DISCONNECT_TIMEOUT) { + if (attempt_reconnect > 0 && attempt_reconnect < 10) { + free_event(event); + return true; // try again + } + } + + free_event(event); + } + return true; +} + +bool ENetServer::service(int timeout) { + ENetEvent event; + + if (!host) return false; + if (!peers_count && !keep_working) return false; + + if (service_events(&event, timeout)) { + + if (prevent_connection && event.type == ENET_EVENT_TYPE_CONNECT) { + enet_peer_reset(event.peer); // ignore and kill + free_event(event); + return true; + } + + handle_event(event); // do init + free_event(event); + } + return true; +} + +bool ENetServer::create(const char* ip, unsigned short port) { + info.ip = ip; + info.port = port; + + if (!create_server()) return false; + return true; +} -}; // namespace pb \ No newline at end of file +}; // namespace pb diff --git a/engine/network.hpp b/engine/network.hpp index 38831b3..ce51a3e 100644 --- a/engine/network.hpp +++ b/engine/network.hpp @@ -216,11 +216,16 @@ class ENetBase : public Static { operator bool() { return host != nullptr; } void set_address(ProtocolInfo i) { info = i; } - /** calls function at the first argument on all the peers connected to this host.*/ + /** calls function at the first argument on all the peers connected to this host. + * it's safe to disconnect/reset peer, passed in callback :) but not any another! + */ void foreach (std::function cb) { - assert(host != nullptr); + if (host != nullptr) return; - for (ENetConnection* conn : peers) { + auto it = peers.begin(); + for (; it != peers.end();) { + auto *conn = *it; + it++; cb(*conn); } } @@ -231,12 +236,17 @@ class ENetBase : public Static { * * call of foreach() is equal to call foreach_handler(), except for extra * callack argument - handler itself :p + * + * it's safe to disconnect/reset peer, passed in callback :) but not any another! */ template void foreach_handler(std::function)> cb) { - assert(host != nullptr); + if (host != nullptr) return; - for (ENetConnection* conn : peers) { + auto it = peers.begin(); + for (; it != peers.end();) { + auto *conn = *it; + it++; auto handler = std::dynamic_pointer_cast(conn->handler); if (handler) cb(*conn, handler); } @@ -258,6 +268,7 @@ class ENetBase : public Static { void force_destroy() { // cleanup all userdata foreach ([](ENetConnection& conn) { conn.reset(); }); + assert(peers.size() == 0); if (host) enet_host_destroy(host); host = nullptr; @@ -282,6 +293,9 @@ class ENetBase : public Static { class ENetClient : public ENetBase { public: + /** @property if > 0 and < 10, client will try to reconnect to the server */ + int attempt_reconnect = 0; + ENetClient(ENetHandlerMaker f) : ENetBase(f) {}; ENetConnection* get_server() { @@ -319,252 +333,30 @@ class ENetClient : public ENetBase { return send(channel, p); } -}; - -class ENetServer : public ENetBase { - -}; - -}; // namespace pb - -#if 0 -/** - * @description ENet Wrapper - */ -namespace pb_old { - -static constexpr unsigned short DEFAULT_PORT = 4792; - -struct ProtocolInfo { - const char* ip = NULL; - unsigned short port = DEFAULT_PORT; - int nchannels = 5; - int nconnections = 1; - - public: - inline ENetAddress getAddress() { - ENetAddress addr; - addr.port = port; + /** services client internally. Returns true if service() should be called again. False if you should disconnect()! */ + bool service(int timeout = 0); - if (ip) - enet_address_set_host(&addr, ip); - else - addr.host = ENET_HOST_ANY; - return addr; - } }; -static void HostDefaultConfig(ENetHost* host) { - // TODO Adjust to better values! - host->maximumPacketSize = 1024 * 32; - host->maximumWaitingData = 1024 * 128; -} - -* represents any data * -using ConnectionData = std::shared_ptr; - -class ENetBase; - -class ENetBase { - protected: - ENetHost* host = nullptr; - ProtocolInfo info; // default - - /** all the connected clients with a data * - std::set connections; - - public: // event handlers - /** handled every time a client connects. @warning does not triggered on a server disconnection process. * - Subject h_connect; - - /** triggered when asuccesfully connected peer is disconnected. Does not work in force_disconnect() case. - * also it's not triggered on a peers, that does not respond with a succesful disconnection. - * - Subject h_disconnect; - - /** - * Triggered every time a message is recieved - * @param basic - * @param basic - * @param int specifies channel id where the message was recieved - * @param string_view message data - */ - Subject h_recieve; // channel_id, data - - /** server shutdown/disconnection handler. all connections are still valid. - * @warning does not called on force_disconnect at all! - */ - Subject h_shutdown; - - bool defer_destroy = false; - - protected: - // handle events servicing - void on_event_recv(ENetEvent& ev); - - protected: - /** @warning do not use inside event handers! */ - void create_server() { - if (host) std::runtime_error("host is already exists!"); - defer_destroy = false; - auto addr = info.getAddress(); - host = enet_host_create(&addr, info.nconnections, info.nchannels, 0, 0); - } - - /** @warning do not use inside event handers! */ - void create_client() { - if (host) std::runtime_error("host is already exists!"); - defer_destroy = false; - host = enet_host_create(NULL, info.nconnections, info.nchannels, 0, 0); - } - - /** calls function at the first argument on all the peers connected to this host.*/ - void foreach (std::function cb) { - if (!host) return; - ENetPeer* currentPeer; - for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { - cb(currentPeer); - } - } - - /** @warning do not use inside event handers! Use defer_destroy=true; instead! - * Also, this function does not invoke h_shutdown and h_disconnect events AT ALL! - */ - void force_destroy() { - // cleanup all userdata - foreach ([](ENetPeer* peer) { - auto v = (ConnectionData*)enet_peer_get_data(peer); - if (v) delete v; - enet_peer_set_data(peer, nullptr); - }) - ; - n_clients = 0; - - if (host) enet_host_destroy(host); - host = nullptr; - } - - /** @warning do not use inside event handers! Use defer_destroy=true; instead! - * Peacefully disconnects clients once, max 5s delay, 8 times more attempts. - * Clients that was not disconnected peacefully will be destroyed without h_desconnect event triggered! - */ - void destroy(); - - /** @warning this does not affect already running Host! You should do destroy() and create()... maybe... */ - void set_address(ProtocolInfo i) { info = i; } +class ENetServer : public ENetBase { public: - ENetBase() {} - ENetBase(const ENetBase&) = delete; - ENetBase(ENetBase&&) = default; - ENetBase& operator=(const ENetBase&) = delete; - ENetBase& operator=(ENetBase&&) = default; - ENetBase(ProtocolInfo i) : info(i) {} - - /** use this oeprator in order to check if server is terminated! - * You may try to reconnect after that or do delete class instance. - */ - operator bool() const { return !!host; } - - /** - * Same as operator bool(). + /** @property server will continue servicing (return true from service()) even when no active connections left + * you should not set this property before create()! because server will never listen to connections in that case */ - bool running() { return !!host; } - - /** does deletes ENet hosts in a way how force_destroy() does, if it wasn't done already. */ - virtual ~ENetBase() { force_destroy(); } + bool keep_working = true; - /** flushes all packages to be finally sended! No need to do if you regulary call a service()! */ - void flush() { - if (host) enet_host_flush(host); - } - - /** all work is done internally, but you may optional get event, but cna't use any data! - * @param timeout in illiseconds + /** @property server will not accept any connections anymore, and serve only already connected clients */ - bool service(ENetEvent* ev = nullptr, uint32_t timeout = 0) { - ENetEvent _dummy; - if (!ev) ev = &_dummy; - - /** runned in case destruction is deferred. that's right :p */ - if (defer_destroy) destroy(); - if (!host) { - *ev = ENetEvent{ENET_EVENT_TYPE_NONE, 0, 0, 0, 0}; - return false; - } - - bool stat = enet_host_service(host, ev, timeout) > 0; - on_event_recv(*ev); - return stat; - } -}; - -class ENetClient : public Default, public ENetBase { - ENetPeer* server = nullptr; + bool prevent_connection = false; public: - ENetClient() { - h_disconnect.subscribe([this](ENetBase&, ENetConnection& conn) { - defer_destroy = true; // shutdown server - server = nullptr; - std::cerr << "Client: Server is disconnected!" << std::endl; - }); - }; - - ENetClient(ProtocolInfo info) : ENetClient() { set_address(info); } - - ~ENetClient() { - server = nullptr; - // all the other stuff is done in the base class! - } + ENetServer(ENetHandlerMaker f) : ENetBase(f) {}; - /** create AND connect a client to the specified server! */ - bool connect(const char* ip, unsigned short port = DEFAULT_PORT); + /** services server internally. Returns true if service() should be called again. False if you should shutdown()! */ + bool service(int timeout = 0); - /** function for a user :) */ - void disconnect() { - if (server) { - enet_peer_reset(server); - server = nullptr; - } - destroy(); // destroy connection, all user data, etc. - } - - /* same as running() + extra check */ - bool is_connected() { return running() && server != nullptr; } - - /** @warning channel starts FROM ZERO!!!! */ - bool send(uint8_t channel, ENetPacket* data) { - if (!server || !host) return false; - return enet_peer_send(server, channel, data); - } - - /** @warning channel starts FROM ZERO!!!! */ - bool send(uint8_t channel, const std::string_view data) { - if (!server || !host) return false; - ENetPacket* p = enet_packet_create(data.data(), data.size(), 0); - if (!p) return false; - return send(channel, p); - } -}; - -class ENetServer : public Default, public ENetBase { - public: - bool shutdown_on_leaving = false; - - public: - ENetServer(ProtocolInfo info) { - set_address(info); - h_disconnect.subscribe([this](ENetBase&, ENetConnection& conn) { - if (shutdown_on_leaving && host->peerCount <= 1) defer_destroy = true; // shutdown server - std::cerr << "Server: client is disconnected!" << std::endl; - }); - } - - bool create() { - ENetBase::create_server(); - return !!host; - } + bool create(const char* ip, unsigned short port = DEFAULT_PORT); void shutdown() { ENetBase::destroy(); // destroy connection, all user data, etc, and fire all callbacks @@ -574,19 +366,12 @@ class ENetServer : public Default, public ENetBase { ENetBase::force_destroy(); // immediate destroy! } - ~ENetServer() { destroy(); } - - /** all work is done internally, but you may optional get event, but cna't use any data! */ - bool service(ENetEvent* ev = nullptr, int timeout = 1) { - ENetEvent _dummy; - if (!ev) ev = &_dummy; - bool stat = enet_host_service(host, ev, timeout) > 0; - on_event_recv(*ev); - return stat; - } + ~ENetServer() {} // all work is done in the base class! void broadcast(uint8_t channel, ENetPacket* data) { - if (host) enet_host_broadcast(host, channel, data); + foreach([channel, data](ENetConnection& conn) { + conn.send(channel, data); + }); } void broadcast(uint8_t channel, const std::string_view v) { @@ -596,10 +381,16 @@ class ENetServer : public Default, public ENetBase { broadcast(channel, p); } - void foreach (std::function cb) { - ENetBase::foreach ([cb](ENetPeer* p) { cb(ENetConnection(p)); }); - } }; +}; // namespace pb + +#if 0 +/** + * @description ENet Wrapper + */ +namespace pb_old { + + }; // namespace pb_old #endif diff --git a/engine/readme.md b/engine/readme.md deleted file mode 100644 index d0a8bd8..0000000 --- a/engine/readme.md +++ /dev/null @@ -1,15 +0,0 @@ -# engine -here are all basic game types and systems are placed + all unique stuff to make it work. -Rendering is placed separately, because there should be no rendering on the server :) . - -## enet wrapper -todo - -## luau -todo in far future - -## database -basic sqlite wrapper with some useful functions - -## vfs -not implemented properly still. TODO: rename to asset manager \ No newline at end of file diff --git a/engine/tests/meta.cpp b/engine/tests/meta.cpp new file mode 100644 index 0000000..9ea46d2 --- /dev/null +++ b/engine/tests/meta.cpp @@ -0,0 +1,137 @@ +/* + * This file is a part of Pixelbox - Infinite 2D sandbox game + * Copyright (C) 2023-2024 UtoECat + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * + */ + +#include "base/doctest.h" +#include "engine/meta.hpp" +#include "base/tests/utils.hpp" +#include "string.hpp" + +TEST_CASE("metadata stability") { + pb::Metadata m; + REQUIRE(m.size() == 0); + m.set(1, 1); + REQUIRE(m.size() == 1); + REQUIRE(m.has_value(1) == true); +} + +#include + +TEST_CASE("metadata values") { + pb::Metadata m; + std::map map = { + {"a", "b"}, + {"c", "d"}, + {"sus", "so much"} + }; + + for (auto &[k, v] : map) { + m.set(pb::HString(k), v); + } + + REQUIRE(m.size() == map.size()); + + for (auto &[k, v] : map) { + REQUIRE(m.has_value(pb::HString(k)) == true); + REQUIRE(m.is_string(pb::HString(k))); + REQUIRE(m.get_string(pb::HString(k)) == v); + } + + pb::Metadata m2 = m.copy(); + + for (auto &[k, v] : map) { + REQUIRE(m2.has_value(pb::HString(k)) == true); + REQUIRE(m2.is_string(pb::HString(k))); + REQUIRE(m2.get_string(pb::HString(k)) == v); + } + +} + +TEST_CASE("metadata Value cast") { + pb::Metadata m; + m.set("1", "1"); + REQUIRE(m.has_value(1) == true); + REQUIRE(m.is_number(1) == true); + std::string res = m.get_string(1); + REQUIRE(pb::strtod(res.c_str(), nullptr) == 1); +} + +TEST_CASE("metadata unvalid values") { + pb::Metadata m; + std::map map = { + {"", "a"}, + {"a", ""} + }; + + for (auto &[k, v] : map) { + m.set(pb::HString(k), v); + } + + REQUIRE(m.size() == 0); + + m.set(0, 0); + REQUIRE(m.size() > 0); + + for (auto &[k, v] : map) { + REQUIRE(m.has_value(pb::HString(k)) == false); + REQUIRE(m.is_string(pb::HString(k)) == false); + REQUIRE(m.get_string(pb::HString(k)) == std::string()); + } + + std::string v = m.get_string(0); + REQUIRE(pb::strtod(v.c_str(), nullptr) == 0); + +} + +#include +#include + +static void fin(pb::Metadata &a, pb::Metadata &b) { + std::stringstream strm; + a.serialize(strm); + + std::string old = strm.str(); + std::cout << "res : " << old << std::endl; + REQUIRE(b.deserialize(strm) == true); + + strm.str(std::string()); strm.clear(); + b.serialize(strm); + std::string curr = strm.str(); + std::cout << "r2 : " << curr << std::endl; + + REQUIRE(old == curr); + + a.clear(); b.clear(); +} + +TEST_CASE("metadata serialization") { + pb::Metadata a, b; + + fin(a, b); + a.set("ab", "ob"); + + fin(a, b); + + std::string v = "a"; + for (int i = 'a'; i < 'm'; i++) { + v[0] = i; + a.set(i - 'a', v); + } + //BRK(); + fin(a, b); +} diff --git a/external/raiisqlite.cpp b/external/raiisqlite.cpp new file mode 100644 index 0000000..b063a9c --- /dev/null +++ b/external/raiisqlite.cpp @@ -0,0 +1,348 @@ +/* + * Copyright (C) UtoECat 2024 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "raiisqlite.hpp" +#include +#include +#include +#include "sqlite3.h" + +// implementation +namespace sqlite { + +DatabaseError Statement::compile(sqlite3 *db, Text src, const char **leftover, int flags) noexcept { + release(); + const char *left = src.src; + int e = SQLITE_EMPTY; // empty statement code + + while (left < src.src + src.size) { + e = sqlite3_prepare_v3(db, src.src, src.size, flags, &stmt, &left); + + if (e != SQLITE_OK) { // if error - exit now + break; + } + + // else skip whitespaces (SQLITE_OK and no statement) + // for OK case it is IMPORTANT to return proper error code! + // (we will return SQLITE_EMPTY if not skip spaces, and that's + // invalid behaviour) + while (left < src.src + src.size && isspace(left[0])) left++; + + // recalculate current pos and size + src.size = (src.src + src.size) - left; + src.src = left; + + // if no error (SQLITE_OK) => break after skip + if (stmt) { + break; + } + + // no data available here + e = SQLITE_EMPTY; + } + + // write position in code + if (leftover) *leftover = left; + return e; +} + +std::string Statement::get_compile_error(sqlite3* db, Text src, const char * const* leftover) noexcept { + const char* limit = leftover ? *leftover : src.src; + if (!src.src || !leftover) + return std::string("get_comile_error misuse : no source code or valid leftover"); // ERROR + if (limit < src.src || limit > src.src + src.size) + return std::string("get_comile_error misuse : size overflow"); // ERROR + + int line = 1; // current line + int pos = 1; // position in the line + + // calculate position in source code + const char* v = src.src; + const char* prev_begin = src.src; + for (; v < limit; v++) { + pos++; + if (*v == '\n') { + line++; + prev_begin = v + 1; + pos = 1; + } + } + + try { + std::stringstream out; + + out << sqlite3_errstr(sqlite3_errcode(db)) << " : " << sqlite3_errmsg(db) << std::endl; + + size_t spacing = out.tellp(); + + out << "" << line << ":" << pos << ": "; + + spacing = (size_t)out.tellp() - spacing; + + // problematic line source preview + const char* preview = prev_begin; + int prev_count = 0; + bool was_space = 0; + while (preview < src.src + src.size) { + if (*preview == '\n') { + prev_count++; + if (prev_count >= 2) break; + } + + bool is_space = isspace(preview[0]); + if (preview[0] != '\n' && is_space && !was_space) { + out << ' '; + was_space = 1; + } else if (!is_space) { + out << preview[0]; + was_space = 0; + } else { + out << "\n"; + for (size_t i = 0; i < spacing - 2; i++) out << ' '; + out << ':'; + } + + preview++; + } + + return out.str(); + } catch (std::exception& e) { // Out of memory? + return std::string("nomem"); + }; +} + +DatabaseError Statement::iterate() noexcept { + // original sqlite_step() returns SQLITE_OK... and it's counterintuitive + if (!stmt) return SQLITE_EMPTY; + int rc = sqlite3_step(stmt); + + if (rc == SQLITE_ROW) { + return rc; + } + + // if (rc != SQLITE_ROW) => reset NOW + int rc2 = sqlite3_reset(stmt); + if (rc == SQLITE_OK && rc2 != SQLITE_OK) rc = rc2; // more error info + return rc; // error or done +} + +Text::Text(const char *s, bool nocopy) : src(s), size(strlen(s)), is_static(nocopy) {} + +DatabaseError Statement::execute(ColumnCallback cb) noexcept { + if (!stmt) return SQLITE_EMPTY; + DatabaseError rc; + + if (cb) { + try { + while ((rc = iterate()) == SQLITE_ROW) { + cb(result()); // execute callback + } + } catch (...) { // callback aborted + reset(); + rc = SQLITE_ABORT; // don't retrow! + } + } else { + while ((rc = iterate()) == SQLITE_ROW) {} + } + + return rc; // should be moved... and not trigger unchecked error... +} + +DatabaseError Backup::execute_until_busy(int n_pages) { + DatabaseError err; + do { + err = iterate(n_pages); + sleep(); + } while (err == SQLITE_OK); + + if (err != SQLITE_DONE && err != SQLITE_BUSY && err != SQLITE_LOCKED) { + throw DatabaseException(sqlite3_errstr(err.get())); + } + return err; +} + +void Backup::execute_sync(int n_pages) { + DatabaseError err; + + do { + err = execute_until_busy(n_pages); + } while (err == SQLITE_OK || err == SQLITE_BUSY); + + if (err != SQLITE_DONE) throw DatabaseException(sqlite3_errstr(err.get())); +} + +void Backup::execute_now() { + struct anon { + Backup* me; + int old; + anon(Backup* a, int b) : me(a), old(b) {} + ~anon() { + me->sleep_amount = old; + } + } _(this, sleep_amount); + + sleep_amount = 0; // no delays + execute_sync(100); // let's go +}; + +DatabaseError Backup::iterate(int n_pages) noexcept { + if (!ctx) return SQLITE_EMPTY; // error? + int err; + + err = sqlite3_backup_step(ctx, n_pages); + if (err == SQLITE_OK || err == SQLITE_BUSY || err == SQLITE_LOCKED) { + return err; // should continue... + } + + // not recoverable OR done + destroy(); + return err; +} + +void Database::close() noexcept { + if (db) sqlite3_close_v2(db); + db = nullptr; +} + +DatabaseError Database::raw_open(const char *url, int flags) noexcept{ + close(); + int e = sqlite3_open_v2(url, &db, flags, nullptr); + return e; +} + +std::string Statement::expanded_sql() { + std::string v; + const char* s = sqlite3_expanded_sql(stmt); + try { + if (s) { + v = s; + } + } catch (...) {}; + sqlite3_free((void*)s); + return v; +} + +namespace impl { + +DatabaseError StaticQueryStorage::compile(Text sql_code) noexcept { + statements.clear(); + index = 0; + + Statement stmt; + const char *leftover = nullptr; + DatabaseError err; + + errmsg.clear(); + while ((err = stmt.compile(database, sql_code, &leftover, SQLITE_PREPARE_PERSISTENT)) == SQLITE_OK) { + // psuh statement + statements.push_back(std::move(stmt)); + + // move current text pointer + sql_code.size = sql_code.src + sql_code.size - leftover; + sql_code.src = leftover; + }; + + if (err != SQLITE_EMPTY) { // means error + errmsg = stmt.get_compile_error(database, sql_code, &leftover); + statements.clear(); + rewind(); + return err; + } + + // good ending :З + return SQLITE_OK; +} + +DatabaseError InterpretQueryStorage::rewind() noexcept { + cpos = sql.begin(); + stmt.release(); + + DatabaseError err = stmt.compile(database, curr(), &cpos); + + // save error message + if (err != SQLITE_OK) { + make_errmsg(); + cpos = sql.begin(); // rewind code position + }; + + return err; // PASS SQLITE_EMPTY! Since empty SQL is an error from the start for us! + } + + DatabaseError InterpretQueryStorage::next() noexcept { + if (!stmt) return SQLITE_MISUSE; + + DatabaseError err = stmt.compile(database, curr(), &cpos); + + // save error message + if (err != SQLITE_OK && err != SQLITE_EMPTY) { + make_errmsg(); + return err; + }; + + return err == SQLITE_EMPTY ? SQLITE_DONE : SQLITE_OK; // turn EMPTY to DONE! + } + +}; // namespace impl + +static void db_open_check(DatabaseError e) { + std::string errmsg = "Can't open database : "; + errmsg += sqlite3_errstr(e.get()); + errmsg += "!"; + + if (e != SQLITE_OK) throw DatabaseException(errmsg, e.get()); +} + +Database connect(const char* url, bool readonly, bool ignore_not_exists) { + Database db; + int flags = 0; + flags |= readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE; + if (!readonly && ignore_not_exists) + flags |= SQLITE_OPEN_CREATE; + + db_open_check(db.raw_open(url, flags)); + return db; +} + +Database create_memory(const char* url) { + Database db; + int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_MEMORY; + db_open_check(db.raw_open(url, flags)); + return db; +} + +Database connect_uri(const char* url) { + Database db; + // last one are required! + db_open_check(db.raw_open(url, SQLITE_OPEN_URI | SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE)); + return db; +} + +Database& Database::operator=(Database && src) { + if (this == &src) return *this; + if (db) close(); // important! + db = src.db; + src.db = nullptr; + return *this; +} + +}; // namespace sqlite \ No newline at end of file diff --git a/external/raiisqlite.hpp b/external/raiisqlite.hpp new file mode 100644 index 0000000..aefba6e --- /dev/null +++ b/external/raiisqlite.hpp @@ -0,0 +1,983 @@ +/* + * Copyright (C) UtoECat 2024 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#pragma once +#include +#include +#include +#include +#include +#include +#include "sqlite3.h" + +/// predefinition +namespace sqlite { + +// uniform friend for unittesting! +class Testing; + +/** Sqlite value wrappers to make advanced bind() possible + * @warning string and blob values threated as non-static by default + * and that's why they will be copied internally by Sqlite. + * for specific cases, you may want to override this, + * by passing true in text/blob constructor, but be careful! + * Texts and Blobs with is_static==true MUST NOT be changed or freed + * during a whole ass lifetime of the Query AND associated Statement + * + * @warning Text and Blob returned from column() are ALWAYS static! + * this means that you MUST copy them or move value from them to use + * later, and do not call iterate()/reset()/restart())/set_source() and + * any fricking thing that will mutate statement, that keeps theese + * values, because they may be freed and you will get USE AFTER FREE = UB! + */ +struct Text; +struct Blob; + +/** +* @brief wrapper around sqlite3_stmt. No copyable, movable only. +* statement will be destroyed when object will destroyed (if any) +* you can recreate statement with new source (and new internal object) +* as well as release() statement early +* +* @warning you MUST NOT release statement if it will be used after destruction +* of this wrapper! (that's probably bad design of your program). +* there is no way to get ownership of created statement! ...to prevent bad design +* Hovewer, you still can std::move() this object somewhere... If it's really important. +*/ +class Statement; + +/** + * @brief represents result of the Query/Statement step + * + * @warning when query is mutated by it's methods (release()/iterate()/etc...) + * or destructed, you cannot use this object anymore. + * It is recommended to never store this object for long time anywhere. + * + * This object will likely to become invalid after callback function returns + * or throws! (because the Statement may be released!) + */ +class QueryResult; + +/** + * @brief Array of compiled statements. + * They are all precompiled early. + * Pros : + * - Very fast, if Program is reused and thus it's not recompiled from scratch every single time. + * - No need to create copy of entire SQL source string (it is done internally by sqlite and only for important parts) + * Cons : + * - May take more memory than statement-by-statement compile()/execute(). + * - may be pointless if you don't give a fuck about reusing existent program and recompile it over and over again. + * follows rules and warnings of the Query class. See below. + * + * @warning do NOT add and use after CREATE TABLE/VIEW/TRIGGER statements here! + * because compilation comes before the evaluation, statements after CREATE will NOT + * find required table and fail to compile => fail to compile an entire program. + * Good rule is to place all CREATE/PRAGMA and other statements that modifies SCHEMA + * in the Query right after opening the database! + */ +class Program; + +/** + * @brief Query is an SQL statements interpreter. + * it just works, but may be not the bset option. + * statements are compiled one by one, like in sqlite_exec(). + * Prons : + * - Less memory overhead. + * Cons : + * - High overhead on recompilation if called in hot code paths... + * - More errors may happen IN the execution process (generally - + * syntax errors), and it may leave some thansaction incompleted! + * + * @warning it does not attempts to do any rollback/commit, because it's your + * responcibility. Program did absolutely similar thing (no commit/rollback), + * but it may fail only on very strong errors or SQLITE_BUSY.... + * + * Also, both Program and Query do rewind() back to the first statement on errors. + * This is intencional. You SHOULD not ignore such errors, and most of simple + * errors like "table already exist" may be prevented directly in SQL code. + * Attempt to "continue no matter what" can lead to hardly-detectable bugs in your code. + * That's why this option is NOT awailable. + */ +class Query; + +/** +* @brief RAII Database wrapper. +* +* @warning You must destroy all objects that uses it before destructing/closing database. +* This includes Statement, Query, Program and Backup! +*/ +class Database; + +/** Optional error, returned by most of the functions. */ +class DatabaseError; + +/** Generic exception, related to raiisqlite module */ +class DatabaseException; + +/** + * @brief Step-by-step database backup routine + * @warning both databases, passed in constructor, must exists and not change + * while Backup routine is not finished. + * + * Not changed means not reopened and not closed. You may still use + * source database in breaks between iterate() calls... (but not dest) + */ +class Backup; + +using Byte = uint8_t; + +/** callback to retrieve data on SQLITE_ROW */ +using ColumnCallback = std::function; + +}; // namespace sqlite + +/// definition +namespace sqlite { + +/** If enabled (default) any unhandled(unchecked) Database + * Error will print to stderr about being unhandled! + */ +static constexpr bool REPORT_UNHANDLED_ERRORS = true; + +class DatabaseError { + friend class Testing; + int errcode = 0; + bool is_error : 1; + bool is_checked : 1; + public: + inline DatabaseError() {is_error = 0; is_checked = 0;} + inline DatabaseError(int sqlerr) { + set_error(sqlerr); + } + public: + + // construction reqires recheck + inline DatabaseError(DatabaseError&& src) { + *this = std::move(src); + is_checked = 0; + } + + /** set error */ + inline void set_error(int sqlerr) { + errcode = sqlerr; + is_checked = 0; + int is_ok = 0; + is_ok |= (sqlerr == SQLITE_OK); + is_ok |= (sqlerr == SQLITE_DONE); + is_ok |= (sqlerr == SQLITE_ROW); + is_error = !is_ok; + } + + /** copy error and errstatus, and set is_checked on source */ + DatabaseError& operator=(DatabaseError&& src) { + errcode = src.errcode; + is_checked = src.is_checked; + src.is_checked = 1; + is_error = src.is_error; + return *this; + } + + bool check() {is_checked = 1; return !is_error;} // error is checked + int get() {is_checked = 1; return errcode;} // error is kinda checked? + void supress() {is_checked = 1;} // threat as we don't care about this error at all + + // to prevent misuse. use get() method for that! + operator int() = delete; + + // convinient operators + inline bool operator==(int sqlerr) { + is_checked = 1; + return errcode == sqlerr; + } + inline bool operator!=(int sqlerr) {return !(*this == sqlerr);} + + inline ~DatabaseError() { + if constexpr (REPORT_UNHANDLED_ERRORS) { + if (is_error && !is_checked) + fprintf(stderr, "unhandled Database Error : %s, %i\n", sqlite3_errstr(errcode), errcode); + } + } + +}; + +class DatabaseException : public std::runtime_error { + public: + friend class Testing; + using re = std::runtime_error; + using re::re; + int errcode = 0; + DatabaseException(const char *msg, int err) : std::runtime_error(msg), errcode(err) {} + DatabaseException(std::string msg, int err) : std::runtime_error(msg), errcode(err) {} +}; + +struct Text { + const char *src = nullptr; + size_t size = 0; + bool is_static = false; + + Text(const char *s, size_t l, bool nocopy = false) : src(s), size(l), is_static(nocopy) {} + Text(const std::string_view s, bool nocopy = false) : src(s.data()), size(s.size()), is_static(nocopy) {} + Text(const std::string &s, bool nocopy = false) : src(s.data()), size(s.size()), is_static(nocopy) {} + + /** @warning uses strlen() to compute string length! */ + Text(const char *s, bool nocopy = false); + + Text(const Text &src) = default; + + operator std::string() { return {src, size}; } + operator std::string_view() { return {src, size}; } +}; + +struct Blob : public Text { + Blob(const void *s, size_t l, bool nocopy = false) : Text((const char *)s, l, nocopy) {} + Blob(const std::vector &s, bool nocopy = false) : Text((const char *)s.data(), s.size(), nocopy) {} + operator std::vector() { return {src, src + size}; } +}; + +/** implementation specific stuff. Do not use directly! */ +namespace impl { + +/** raw bind value to statement */ +inline bool sqlite_unbind(sqlite3_stmt *stmt, int idx) { return sqlite3_bind_null(stmt, idx); } + +/// use macros to keep my sanity healthy +#define _TMP_MAGIC_SHIT(TYPE, SIGNA, ...) \ + inline bool sqlite_bind(sqlite3_stmt *stmt, int idx, [[maybe_unused]] TYPE v) { return sqlite3_bind_##SIGNA(stmt, idx, ##__VA_ARGS__); } + +_TMP_MAGIC_SHIT(std::nullptr_t, null); +_TMP_MAGIC_SHIT(int, int, v); +_TMP_MAGIC_SHIT(unsigned int, int, v); +_TMP_MAGIC_SHIT(int64_t, int64, v); +_TMP_MAGIC_SHIT(uint64_t, int64, v); +_TMP_MAGIC_SHIT(double, double, v); +_TMP_MAGIC_SHIT(float, double, v); + +_TMP_MAGIC_SHIT(Text, text, v.src, v.size, v.is_static ? SQLITE_STATIC : SQLITE_TRANSIENT); +_TMP_MAGIC_SHIT(Blob, blob, v.src, v.size, v.is_static ? SQLITE_STATIC : SQLITE_TRANSIENT); + +#undef _TMP_MAGIC_SHIT + +/** raw column */ +template +inline T sqlite_column(sqlite3_stmt *, int) { + static_assert("not implemented!"); +} + +template <> +inline std::nullptr_t sqlite_column(sqlite3_stmt *, int) { + return nullptr; +} + +template <> +inline void sqlite_column(sqlite3_stmt *, int) {} + +#define _TMP_COLUMN_SHIT(TYPE, SIGNA) \ + template <> \ + inline TYPE sqlite_column(sqlite3_stmt * stmt, int idx) { \ + return sqlite3_column_##SIGNA(stmt, idx); \ + } + +_TMP_COLUMN_SHIT(int, int); +_TMP_COLUMN_SHIT(unsigned int, int); +_TMP_COLUMN_SHIT(int64_t, int64); +_TMP_COLUMN_SHIT(uint64_t, int64); +_TMP_COLUMN_SHIT(double, double); +_TMP_COLUMN_SHIT(float, double); + +#undef _TMP_COLUMN_SHIT + +template <> +inline Text sqlite_column(sqlite3_stmt *stmt, int idx) { + return {(char *)sqlite3_column_text(stmt, idx), (size_t)sqlite3_column_bytes(stmt, idx)}; +} + +template <> +inline Blob sqlite_column(sqlite3_stmt *stmt, int idx) { + return Blob(sqlite3_column_blob(stmt, idx), (size_t)sqlite3_column_bytes(stmt, idx)); +} + +/** general cases */ +template <> +inline std::string_view sqlite_column(sqlite3_stmt *stmt, int idx) { + return sqlite_column(stmt, idx); +} +template <> +inline std::string sqlite_column(sqlite3_stmt *stmt, int idx) { + return sqlite_column(stmt, idx); +} +template <> +inline std::vector sqlite_column>(sqlite3_stmt *stmt, int idx) { + return sqlite_column(stmt, idx); +} + +}; // namespace impl + +class QueryResult { + friend class Testing; + protected: + friend class Statement; + sqlite3_stmt *stmt; + QueryResult(sqlite3_stmt *v) : stmt(v) { + if (v == nullptr) throw DatabaseException("nullptr statement!", 0); + } + + public: + QueryResult(const QueryResult &) = default; + QueryResult(QueryResult &&) = default; + QueryResult &operator=(const QueryResult &) = delete; // error-prone + QueryResult &operator=(QueryResult &&) = default; + + /** return count of avaliable data. Max valid index is size()-1 */ + size_t count() const { return sqlite3_data_count(stmt); } + + /** return length of the string or blob at this index */ + size_t length(int index) { return sqlite3_column_bytes(stmt, index); } + + /** recieve data at specified index as specified type. Sqlite may do some convertions + * under the hood! */ + template + T get(int index) { + return impl::sqlite_column(stmt, index); + } + + /** return SQLITE_XXXX type */ + int type(int index) { return sqlite3_column_type(stmt, index); } +}; + +class Statement { + private: + friend class Testing; + sqlite3_stmt *stmt = nullptr; + + public: + Statement() = default; + Statement(const Statement &) = delete; + Statement &operator=(const Statement &) = delete; + inline Statement(Statement &&src) { *this = std::move(src); } + inline Statement &operator=(Statement &&src) { + if (this == &src) return *this; + stmt = src.stmt; + src.stmt = nullptr; + return *this; + }; + + /** may be in invalid(released) state! check if statement actualy exists! */ + operator bool() const noexcept { return !!stmt; } + + /** releases statement early */ + inline void release() noexcept { + if (stmt) sqlite3_finalize(stmt); + stmt = nullptr; + } + + /** creates statement from specified SQL code. Will set leftover pointer to the rest of not used sql code. + * also accepts SQLITE_PREPARE_* flags + * it will ignore empty spaces in SQL code, however, will return + * @return SQLITE_OK or error code (SQLITE_EMPTY for no code). + * @warning this operation ALWAYS destroys previous statement (and invalidates + * old statement pointer!) + * + * you may get compilation error using get_compile_error() method, HOWEVER, it have some limitations. + * see @ref get_compile_error() docs for more info. + */ + DatabaseError compile(sqlite3 *db, Text src, const char **leftover = nullptr, int flags = 0) noexcept; + + /** Executes statement. + * returns SQLITE_DONE => execution is complete successfully, SQLITE_ROW => we have result data to process, any other value => error. + */ + DatabaseError iterate() noexcept; + + /** get results from statetment. + * You can only call this when iterate() returns SQLITE_ROW! + * @warning see warnings in QueryResult class documentartion! + */ + inline QueryResult result() const { + if (!stmt) throw DatabaseException("cannot retrieve data from an empty statement"); + return QueryResult(stmt); + } + + /** resets statement executon position. + * you may call iterate() again after that :) + * but you shouldn't call this AFTER iterate()! + * internally iterate always resets statement when it + * finishes ar returns ERROR! + */ + inline void reset() noexcept { + if (stmt) sqlite3_reset(stmt); + } + + /** just do it, again (c) + * does exact the same as ``` + * DatabaseError err; + * + * while ((err = stmt.iterate()) == SQLITE_ROW) { + * cb(stmt.result()); + * } + * return err; + * ``` with an additional reset() and break on exception + * + * you may break execution by throwing std::exception. + * but be careful, your exception will not be rethrown! + */ + DatabaseError execute(ColumnCallback cb = nullptr) noexcept; + + /** bind arguments... some black magic, ya? */ + template + void bind(Args &&...args) noexcept { + if (!stmt) return; + const int lim = sqlite3_bind_parameter_count(stmt); + + // return early + if (lim < 1) return; + + /** bind values */ + int i = 1; // start from 1 + + ( + [&] { + if (i <= lim) impl::sqlite_bind(stmt, i, args); + ++i; + }(), + ...); + } + + /** unbind all parameters */ + void unbind() {sqlite3_clear_bindings(stmt);} + + bool is_busy() {return sqlite3_stmt_busy(stmt);} + + /** for debugging purposes. */ + std::string expanded_sql(); + + /** pretty error ouput + * @warning SQLITE keeps compilation error message instde database handle. + * this means that you will not get valid error message if you will trigger + * any other error after compile error and between call to this function! + * + * db is a database, passed in compile() routine. + * Text should be original SQL code, passed to compile() routine, and + * leftover should be pointer, untouched by you, that was passed to compile() too. + * + * returns empty string on error. Or empty error message. + */ + std::string get_compile_error(sqlite3* db, Text src, const char * const* leftover) noexcept; + + operator sqlite3_stmt *() noexcept { return stmt; } + + ~Statement() { release(); } +}; + +class Backup { + friend class Testing; + protected: + sqlite3_backup *ctx = nullptr; + int sleep_amount = 100; + public: + Backup(const Backup &) = delete; + Backup(Backup &&) = default; + Backup &operator=(const Backup &) = delete; + Backup &operator=(Backup &&) = default; + Backup() = default; + + template + inline Backup(sqlite3 *src, sqlite3 *dest, Args&&... args) { + if (!src || !dest) throw DatabaseException("database is nullptr!"); + if (!start(src, dest, std::forward(args)...).check()) { + throw DatabaseException(sqlite3_errmsg(dest)); + } + } + + operator bool() { + return !!ctx; + } + + /** initializes backup operation + * returns errors, not throws. + * you can get error message from dest database! + * + * set delay argument to zero to never sleep in + * execute() routines! + * this may be important in cases you know that + * databases will never be locked/busy, or busy for long + */ + DatabaseError start(sqlite3 *src, sqlite3 *dest, int delay=25, const char* schema_name = "main") { + if (!src || !dest) throw DatabaseException("database is nullptr!"); + if (!schema_name) schema_name = "main"; + ctx = sqlite3_backup_init(dest, schema_name, src, schema_name); + if (!ctx) { + return DatabaseError(sqlite3_errcode(dest)); + } + sleep_amount = delay > 0 ? delay : 0; + return DatabaseError(); // OK + } + + int remaining() const noexcept { return ctx ? sqlite3_backup_remaining(ctx) : 0; } + int length() const noexcept { return ctx? sqlite3_backup_pagecount(ctx) : 0; } + int position() const noexcept { return length() - remaining(); } + + // wait to allow db to work a bit in background. + void sleep() noexcept { + if (sleep_amount > 0) + sqlite3_sleep(sleep_amount); + } + + /** may return error */ + DatabaseError destroy() { + if (!ctx) return SQLITE_EMPTY; + + /* Release resources allocated by backup_init(). */ + int err = sqlite3_backup_finish(ctx); + ctx = nullptr; + return err; + } + + /** iterator. returns SQLITE_*** codes. + * you should explicitly handle SQLITE_BUSY and SQLITE_LOCKED errors + * SQLITE_DONE and SQLITE_OK notifies about SUCCESS! + * does not throw exceptions + */ + DatabaseError iterate(int n_pages = 10) noexcept; + + /** differennt backup executors. + * ths function will return err if SQLITE_BUSY/SQLITE_LOCKED + * error is recieved, to give application opportunity + * to make a desicion. Sleeps between steps. + * throws on other errors + * returns SQLITE_DONE on success; + * + * @example ``` + * DatabaseError err; + * while ((err = backup.execute_until_busy()) != SQLITE_DONE) { + * // do some stuff while we are busy! + * } + * ``` + */ + DatabaseError execute_until_busy(int n_pages = 10); + + /** differennt backup executors. + * this one assumes that src database will never + * be busy for too long (or busy at all). + * throws on errors and SQLITE_LOCKED! + */ + void execute_sync(int n_pages = 10); + + /** different backup executors. + * executes backup procedure without delays and NOW! + * assumes that src database will never be busy for too long (or busy at all). + * Throws on errors. + */ + void execute_now(); + + /// rolls back everything if backup was not finished! + ~Backup() { destroy().supress(); } +}; + +namespace impl { + +/** @interface description of QueryStorage Base class + * + * constructor() - takes database as argument. + * get() - returns current statement, if available. + * should return statement after rewind() or compile()! + * Return nullptr at end or error! + * release() - self explainotary... + * database field MUST be acessable. + * + * get_error() - returns std::string_view with an error message! + * may return empty get_error(). + * + * All functions below return SQLITE_OK on successs and errors in case of error, all noexcept! + * compile() - compile or save for later sql code + compile at least 1 statement. + * rewind() - rewinds back to the first statement + reset current executed. + * next() - goto next statement. return SQLITE_DONE on END or return error if error. + * Old statement may be invalidated after this. + */ + +/** there is only one statement built at the time. SQL code is copied! + * does not check for errors on compile, but at execution time. + */ +class InterpretQueryStorage { + friend class sqlite::Testing; + protected: + friend class sqlite::Testing; + Statement stmt; + std::string tmpbuff; // tmpbuffer for sql + std::string errmsg; // error message + + std::string_view sql; // on std::string SQL + const char *cpos = nullptr; // position in code + + Text curr() { return Text(cpos, (size_t)(sql.end() - cpos)); } + + public: + sqlite3 *database = nullptr; + + InterpretQueryStorage(InterpretQueryStorage &&) = default; + InterpretQueryStorage(sqlite3 *db) : database(db) { + if (!db) throw nullptr; + } + + public: + // called in iterate() and here to build an error message + void make_errmsg() { + errmsg = stmt.get_compile_error(database, sql, &cpos); + } + + DatabaseError rewind() noexcept; + + DatabaseError compile(Text sql_code) noexcept { + tmpbuff = sql_code; // make a copy of it! + sql = tmpbuff; + return rewind(); + } + + DatabaseError next() noexcept; + + void release() noexcept { + stmt.release(); + errmsg.clear(); + sql = std::string_view(); + } + + Statement *get() noexcept { return stmt ? &stmt : nullptr; } + + std::string get_error() {return errmsg;} +}; + +/** Static query storage. Precompiles whole SQL source into the vector of statements. + * has no beautiful error messages at execution time :) + */ +class StaticQueryStorage { + friend class sqlite::Testing; + public: + // for v.1.1 bind_all()! DO NOT MESS AROUND WITH IT! + std::vector statements; + protected: + int index; + std::string errmsg; + + Statement *curr() { return index < (int)statements.size() ? &statements[index] : nullptr; } + + public: + sqlite3 *database = nullptr; + + StaticQueryStorage(StaticQueryStorage &&) = default; + StaticQueryStorage(sqlite3 *db) : database(db) { + if (!db) throw nullptr; + } + + public: + DatabaseError rewind() noexcept { + if (curr()) { + curr()->reset(); + } + index = 0; + // empty code is an error! + return curr() ? SQLITE_OK : SQLITE_EMPTY; + } + + DatabaseError compile(Text sql_code) noexcept; + + void release() noexcept { + statements.clear(); + index = 0; + errmsg.clear(); + } + + DatabaseError next() noexcept { + if (!curr()) return false; + curr()->reset(); + index++; + return curr() ? SQLITE_OK : SQLITE_DONE; + } + + Statement *get() { return curr(); } + + void make_errmsg() { + errmsg = "#"; errmsg += index; errmsg += " : "; + errmsg += sqlite3_errmsg(database); + } + std::string get_error() {return errmsg;} +}; + +template +class BasicQuery { + friend class sqlite::Testing; + protected: + QueryStorage storage; // implementation is here :p + + /** restart at error - don't override error message! */ + void restart_and_throw(DatabaseError& e) { + std::string msg = storage.get_error(); + if (storage.rewind() != SQLITE_OK) { + msg += "\nFailed to restart() properly! This Query/Statement will be unusable!"; + }; + throw DatabaseException(msg, e.get()); + } + + public: + BasicQuery(sqlite3 *db) : storage(db) {} + + /** release current statement, if any */ + void release() { storage.release(); } + + /** throws on error */ + void set_code(Text src) { + release(); + if (storage.compile(src).get() != SQLITE_OK) // compile error + throw DatabaseException(storage.get_error()); + } + + /** returns SQL code compilation/execution error. */ + std::string get_error() {return storage.get_error();} + + operator bool() { return !!storage.get(); } + + /** bind arguments */ + template + void bind(Args &&...args) { + Statement *stmt = storage.get(); + if (stmt) stmt->bind(std::forward(args)...); + } + + void restart() { + DatabaseError e; + if ((e=storage.rewind()) != SQLITE_OK) { + std::string s = "Unable to restart()! : "; + s += storage.get_error(); + throw DatabaseException(s, e.get()); + } + if (storage.get() == nullptr) // unlikely + throw DatabaseException("empty statement : malfunction"); + } + + /** execute current statement. you should call it again if returns true. throws on + * error. Rewinds back to the start on error! + * @warning unlike execute() it does not restarts execution! + * you will get an execption if you will try to iterate() over completed query/program. + * call restart() to reexecute program again :) + */ + bool iterate(ColumnCallback cb = nullptr) { + Statement *stmt = storage.get(); + if (!stmt) throw DatabaseException("iteration over an empty statement!"); + + DatabaseError rc = stmt->execute(cb); + + // execution error + if (rc != SQLITE_DONE && rc != SQLITE_ABORT) { + storage.make_errmsg(); // record error (storage does not handle execution!) + restart_and_throw(rc); // yep + } + + // next statement + rc = std::move(storage.next()); + if (rc == SQLITE_DONE) return false; // end + + // compilation or switching error + if (rc != SQLITE_OK) { + restart_and_throw(rc); // we should hae an errormesage here! + } + + return true; // should execute next statement + } + + /** just execute it (c) + * calls restart() before iteration :) + */ + template + void execute(ColumnCallback cb = nullptr, Args &&...args) { + bool stat = 0; + restart(); + + do { + Statement *stmt = storage.get(); + if (stmt) stmt->bind(std::forward(args)...); + stat = iterate(cb); + } while (stat > 0); + } +}; + +}; // namespace impl + +class Program: public impl::BasicQuery { + friend class Testing; + public: + using Base = impl::BasicQuery; + using Base::Base; + + /// v 1.1 extension. Ability to bind all parameters for all statements early, + /// without requirement to execute() first! + /// does not resets() program or anything! exclusive to Program class! + template + void bind_all(Args &&...args) { + for (auto& stmt : storage.statements) { + if (stmt) stmt.bind(std::forward(args)...); + } + } + + /// v.1.1 extension. Ability to unbind() all parameters from all statements. + /// follows bind_all() properties! + void unbind_all() { + for (auto& stmt : storage.statements) { + if (stmt) stmt.unbind(); + } + } +}; + +class Query : public impl::BasicQuery { + friend class Testing; + public: + using Base = impl::BasicQuery; + using Base::Base; +}; + +/** opens database at specified path OR creatres new one. + * database is ALWAYS opened in readwrite mode! + * throws on errors. + */ +Database connect_or_create(const char* url); + +/** opens database using URI. + * it allows to pass all arguments directly using one string, extra args to select VFS and so on. + * see https://www.sqlite.org/c3ref/open.html URI filenames section fore more info, warnings and examples + * @warning you REALLY should visit link above, because certain URI settings can lead to database corruption + * + * @example "file:///home/fred/data.db" + * @example "file:data.db?mode=ro&cache=private" + * @example "file:/home/fred/data.db?vfs=unix-dotfile" + */ +Database connect_uri(const char* uri); + +class Database { + friend class Testing; + sqlite3 *db = nullptr; + public: + Database() = default; + Database(const Database &) = delete; + Database &operator=(const Database &) = delete; + + // v.1.1 allow move construction + Database(Database && src) { + db = src.db; src.db = nullptr; + } + + Database &operator=(Database && src); + + Database(const char *url) : Database(connect_or_create(url)) {} + ~Database() { close(); } + + public: + operator bool() const { return !!db; } + /** + * closes database handler + * @warning you must not close/destroy database while it's handler is used somewhere else + * (for example in Statements/Queries/Programs created from it, and objects created from them) + */ + void close() noexcept; + + /** raw database open function + * does not throw exceptions. Used by factories-functions! + * and CAN be used directly! + */ + DatabaseError raw_open(const char *path, int flags) noexcept; + + /// same as sqlite::connect_or_create()! + void open(const char* path) {*this = sqlite::connect_or_create(path);} + + /// same as sqlite::connect_uri()! + void open_uri(const char* uri) {*this = sqlite::connect_uri(uri);} + + /** flushes all cache into disk to write results. */ + void flush() { + if (db) sqlite3_db_cacheflush(db); + } + + /** attempts to free as much memory as possible. Use in case of OOM */ + void shrink_to_fit() { + if (db) sqlite3_db_release_memory(db); + } + + /** check is database readonly. result on closed/not opened database is undefined. */ + bool is_readonly() { return db ? sqlite3_db_readonly(db, "main") : false; } + + /** fire and forget. sqlite3_exec()-like, but with bind() available. throws on error! */ + template + inline void exec(Text sql, Args&& ...args) { + if (!db) throw DatabaseException("database is not opened!"); + Query query(db); + query.set_code(sql); + query.execute(nullptr, std::forward(args)...); + }; + + /** fire and forget. sqlite3_exec()-like, but with bind() available. throws on error! */ + template + inline void query(Text sql, ColumnCallback cb, Args &&...args) { + if (!db) throw DatabaseException("database is not opened!"); + Query query(db); + query.set_code(sql); + query.execute(cb, std::forward(args)...); + }; + + /** compile Program to be executed + * @warning read Program docs for more info! + */ + Program compile(Text sql) { + if (!db) throw DatabaseException("database is not opened!"); + Program res(db); + res.set_code(sql); + return res; + } + + /// v.1.1 extension. Bind arguments early, without execute()! + template + inline Program compile(Text sql, Args&& ...args) { + Program res = compile(sql); + res.bind_all(std::forward(args)...); + return res; + } + + public: + operator sqlite3 *() const { return db; } +}; + +/** opens database at specified path with default settings + * will NOT attempt to create database, if it does not exists by default! + * throws on errors. + * readonly parameter is self-explainatory. + * ignore_not_exists - database will be created, if not exists and mode is NOT readonly + */ +Database connect(const char* path, bool readonly = false, bool ignore_not_exists = false); + +/** opens database at specified path OR creatres new one. + * database is ALWAYS opened in readwrite mode! + * throws on errors. + */ +inline Database connect_or_create(const char* path) { + return connect(path, false, true); +} + +/** opens in-memory database. No data will be readed/writed from/to disk! + */ +Database create_memory(const char* path = ""); + +}; diff --git a/game/server.hpp b/game/server.hpp index bbdfe5a..03d9fc6 100644 --- a/game/server.hpp +++ b/game/server.hpp @@ -21,14 +21,14 @@ #include #include "engine/network.hpp" #include "base.hpp" -#include "engine/database.hpp" +#include "external/raiisqlite.hpp" namespace pb { class GamerServerBase { protected: std::unique_ptr server; - Database database; + sqlite::Database database; public: GamerServerBase(const char* db_file, int port, int max_cli = 1) { // create server @@ -42,4 +42,4 @@ namespace pb { } }; -}; \ No newline at end of file +}; diff --git a/game/world_view.hpp b/game/world_view.hpp index 0cc92fe..5c2c213 100644 --- a/game/world_view.hpp +++ b/game/world_view.hpp @@ -17,12 +17,12 @@ * along with this program. If not, see . */ - #pragma once +#pragma once +#include "base.hpp" - #include "base.hpp" namespace pb { - class WorldViewer : public Default { + class WorldViewer : public Moveable { public: WorldViewer(); ~WorldViewer(); @@ -32,4 +32,4 @@ namespace pb { WorldViewer &operator=(WorldViewer &&); }; -}; \ No newline at end of file +}; diff --git a/imgui.ini b/imgui.ini index 18f7b8c..4f47e14 100644 --- a/imgui.ini +++ b/imgui.ini @@ -4,6 +4,6 @@ Size=400,400 [Window][Tools Master] Pos=60,60 -Size=82,411 +Size=150,462 Collapsed=1 diff --git a/launch.json b/launch.json deleted file mode 100644 index 2c96c3d..0000000 --- a/launch.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Debug", - "type": "gdb", - "request": "launch", - "program": "${workspaceRoot}/build/pixelbox", // Binary to exec - "args": ["-t"], // Arguments passed - "cwd" : "${workspaceRoot}", - "stopAtEntry": true, - "preLaunchTask": "cmake-build" - } - ] -} \ No newline at end of file diff --git a/readme.md b/readme.md index 4f1b47d..fa2acca 100644 --- a/readme.md +++ b/readme.md @@ -25,4 +25,11 @@ This new version **WILL** include features like : - Achievements and achievement system - World Leveling Up: You need to sacrifize some amount of resources to improve your world - Save information about laoded chunks when exiting the game. Load all them back when loading again, and only then start physic simulation (REQUIRES A LOT OF TESTING, VALUE-TWEAKING!) -- Improve Out Of Memory stability \ No newline at end of file +- Improve Out Of Memory stability + +# REpo structure +repo strutures is : +- `back_sdl` - default graphical backend to SDL + OpenGL 3 ES. +- `external` - all external dependencies, used in any modules, not maintained by pixelbox team. +- `engine` TODO merge base here - basic C++ delarations/classes/methods used everywhere. +- `modules` - luau c++ modules, including require() and additional basic functions.