From f19f37ae3eff84161f86e62a26fbd8b68f8f91a9 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Sat, 7 Dec 2024 15:29:33 +0100
Subject: make SaveManager no longer a singleton

---
 src/crepe/manager/Mediator.h      |  4 ++--
 src/crepe/manager/SaveManager.cpp | 32 +++++++++++++---------------
 src/crepe/manager/SaveManager.h   | 27 ++++++++++--------------
 src/example/CMakeLists.txt        |  2 --
 src/example/asset_manager.cpp     | 36 --------------------------------
 src/example/savemgr.cpp           | 44 ---------------------------------------
 src/test/CMakeLists.txt           |  1 +
 src/test/SaveManagerTest.cpp      | 41 ++++++++++++++++++++++++++++++++++++
 8 files changed, 69 insertions(+), 118 deletions(-)
 delete mode 100644 src/example/asset_manager.cpp
 delete mode 100644 src/example/savemgr.cpp
 create mode 100644 src/test/SaveManagerTest.cpp

(limited to 'src')

diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h
index 8094d80..6507a74 100644
--- a/src/crepe/manager/Mediator.h
+++ b/src/crepe/manager/Mediator.h
@@ -5,13 +5,13 @@
 // TODO: remove these singletons:
 #include "../facade/SDLContext.h"
 #include "EventManager.h"
-#include "SaveManager.h"
 #include "api/LoopTimer.h"
 
 namespace crepe {
 
 class ComponentManager;
 class SceneManager;
+class SaveManager;
 
 /**
  * Struct to pass references to classes that would otherwise need to be singletons down to
@@ -28,7 +28,7 @@ class SceneManager;
 struct Mediator {
 	OptionalRef<ComponentManager> component_manager;
 	OptionalRef<SceneManager> scene_manager;
-	OptionalRef<SaveManager> save_manager = SaveManager::get_instance();
+	OptionalRef<SaveManager> save_manager;
 	OptionalRef<EventManager> event_manager = EventManager::get_instance();
 	OptionalRef<SDLContext> sdl_context = SDLContext::get_instance();
 	OptionalRef<LoopTimer> timer = LoopTimer::get_instance();
diff --git a/src/crepe/manager/SaveManager.cpp b/src/crepe/manager/SaveManager.cpp
index d4ed1c1..292e8fd 100644
--- a/src/crepe/manager/SaveManager.cpp
+++ b/src/crepe/manager/SaveManager.cpp
@@ -1,13 +1,24 @@
 #include "../ValueBroker.h"
 #include "../api/Config.h"
 #include "../facade/DB.h"
-#include "../util/Log.h"
 
 #include "SaveManager.h"
 
 using namespace std;
 using namespace crepe;
 
+SaveManager::SaveManager(Mediator & mediator) : Manager(mediator) {
+	mediator.save_manager = *this;
+}
+
+DB & SaveManager::get_db() {
+	if (this->db == nullptr) {
+		Config & cfg = Config::get_instance();
+		this->db = make_unique<DB>(cfg.savemgr.location);
+	}
+	return *this->db;
+}
+
 template <>
 string SaveManager::serialize(const string & value) const noexcept {
 	return value;
@@ -90,22 +101,6 @@ int32_t SaveManager::deserialize(const string & value) const noexcept {
 	return deserialize<int64_t>(value);
 }
 
-SaveManager::SaveManager() { dbg_trace(); }
-
-SaveManager & SaveManager::get_instance() {
-	dbg_trace();
-	static SaveManager instance;
-	return instance;
-}
-
-DB & SaveManager::get_db() {
-	Config & cfg = Config::get_instance();
-	// TODO: make this path relative to XDG_DATA_HOME on Linux and whatever the
-	// default equivalent is on Windows using some third party library
-	static DB db(cfg.savemgr.location);
-	return db;
-}
-
 bool SaveManager::has(const string & key) {
 	DB & db = this->get_db();
 	return db.has(key);
@@ -155,7 +150,8 @@ ValueBroker<T> SaveManager::get(const string & key) {
 	return {
 		[this, key](const T & target) { this->set<T>(key, target); },
 		[this, key, value]() mutable -> const T & {
-			value = this->deserialize<T>(this->get_db().get(key));
+			DB & db = this->get_db();
+			value = this->deserialize<T>(db.get(key));
 			return value;
 		},
 	};
diff --git a/src/crepe/manager/SaveManager.h b/src/crepe/manager/SaveManager.h
index 3d8c852..d13a97a 100644
--- a/src/crepe/manager/SaveManager.h
+++ b/src/crepe/manager/SaveManager.h
@@ -4,6 +4,8 @@
 
 #include "../ValueBroker.h"
 
+#include "Manager.h"
+
 namespace crepe {
 
 class DB;
@@ -18,7 +20,7 @@ class DB;
  *
  * The underlying database is a key-value store.
  */
-class SaveManager {
+class SaveManager : public Manager {
 public:
 	/**
 	 * \brief Get a read/write reference to a value and initialize it if it does not yet exist
@@ -63,8 +65,8 @@ public:
 	 */
 	bool has(const std::string & key);
 
-private:
-	SaveManager();
+public:
+	SaveManager(Mediator & mediator);
 	virtual ~SaveManager() = default;
 
 private:
@@ -90,25 +92,18 @@ private:
 	T deserialize(const std::string & value) const noexcept;
 
 public:
-	// singleton
-	static SaveManager & get_instance();
 	SaveManager(const SaveManager &) = delete;
 	SaveManager(SaveManager &&) = delete;
 	SaveManager & operator=(const SaveManager &) = delete;
 	SaveManager & operator=(SaveManager &&) = delete;
 
+protected:
+	//! Create or return DB
+	virtual DB & get_db();
+
 private:
-	/**
-	 * \brief Create an instance of DB and return its reference
-	 *
-	 * \returns DB instance
-	 *
-	 * This function exists because DB is a facade class, which can't directly be used in the API
-	 * without workarounds
-	 *
-	 * TODO: better solution
-	 */
-	static DB & get_db();
+	//! Database
+	std::unique_ptr<DB> db = nullptr;
 };
 
 } // namespace crepe
diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt
index 8ef71bb..5a93b1f 100644
--- a/src/example/CMakeLists.txt
+++ b/src/example/CMakeLists.txt
@@ -16,8 +16,6 @@ function(add_example target_name)
 	add_dependencies(examples ${target_name})
 endfunction()
 
-add_example(asset_manager)
-add_example(savemgr)
 add_example(rendering_particle)
 add_example(game)
 add_example(button)
diff --git a/src/example/asset_manager.cpp b/src/example/asset_manager.cpp
deleted file mode 100644
index 917b547..0000000
--- a/src/example/asset_manager.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-#include <crepe/api/AssetManager.h>
-#include <crepe/api/Texture.h>
-#include <crepe/facade/Sound.h>
-
-using namespace crepe;
-
-int main() {
-
-	// this needs to be called before the asset manager otherwise the destructor of sdl is not in
-	// the right order
-	{ Texture test("../asset/texture/img.png"); }
-	// FIXME: make it so the issue described by the above comment is not possible (i.e. the order
-	// in which internal classes are instantiated should not impact the way the engine works).
-
-	auto & mgr = AssetManager::get_instance();
-
-	{
-		// TODO: [design] the Sound class can't be directly included by the user as it includes
-		// SoLoud headers.
-		auto bgm = mgr.cache<Sound>("../mwe/audio/bgm.ogg");
-		auto sfx1 = mgr.cache<Sound>("../mwe/audio/sfx1.wav");
-		auto sfx2 = mgr.cache<Sound>("../mwe/audio/sfx2.wav");
-
-		auto img = mgr.cache<Texture>("../asset/texture/img.png");
-		auto img1 = mgr.cache<Texture>("../asset/texture/second.png");
-	}
-
-	{
-		auto bgm = mgr.cache<Sound>("../mwe/audio/bgm.ogg");
-		auto sfx1 = mgr.cache<Sound>("../mwe/audio/sfx1.wav");
-		auto sfx2 = mgr.cache<Sound>("../mwe/audio/sfx2.wav");
-
-		auto img = mgr.cache<Texture>("../asset/texture/img.png");
-		auto img1 = mgr.cache<Texture>("../asset/texture/second.png");
-	}
-}
diff --git a/src/example/savemgr.cpp b/src/example/savemgr.cpp
deleted file mode 100644
index 65c4a34..0000000
--- a/src/example/savemgr.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-/** \file
- * 
- * Standalone example for usage of the save manager
- */
-
-#include <cassert>
-#include <crepe/api/Config.h>
-#include <crepe/api/SaveManager.h>
-#include <crepe/util/Log.h>
-#include <crepe/util/Proxy.h>
-
-using namespace crepe;
-
-// unrelated setup code
-int _ = []() {
-	// make sure all log messages get printed
-	auto & cfg = Config::get_instance();
-	cfg.log.level = Log::Level::TRACE;
-
-	return 0; // satisfy compiler
-}();
-
-int main() {
-	const char * key = "mygame.test";
-
-	SaveManager & mgr = SaveManager::get_instance();
-
-	dbg_logf("has key = {}", mgr.has(key));
-	ValueBroker<int> prop = mgr.get<int>(key, 0);
-	Proxy<int> val = mgr.get<int>(key, 0);
-
-	dbg_logf("val = {}", mgr.get<int>(key).get());
-	prop.set(1);
-	dbg_logf("val = {}", mgr.get<int>(key).get());
-	val = 2;
-	dbg_logf("val = {}", mgr.get<int>(key).get());
-	mgr.set<int>(key, 3);
-	dbg_logf("val = {}", mgr.get<int>(key).get());
-
-	dbg_logf("has key = {}", mgr.has(key));
-	assert(true == mgr.has(key));
-
-	return 0;
-}
diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt
index c9cbac5..734e3ee 100644
--- a/src/test/CMakeLists.txt
+++ b/src/test/CMakeLists.txt
@@ -17,4 +17,5 @@ target_sources(test_main PUBLIC
 	ScriptEventTest.cpp
 	ScriptSceneTest.cpp
 	Profiling.cpp
+	SaveManagerTest.cpp
 )
diff --git a/src/test/SaveManagerTest.cpp b/src/test/SaveManagerTest.cpp
new file mode 100644
index 0000000..a1efc33
--- /dev/null
+++ b/src/test/SaveManagerTest.cpp
@@ -0,0 +1,41 @@
+#include <gtest/gtest.h>
+
+#include <crepe/manager/SaveManager.h>
+#include <crepe/facade/DB.h>
+#include <crepe/ValueBroker.h>
+
+using namespace std;
+using namespace crepe;
+using namespace testing;
+
+class SaveManagerTest : public Test {
+	Mediator m;
+	class TestSaveManager : public SaveManager {
+		using SaveManager::SaveManager;
+
+		// in-memory database for testing
+		DB db{};
+		virtual DB & get_db() override { return this->db; }
+	};
+
+public:
+	TestSaveManager mgr{m};
+};
+
+TEST_F(SaveManagerTest, ReadWrite) {
+	ASSERT_FALSE(mgr.has("foo"));
+	mgr.set<string>("foo", "bar");
+	ASSERT_TRUE(mgr.has("foo"));
+
+	ValueBroker value = mgr.get<string>("foo");
+	EXPECT_EQ(value.get(), "bar");
+}
+
+TEST_F(SaveManagerTest, DefaultValue) {
+	ValueBroker value = mgr.get<int>("foo", 3);
+
+	ASSERT_EQ(value.get(), 3);
+	value.set(5);
+	ASSERT_EQ(value.get(), 5);
+}
+
-- 
cgit v1.2.3