Merge pull request 'feat/filesystem' (#8) from feat/filesystem into main
Reviewed-on: #8
This commit was merged in pull request #8.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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/<name>.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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
+1
Submodule external/snkv added at a9370019a7
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
class asIScriptEngine;
|
||||
|
||||
void RegisterGlobalsBindings(asIScriptEngine* engine);
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
class asIScriptEngine;
|
||||
|
||||
void RegisterSNKVBindings(asIScriptEngine* engine);
|
||||
+13
-16
@@ -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"
|
||||
|
||||
|
||||
@@ -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<uint8> &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<uint8> &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<uint8> &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<uint8> &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<uint8> &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<uint8> &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;
|
||||
|
||||
+6
-3
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
+34
-4
@@ -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() {
|
||||
|
||||
+32
-15
@@ -12,6 +12,7 @@
|
||||
#include <thread>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <entt.hpp>
|
||||
#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);
|
||||
|
||||
@@ -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<PHYSFS_sint64>(INT32_MAX)) {
|
||||
PHYSFS_close(file);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
char* text = static_cast<char*>(MemAlloc(static_cast<size_t>(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());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
#include "scripting/GlobalsBindings.h"
|
||||
#include "scriptarray.h"
|
||||
#include <angelscript.h>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
enum class ValueType {
|
||||
String,
|
||||
Int,
|
||||
UInt,
|
||||
Int64,
|
||||
Float,
|
||||
Double,
|
||||
Bool,
|
||||
Bytes
|
||||
};
|
||||
|
||||
using ValueData = std::variant<int, asUINT, int64_t, float, double, bool, std::string, std::vector<uint8_t>>;
|
||||
|
||||
struct Value {
|
||||
ValueType type;
|
||||
ValueData data;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, Value> g_values;
|
||||
|
||||
std::vector<uint8_t> ArrayToBytes(const CScriptArray* value) {
|
||||
std::vector<uint8_t> 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<const uint8_t*>(value->At(i));
|
||||
bytes[i] = *element;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
bool BytesToArray(const std::vector<uint8_t>& bytes, CScriptArray* outValue) {
|
||||
if (!outValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue->Resize(static_cast<asUINT>(bytes.size()));
|
||||
for (asUINT i = 0; i < outValue->GetSize(); ++i) {
|
||||
*static_cast<uint8_t*>(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<std::string>(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<int>(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<asUINT>(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<int64_t>(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<float>(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<double>(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<bool>(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<std::vector<uint8_t>>(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<uint8> &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<uint8> &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("");
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
@@ -11,6 +11,7 @@ bool InitPhysFSTest(const std::filesystem::path& writeDir) {
|
||||
g_initialized = true;
|
||||
}
|
||||
|
||||
PhysFSManager::SetEditorEnabled(true);
|
||||
PhysFSManager::SetWriteDir(writeDir.string());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user