From d113dba42f2daefa178a9104ceecd9237a3a7331 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Tue, 5 Nov 2024 02:38:36 -0500 Subject: [PATCH] Almost working save sytem but the data I store is totally wrong. I need to also save the entity IDs being used and map them to the components. --- combat.cpp | 18 ++++++----- combat.hpp | 16 +++++----- components.hpp | 8 ++++- dinkyecs.hpp | 3 ++ gui.cpp | 11 ++++++- gui.hpp | 1 + meson.build | 3 ++ point.hpp | 3 ++ save.cpp | 63 ++++++++++++++++++++++++++++++++++++++ save.hpp | 21 +++++++++++++ systems.cpp | 2 +- systems.hpp | 2 +- tests/config.cpp | 45 +-------------------------- tests/save.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ tser.hpp | 1 + 15 files changed, 213 insertions(+), 64 deletions(-) create mode 100644 save.cpp create mode 100644 save.hpp create mode 100644 tests/save.cpp diff --git a/combat.cpp b/combat.cpp index 6b4fea2..48d38fb 100644 --- a/combat.cpp +++ b/combat.cpp @@ -1,14 +1,16 @@ #include "combat.hpp" #include "rand.hpp" -int Combat::attack(Combat &target) { - int attack = Random::uniform(0,1); - int my_dmg = 0; +namespace components { + int Combat::attack(Combat &target) { + int attack = Random::uniform(0,1); + int my_dmg = 0; - if(attack) { - my_dmg = Random::uniform(1, damage); - target.hp -= my_dmg; - } + if(attack) { + my_dmg = Random::uniform(1, damage); + target.hp -= my_dmg; + } - return my_dmg; + return my_dmg; + } } diff --git a/combat.hpp b/combat.hpp index a5ffc30..a7c48f9 100644 --- a/combat.hpp +++ b/combat.hpp @@ -1,11 +1,11 @@ #pragma once -#include "components.hpp" +namespace components { + struct Combat { + int hp; + int damage; + bool dead; -struct Combat { - int hp; - int damage; - bool dead; - - int attack(Combat &target); -}; + int attack(Combat &target); + }; +} diff --git a/components.hpp b/components.hpp index 7f0ebb1..ab74de0 100644 --- a/components.hpp +++ b/components.hpp @@ -3,27 +3,33 @@ #include "map.hpp" #include "combat.hpp" #include +#include "tser.hpp" -namespace Components { +namespace components { struct Player { DinkyECS::Entity entity; + DEFINE_SERIALIZABLE(Player, entity); }; struct Position { Point location; + DEFINE_SERIALIZABLE(Position, location); }; struct Motion { int dx; int dy; + DEFINE_SERIALIZABLE(Motion, dx, dy); }; struct Treasure { int amount; + DEFINE_SERIALIZABLE(Treasure, amount); }; struct Tile { std::string chr = "!"; + DEFINE_SERIALIZABLE(Tile, chr); }; struct MapConfig { diff --git a/dinkyecs.hpp b/dinkyecs.hpp index 399115b..c8d8190 100644 --- a/dinkyecs.hpp +++ b/dinkyecs.hpp @@ -7,6 +7,7 @@ #include #include #include +#include "tser.hpp" namespace DinkyECS { @@ -116,4 +117,6 @@ namespace DinkyECS { return !queue.empty(); } }; + + } diff --git a/gui.cpp b/gui.cpp index 51c10f5..ca667ee 100644 --- a/gui.cpp +++ b/gui.cpp @@ -24,7 +24,7 @@ using std::string; using namespace fmt; using namespace std::chrono_literals; using namespace ftxui; -using namespace Components; +using namespace components; GUI::GUI(DinkyECS::World &world, Map& game_map) : @@ -56,6 +56,13 @@ void GUI::resize_map(int new_size) { } } +void GUI::save_world() { + tser::BinaryArchive archive; + archive.save($world); + std::string_view archive_view = archive.get_buffer(); + $log.log(format("Game saved! {} bytes.", archive_view.size())); +} + void GUI::create_renderer() { Terminal::SetColorSupport(Terminal::Color::TrueColor); auto player = $world.get_the(); @@ -151,6 +158,8 @@ bool GUI::handle_ui_events() { resize_map(map_font_size + 10); } else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Hyphen)) { resize_map(map_font_size - 10); + } else if(sf::Keyboard::isKeyPressed(sf::Keyboard::S)) { + save_world(); } } } diff --git a/gui.hpp b/gui.hpp index e727b9b..b7bb703 100644 --- a/gui.hpp +++ b/gui.hpp @@ -58,6 +58,7 @@ public: void handle_world_events(); void draw_screen(bool clear=true, float map_off_x=0.0f, float map_off_y=0.0f); void run_systems(); + void save_world(); int main(); }; diff --git a/meson.build b/meson.build index e271b94..19a4486 100644 --- a/meson.build +++ b/meson.build @@ -21,6 +21,7 @@ runtests = executable('runtests', [ 'collider.cpp', 'ansi_parser.cpp', 'config.cpp', + 'save.cpp', 'tests/fsm.cpp', 'tests/dbc.cpp', 'tests/map.cpp', @@ -29,6 +30,7 @@ runtests = executable('runtests', [ 'tests/dinkyecs.cpp', 'tests/ansi_parser.cpp', 'tests/config.cpp', + 'tests/save.cpp', ], dependencies: dependencies) @@ -45,6 +47,7 @@ roguish = executable('roguish', [ 'ansi_parser.cpp', 'render.cpp', 'config.cpp', + 'save.cpp', ], dependencies: dependencies) diff --git a/point.hpp b/point.hpp index 7d2a5da..e67f742 100644 --- a/point.hpp +++ b/point.hpp @@ -1,4 +1,5 @@ #pragma once +#include "tser.hpp" struct Point { size_t x = 0; @@ -7,6 +8,8 @@ struct Point { bool operator==(const Point& other) const { return other.x == x && other.y == y; } + + DEFINE_SERIALIZABLE(Point, x, y); }; typedef std::vector PointList; diff --git a/save.cpp b/save.cpp new file mode 100644 index 0000000..175a46c --- /dev/null +++ b/save.cpp @@ -0,0 +1,63 @@ +#include "save.hpp" +#include +#include "dbc.hpp" +#include + +using namespace components; + + +template +inline void extract(DinkyECS::World &world, std::vector &into) { + auto from_world = world.entity_map_for(); + for(auto [entity, value] : from_world) { + into.push_back(std::any_cast(value)); + } +} + +void save::to_file(std::string path, DinkyECS::World &world) { + SaveData save_data; + tser::BinaryArchive archive; + + save_data.player = world.get_the(); + extract(world, save_data.position); + extract(world, save_data.combat); + extract(world, save_data.motion); + + archive.save(save_data); + std::string_view archive_view = archive.get_buffer(); + + std::ofstream out(path, std::ios::binary); + out << archive_view; + out.flush(); +} + + +void save::from_file(std::string path, DinkyECS::World &world_out) { + tser::BinaryArchive archive(0); + + if(std::ifstream in_file{path, std::ios::binary | std::ios::ate}) { + auto size = in_file.tellg(); + std::string in_data(size, '\0'); + in_file.seekg(0); + + if(in_file.read(&in_data[0], size)) { + std::string_view in_view(in_data); + archive.initialize(in_view); + } else { + dbc::sentinel(fmt::format("wrong size or error reading {}", path)); + } + } else { + dbc::sentinel(fmt::format("failed to load file {}", path)); + } + + auto save_data = archive.load(); + + // BUG: need the entities! + world_out.set_the(save_data.player); + + for(auto position : save_data.position) { + auto entity = world_out.entity(); + // BUG: actually do need the entities + world_out.set(entity, position); + } +} diff --git a/save.hpp b/save.hpp new file mode 100644 index 0000000..6eedef0 --- /dev/null +++ b/save.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "components.hpp" +#include "dinkyecs.hpp" +#include "tser.hpp" +#include +#include + +namespace save { + struct SaveData { + components::Player player; + std::vector position; + std::vector motion; + std::vector combat; + + DEFINE_SERIALIZABLE(SaveData, player, position, motion, combat); + }; + + void to_file(std::string path, DinkyECS::World &world); + void from_file(std::string path, DinkyECS::World &world_out); +} diff --git a/systems.cpp b/systems.cpp index 6d792c8..4802863 100644 --- a/systems.cpp +++ b/systems.cpp @@ -10,7 +10,7 @@ using std::string; using namespace fmt; -using namespace Components; +using namespace components; using ftxui::Color; void System::enemy_pathing(DinkyECS::World &world, Map &game_map, Player &player) { diff --git a/systems.hpp b/systems.hpp index 732fe29..9f1dc31 100644 --- a/systems.hpp +++ b/systems.hpp @@ -4,7 +4,7 @@ #include "components.hpp" #include -using namespace Components; +using namespace components; namespace System { void motion(DinkyECS::World &world, Map &game_map); diff --git a/tests/config.cpp b/tests/config.cpp index bbd2f58..1231107 100644 --- a/tests/config.cpp +++ b/tests/config.cpp @@ -6,6 +6,7 @@ #include "dinkyecs.hpp" #include "components.hpp" + using namespace fmt; using std::string; @@ -61,47 +62,3 @@ TEST_CASE("can go into a world", "[config]") { Config &cfg = world.get_the(); REQUIRE(cfg["types"]["NUMBER"] == 1234); } - - -#include -#include -#include "tser.hpp" - -enum class Item : char { - RADAR = 'R', - TRAP = 'T', - ORE = 'O' -}; - -struct Pixel { - int x = 0; - int y = 0; - - DEFINE_SERIALIZABLE(Pixel, x, y); -}; - -struct Robot { - Pixel point; - std::wstring name; - std::optional item; - - DEFINE_SERIALIZABLE(Robot, point, name, item); -}; - -TEST_CASE("test using tser for serialization", "[config]") { - auto robot = Robot{ Pixel{3,4}, L"BIG NAME", Item::RADAR}; - std::cout << robot << '\n'; - - tser::BinaryArchive archive; - archive.save(robot); - std::string_view archive_view = archive.get_buffer(); - - tser::BinaryArchive archive2(0); - archive2.initialize(archive_view); - auto loadedRobot = archive2.load(); - - REQUIRE(loadedRobot.point.x == robot.point.x); - REQUIRE(loadedRobot.point.y == robot.point.y); - REQUIRE(loadedRobot.name == robot.name); - REQUIRE(loadedRobot.item == robot.item); -} diff --git a/tests/save.cpp b/tests/save.cpp new file mode 100644 index 0000000..20c29c3 --- /dev/null +++ b/tests/save.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include "dinkyecs.hpp" +#include "components.hpp" +#include "save.hpp" +#include +#include +#include "tser.hpp" + +using namespace fmt; +using std::string; +using namespace components; + +enum class Item : char { + RADAR = 'R', + TRAP = 'T', + ORE = 'O' +}; + +struct Pixel { + int x = 0; + int y = 0; + + DEFINE_SERIALIZABLE(Pixel, x, y); +}; + +struct Robot { + Pixel point; + std::wstring name; + std::optional item; + + DEFINE_SERIALIZABLE(Robot, point, name, item); +}; + + +TEST_CASE("test using tser for serialization", "[config]") { + auto robot = Robot{ Pixel{3,4}, L"BIG NAME", Item::RADAR}; + std::cout << robot << '\n'; + + tser::BinaryArchive archive; + archive.save(robot); + std::string_view archive_view = archive.get_buffer(); + + tser::BinaryArchive archive2(0); + archive2.initialize(archive_view); + auto loadedRobot = archive2.load(); + + REQUIRE(loadedRobot.point.x == robot.point.x); + REQUIRE(loadedRobot.point.y == robot.point.y); + REQUIRE(loadedRobot.name == robot.name); + REQUIRE(loadedRobot.item == robot.item); +} + +TEST_CASE("basic save a world", "[save]") { + DinkyECS::World world; + + // configure a player as a fact of the world + Player player{world.entity()}; + world.set_the(player); + + world.set(player.entity, {10,10}); + world.set(player.entity, {0, 0}); + world.set(player.entity, {100, 10}); + + save::to_file("./savetest.world", world); + + DinkyECS::World in_world; + save::from_file("./savetest.world", in_world); + + Position &position1 = world.get(player.entity); + Position &position2 = in_world.get(player.entity); + + // BUGGGGGGGG! This doesn't actually work, it's all fake + // The world uses an internal id to increment entities so + // by default player gets the first one, but all data after + // that is wrong. + REQUIRE(position1.location.x == position2.location.x); + REQUIRE(position1.location.y == position2.location.y); +} diff --git a/tser.hpp b/tser.hpp index 0c56038..b5a60d0 100644 --- a/tser.hpp +++ b/tser.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include namespace tser{