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.

main
Zed A. Shaw 2 weeks ago
parent babc190525
commit d113dba42f
  1. 18
      combat.cpp
  2. 16
      combat.hpp
  3. 8
      components.hpp
  4. 3
      dinkyecs.hpp
  5. 11
      gui.cpp
  6. 1
      gui.hpp
  7. 3
      meson.build
  8. 3
      point.hpp
  9. 63
      save.cpp
  10. 21
      save.hpp
  11. 2
      systems.cpp
  12. 2
      systems.hpp
  13. 45
      tests/config.cpp
  14. 80
      tests/save.cpp
  15. 1
      tser.hpp

@ -1,14 +1,16 @@
#include "combat.hpp" #include "combat.hpp"
#include "rand.hpp" #include "rand.hpp"
int Combat::attack(Combat &target) { namespace components {
int attack = Random::uniform<int>(0,1); int Combat::attack(Combat &target) {
int my_dmg = 0; int attack = Random::uniform<int>(0,1);
int my_dmg = 0;
if(attack) { if(attack) {
my_dmg = Random::uniform<int>(1, damage); my_dmg = Random::uniform<int>(1, damage);
target.hp -= my_dmg; target.hp -= my_dmg;
} }
return my_dmg; return my_dmg;
}
} }

@ -1,11 +1,11 @@
#pragma once #pragma once
#include "components.hpp" namespace components {
struct Combat {
int hp;
int damage;
bool dead;
struct Combat { int attack(Combat &target);
int hp; };
int damage; }
bool dead;
int attack(Combat &target);
};

@ -3,27 +3,33 @@
#include "map.hpp" #include "map.hpp"
#include "combat.hpp" #include "combat.hpp"
#include <deque> #include <deque>
#include "tser.hpp"
namespace Components { namespace components {
struct Player { struct Player {
DinkyECS::Entity entity; DinkyECS::Entity entity;
DEFINE_SERIALIZABLE(Player, entity);
}; };
struct Position { struct Position {
Point location; Point location;
DEFINE_SERIALIZABLE(Position, location);
}; };
struct Motion { struct Motion {
int dx; int dx;
int dy; int dy;
DEFINE_SERIALIZABLE(Motion, dx, dy);
}; };
struct Treasure { struct Treasure {
int amount; int amount;
DEFINE_SERIALIZABLE(Treasure, amount);
}; };
struct Tile { struct Tile {
std::string chr = "!"; std::string chr = "!";
DEFINE_SERIALIZABLE(Tile, chr);
}; };
struct MapConfig { struct MapConfig {

@ -7,6 +7,7 @@
#include <any> #include <any>
#include <tuple> #include <tuple>
#include <queue> #include <queue>
#include "tser.hpp"
namespace DinkyECS { namespace DinkyECS {
@ -116,4 +117,6 @@ namespace DinkyECS {
return !queue.empty(); return !queue.empty();
} }
}; };
} }

@ -24,7 +24,7 @@ using std::string;
using namespace fmt; using namespace fmt;
using namespace std::chrono_literals; using namespace std::chrono_literals;
using namespace ftxui; using namespace ftxui;
using namespace Components; using namespace components;
GUI::GUI(DinkyECS::World &world, Map& game_map) : 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() { void GUI::create_renderer() {
Terminal::SetColorSupport(Terminal::Color::TrueColor); Terminal::SetColorSupport(Terminal::Color::TrueColor);
auto player = $world.get_the<Player>(); auto player = $world.get_the<Player>();
@ -151,6 +158,8 @@ bool GUI::handle_ui_events() {
resize_map(map_font_size + 10); resize_map(map_font_size + 10);
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Hyphen)) { } else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Hyphen)) {
resize_map(map_font_size - 10); resize_map(map_font_size - 10);
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::S)) {
save_world();
} }
} }
} }

@ -58,6 +58,7 @@ public:
void handle_world_events(); void handle_world_events();
void draw_screen(bool clear=true, float map_off_x=0.0f, float map_off_y=0.0f); void draw_screen(bool clear=true, float map_off_x=0.0f, float map_off_y=0.0f);
void run_systems(); void run_systems();
void save_world();
int main(); int main();
}; };

@ -21,6 +21,7 @@ runtests = executable('runtests', [
'collider.cpp', 'collider.cpp',
'ansi_parser.cpp', 'ansi_parser.cpp',
'config.cpp', 'config.cpp',
'save.cpp',
'tests/fsm.cpp', 'tests/fsm.cpp',
'tests/dbc.cpp', 'tests/dbc.cpp',
'tests/map.cpp', 'tests/map.cpp',
@ -29,6 +30,7 @@ runtests = executable('runtests', [
'tests/dinkyecs.cpp', 'tests/dinkyecs.cpp',
'tests/ansi_parser.cpp', 'tests/ansi_parser.cpp',
'tests/config.cpp', 'tests/config.cpp',
'tests/save.cpp',
], ],
dependencies: dependencies) dependencies: dependencies)
@ -45,6 +47,7 @@ roguish = executable('roguish', [
'ansi_parser.cpp', 'ansi_parser.cpp',
'render.cpp', 'render.cpp',
'config.cpp', 'config.cpp',
'save.cpp',
], ],
dependencies: dependencies) dependencies: dependencies)

@ -1,4 +1,5 @@
#pragma once #pragma once
#include "tser.hpp"
struct Point { struct Point {
size_t x = 0; size_t x = 0;
@ -7,6 +8,8 @@ struct Point {
bool operator==(const Point& other) const { bool operator==(const Point& other) const {
return other.x == x && other.y == y; return other.x == x && other.y == y;
} }
DEFINE_SERIALIZABLE(Point, x, y);
}; };
typedef std::vector<Point> PointList; typedef std::vector<Point> PointList;

@ -0,0 +1,63 @@
#include "save.hpp"
#include <fstream>
#include "dbc.hpp"
#include <fmt/core.h>
using namespace components;
template<typename CompT>
inline void extract(DinkyECS::World &world, std::vector<CompT> &into) {
auto from_world = world.entity_map_for<CompT>();
for(auto [entity, value] : from_world) {
into.push_back(std::any_cast<CompT>(value));
}
}
void save::to_file(std::string path, DinkyECS::World &world) {
SaveData save_data;
tser::BinaryArchive archive;
save_data.player = world.get_the<Player>();
extract<Position>(world, save_data.position);
extract<Combat>(world, save_data.combat);
extract<Motion>(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<SaveData>();
// BUG: need the entities!
world_out.set_the<Player>(save_data.player);
for(auto position : save_data.position) {
auto entity = world_out.entity();
// BUG: actually do need the entities
world_out.set<Position>(entity, position);
}
}

@ -0,0 +1,21 @@
#pragma once
#include "components.hpp"
#include "dinkyecs.hpp"
#include "tser.hpp"
#include <string>
#include <vector>
namespace save {
struct SaveData {
components::Player player;
std::vector<components::Position> position;
std::vector<components::Motion> motion;
std::vector<components::Combat> 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);
}

@ -10,7 +10,7 @@
using std::string; using std::string;
using namespace fmt; using namespace fmt;
using namespace Components; using namespace components;
using ftxui::Color; using ftxui::Color;
void System::enemy_pathing(DinkyECS::World &world, Map &game_map, Player &player) { void System::enemy_pathing(DinkyECS::World &world, Map &game_map, Player &player) {

@ -4,7 +4,7 @@
#include "components.hpp" #include "components.hpp"
#include <ftxui/dom/canvas.hpp> #include <ftxui/dom/canvas.hpp>
using namespace Components; using namespace components;
namespace System { namespace System {
void motion(DinkyECS::World &world, Map &game_map); void motion(DinkyECS::World &world, Map &game_map);

@ -6,6 +6,7 @@
#include "dinkyecs.hpp" #include "dinkyecs.hpp"
#include "components.hpp" #include "components.hpp"
using namespace fmt; using namespace fmt;
using std::string; using std::string;
@ -61,47 +62,3 @@ TEST_CASE("can go into a world", "[config]") {
Config &cfg = world.get_the<Config>(); Config &cfg = world.get_the<Config>();
REQUIRE(cfg["types"]["NUMBER"] == 1234); REQUIRE(cfg["types"]["NUMBER"] == 1234);
} }
#include <optional>
#include <iostream>
#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> 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<Robot>();
REQUIRE(loadedRobot.point.x == robot.point.x);
REQUIRE(loadedRobot.point.y == robot.point.y);
REQUIRE(loadedRobot.name == robot.name);
REQUIRE(loadedRobot.item == robot.item);
}

@ -0,0 +1,80 @@
#include <catch2/catch_test_macros.hpp>
#include <fmt/core.h>
#include <string>
#include "dinkyecs.hpp"
#include "components.hpp"
#include "save.hpp"
#include <optional>
#include <iostream>
#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> 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<Robot>();
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>(player);
world.set<Position>(player.entity, {10,10});
world.set<Motion>(player.entity, {0, 0});
world.set<Combat>(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<Position>(player.entity);
Position &position2 = in_world.get<Position>(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);
}

@ -8,6 +8,7 @@
#include <string_view> #include <string_view>
#include <type_traits> #include <type_traits>
#include <tuple> #include <tuple>
#include <locale>
#include <codecvt> #include <codecvt>
namespace tser{ namespace tser{

Loading…
Cancel
Save