diff --git a/.gitmodules b/.gitmodules index 482df24..2d95eb7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "external/physfs"] path = external/physfs url = https://github.com/icculus/physfs +[submodule "external/snkv"] + path = external/snkv + url = https://github.com/hash-anu/snkv diff --git a/CMakeLists.txt b/CMakeLists.txt index 3102dca..87e736e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,43 @@ add_subdirectory(external/angelscript/sdk/angelscript/projects/cmake) # ------------------------- add_subdirectory(external/physfs) +# ------------------------- +# SNKV (submodule) +# ------------------------- +add_library(snkv STATIC + external/snkv/src/kvstore.c + external/snkv/src/btree.c + external/snkv/src/btmutex.c + external/snkv/src/pager.c + external/snkv/src/pcache.c + external/snkv/src/pcache1.c + external/snkv/src/wal.c + external/snkv/src/memjournal.c + external/snkv/src/bitvec.c + external/snkv/src/os.c + external/snkv/src/os_unix.c + external/snkv/src/os_win.c + external/snkv/src/os_kv.c + external/snkv/src/mutex.c + external/snkv/src/mutex_noop.c + external/snkv/src/mutex_unix.c + external/snkv/src/mutex_w32.c + external/snkv/src/malloc.c + external/snkv/src/status.c + external/snkv/src/global.c + external/snkv/src/hash.c + external/snkv/src/util.c + external/snkv/src/printf.c + external/snkv/src/random.c + external/snkv/src/threads.c + external/snkv/src/fault.c + external/snkv/src/mem1.c +) + +target_include_directories(snkv PUBLIC + external/snkv/include +) + # ------------------------- # Dear ImGui (docking branch) # ------------------------- @@ -99,6 +136,8 @@ add_library(simian_core STATIC src/scripting/InputBindings.cpp src/scripting/SceneBindings.cpp src/scripting/PhysFSBindings.cpp + src/scripting/SNKVBindings.cpp + src/scripting/GlobalsBindings.cpp src/HotReload.cpp src/gui/GuiManager.cpp src/gui/ImViewGuizmoIntegration.cpp @@ -123,6 +162,7 @@ target_include_directories(simian_core PUBLIC external/angelscript/sdk/add_on/scriptbuilder external/angelscript/sdk/add_on/scriptarray external/physfs/src + external/snkv/include external/imgui external/ImViewGuizmo ) @@ -131,6 +171,7 @@ target_link_libraries(simian_core PUBLIC raylib angelscript PhysFS::PhysFS-static + snkv scriptstdstring scriptbuilder scriptarray diff --git a/README.md b/README.md index 22e8012..18016aa 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,7 @@ cd ../.. && git add external/raylib external/angelscript && git commit -m "Updat - The project was developed and tested on Windows with Visual Studio; Linux builds are supported but may need X11 / audio dev packages installed depending on your environment. - The codebase avoids changing tracked submodule contents; use the `cmake/` wrappers when a submodule doesn't include a CMake build. +- Runtime writes are limited to `user/`. Scene saves and asset registry writes require `--editor`. ## License @@ -139,3 +140,4 @@ cd ../.. && git add external/raylib external/angelscript && git commit -m "Updat * AngelScript: MIT License (see https://github.com/angelcode/angelscript/blob/master/license.txt) * Simian: Not open source (yet) * Entt: MIT License (see https://github.com/skypjack/entt/blob/main/LICENSE) +* SNKV: Apache-2.0 License (see https://github.com/hash-anu/snkv/blob/master/LICENSE) diff --git a/assets/registry.toml b/assets/registry.toml index 0d52586..ad45828 100644 --- a/assets/registry.toml +++ b/assets/registry.toml @@ -5,7 +5,7 @@ key = "Torus" type = "primitive" primitive = "torus" -params = [0.5, 1, 16, 8] +params = [0.5, 1, 32, 16] [[model]] key = "demo_cube" diff --git a/docs/Scripting.md b/docs/Scripting.md index 0309cc5..19f2d8e 100644 --- a/docs/Scripting.md +++ b/docs/Scripting.md @@ -55,6 +55,55 @@ ECS::AddTexture(e, Asset::GetTextureId("grid")); ECS::AddMaterial(e, Asset::GetMaterialId("toon_mat")); ``` +## Key-Value Store (SNKV) + +- Databases are stored under `user/db/.db`. +- `Open()` returns a handle (0 on failure). Use that handle for all operations. +- TTL values are in milliseconds and are applied relative to the current time. + +Example: + +```angelscript +uint db = KV::Open("session_cache"); +if (db != 0) { + KV::Put(db, "player", "nick"); + + // Expire in 30 seconds + KV::PutTtl(db, "token", "abc123", 30000); + + string value; + int64 remainingMs = 0; + if (KV::GetTtl(db, "token", value, remainingMs)) { + Print("token=" + value + " remaining=" + remainingMs); + } + + KV::Close(db); +} +``` + +## Globals (in-memory) + +- Cross-scene, in-memory store for runtime state. +- Uses the same typed overloads as the KV API. +- Values persist while the app is running, but are not saved to disk. +- `Get()` returns `false` if the key does not exist or the type does not match. + +Example: + +```angelscript +Globals::Set("difficulty", 2); +Globals::Set("player_name", "Nina"); + +int diff = 0; +string name; +if (Globals::Get("difficulty", diff)) { + Print("difficulty=" + diff); +} +if (Globals::Get("player_name", name)) { + Print("player=" + name); +} +``` + ## Examples - `scripts/game.as` — demo showing ECS usage, rendering, scenegraph and input actions. diff --git a/docs/Usage.md b/docs/Usage.md index 2ed7f30..f2439e2 100644 --- a/docs/Usage.md +++ b/docs/Usage.md @@ -52,6 +52,14 @@ ctest --test-dir build --output-on-failure - The engine watches the scripts directory and will hot-reload scripts when changed. - Predefined API is declared in `scripts/as.predefined`. +## Filesystem / Writable Data + +- `user/` is the only writable directory in runtime mode. +- In editor mode (`--editor`), scene files in `scenes/` and the asset registry at + `assets/registry.toml` are writable. +- Scene saves are blocked outside editor mode. +- Logs are written to `user/log.txt`. + ## Documentation & Wiki Docs source lives in the `docs/` folder. The repository contains a CI workflow and a local script to mirror `docs/` to the Gitea wiki. diff --git a/external/snkv b/external/snkv new file mode 160000 index 0000000..a937001 --- /dev/null +++ b/external/snkv @@ -0,0 +1 @@ +Subproject commit a9370019a7d13eff5c04b5d294f624090283d12b diff --git a/imgui.ini b/imgui.ini index 74bdeae..e4db4dd 100644 --- a/imgui.ini +++ b/imgui.ini @@ -21,7 +21,7 @@ Collapsed=0 DockId=0x00000002,0 [Window][##TOAST0] -Pos=1035,637 +Pos=1609,928 Size=225,63 Collapsed=0 @@ -31,12 +31,12 @@ Size=225,63 Collapsed=0 [Window][##TOAST2] -Pos=1035,491 +Pos=1675,814 Size=225,63 Collapsed=0 [Window][##TOAST3] -Pos=1675,741 +Pos=1035,418 Size=225,63 Collapsed=0 diff --git a/include/PhysFSManager.h b/include/PhysFSManager.h index c790bd7..f140b35 100644 --- a/include/PhysFSManager.h +++ b/include/PhysFSManager.h @@ -18,6 +18,7 @@ void ClearSearchPath(); bool SetWriteDir(const std::string& path); bool EnsureDir(const std::string& path); +void SetEditorEnabled(bool enabled); bool Exists(const std::string& path); bool IsDirectory(const std::string& path); diff --git a/include/scripting/GlobalsBindings.h b/include/scripting/GlobalsBindings.h new file mode 100644 index 0000000..07195c2 --- /dev/null +++ b/include/scripting/GlobalsBindings.h @@ -0,0 +1,5 @@ +#pragma once + +class asIScriptEngine; + +void RegisterGlobalsBindings(asIScriptEngine* engine); diff --git a/include/scripting/SNKVBindings.h b/include/scripting/SNKVBindings.h new file mode 100644 index 0000000..276e410 --- /dev/null +++ b/include/scripting/SNKVBindings.h @@ -0,0 +1,5 @@ +#pragma once + +class asIScriptEngine; + +void RegisterSNKVBindings(asIScriptEngine* engine); diff --git a/scenes/demo.toml b/scenes/demo.toml index b8601dc..0834e25 100644 --- a/scenes/demo.toml +++ b/scenes/demo.toml @@ -11,31 +11,29 @@ rotation_deg = [0.000, 0.000, 0.000] id = "entity_2" parent = "entity_1" tag = "Parent" -position = [2.000, -1.555, 0.000] +position = [2.000, -1.100, 0.000] scale = [1.000, 1.000, 1.000] rotation_deg = [0.000, 0.000, 0.000] model_asset = "Torus" -model_id = 2 +model_id = 1 color = 0x00FF00FF outline_size = 0.000 shader_vs = "shaders/toon.vs" shader_fs = "shaders/toon.fs" script = "scripts/ball.as" -script_var_phase = "0.000000" -script_var_speed = "0.699000" script_var_amplitude = "1.748000" +script_var_speed = "0.699000" +script_var_phase = "0.000000" [[entity]] id = "entity_3" parent = "entity_2" tag = "Child" -position = [0.500, -3.100, 1.600] +position = [0.500, -2.175, 1.600] scale = [0.600, 0.600, 0.600] rotation_deg = [0.000, 0.000, 0.000] -model = "sphere" -radius = 0.500 -rings = 16 -slices = 16 +model_asset = "Torus" +model_id = 1 color = 0xFF0000FF outline_size = 0.000 shader_vs = "shaders/toon.vs" @@ -47,19 +45,18 @@ script_var_amplitude = "1.452000" id = "entity_6" parent = "entity_2" tag = "Child 2" -position = [0.000, 0.351, 1.900] +position = [0.000, 0.871, 1.900] scale = [1.000, 1.000, 1.000] rotation_deg = [0.000, 0.000, 0.000] -model = "sphere" -radius = 0.500 -rings = 16 -slices = 16 +model_asset = "demo_cube" +model = "cube" +model_size = [1.000, 1.000, 1.000] color = 0x4523BAFF outline_size = 0.000 shader_vs = "shaders/toon.vs" shader_fs = "shaders/toon.fs" script = "scripts/ball.as" -script_var_amplitude = "0.568000" -script_var_phase = "0.472000" script_var_speed = "10.000000" +script_var_phase = "0.472000" +script_var_amplitude = "0.568000" diff --git a/scripts/as.predefined b/scripts/as.predefined index 9c6d912..ec069ba 100644 --- a/scripts/as.predefined +++ b/scripts/as.predefined @@ -263,6 +263,70 @@ namespace MouseButton { const int MIDDLE = 2; } +// Key-value store (SNKV) +namespace KV { + uint Open(const string &in name); + bool Close(uint handle); + bool Put(uint handle, const string &in key, const string &in value); + bool Put(uint handle, const string &in key, int value); + bool Put(uint handle, const string &in key, uint value); + bool Put(uint handle, const string &in key, int64 value); + bool Put(uint handle, const string &in key, float value); + bool Put(uint handle, const string &in key, double value); + bool Put(uint handle, const string &in key, bool value); + bool Put(uint handle, const string &in key, const array &in value); + bool PutTtl(uint handle, const string &in key, const string &in value, int64 ttlMs); + bool PutTtl(uint handle, const string &in key, int value, int64 ttlMs); + bool PutTtl(uint handle, const string &in key, uint value, int64 ttlMs); + bool PutTtl(uint handle, const string &in key, int64 value, int64 ttlMs); + bool PutTtl(uint handle, const string &in key, float value, int64 ttlMs); + bool PutTtl(uint handle, const string &in key, double value, int64 ttlMs); + bool PutTtl(uint handle, const string &in key, bool value, int64 ttlMs); + bool PutTtl(uint handle, const string &in key, const array &in value, int64 ttlMs); + bool Get(uint handle, const string &in key, string &out value); + bool Get(uint handle, const string &in key, int &out value); + bool Get(uint handle, const string &in key, uint &out value); + bool Get(uint handle, const string &in key, int64 &out value); + bool Get(uint handle, const string &in key, float &out value); + bool Get(uint handle, const string &in key, double &out value); + bool Get(uint handle, const string &in key, bool &out value); + bool Get(uint handle, const string &in key, array &out value); + bool GetTtl(uint handle, const string &in key, string &out value, int64 &out remainingMs); + bool GetTtl(uint handle, const string &in key, int &out value, int64 &out remainingMs); + bool GetTtl(uint handle, const string &in key, uint &out value, int64 &out remainingMs); + bool GetTtl(uint handle, const string &in key, int64 &out value, int64 &out remainingMs); + bool GetTtl(uint handle, const string &in key, float &out value, int64 &out remainingMs); + bool GetTtl(uint handle, const string &in key, double &out value, int64 &out remainingMs); + bool GetTtl(uint handle, const string &in key, bool &out value, int64 &out remainingMs); + bool GetTtl(uint handle, const string &in key, array &out value, int64 &out remainingMs); + bool Delete(uint handle, const string &in key); + bool Exists(uint handle, const string &in key); + bool TtlRemaining(uint handle, const string &in key, int64 &out remainingMs); + int PurgeExpired(uint handle); +} + +// In-memory globals (cross-scene, not persisted) +namespace Globals { + bool Set(const string &in key, const string &in value); + bool Set(const string &in key, int value); + bool Set(const string &in key, uint value); + bool Set(const string &in key, int64 value); + bool Set(const string &in key, float value); + bool Set(const string &in key, double value); + bool Set(const string &in key, bool value); + bool Set(const string &in key, const array &in value); + bool Get(const string &in key, string &out value); + bool Get(const string &in key, int &out value); + bool Get(const string &in key, uint &out value); + bool Get(const string &in key, int64 &out value); + bool Get(const string &in key, float &out value); + bool Get(const string &in key, double &out value); + bool Get(const string &in key, bool &out value); + bool Get(const string &in key, array &out value); + bool Has(const string &in key); + bool Remove(const string &in key); +} + namespace GamepadButton { const int UNKNOWN = 0; const int LEFT_FACE_UP = 1; diff --git a/scripts/autoload.as b/scripts/autoload.as index 7c5cb34..2d95846 100644 --- a/scripts/autoload.as +++ b/scripts/autoload.as @@ -2,8 +2,7 @@ // Runs before any scene/game script to configure input and preload resources. void Autoload() { - // Pick the initial scene to load. - Scene::Load("scenes/demo.toml"); + ClearAllActions(); @@ -36,5 +35,9 @@ void Autoload() { BindGamepadAxis("move_left", 0, GamepadAxis::LEFT_X, 0.3f, true); BindGamepadAxis("move_right", 0, GamepadAxis::LEFT_X, 0.3f, false); - // TODO: Preload assets and other config here. + // Pick the initial scene to load. + Scene::Load("scenes/demo.toml"); + + Globals::Set("demo_var",100); } + diff --git a/scripts/ball.as b/scripts/ball.as index b94dcec..dc771e8 100644 --- a/scripts/ball.as +++ b/scripts/ball.as @@ -40,6 +40,7 @@ class EntityScript phase += dt; float offset = Math::Sin(phase * speed + float(entity) * 0.1f) * amplitude; ECS::SetPosition(entity, baseX, baseY + offset, baseZ); + ECS::SetRotationEuler(entity, 0.0f, phase * 50.0f * dt, 0.0f); } void Shutdown(uint entity) diff --git a/scripts/demo_scene.as b/scripts/demo_scene.as index 0b23fdc..31de377 100644 --- a/scripts/demo_scene.as +++ b/scripts/demo_scene.as @@ -6,18 +6,48 @@ class SceneScript { uint cube = 0; float time = 0.0f; bool highlight = false; - void Init() { + uint kvStore = KV::Open("demo_scene"); Log(LOG_INFO, "Demo scene script init"); + int var; + if (Globals::Get("demo_var", var)) { + Log(LOG_INFO, "Loaded global variable 'demo_var' with value: " + var); + } else { + Log(LOG_INFO, "No global variable 'demo_var' found, starting fresh."); + var = 42; + } + Globals::Set("demo_var", var + 1); + int value; + string strValue; + if (KV::Get(kvStore, "int", value)) { + Log(LOG_INFO, "Loaded int value from KV store: " + value); + } else { + Log(LOG_INFO, "No int value found in KV store, starting fresh."); + value = 0; + } + + if (KV::Get(kvStore, "str", strValue)) { + Log(LOG_INFO, "Loaded string value from KV store: " + strValue); + } else { + Log(LOG_INFO, "No string value found in KV store, starting fresh."); + strValue = "Hello, KV!"; + } cube = ECS::CreateEntity(); - ECS::AddTransform(cube, 0.0f, 1.5f, 0.0f); - uint modelId = Asset::GetModelId("demo_cube"); + ECS::AddTransform(cube, 0.0f, 2.5f, 0.0f); + uint modelId = Asset::GetModelId("demo2_cube"); if (modelId == 0) { - modelId = Asset::LoadCube("demo_cube", 1.0f, 1.0f, 1.0f); + modelId = Asset::LoadCube("demo2_cube", 1.0f, 1.0f, 1.0f); } ECS::AddModelRenderer(cube, int(modelId), 0xFFAA00FF, 0.0f); ECS::AddTag(cube, "InputCube"); + + KV::Put(kvStore, "int", value+1); + KV::Put(kvStore, "str", strValue + " (updated " + value + ")"); + var = 0; + if (Globals::Get("demo_var", var)) { + Log(LOG_INFO, "Updated global variable 'demo_var' to value: " + var); + } } void Shutdown() { diff --git a/src/Application.cpp b/src/Application.cpp index cd4b0ac..bdb92fd 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "log/log.h" @@ -24,7 +25,8 @@ const char *Application::WINDOW_TITLE = "Simian"; static const char* kAutoloadScriptFile = "scripts/autoload.as"; static const char* kAssetRegistryFile = "assets/registry.toml"; -static const char* kLogFilePath = "log.txt"; +static const char* kLogFilePath = "user/log.txt"; +static const char* kPackageFile = "game.zip"; static void PhysFSLogCallback(log_Event* ev) { @@ -72,20 +74,6 @@ bool Application::Initialize(int argc, char *argv[]) return false; } - PhysFSManager::SetWriteDir("."); - - const char* mountDirs[] = {"scripts", "scenes", "assets", "shaders", "fonts", "textures", "models"}; - for (const char* dir : mountDirs) { - PhysFSManager::MountPath(dir, dir, true); - } - - logFile = PhysFSManager::OpenWrite(kLogFilePath); - if (logFile) { - log_add_callback(PhysFSLogCallback, logFile, LOG_TRACE); - } - - SetTraceLogCallback(raylib_log); - enableEditor = false; // Parse command-line arguments @@ -97,6 +85,35 @@ bool Application::Initialize(int argc, char *argv[]) } } + std::filesystem::create_directories("user/db"); + + PhysFSManager::SetEditorEnabled(enableEditor); + PhysFSManager::SetWriteDir("."); + + PhysFSManager::MountPath("user", "user", true); + + bool mountedPackage = false; + if (std::filesystem::exists(kPackageFile)) { + mountedPackage = PhysFSManager::MountPath(kPackageFile, "", true); + if (!mountedPackage) { + log_warn("Failed to mount package: %s", kPackageFile); + } + } + + if (!mountedPackage) { + const char* mountDirs[] = {"scripts", "scenes", "assets", "shaders", "fonts", "textures", "models"}; + for (const char* dir : mountDirs) { + PhysFSManager::MountPath(dir, dir, true); + } + } + + logFile = PhysFSManager::OpenWrite(kLogFilePath); + if (logFile) { + log_add_callback(PhysFSLogCallback, logFile, LOG_TRACE); + } + + SetTraceLogCallback(raylib_log); + // Initialize Raylib SetConfigFlags(FLAG_WINDOW_RESIZABLE); InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE); diff --git a/src/PhysFSManager.cpp b/src/PhysFSManager.cpp index 4b50d5a..0e844b3 100644 --- a/src/PhysFSManager.cpp +++ b/src/PhysFSManager.cpp @@ -9,6 +9,50 @@ namespace PhysFSManager { static bool g_initialized = false; +static bool g_editorEnabled = false; + +static std::string NormalizePath(std::string path) { + std::replace(path.begin(), path.end(), '\\', '/'); + while (path.rfind("./", 0) == 0) { + path.erase(0, 2); + } + while (!path.empty() && path.front() == '/') { + path.erase(path.begin()); + } + return path; +} + +static bool StartsWith(const std::string& value, const std::string& prefix) { + return value.size() >= prefix.size() && value.compare(0, prefix.size(), prefix) == 0; +} + +static bool IsWriteAllowed(const std::string& rawPath) { + std::string path = NormalizePath(rawPath); + if (path.empty()) { + return false; + } + if (path.find("..") != std::string::npos) { + return false; + } + + if (path == "user" || StartsWith(path, "user/")) { + return true; + } + + if (!g_editorEnabled) { + return false; + } + + if (path == "scenes" || StartsWith(path, "scenes/")) { + return true; + } + + if (path == "assets" || path == "assets/registry.toml") { + return true; + } + + return false; +} static unsigned char* RaylibLoadFileDataCallback(const char* fileName, int* dataSize) { if (!fileName || !dataSize) { @@ -47,11 +91,49 @@ static unsigned char* RaylibLoadFileDataCallback(const char* fileName, int* data return data; } +static char* RaylibLoadFileTextCallback(const char* fileName) { + if (!fileName) { + return nullptr; + } + + PHYSFS_File* file = PHYSFS_openRead(fileName); + if (!file) { + return nullptr; + } + + PHYSFS_sint64 length = PHYSFS_fileLength(file); + if (length <= 0 || length > static_cast(INT32_MAX)) { + PHYSFS_close(file); + return nullptr; + } + + char* text = static_cast(MemAlloc(static_cast(length) + 1)); + if (!text) { + PHYSFS_close(file); + return nullptr; + } + + PHYSFS_sint64 read = PHYSFS_readBytes(file, text, length); + PHYSFS_close(file); + if (read != length) { + MemFree(text); + return nullptr; + } + + text[length] = '\0'; + return text; +} + static bool RaylibSaveFileDataCallback(const char* fileName, void* data, int dataSize) { if (!fileName || !data || dataSize <= 0) { return false; } + if (!IsWriteAllowed(fileName)) { + log_warn("PhysFS write blocked: %s", fileName); + return false; + } + PHYSFS_File* file = PHYSFS_openWrite(fileName); if (!file) { return false; @@ -76,6 +158,7 @@ bool Initialize(const char* argv0) { // Configure raylib to use PhysFS for all file reads/writes. SetLoadFileDataCallback(RaylibLoadFileDataCallback); + SetLoadFileTextCallback(RaylibLoadFileTextCallback); SetSaveFileDataCallback(RaylibSaveFileDataCallback); g_initialized = true; @@ -136,11 +219,20 @@ bool SetWriteDir(const std::string& path) { return true; } +void SetEditorEnabled(bool enabled) { + g_editorEnabled = enabled; +} + bool EnsureDir(const std::string& path) { if (path.empty()) { return true; } + if (!IsWriteAllowed(path)) { + log_warn("PhysFS mkdir blocked: %s", path.c_str()); + return false; + } + std::string normalized = path; std::replace(normalized.begin(), normalized.end(), '\\', '/'); @@ -223,6 +315,10 @@ bool ReadTextFile(const std::string& path, std::string& out) { } bool WriteTextFile(const std::string& path, const std::string& contents) { + if (!IsWriteAllowed(path)) { + log_warn("PhysFS write blocked: %s", path.c_str()); + return false; + } PHYSFS_File* file = PHYSFS_openWrite(path.c_str()); if (!file) { return false; @@ -238,6 +334,10 @@ PHYSFS_File* OpenRead(const std::string& path) { } PHYSFS_File* OpenWrite(const std::string& path) { + if (!IsWriteAllowed(path)) { + log_warn("PhysFS write blocked: %s", path.c_str()); + return nullptr; + } return PHYSFS_openWrite(path.c_str()); } diff --git a/src/scripting/GlobalsBindings.cpp b/src/scripting/GlobalsBindings.cpp new file mode 100644 index 0000000..538498e --- /dev/null +++ b/src/scripting/GlobalsBindings.cpp @@ -0,0 +1,252 @@ +#include "scripting/GlobalsBindings.h" +#include "scriptarray.h" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +enum class ValueType { + String, + Int, + UInt, + Int64, + Float, + Double, + Bool, + Bytes +}; + +using ValueData = std::variant>; + +struct Value { + ValueType type; + ValueData data; +}; + +std::unordered_map g_values; + +std::vector ArrayToBytes(const CScriptArray* value) { + std::vector bytes; + if (!value) { + return bytes; + } + + const asUINT size = value->GetSize(); + bytes.resize(size); + for (asUINT i = 0; i < size; ++i) { + const uint8_t* element = static_cast(value->At(i)); + bytes[i] = *element; + } + return bytes; +} + +bool BytesToArray(const std::vector& bytes, CScriptArray* outValue) { + if (!outValue) { + return false; + } + + outValue->Resize(static_cast(bytes.size())); + for (asUINT i = 0; i < outValue->GetSize(); ++i) { + *static_cast(outValue->At(i)) = bytes[i]; + } + return true; +} + +bool SetValue(const std::string& key, ValueType type, ValueData data) { + g_values[key] = Value{type, std::move(data)}; + return true; +} + +const Value* GetValue(const std::string& key, ValueType type) { + auto it = g_values.find(key); + if (it == g_values.end()) { + return nullptr; + } + if (it->second.type != type) { + return nullptr; + } + return &it->second; +} + +bool AS_SetString(const std::string& key, const std::string& value) { + return SetValue(key, ValueType::String, value); +} + +bool AS_SetInt(const std::string& key, int value) { + return SetValue(key, ValueType::Int, value); +} + +bool AS_SetUInt(const std::string& key, asUINT value) { + return SetValue(key, ValueType::UInt, value); +} + +bool AS_SetInt64(const std::string& key, int64_t value) { + return SetValue(key, ValueType::Int64, value); +} + +bool AS_SetFloat(const std::string& key, float value) { + return SetValue(key, ValueType::Float, value); +} + +bool AS_SetDouble(const std::string& key, double value) { + return SetValue(key, ValueType::Double, value); +} + +bool AS_SetBool(const std::string& key, bool value) { + return SetValue(key, ValueType::Bool, value); +} + +bool AS_SetArray(const std::string& key, const CScriptArray* value) { + return SetValue(key, ValueType::Bytes, ArrayToBytes(value)); +} + +bool AS_GetString(const std::string& key, std::string& outValue) { + const Value* value = GetValue(key, ValueType::String); + if (!value) { + return false; + } + outValue = std::get(value->data); + return true; +} + +bool AS_GetInt(const std::string& key, int& outValue) { + const Value* value = GetValue(key, ValueType::Int); + if (!value) { + return false; + } + outValue = std::get(value->data); + return true; +} + +bool AS_GetUInt(const std::string& key, asUINT& outValue) { + const Value* value = GetValue(key, ValueType::UInt); + if (!value) { + return false; + } + outValue = std::get(value->data); + return true; +} + +bool AS_GetInt64(const std::string& key, int64_t& outValue) { + const Value* value = GetValue(key, ValueType::Int64); + if (!value) { + return false; + } + outValue = std::get(value->data); + return true; +} + +bool AS_GetFloat(const std::string& key, float& outValue) { + const Value* value = GetValue(key, ValueType::Float); + if (!value) { + return false; + } + outValue = std::get(value->data); + return true; +} + +bool AS_GetDouble(const std::string& key, double& outValue) { + const Value* value = GetValue(key, ValueType::Double); + if (!value) { + return false; + } + outValue = std::get(value->data); + return true; +} + +bool AS_GetBool(const std::string& key, bool& outValue) { + const Value* value = GetValue(key, ValueType::Bool); + if (!value) { + return false; + } + outValue = std::get(value->data); + return true; +} + +bool AS_GetArray(const std::string& key, CScriptArray* outValue) { + const Value* value = GetValue(key, ValueType::Bytes); + if (!value) { + return false; + } + return BytesToArray(std::get>(value->data), outValue); +} + +bool AS_Has(const std::string& key) { + return g_values.find(key) != g_values.end(); +} + +bool AS_Remove(const std::string& key) { + return g_values.erase(key) > 0; +} + +} // namespace + +void RegisterGlobalsBindings(asIScriptEngine* engine) { + int r; + + engine->SetDefaultNamespace("Globals"); + + r = engine->RegisterGlobalFunction("bool Set(const string &in key, const string &in value)", + asFUNCTION(AS_SetString), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Set(const string &in key, int value)", + asFUNCTION(AS_SetInt), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Set(const string &in key, uint value)", + asFUNCTION(AS_SetUInt), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Set(const string &in key, int64 value)", + asFUNCTION(AS_SetInt64), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Set(const string &in key, float value)", + asFUNCTION(AS_SetFloat), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Set(const string &in key, double value)", + asFUNCTION(AS_SetDouble), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Set(const string &in key, bool value)", + asFUNCTION(AS_SetBool), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Set(const string &in key, const array &in value)", + asFUNCTION(AS_SetArray), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("bool Get(const string &in key, string &out value)", + asFUNCTION(AS_GetString), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Get(const string &in key, int &out value)", + asFUNCTION(AS_GetInt), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Get(const string &in key, uint &out value)", + asFUNCTION(AS_GetUInt), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Get(const string &in key, int64 &out value)", + asFUNCTION(AS_GetInt64), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Get(const string &in key, float &out value)", + asFUNCTION(AS_GetFloat), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Get(const string &in key, double &out value)", + asFUNCTION(AS_GetDouble), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Get(const string &in key, bool &out value)", + asFUNCTION(AS_GetBool), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Get(const string &in key, array &out value)", + asFUNCTION(AS_GetArray), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("bool Has(const string &in key)", + asFUNCTION(AS_Has), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Remove(const string &in key)", + asFUNCTION(AS_Remove), asCALL_CDECL); + assert(r >= 0); + + engine->SetDefaultNamespace(""); +} diff --git a/src/scripting/SNKVBindings.cpp b/src/scripting/SNKVBindings.cpp new file mode 100644 index 0000000..66a9e39 --- /dev/null +++ b/src/scripting/SNKVBindings.cpp @@ -0,0 +1,552 @@ +#include "scripting/SNKVBindings.h" +#define SNKV_IMPLEMENTATION +#include "kvstore.h" +#include "log.h" +#include "scriptarray.h" +#include +#include +#include +#include +#include +#include +#include + +static std::unordered_map g_stores; +static asUINT g_nextHandle = 1; + +static bool StartsWith(const std::string& value, const std::string& prefix) { + return value.size() >= prefix.size() && value.compare(0, prefix.size(), prefix) == 0; +} + +static bool EndsWith(const std::string& value, const std::string& suffix) { + return value.size() >= suffix.size() && value.compare(value.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +static bool IsSafeDbName(const std::string& name) { + if (name.empty()) { + return false; + } + if (name.find('/') != std::string::npos || name.find('\\') != std::string::npos) { + return false; + } + if (name.find("..") != std::string::npos) { + return false; + } + if (StartsWith(name, ".")) { + return false; + } + return true; +} + +static std::string BuildDbPath(const std::string& name) { + std::string fileName = name; + if (!EndsWith(fileName, ".db")) { + fileName += ".db"; + } + return std::string("user/db/") + fileName; +} + +static KVStore* GetStore(asUINT handle) { + auto it = g_stores.find(handle); + if (it == g_stores.end()) { + return nullptr; + } + return it->second; +} + +static asUINT AS_Open(const std::string& name) { + if (!IsSafeDbName(name)) { + log_warn("SNKV open blocked (invalid name): %s", name.c_str()); + return 0; + } + + std::filesystem::create_directories("user/db"); + std::string path = BuildDbPath(name); + + KVStore* store = nullptr; + int rc = kvstore_open(path.c_str(), &store, KVSTORE_JOURNAL_WAL); + if (rc != KVSTORE_OK) { + log_warn("SNKV open failed for %s (code %d)", path.c_str(), rc); + return 0; + } + + asUINT handle = g_nextHandle++; + g_stores[handle] = store; + return handle; +} + +static bool AS_Close(asUINT handle) { + auto it = g_stores.find(handle); + if (it == g_stores.end()) { + return false; + } + + kvstore_close(it->second); + g_stores.erase(it); + return true; +} + +static bool AS_PutBytes(asUINT handle, const std::string& key, const std::string& value) { + KVStore* store = GetStore(handle); + if (!store) { + return false; + } + + int rc = kvstore_put(store, key.data(), static_cast(key.size()), value.data(), static_cast(value.size())); + return rc == KVSTORE_OK; +} + +static bool AS_PutBytesTtl(asUINT handle, const std::string& key, const std::string& value, int64_t ttlMs) { + KVStore* store = GetStore(handle); + if (!store) { + return false; + } + + int64_t expireMs = 0; + if (ttlMs > 0) { + expireMs = kvstore_now_ms() + ttlMs; + } + + int rc = kvstore_put_ttl(store, key.data(), static_cast(key.size()), + value.data(), static_cast(value.size()), expireMs); + return rc == KVSTORE_OK; +} + +static bool AS_GetBytes(asUINT handle, const std::string& key, std::string& outValue) { + outValue.clear(); + KVStore* store = GetStore(handle); + if (!store) { + return false; + } + + void* value = nullptr; + int length = 0; + int rc = kvstore_get(store, key.data(), static_cast(key.size()), &value, &length); + if (rc != KVSTORE_OK) { + return false; + } + + outValue.assign(static_cast(value), static_cast(length)); + snkv_free(value); + return true; +} + +static bool AS_GetBytesTtl(asUINT handle, const std::string& key, std::string& outValue, int64_t& outRemainingMs) { + outValue.clear(); + outRemainingMs = 0; + KVStore* store = GetStore(handle); + if (!store) { + return false; + } + + void* value = nullptr; + int length = 0; + int rc = kvstore_get_ttl(store, key.data(), static_cast(key.size()), &value, &length, &outRemainingMs); + if (rc != KVSTORE_OK) { + return false; + } + + outValue.assign(static_cast(value), static_cast(length)); + snkv_free(value); + return true; +} + +template +static std::string ToBytes(const T& value) { + std::string buffer(sizeof(T), '\0'); + std::memcpy(buffer.data(), &value, sizeof(T)); + return buffer; +} + +template +static bool FromBytes(const std::string& buffer, T& outValue) { + if (buffer.size() != sizeof(T)) { + return false; + } + std::memcpy(&outValue, buffer.data(), sizeof(T)); + return true; +} + +static std::string ArrayToBytes(const CScriptArray* value) { + std::string buffer; + if (!value) { + return buffer; + } + + const asUINT size = value->GetSize(); + buffer.resize(size); + for (asUINT i = 0; i < size; ++i) { + const uint8_t* element = static_cast(value->At(i)); + buffer[i] = static_cast(*element); + } + return buffer; +} + +static bool BytesToArray(const std::string& buffer, CScriptArray* outValue) { + if (!outValue) { + return false; + } + + outValue->Resize(static_cast(buffer.size())); + for (asUINT i = 0; i < outValue->GetSize(); ++i) { + *static_cast(outValue->At(i)) = static_cast(buffer[i]); + } + return true; +} + +static bool AS_PutString(asUINT handle, const std::string& key, const std::string& value) { + return AS_PutBytes(handle, key, value); +} + +static bool AS_PutStringTtl(asUINT handle, const std::string& key, const std::string& value, int64_t ttlMs) { + return AS_PutBytesTtl(handle, key, value, ttlMs); +} + +static bool AS_GetString(asUINT handle, const std::string& key, std::string& outValue) { + return AS_GetBytes(handle, key, outValue); +} + +static bool AS_GetStringTtl(asUINT handle, const std::string& key, std::string& outValue, int64_t& outRemainingMs) { + return AS_GetBytesTtl(handle, key, outValue, outRemainingMs); +} + +static bool AS_PutInt(asUINT handle, const std::string& key, int value) { + return AS_PutBytes(handle, key, ToBytes(value)); +} + +static bool AS_PutIntTtl(asUINT handle, const std::string& key, int value, int64_t ttlMs) { + return AS_PutBytesTtl(handle, key, ToBytes(value), ttlMs); +} + +static bool AS_GetInt(asUINT handle, const std::string& key, int& outValue) { + std::string buffer; + if (!AS_GetBytes(handle, key, buffer)) { + return false; + } + return FromBytes(buffer, outValue); +} + +static bool AS_GetIntTtl(asUINT handle, const std::string& key, int& outValue, int64_t& outRemainingMs) { + std::string buffer; + if (!AS_GetBytesTtl(handle, key, buffer, outRemainingMs)) { + return false; + } + return FromBytes(buffer, outValue); +} + +static bool AS_PutUInt(asUINT handle, const std::string& key, asUINT value) { + return AS_PutBytes(handle, key, ToBytes(value)); +} + +static bool AS_PutUIntTtl(asUINT handle, const std::string& key, asUINT value, int64_t ttlMs) { + return AS_PutBytesTtl(handle, key, ToBytes(value), ttlMs); +} + +static bool AS_GetUInt(asUINT handle, const std::string& key, asUINT& outValue) { + std::string buffer; + if (!AS_GetBytes(handle, key, buffer)) { + return false; + } + return FromBytes(buffer, outValue); +} + +static bool AS_GetUIntTtl(asUINT handle, const std::string& key, asUINT& outValue, int64_t& outRemainingMs) { + std::string buffer; + if (!AS_GetBytesTtl(handle, key, buffer, outRemainingMs)) { + return false; + } + return FromBytes(buffer, outValue); +} + +static bool AS_PutInt64(asUINT handle, const std::string& key, int64_t value) { + return AS_PutBytes(handle, key, ToBytes(value)); +} + +static bool AS_PutInt64Ttl(asUINT handle, const std::string& key, int64_t value, int64_t ttlMs) { + return AS_PutBytesTtl(handle, key, ToBytes(value), ttlMs); +} + +static bool AS_GetInt64(asUINT handle, const std::string& key, int64_t& outValue) { + std::string buffer; + if (!AS_GetBytes(handle, key, buffer)) { + return false; + } + return FromBytes(buffer, outValue); +} + +static bool AS_GetInt64Ttl(asUINT handle, const std::string& key, int64_t& outValue, int64_t& outRemainingMs) { + std::string buffer; + if (!AS_GetBytesTtl(handle, key, buffer, outRemainingMs)) { + return false; + } + return FromBytes(buffer, outValue); +} + +static bool AS_PutFloat(asUINT handle, const std::string& key, float value) { + return AS_PutBytes(handle, key, ToBytes(value)); +} + +static bool AS_PutFloatTtl(asUINT handle, const std::string& key, float value, int64_t ttlMs) { + return AS_PutBytesTtl(handle, key, ToBytes(value), ttlMs); +} + +static bool AS_GetFloat(asUINT handle, const std::string& key, float& outValue) { + std::string buffer; + if (!AS_GetBytes(handle, key, buffer)) { + return false; + } + return FromBytes(buffer, outValue); +} + +static bool AS_GetFloatTtl(asUINT handle, const std::string& key, float& outValue, int64_t& outRemainingMs) { + std::string buffer; + if (!AS_GetBytesTtl(handle, key, buffer, outRemainingMs)) { + return false; + } + return FromBytes(buffer, outValue); +} + +static bool AS_PutDouble(asUINT handle, const std::string& key, double value) { + return AS_PutBytes(handle, key, ToBytes(value)); +} + +static bool AS_PutDoubleTtl(asUINT handle, const std::string& key, double value, int64_t ttlMs) { + return AS_PutBytesTtl(handle, key, ToBytes(value), ttlMs); +} + +static bool AS_GetDouble(asUINT handle, const std::string& key, double& outValue) { + std::string buffer; + if (!AS_GetBytes(handle, key, buffer)) { + return false; + } + return FromBytes(buffer, outValue); +} + +static bool AS_GetDoubleTtl(asUINT handle, const std::string& key, double& outValue, int64_t& outRemainingMs) { + std::string buffer; + if (!AS_GetBytesTtl(handle, key, buffer, outRemainingMs)) { + return false; + } + return FromBytes(buffer, outValue); +} + +static bool AS_PutBool(asUINT handle, const std::string& key, bool value) { + return AS_PutBytes(handle, key, ToBytes(value)); +} + +static bool AS_PutBoolTtl(asUINT handle, const std::string& key, bool value, int64_t ttlMs) { + return AS_PutBytesTtl(handle, key, ToBytes(value), ttlMs); +} + +static bool AS_GetBool(asUINT handle, const std::string& key, bool& outValue) { + std::string buffer; + if (!AS_GetBytes(handle, key, buffer)) { + return false; + } + return FromBytes(buffer, outValue); +} + +static bool AS_GetBoolTtl(asUINT handle, const std::string& key, bool& outValue, int64_t& outRemainingMs) { + std::string buffer; + if (!AS_GetBytesTtl(handle, key, buffer, outRemainingMs)) { + return false; + } + return FromBytes(buffer, outValue); +} + +static bool AS_PutArray(asUINT handle, const std::string& key, const CScriptArray* value) { + return AS_PutBytes(handle, key, ArrayToBytes(value)); +} + +static bool AS_PutArrayTtl(asUINT handle, const std::string& key, const CScriptArray* value, int64_t ttlMs) { + return AS_PutBytesTtl(handle, key, ArrayToBytes(value), ttlMs); +} + +static bool AS_GetArray(asUINT handle, const std::string& key, CScriptArray* outValue) { + std::string buffer; + if (!AS_GetBytes(handle, key, buffer)) { + return false; + } + return BytesToArray(buffer, outValue); +} + +static bool AS_GetArrayTtl(asUINT handle, const std::string& key, CScriptArray* outValue, int64_t& outRemainingMs) { + std::string buffer; + if (!AS_GetBytesTtl(handle, key, buffer, outRemainingMs)) { + return false; + } + return BytesToArray(buffer, outValue); +} + +static bool AS_Delete(asUINT handle, const std::string& key) { + KVStore* store = GetStore(handle); + if (!store) { + return false; + } + + int rc = kvstore_delete(store, key.data(), static_cast(key.size())); + return rc == KVSTORE_OK; +} + +static bool AS_Exists(asUINT handle, const std::string& key) { + KVStore* store = GetStore(handle); + if (!store) { + return false; + } + + int exists = 0; + int rc = kvstore_exists(store, key.data(), static_cast(key.size()), &exists); + return rc == KVSTORE_OK && exists != 0; +} + +static bool AS_TtlRemaining(asUINT handle, const std::string& key, int64_t& outRemainingMs) { + outRemainingMs = 0; + KVStore* store = GetStore(handle); + if (!store) { + return false; + } + + int rc = kvstore_ttl_remaining(store, key.data(), static_cast(key.size()), &outRemainingMs); + return rc == KVSTORE_OK; +} + +static int AS_PurgeExpired(asUINT handle) { + KVStore* store = GetStore(handle); + if (!store) { + return -1; + } + + int deleted = 0; + int rc = kvstore_purge_expired(store, &deleted); + if (rc != KVSTORE_OK) { + return -1; + } + return deleted; +} + +void RegisterSNKVBindings(asIScriptEngine* engine) { + int r; + + engine->SetDefaultNamespace("KV"); + + r = engine->RegisterGlobalFunction("uint Open(const string &in name)", asFUNCTION(AS_Open), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("bool Close(uint handle)", asFUNCTION(AS_Close), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("bool Put(uint handle, const string &in key, const string &in value)", + asFUNCTION(AS_PutString), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Put(uint handle, const string &in key, int value)", + asFUNCTION(AS_PutInt), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Put(uint handle, const string &in key, uint value)", + asFUNCTION(AS_PutUInt), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Put(uint handle, const string &in key, int64 value)", + asFUNCTION(AS_PutInt64), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Put(uint handle, const string &in key, float value)", + asFUNCTION(AS_PutFloat), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Put(uint handle, const string &in key, double value)", + asFUNCTION(AS_PutDouble), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Put(uint handle, const string &in key, bool value)", + asFUNCTION(AS_PutBool), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Put(uint handle, const string &in key, const array &in value)", + asFUNCTION(AS_PutArray), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("bool PutTtl(uint handle, const string &in key, const string &in value, int64 ttlMs)", + asFUNCTION(AS_PutStringTtl), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool PutTtl(uint handle, const string &in key, int value, int64 ttlMs)", + asFUNCTION(AS_PutIntTtl), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool PutTtl(uint handle, const string &in key, uint value, int64 ttlMs)", + asFUNCTION(AS_PutUIntTtl), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool PutTtl(uint handle, const string &in key, int64 value, int64 ttlMs)", + asFUNCTION(AS_PutInt64Ttl), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool PutTtl(uint handle, const string &in key, float value, int64 ttlMs)", + asFUNCTION(AS_PutFloatTtl), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool PutTtl(uint handle, const string &in key, double value, int64 ttlMs)", + asFUNCTION(AS_PutDoubleTtl), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool PutTtl(uint handle, const string &in key, bool value, int64 ttlMs)", + asFUNCTION(AS_PutBoolTtl), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool PutTtl(uint handle, const string &in key, const array &in value, int64 ttlMs)", + asFUNCTION(AS_PutArrayTtl), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("bool Get(uint handle, const string &in key, string &out value)", + asFUNCTION(AS_GetString), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Get(uint handle, const string &in key, int &out value)", + asFUNCTION(AS_GetInt), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Get(uint handle, const string &in key, uint &out value)", + asFUNCTION(AS_GetUInt), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Get(uint handle, const string &in key, int64 &out value)", + asFUNCTION(AS_GetInt64), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Get(uint handle, const string &in key, float &out value)", + asFUNCTION(AS_GetFloat), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Get(uint handle, const string &in key, double &out value)", + asFUNCTION(AS_GetDouble), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Get(uint handle, const string &in key, bool &out value)", + asFUNCTION(AS_GetBool), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool Get(uint handle, const string &in key, array &out value)", + asFUNCTION(AS_GetArray), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("bool GetTtl(uint handle, const string &in key, string &out value, int64 &out remainingMs)", + asFUNCTION(AS_GetStringTtl), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool GetTtl(uint handle, const string &in key, int &out value, int64 &out remainingMs)", + asFUNCTION(AS_GetIntTtl), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool GetTtl(uint handle, const string &in key, uint &out value, int64 &out remainingMs)", + asFUNCTION(AS_GetUIntTtl), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool GetTtl(uint handle, const string &in key, int64 &out value, int64 &out remainingMs)", + asFUNCTION(AS_GetInt64Ttl), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool GetTtl(uint handle, const string &in key, float &out value, int64 &out remainingMs)", + asFUNCTION(AS_GetFloatTtl), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool GetTtl(uint handle, const string &in key, double &out value, int64 &out remainingMs)", + asFUNCTION(AS_GetDoubleTtl), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool GetTtl(uint handle, const string &in key, bool &out value, int64 &out remainingMs)", + asFUNCTION(AS_GetBoolTtl), asCALL_CDECL); + assert(r >= 0); + r = engine->RegisterGlobalFunction("bool GetTtl(uint handle, const string &in key, array &out value, int64 &out remainingMs)", + asFUNCTION(AS_GetArrayTtl), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("bool Delete(uint handle, const string &in key)", asFUNCTION(AS_Delete), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("bool Exists(uint handle, const string &in key)", asFUNCTION(AS_Exists), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("bool TtlRemaining(uint handle, const string &in key, int64 &out remainingMs)", + asFUNCTION(AS_TtlRemaining), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("int PurgeExpired(uint handle)", asFUNCTION(AS_PurgeExpired), asCALL_CDECL); + assert(r >= 0); + + engine->SetDefaultNamespace(""); +} \ No newline at end of file diff --git a/src/scripting/ScriptBindings.cpp b/src/scripting/ScriptBindings.cpp index 40a2403..a9218f4 100644 --- a/src/scripting/ScriptBindings.cpp +++ b/src/scripting/ScriptBindings.cpp @@ -9,6 +9,8 @@ #include "scripting/InputBindings.h" #include "scripting/SceneBindings.h" #include "scripting/PhysFSBindings.h" +#include "scripting/SNKVBindings.h" +#include "scripting/GlobalsBindings.h" void ScriptBindings::RegisterAll(asIScriptEngine *engine) { @@ -23,4 +25,6 @@ void ScriptBindings::RegisterAll(asIScriptEngine *engine) RegisterInputBindings(engine); RegisterSceneBindings(engine); RegisterPhysFSBindings(engine); + RegisterSNKVBindings(engine); + RegisterGlobalsBindings(engine); } \ No newline at end of file diff --git a/tests/physfs_test_utils.cpp b/tests/physfs_test_utils.cpp index f293dd4..acb8587 100644 --- a/tests/physfs_test_utils.cpp +++ b/tests/physfs_test_utils.cpp @@ -11,6 +11,7 @@ bool InitPhysFSTest(const std::filesystem::path& writeDir) { g_initialized = true; } + PhysFSManager::SetEditorEnabled(true); PhysFSManager::SetWriteDir(writeDir.string()); return true; } diff --git a/tests/resource_tests.cpp b/tests/resource_tests.cpp index 78aa947..2b2e0f9 100644 --- a/tests/resource_tests.cpp +++ b/tests/resource_tests.cpp @@ -248,7 +248,7 @@ void test_resource_bindings_script(void) { void test_asset_registry_material(void) { std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_asset_registry"; std::filesystem::create_directories(tmpDir); - std::filesystem::path registryPath = tmpDir / "registry.toml"; + std::filesystem::path registryPath = tmpDir / "assets" / "registry.toml"; TEST_CHECK(InitPhysFSTest(tmpDir) == true); TEST_CHECK(MountPhysFSTest(tmpDir, "") == true); @@ -261,7 +261,7 @@ void test_asset_registry_material(void) { unsigned int materialId = assets.CreateMaterial("test_mat", 0, albedo); TEST_CHECK(materialId != 0); - bool saved = assets.SaveRegistry("registry.toml"); + bool saved = assets.SaveRegistry("assets/registry.toml"); TEST_CHECK(saved == true); assets.Clear(); @@ -271,7 +271,7 @@ void test_asset_registry_material(void) { AssetManager assetsReload; assetsReload.SetManagers(nullptr, nullptr, &materialManagerReload, nullptr); - bool loaded = assetsReload.LoadRegistry("registry.toml"); + bool loaded = assetsReload.LoadRegistry("assets/registry.toml"); TEST_CHECK(loaded == true); unsigned int loadedId = assetsReload.GetMaterialId("test_mat"); @@ -288,5 +288,6 @@ void test_asset_registry_material(void) { ShutdownPhysFSTest(); std::filesystem::remove(registryPath); + std::filesystem::remove(registryPath.parent_path()); std::filesystem::remove(tmpDir); } diff --git a/tests/scenegraph_tests.cpp b/tests/scenegraph_tests.cpp index 0bfbb2d..5929310 100644 --- a/tests/scenegraph_tests.cpp +++ b/tests/scenegraph_tests.cpp @@ -265,7 +265,7 @@ void test_scene_loader_asset_keys(void) { std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_scene_assets"; std::filesystem::create_directories(tmpDir); - std::filesystem::path registryPath = tmpDir / "registry.toml"; + std::filesystem::path registryPath = tmpDir / "assets" / "registry.toml"; std::filesystem::path scenePath = tmpDir / "scene_asset.toml"; TEST_CHECK(InitPhysFSTest(tmpDir) == true); @@ -278,7 +278,7 @@ void test_scene_loader_asset_keys(void) { AssetManager assets; assets.SetManagers(&modelManager, &shaderManager, nullptr, &textureManager); assets.LoadCube("demo_cube", 1.0f, 1.0f, 1.0f); - assets.SaveRegistry("registry.toml"); + assets.SaveRegistry("assets/registry.toml"); } entt::registry registry; @@ -288,7 +288,7 @@ void test_scene_loader_asset_keys(void) { TextureManager textureManager; AssetManager assets; assets.SetManagers(&modelManager, &shaderManager, nullptr, &textureManager); - assets.LoadRegistry("registry.toml"); + assets.LoadRegistry("assets/registry.toml"); SetSceneContext(®istry, &modelManager, &shaderManager, &textureManager); SetSceneAssetManager(&assets); @@ -335,6 +335,7 @@ void test_scene_loader_asset_keys(void) { std::filesystem::remove(scenePath); std::filesystem::remove(registryPath); + std::filesystem::remove(registryPath.parent_path()); std::filesystem::remove(tmpDir); }