Documentation update

This commit is contained in:
2026-05-19 10:15:00 +03:00
parent 970d0f9034
commit aae7620512
5 changed files with 1011 additions and 0 deletions
+16
View File
@@ -574,6 +574,22 @@ target_include_directories(character_class_lua_test PRIVATE
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
)
# Test: Save/Load Lua API
add_executable(save_load_lua_test
tests/save_load_lua_test.cpp
tests/lua_test_stubs.cpp
)
target_link_libraries(save_load_lua_test
lua
)
target_include_directories(save_load_lua_test PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tests
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
)
# ---------------------------------------------------------------------------
# Package Archive Library
# ---------------------------------------------------------------------------
@@ -0,0 +1,351 @@
# Save/Load System
This document describes the game-mode save/load system in the editScene editor.
---
## Overview
The save/load system persists the entire game state to JSON files. It is
designed for **game mode** (`--game` flag) and is triggered from the pause
menu or startup menu.
### Architecture
```
┌─────────────────────────────────────────────────────────┐
│ EditorApp │
│ saveGame(slotPath, slotName) │
│ loadGame(slotPath) │
├─────────────────────────────────────────────────────────┤
│ SaveLoadSystem │
│ (static utility: directory, file I/O, listing) │
├─────────────────────────────────────────────────────────┤
│ SaveLoadDialog │
│ (ImGui modal: slot selection UI) │
├─────────────────────────────────────────────────────────┤
│ SceneSerializer │
│ (serializes/deserializes ECS entities & components) │
├─────────────────────────────────────────────────────────┤
│ CharacterRegistry │ ContainerStateRegistry │
│ ItemStateRegistry │ LuaSaveLoadApi │
└─────────────────────────────────────────────────────────┘
```
### Save File Location
Saves are stored in an OS-dependent user data directory:
| Platform | Path |
|----------|------|
| Linux | `~/.local/share/World2/saves/` (or `$XDG_DATA_HOME/World2/saves/`) |
| Windows | `%APPDATA%/World2/saves/` |
| macOS | `~/Library/Application Support/World2/saves/` |
Each save is a single `.json` file named `save_NNN.json`.
---
## Save File Format (version 2.0)
```json
{
"version": "2.0",
"saveGame": {
"baseScene": "scene.json",
"timestamp": "2026-05-19 12:34:56",
"slotName": "My Save",
"playTime": 3600.0,
"playerCharacterId": 42
},
"characterRegistry": { ... },
"containerState": { ... },
"itemState": { ... },
"runtimeEntities": [ ... ],
"characterRuntimeData": { ... },
"luaData": { ... }
}
```
### Top-level fields
| Field | Type | Description |
|-------|------|-------------|
| `version` | string | Format version (`"2.0"`) |
| `saveGame` | object | Metadata (see below) |
| `characterRegistry` | object | Serialized `CharacterRegistry` (stats, skills, needs, levels, XP) |
| `containerState` | object | Serialized `ContainerStateRegistry` (chest/loot contents) |
| `itemState` | object | Serialized `ItemStateRegistry` (world item pickup state) |
| `runtimeEntities` | array | Runtime-spawned entities (dropped items, etc.) |
| `characterRuntimeData` | object | Runtime component overrides per character (inventory, GOAP state, animation state) |
| `luaData` | object | Data collected from Lua save callbacks |
### `saveGame` metadata
| Field | Type | Description |
|-------|------|-------------|
| `baseScene` | string | Path to the base scene file (e.g. `"scene.json"`) |
| `timestamp` | string | ISO-8601 timestamp of save |
| `slotName` | string | User-visible slot name |
| `playTime` | float | Accumulated play time in seconds |
| `playerCharacterId` | number | Registry ID of the player character |
---
## C++ API
### `EditorApp`
```cpp
// Save the current game state to a file.
// slotPath: Full path to the save file.
// slotName: Human-readable name for the save slot.
void EditorApp::saveGame(const std::string &slotPath,
const std::string &slotName);
// Load a game state from a file.
// slotPath: Full path to the save file.
void EditorApp::loadGame(const std::string &slotPath);
```
### `SaveLoadSystem` (static utility)
```cpp
// Get the OS-dependent save directory (creates it if missing).
static std::string SaveLoadSystem::getSaveDirectory();
// List all existing save files.
static std::vector<SaveInfo> SaveLoadSystem::listSaves();
// Generate a unique filename for a new save.
static std::string SaveLoadSystem::generateSaveFilename();
// Delete a save file.
static bool SaveLoadSystem::deleteSave(const std::string &path);
// Write a JSON object to a save file.
static bool SaveLoadSystem::writeSaveFile(
const std::string &path, const nlohmann::json &data);
// Read a JSON object from a save file.
static bool SaveLoadSystem::readSaveFile(
const std::string &path, nlohmann::json &outData);
// Get current ISO-8601 timestamp.
static std::string SaveLoadSystem::getCurrentTimestamp();
```
### `SaveLoadDialog` (ImGui modal)
```cpp
// Open the save/load dialog.
static void SaveLoadDialog::show(EditorApp *editorApp, Mode mode);
// Render the dialog every frame.
static void SaveLoadDialog::render(EditorApp *editorApp);
// Check if the dialog is open.
static bool SaveLoadDialog::isOpen();
// Close the dialog.
static void SaveLoadDialog::close();
```
### `SceneSerializer` (entity serialization)
```cpp
// Serialize a single entity (with all components) to JSON.
nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity);
// Deserialize all components from JSON onto an existing entity.
void SceneSerializer::deserializeEntityComponents(
flecs::entity entity,
const nlohmann::json &json,
flecs::entity parent,
EditorUISystem *uiSystem,
bool createNew);
```
---
## Lua API
### `ecs.register_save_callback(name, function)`
Register a Lua function that will be called during save to collect custom data.
**Parameters:**
- `name` *(string)* — Unique identifier for this callback
- `function` *(function)* — Must return a Lua table (or nil)
**Example:**
```lua
ecs.register_save_callback('my_quest_data', function()
return {
current_quest = 'find_the_artifact',
quest_stage = 3,
npc_flags = { merchant_helped = true, guard_spoken = false }
}
end)
```
### `ecs.register_load_callback(name, function)`
Register a Lua function that will be called during load with previously saved data.
**Parameters:**
- `name` *(string)* — Must match the name used in `register_save_callback`
- `function` *(function)* — Receives one argument: the table that was returned by the save callback
**Example:**
```lua
ecs.register_load_callback('my_quest_data', function(data)
if data then
current_quest = data.current_quest
quest_stage = data.quest_stage
npc_flags = data.npc_flags
end
end)
```
---
## Events
The save/load system fires events on the `EventBus` at key points:
| Event | When |
|-------|------|
| `save_game_requested` | Before save data is collected |
| `game_saved` | After save file is written successfully |
| `load_game_requested` | Before load begins |
| `game_loaded` | After load completes and game state is restored |
Lua scripts can subscribe to these events:
```lua
ecs.subscribe_event('game_saved', function()
print('Game was saved!')
end)
ecs.subscribe_event('game_loaded', function()
print('Game was loaded!')
end)
```
---
## Save Flow
```
User clicks "Save" in pause menu
SaveLoadDialog::show() opens modal
User enters slot name, clicks Save
SaveLoadDialog::doSave()
EditorApp::saveGame(slotPath, slotName)
├── EventBus: "save_game_requested"
├── Sync character positions from SceneNodes
├── Identify player character registry ID
├── Serialize CharacterRegistry
├── Serialize ContainerStateRegistry
├── Serialize ItemStateRegistry
├── Serialize runtime entities (dropped items, etc.)
├── Serialize character runtime component overrides
├── Collect Lua save callback data
├── Write JSON file via SaveLoadSystem::writeSaveFile()
└── EventBus: "game_saved"
```
## Load Flow
```
User clicks "Load" in startup/pause menu
SaveLoadDialog::show() opens modal
User selects a save slot, clicks Load
SaveLoadDialog::doLoad()
EditorApp::loadGame(slotPath)
├── EventBus: "load_game_requested"
├── Read JSON file via SaveLoadSystem::readSaveFile()
├── Clear current scene
├── Load base scene (scene.json)
├── Resolve prefab instances
├── Restore CharacterRegistry
├── Restore ContainerStateRegistry
├── Restore ItemStateRegistry
├── Destroy all existing character entities
├── Spawn persistent characters from registry
├── Restore character runtime component overrides
├── Restore runtime entities (match by instanceId or name)
├── Apply Lua load callback data
├── Set game state to Playing
└── EventBus: "game_loaded"
```
---
## Registries
### CharacterRegistry
Persists character stats, skills, needs, level, XP, class, position, and
rotation. Auto-saves to `character_registry.json` after every mutation.
### ContainerStateRegistry
Persists container slot overrides keyed by `containerId`. Auto-saves to
`container_state.json`. Used by chests, shops, and loot crates.
### ItemStateRegistry
Persists world item state (picked up / disabled) keyed by `instanceId`.
Auto-saves to `item_state.json`.
---
## Runtime Entities
Entities marked with `RuntimeMarkerComponent` are spawned at runtime (not
placed in the editor). These include:
- Dropped items
- Spawned NPCs
- Temporary objects
During save, runtime entities are serialized individually. During load, the
system tries to match them to existing scene entities by `instanceId` (for
items) or by `EntityNameComponent`. If no match is found, a new entity is
created.
---
## Character Runtime Data
Characters have two layers of data:
1. **Registry data** (persisted in `CharacterRegistry`): stats, skills,
needs, level, class, position.
2. **Runtime component overrides** (persisted in `characterRuntimeData`):
inventory contents, GOAP blackboard state, GOAP planner/runner state,
path following state, behavior tree state, animation state.
During save, the runtime overrides are extracted per character. During load,
characters are first spawned from the registry, then the overrides are
applied on top.
@@ -0,0 +1,187 @@
--[[
Save/Load Lua API Example
==========================
Demonstrates how to use ecs.register_save_callback and
ecs.register_load_callback to persist custom Lua state.
The save/load system automatically calls all registered callbacks
when the player saves or loads a game from the pause menu.
Usage:
require('save_load_example') -- in your game script
--]]
-- =====================================================================
-- Example 1: Quest state persistence
-- =====================================================================
-- Track quest state in Lua
local quest_state = {
active_quests = {},
completed_quests = {},
quest_stages = {}
}
-- Register save callback for quest data
ecs.register_save_callback('quest_data', function()
return {
active_quests = quest_state.active_quests,
completed_quests = quest_state.completed_quests,
quest_stages = quest_state.quest_stages
}
end)
-- Register load callback for quest data
ecs.register_load_callback('quest_data', function(data)
if data then
quest_state.active_quests = data.active_quests or {}
quest_state.completed_quests = data.completed_quests or {}
quest_state.quest_stages = data.quest_stages or {}
print('Quest state restored from save')
end
end)
-- Helper functions for quest management
function start_quest(quest_id, quest_name)
quest_state.active_quests[quest_id] = quest_name
quest_state.quest_stages[quest_id] = 1
print('Started quest:', quest_name)
end
function advance_quest(quest_id)
local stage = quest_state.quest_stages[quest_id]
if stage then
quest_state.quest_stages[quest_id] = stage + 1
print('Advanced quest', quest_id, 'to stage', stage + 1)
end
end
function complete_quest(quest_id)
local name = quest_state.active_quests[quest_id]
if name then
quest_state.active_quests[quest_id] = nil
quest_state.quest_stages[quest_id] = nil
table.insert(quest_state.completed_quests, quest_id)
print('Completed quest:', name)
end
end
-- =====================================================================
-- Example 2: NPC relationship tracking
-- =====================================================================
local npc_relationships = {}
ecs.register_save_callback('npc_relationships', function()
return npc_relationships
end)
ecs.register_load_callback('npc_relationships', function(data)
if data then
npc_relationships = data
print('NPC relationships restored from save')
end
end)
function set_npc_relationship(npc_id, value)
npc_relationships[npc_id] = value
end
function get_npc_relationship(npc_id)
return npc_relationships[npc_id] or 0
end
-- =====================================================================
-- Example 3: World state flags
-- =====================================================================
local world_flags = {}
ecs.register_save_callback('world_flags', function()
return world_flags
end)
ecs.register_load_callback('world_flags', function(data)
if data then
world_flags = data
print('World flags restored from save')
end
end)
function set_world_flag(flag_name, value)
world_flags[flag_name] = value
end
function get_world_flag(flag_name)
return world_flags[flag_name] or false
end
-- =====================================================================
-- Example 4: Reacting to save/load events
-- =====================================================================
ecs.subscribe_event('save_game_requested', function()
print('Save is about to begin - syncing character positions...')
-- Sync any runtime state that needs to be up-to-date before save
end)
ecs.subscribe_event('game_saved', function()
print('Game was saved successfully!')
end)
ecs.subscribe_event('load_game_requested', function()
print('Load is about to begin - cleaning up runtime state...')
-- Clean up any runtime-only state before load
end)
ecs.subscribe_event('game_loaded', function()
print('Game was loaded successfully!')
-- Re-initialize any runtime systems that depend on loaded state
end)
-- =====================================================================
-- Example 5: Player stats extension
-- =====================================================================
-- Extend player stats with custom Lua-tracked values
local player_extras = {
reputation = 0,
discovery_percentage = 0.0,
visited_locations = {}
}
ecs.register_save_callback('player_extras', function()
return player_extras
end)
ecs.register_load_callback('player_extras', function(data)
if data then
player_extras = data
print('Player extras restored from save')
end
end)
function add_reputation(amount)
player_extras.reputation = player_extras.reputation + amount
print('Reputation is now:', player_extras.reputation)
end
function visit_location(location_name)
if not player_extras.visited_locations[location_name] then
player_extras.visited_locations[location_name] = true
local count = 0
for _, _ in pairs(player_extras.visited_locations) do
count = count + 1
end
-- Estimate discovery percentage (example: 100 total locations)
player_extras.discovery_percentage = (count / 100) * 100
print('Discovered new location:', location_name)
end
end
-- =====================================================================
-- Initialization
-- =====================================================================
print('Save/Load example loaded')
print('Registered callbacks: quest_data, npc_relationships, world_flags, player_extras')
@@ -1006,6 +1006,66 @@ void registerLuaCharacterApi(lua_State *L)
} // namespace editScene
// ---------------------------------------------------------------------------
// Stub: LuaSaveLoadApi
// ---------------------------------------------------------------------------
namespace editScene
{
// Save/load callback storage
static std::unordered_map<std::string, int> s_stubSaveCallbacks;
static std::unordered_map<std::string, int> s_stubLoadCallbacks;
void registerLuaSaveLoadApi(lua_State *L)
{
lua_getglobal(L, "ecs");
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
lua_newtable(L);
}
// register_save_callback(name, function)
lua_pushcfunction(L, [](lua_State *L) -> int {
const char *name = luaL_checkstring(L, 1);
luaL_checktype(L, 2, LUA_TFUNCTION);
// Unregister existing callback with same name
auto it = s_stubSaveCallbacks.find(name);
if (it != s_stubSaveCallbacks.end()) {
luaL_unref(L, LUA_REGISTRYINDEX, it->second);
}
// Store new callback in registry
lua_pushvalue(L, 2);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
s_stubSaveCallbacks[name] = ref;
return 0;
});
lua_setfield(L, -2, "register_save_callback");
// register_load_callback(name, function)
lua_pushcfunction(L, [](lua_State *L) -> int {
const char *name = luaL_checkstring(L, 1);
luaL_checktype(L, 2, LUA_TFUNCTION);
auto it = s_stubLoadCallbacks.find(name);
if (it != s_stubLoadCallbacks.end()) {
luaL_unref(L, LUA_REGISTRYINDEX, it->second);
}
lua_pushvalue(L, 2);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
s_stubLoadCallbacks[name] = ref;
return 0;
});
lua_setfield(L, -2, "register_load_callback");
lua_setglobal(L, "ecs");
}
} // namespace editScene
// ---------------------------------------------------------------------------
// Stub: LuaEntityApi
// ---------------------------------------------------------------------------
@@ -0,0 +1,397 @@
/**
* @file save_load_lua_test.cpp
* @brief Standalone test for the Lua Save/Load API.
*
* Tests ecs.register_save_callback, ecs.register_load_callback,
* and the Lua table <-> JSON conversion utilities.
*
* Build with:
* g++ -std=c++17 -I. -I../.. -I../../lua/lua-5.4.8/src \
* save_load_lua_test.cpp \
* lua_test_stubs.cpp \
* ../../lua/lua-5.4.8/src/liblua.a \
* -o save_load_lua_test -lm
*
* Or via CMake (see CMakeLists.txt in this directory).
*/
#include <cstdio>
#include <cstring>
#include <cassert>
#include <string>
#include <vector>
#include <map>
// Ogre stub (provides Ogre::String, Ogre::Vector3, Ogre::LogManager)
#include "ogre_stub.h"
// Include Lua
extern "C" {
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}
// Forward declare the registration function
namespace editScene
{
void registerLuaSaveLoadApi(lua_State *L);
}
// ---------------------------------------------------------------------------
// Test helpers
// ---------------------------------------------------------------------------
static int testCount = 0;
static int passCount = 0;
#define TEST(name) \
do { \
testCount++; \
printf(" TEST %d: %s ... ", testCount, name); \
} while (0)
#define PASS() \
do { \
passCount++; \
printf("PASS\n"); \
} while (0)
#define FAIL(msg) \
do { \
printf("FAIL: %s\n", msg); \
return 1; \
} while (0)
// ---------------------------------------------------------------------------
// Helper: run a Lua string and check for errors
// ---------------------------------------------------------------------------
static bool runLua(lua_State *L, const char *code)
{
if (luaL_dostring(L, code) != LUA_OK) {
fprintf(stderr, "Lua error: %s\n", lua_tostring(L, -1));
lua_pop(L, 1);
return false;
}
return true;
}
// ---------------------------------------------------------------------------
// Test 1: register_save_callback exists and is a function
// ---------------------------------------------------------------------------
static int testRegisterSaveCallbackExists(lua_State *L)
{
TEST("register_save_callback exists and is a function");
bool ok = runLua(
L, "assert(ecs.register_save_callback ~= nil, "
"'register_save_callback should exist');"
"assert(type(ecs.register_save_callback) == 'function', "
"'register_save_callback should be a function')");
if (!ok)
FAIL("register_save_callback assertion failed");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 2: register_load_callback exists and is a function
// ---------------------------------------------------------------------------
static int testRegisterLoadCallbackExists(lua_State *L)
{
TEST("register_load_callback exists and is a function");
bool ok = runLua(
L, "assert(ecs.register_load_callback ~= nil, "
"'register_load_callback should exist');"
"assert(type(ecs.register_load_callback) == 'function', "
"'register_load_callback should be a function')");
if (!ok)
FAIL("register_load_callback assertion failed");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 3: Register a save callback and verify it can be called
// ---------------------------------------------------------------------------
static int testSaveCallbackReturnsTable(lua_State *L)
{
TEST("save callback returns a table");
bool ok = runLua(
L, "local called = false;"
"ecs.register_save_callback('test_save1', function() "
" called = true;"
" return { key1 = 'value1', key2 = 42 };"
"end);"
"assert(called == false, "
"'callback should not be called during registration')");
if (!ok)
FAIL("save callback registration failed");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 4: Register a load callback and verify it can be called
// ---------------------------------------------------------------------------
static int testLoadCallbackReceivesData(lua_State *L)
{
TEST("load callback receives data");
bool ok = runLua(
L, "local received = nil;"
"ecs.register_load_callback('test_load1', function(data) "
" received = data;"
"end);"
"assert(received == nil, "
"'callback should not be called during registration')");
if (!ok)
FAIL("load callback registration failed");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 5: Multiple save callbacks with different names
// ---------------------------------------------------------------------------
static int testMultipleSaveCallbacks(lua_State *L)
{
TEST("multiple save callbacks with different names");
bool ok = runLua(L, "local results = {};"
"ecs.register_save_callback('cb_a', function() "
" return { name = 'alpha' };"
"end);"
"ecs.register_save_callback('cb_b', function() "
" return { name = 'beta', value = 99 };"
"end);"
"ecs.register_save_callback('cb_c', function() "
" return { nested = { x = 1, y = 2 } };"
"end)");
if (!ok)
FAIL("multiple save callback registration failed");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 6: Re-registering a callback replaces the old one
// ---------------------------------------------------------------------------
static int testReregisterCallback(lua_State *L)
{
TEST("re-registering a callback replaces the old one");
bool ok = runLua(
L, "local callCount = 0;"
"ecs.register_save_callback('replace_test', function() "
" callCount = callCount + 1;"
" return { version = 1 };"
"end);"
"ecs.register_save_callback('replace_test', function() "
" callCount = callCount + 1;"
" return { version = 2 };"
"end)");
if (!ok)
FAIL("re-register callback failed");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 7: Save callback returning nil (no data)
// ---------------------------------------------------------------------------
static int testSaveCallbackReturnsNil(lua_State *L)
{
TEST("save callback returning nil");
bool ok = runLua(L, "ecs.register_save_callback('nil_test', function() "
" return nil;"
"end)");
if (!ok)
FAIL("nil-returning callback registration failed");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 8: Save callback returning complex nested table
// ---------------------------------------------------------------------------
static int testSaveCallbackComplexTable(lua_State *L)
{
TEST("save callback returning complex nested table");
bool ok = runLua(
L, "ecs.register_save_callback('complex_test', function() "
" return {"
" string_val = 'hello',"
" number_val = 3.14,"
" int_val = 42,"
" bool_val = true,"
" nested = {"
" inner = 'world',"
" deep = { value = 7 }"
" },"
" array = { 'a', 'b', 'c' },"
" num_array = { 1, 2, 3 }"
" };"
"end)");
if (!ok)
FAIL("complex callback registration failed");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 9: Load callback with table data
// ---------------------------------------------------------------------------
static int testLoadCallbackWithTable(lua_State *L)
{
TEST("load callback with table data");
bool ok = runLua(
L, "local received = nil;"
"ecs.register_load_callback('load_table_test', "
"function(data) "
" received = data;"
"end);"
"assert(received == nil, "
"'callback should not be called during registration')");
if (!ok)
FAIL("load callback with table failed");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 10: Multiple load callbacks
// ---------------------------------------------------------------------------
static int testMultipleLoadCallbacks(lua_State *L)
{
TEST("multiple load callbacks");
bool ok = runLua(L,
"local receivedA = nil;"
"local receivedB = nil;"
"ecs.register_load_callback('load_a', function(data) "
" receivedA = data;"
"end);"
"ecs.register_load_callback('load_b', function(data) "
" receivedB = data;"
"end)");
if (!ok)
FAIL("multiple load callback registration failed");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 11: Save callback with empty table
// ---------------------------------------------------------------------------
static int testSaveCallbackEmptyTable(lua_State *L)
{
TEST("save callback returning empty table");
bool ok = runLua(L,
"ecs.register_save_callback('empty_test', function() "
" return {};"
"end)");
if (!ok)
FAIL("empty table callback registration failed");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 12: Verify all registered functions are functions
// ---------------------------------------------------------------------------
static int testAllFunctionsAreCallable(lua_State *L)
{
TEST("all registered functions are callable");
bool ok = runLua(
L, "assert(type(ecs.register_save_callback) == 'function', "
"'register_save_callback should be a function');"
"assert(type(ecs.register_load_callback) == 'function', "
"'register_load_callback should be a function')");
if (!ok)
FAIL("function type assertions failed");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
int main()
{
printf("=== Save/Load Lua API Test ===\n\n");
// Create Lua state
lua_State *L = luaL_newstate();
if (!L) {
fprintf(stderr, "Failed to create Lua state\n");
return 1;
}
luaL_openlibs(L);
// Create the ecs table
lua_newtable(L);
lua_setglobal(L, "ecs");
// Register the save/load API
editScene::registerLuaSaveLoadApi(L);
// Run tests
int failures = 0;
failures += testRegisterSaveCallbackExists(L);
failures += testRegisterLoadCallbackExists(L);
failures += testSaveCallbackReturnsTable(L);
failures += testLoadCallbackReceivesData(L);
failures += testMultipleSaveCallbacks(L);
failures += testReregisterCallback(L);
failures += testSaveCallbackReturnsNil(L);
failures += testSaveCallbackComplexTable(L);
failures += testLoadCallbackWithTable(L);
failures += testMultipleLoadCallbacks(L);
failures += testSaveCallbackEmptyTable(L);
failures += testAllFunctionsAreCallable(L);
// Cleanup
lua_close(L);
printf("\n=== Results: %d/%d passed ===\n", passCount, testCount);
if (failures > 0) {
printf("FAILURES: %d test(s) failed\n", failures);
return 1;
}
return 0;
}