From 827f09031e2d3cc15e956b242774a4566e1403c1 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Thu, 7 Nov 2024 20:52:06 +0100
Subject: more WIP audio system

---
 src/crepe/Component.h             |  2 --
 src/crepe/api/AudioSource.cpp     | 21 ++++++++++-----------
 src/crepe/api/AudioSource.h       | 34 +++++++++++++++++++++++-----------
 src/crepe/system/AudioSystem.cpp  | 24 ++++++++++++++++++++++++
 src/crepe/system/AudioSystem.h    | 21 +++++++++++++++++++++
 src/crepe/system/CMakeLists.txt   |  3 +++
 src/crepe/system/ScriptSystem.cpp |  3 +--
 src/crepe/system/System.cpp       |  7 +++++++
 src/crepe/system/System.h         | 10 +++++++++-
 9 files changed, 98 insertions(+), 27 deletions(-)
 create mode 100644 src/crepe/system/AudioSystem.cpp
 create mode 100644 src/crepe/system/AudioSystem.h
 create mode 100644 src/crepe/system/System.cpp

(limited to 'src')

diff --git a/src/crepe/Component.h b/src/crepe/Component.h
index 0fe60b2..e8d18d9 100644
--- a/src/crepe/Component.h
+++ b/src/crepe/Component.h
@@ -2,8 +2,6 @@
 
 #include "types.h"
 
-#include <cstdint>
-
 namespace crepe {
 
 class ComponentManager;
diff --git a/src/crepe/api/AudioSource.cpp b/src/crepe/api/AudioSource.cpp
index 63fd0d7..b0cf28c 100644
--- a/src/crepe/api/AudioSource.cpp
+++ b/src/crepe/api/AudioSource.cpp
@@ -1,23 +1,22 @@
 #include <memory>
 
-#include "../facade/Sound.h"
-
 #include "AudioSource.h"
 
 using namespace crepe;
+using namespace std;
 
-AudioSource::AudioSource(std::unique_ptr<Asset> audio_clip) {
-	this->sound = std::make_unique<crepe::Sound>(std::move(audio_clip));
-}
-
-void AudioSource::play() { return this->play(false); }
+AudioSource::AudioSource(game_object_id_t id, unique_ptr<Asset> audio_clip) :
+	Component(id),
+	audio_clip(std::move(audio_clip))
+{ }
 
 void AudioSource::play(bool looping) {
-	this->sound->set_looping(looping);
-	this->sound->play();
+	this->loop = looping;
+	this->playing = true;
 }
 
 void AudioSource::stop() {
-	this->sound->pause();
-	this->sound->rewind();
+	this->playing = false;
+	this->rewind = true;
 }
+
diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h
index 1e24ae8..fd2b6f1 100644
--- a/src/crepe/api/AudioSource.h
+++ b/src/crepe/api/AudioSource.h
@@ -4,36 +4,48 @@
 
 #include "../Asset.h"
 #include "../Component.h"
+#include "../types.h"
 
 namespace crepe {
 
-class Sound;
-
 //! Audio source component
 class AudioSource : public Component {
 public:
-	AudioSource(std::unique_ptr<Asset> audio_clip);
+	AudioSource(game_object_id_t id, std::unique_ptr<Asset> audio_clip);
 	virtual ~AudioSource() = default;
 
 public:
 	//! Start or resume this audio source
-	void play();
-	void play(bool looping);
+	void play(bool looping = false);
 	//! Stop this audio source
 	void stop();
 
 public:
 	//! Sample file location
-	std::unique_ptr<Asset> audio_clip;
-	//! TODO: ?????
-	bool play_on_awake;
+	const std::unique_ptr<Asset> audio_clip;
+	//! Play when this component becomes active
+	bool play_on_awake = false;
 	//! Repeat the current audio clip during playback
-	bool loop;
+	bool loop = false;
 	//! Normalized volume (0.0 - 1.0)
-	float volume;
+	float volume = 1.0;
+
+private:
+	//! If this source is playing audio
+	bool playing = false;
+	//! Rewind the sample location
+	bool rewind = false;
 
 private:
-	std::unique_ptr<Sound> sound;
+	//! Value of \c active after last system update
+	bool last_active = false;
+	//! Value of \c playing after last system update
+	bool last_playing = false;
+	//! Value of \c volume after last system update
+	float last_volume = 1.0;
+	//! Value of \c loop after last system update
+	bool last_loop = false;
 };
 
 } // namespace crepe
+
diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
new file mode 100644
index 0000000..93c0955
--- /dev/null
+++ b/src/crepe/system/AudioSystem.cpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "AudioSystem.h"
+#include "ComponentManager.h"
+
+#include "../api/AudioSource.h"
+
+using namespace crepe;
+using namespace std;
+
+AudioSystem::AudioSystem(SoundContext & ctx) : ctx(ctx) {}
+
+void AudioSystem::update() {
+	vector<reference_wrapper<AudioSource>> components = this->compmgr.get_components_by_type<AudioSource>();
+
+	for (auto component_ref : components) {
+		AudioSource & component = component_ref.get();
+		if (!component.active) continue;
+
+		// TODO: fetch Sound instance from resourcemanager
+		// TODO: lots of state diffing
+	}
+}
+
diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h
new file mode 100644
index 0000000..8fb681f
--- /dev/null
+++ b/src/crepe/system/AudioSystem.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "../facade/SoundContext.h"
+
+#include "System.h"
+
+namespace crepe {
+
+class AudioSystem : public System {
+public:
+	AudioSystem(SoundContext & ctx);
+
+public:
+	void update();
+
+private:
+	SoundContext & ctx;
+};
+
+} // namespace crepe
+
diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt
index ff6f66f..ca62add 100644
--- a/src/crepe/system/CMakeLists.txt
+++ b/src/crepe/system/CMakeLists.txt
@@ -1,9 +1,11 @@
 target_sources(crepe PUBLIC
+	System.cpp
 	ParticleSystem.cpp
 	ScriptSystem.cpp
 	PhysicsSystem.cpp
 	CollisionSystem.cpp
 	RenderSystem.cpp
+	AudioSystem.cpp
 )
 
 target_sources(crepe PUBLIC FILE_SET HEADERS FILES
@@ -12,4 +14,5 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
 	PhysicsSystem.h
 	CollisionSystem.h
 	RenderSystem.h
+	AudioSystem.h
 )
diff --git a/src/crepe/system/ScriptSystem.cpp b/src/crepe/system/ScriptSystem.cpp
index f2673e7..68fbb02 100644
--- a/src/crepe/system/ScriptSystem.cpp
+++ b/src/crepe/system/ScriptSystem.cpp
@@ -22,9 +22,8 @@ void ScriptSystem::update() {
 
 forward_list<Script *> ScriptSystem::get_scripts() {
 	forward_list<Script *> scripts = {};
-	ComponentManager & mgr = ComponentManager::get_instance();
 	vector<reference_wrapper<BehaviorScript>> behavior_scripts
-		= mgr.get_components_by_type<BehaviorScript>();
+		= this->compmgr.get_components_by_type<BehaviorScript>();
 
 	for (auto behavior_script_ref : behavior_scripts) {
 		BehaviorScript & behavior_script = behavior_script_ref.get();
diff --git a/src/crepe/system/System.cpp b/src/crepe/system/System.cpp
new file mode 100644
index 0000000..fa51d2f
--- /dev/null
+++ b/src/crepe/system/System.cpp
@@ -0,0 +1,7 @@
+#include "System.h"
+
+using namespace crepe;
+
+// TODO: ComponentManager shouldn't be a singleton
+System::System() : compmgr(ComponentManager::get_instance()) {}
+
diff --git a/src/crepe/system/System.h b/src/crepe/system/System.h
index 3b81bef..5091977 100644
--- a/src/crepe/system/System.h
+++ b/src/crepe/system/System.h
@@ -1,14 +1,22 @@
 #pragma once
 
+#include "../ComponentManager.h"
+
 namespace crepe {
 
+//! ECS system base class
 class System {
 public:
+	//! Process components belonging to this system
 	virtual void update() = 0;
 
 public:
-	System() = default;
+	System();
 	virtual ~System() = default;
+
+public:
+	//! Reference to component manager
+	ComponentManager & compmgr;
 };
 
 } // namespace crepe
-- 
cgit v1.2.3


From c58fbbefd5a426c38b1182e9e760f149f0091670 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Wed, 13 Nov 2024 12:31:59 +0100
Subject: move some files from `loek/tests` to `loek/audio`

---
 src/crepe/Resource.h              | 33 +++++++++++++++++++++++++++++++++
 src/crepe/facade/Sound.cpp        | 30 ++++++++++++++----------------
 src/crepe/facade/Sound.h          | 14 +++++++-------
 src/crepe/facade/SoundContext.cpp |  5 -----
 src/crepe/facade/SoundContext.h   |  5 ++---
 5 files changed, 56 insertions(+), 31 deletions(-)
 create mode 100644 src/crepe/Resource.h

(limited to 'src')

diff --git a/src/crepe/Resource.h b/src/crepe/Resource.h
new file mode 100644
index 0000000..dcf3dbd
--- /dev/null
+++ b/src/crepe/Resource.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <memory>
+
+namespace crepe {
+
+class ResourceManager;
+class Asset;
+
+/**
+ * Resource is an interface class used to represent a (deserialized) game
+ * resource (e.g. textures, sounds).
+ */
+class Resource {
+private:
+	/**
+	 * \brief Prototype pattern clone function.
+	 *
+	 * \param src Source file of new resource (abstraction for file saved on
+	 * disk)
+	 *
+	 * \returns New instance of concrete resource
+	 */
+	virtual std::unique_ptr<Resource> clone(const Asset & src) const = 0;
+	/**
+	 * The resource manager uses \c clone to create new instances of the concrete
+	 * resource class. This may be used to inherit references to classes that
+	 * would otherwise need to be implemented as singletons.
+	 */
+	friend class ResourceManager;
+};
+
+} // namespace crepe
diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp
index 648ec81..b8ea71a 100644
--- a/src/crepe/facade/Sound.cpp
+++ b/src/crepe/facade/Sound.cpp
@@ -1,26 +1,24 @@
+#include <memory>
+
+#include "../Asset.h"
 #include "../util/log.h"
 
 #include "Sound.h"
 #include "SoundContext.h"
 
 using namespace crepe;
+using namespace std;
 
-Sound::Sound(std::unique_ptr<Asset> res) {
-	dbg_trace();
-	this->load(std::move(res));
-}
-
-Sound::Sound(const char * src) {
-	dbg_trace();
-	this->load(std::make_unique<Asset>(src));
-}
+Sound::Sound(SoundContext & ctx) : context(ctx) { dbg_trace(); }
 
-void Sound::load(std::unique_ptr<Asset> res) {
-	this->sample.load(res->canonical());
+unique_ptr<Resource> Sound::clone(const Asset & src) const {
+	auto instance = make_unique<Sound>(*this);
+	instance->sample.load(src.canonical());
+	return instance;
 }
 
 void Sound::play() {
-	SoundContext & ctx = SoundContext::get_instance();
+	SoundContext & ctx = this->context;
 	if (ctx.engine.getPause(this->handle)) {
 		// resume if paused
 		ctx.engine.setPause(this->handle, false);
@@ -32,13 +30,13 @@ void Sound::play() {
 }
 
 void Sound::pause() {
-	SoundContext & ctx = SoundContext::get_instance();
+	SoundContext & ctx = this->context;
 	if (ctx.engine.getPause(this->handle)) return;
 	ctx.engine.setPause(this->handle, true);
 }
 
 void Sound::rewind() {
-	SoundContext & ctx = SoundContext::get_instance();
+	SoundContext & ctx = this->context;
 	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
 	ctx.engine.seek(this->handle, 0);
 }
@@ -46,7 +44,7 @@ void Sound::rewind() {
 void Sound::set_volume(float volume) {
 	this->volume = volume;
 
-	SoundContext & ctx = SoundContext::get_instance();
+	SoundContext & ctx = this->context;
 	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
 	ctx.engine.setVolume(this->handle, this->volume);
 }
@@ -54,7 +52,7 @@ void Sound::set_volume(float volume) {
 void Sound::set_looping(bool looping) {
 	this->looping = looping;
 
-	SoundContext & ctx = SoundContext::get_instance();
+	SoundContext & ctx = this->context;
 	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
 	ctx.engine.setLooping(this->handle, this->looping);
 }
diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h
index 183bd7c..e5b2f19 100644
--- a/src/crepe/facade/Sound.h
+++ b/src/crepe/facade/Sound.h
@@ -4,11 +4,13 @@
 #include <soloud/soloud.h>
 #include <soloud/soloud_wav.h>
 
-#include "../Asset.h"
+#include "../Resource.h"
 
 namespace crepe {
 
-class Sound {
+class SoundContext;
+
+class Sound : public Resource {
 public:
 	/**
 	 * \brief Pause this sample
@@ -64,15 +66,13 @@ public:
 	bool get_looping() const { return this->looping; }
 
 public:
-	Sound(const char * src);
-	Sound(std::unique_ptr<Asset> res);
-
-private:
-	void load(std::unique_ptr<Asset> res);
+	Sound(SoundContext & ctx);
+	std::unique_ptr<Resource> clone(const Asset & src) const override;
 
 private:
 	SoLoud::Wav sample;
 	SoLoud::handle handle;
+	SoundContext & context;
 
 	float volume = 1.0f;
 	bool looping = false;
diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp
index 5e5a3a9..b5f3db3 100644
--- a/src/crepe/facade/SoundContext.cpp
+++ b/src/crepe/facade/SoundContext.cpp
@@ -4,11 +4,6 @@
 
 using namespace crepe;
 
-SoundContext & SoundContext::get_instance() {
-	static SoundContext instance;
-	return instance;
-}
-
 SoundContext::SoundContext() {
 	dbg_trace();
 	engine.init();
diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h
index d3123d2..8d9e396 100644
--- a/src/crepe/facade/SoundContext.h
+++ b/src/crepe/facade/SoundContext.h
@@ -7,12 +7,10 @@
 namespace crepe {
 
 class SoundContext {
-private:
+public:
 	SoundContext();
 	virtual ~SoundContext();
 
-	// singleton
-	static SoundContext & get_instance();
 	SoundContext(const SoundContext &) = delete;
 	SoundContext(SoundContext &&) = delete;
 	SoundContext & operator=(const SoundContext &) = delete;
@@ -20,6 +18,7 @@ private:
 
 private:
 	SoLoud::Soloud engine;
+	//! Sound directly calls methods on \c engine
 	friend class Sound;
 };
 
-- 
cgit v1.2.3


From 319d511135da1b58acb78ca939d7ee01fee4bbf3 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Wed, 13 Nov 2024 13:11:42 +0100
Subject: move more files from `loek/tests` to `loek/audio`

---
 src/crepe/Asset.cpp | 18 +++++++++++-------
 src/crepe/Asset.h   |  2 +-
 2 files changed, 12 insertions(+), 8 deletions(-)

(limited to 'src')

diff --git a/src/crepe/Asset.cpp b/src/crepe/Asset.cpp
index 8a2a11c..3d4df53 100644
--- a/src/crepe/Asset.cpp
+++ b/src/crepe/Asset.cpp
@@ -1,16 +1,20 @@
 #include <filesystem>
 
 #include "Asset.h"
+#include "Exception.h"
 
 using namespace crepe;
+using namespace std;
 
-Asset::Asset(const std::string & src) {
-	// FIXME: restore this
-	// this->src = std::filesystem::canonical(src);
-	this->src = src;
-	this->file = std::ifstream(this->src, std::ios::in | std::ios::binary);
+Asset::Asset(const string & src) : src(src) {
+	try {
+		this->src = filesystem::canonical(src);
+	} catch (filesystem::filesystem_error & e) {
+		throw Exception("Asset error: %s", e.what());
+	}
+	this->file = ifstream(this->src, ios::in | ios::binary);
 }
 
-const std::istream & Asset::read() { return this->file; }
+const istream & Asset::read() { return this->file; }
 
-const char * Asset::canonical() { return this->src.c_str(); }
+const char * Asset::canonical() const { return this->src.c_str(); }
diff --git a/src/crepe/Asset.h b/src/crepe/Asset.h
index 0cb5834..d4e4ba1 100644
--- a/src/crepe/Asset.h
+++ b/src/crepe/Asset.h
@@ -23,7 +23,7 @@ public:
 	//! Get an input stream to the contents of this resource
 	const std::istream & read();
 	//! Get the canonical path to this resource
-	const char * canonical();
+	const char * canonical() const;
 
 private:
 	std::string src;
-- 
cgit v1.2.3


From 07adbf48e0781cd8c95983c1871a84b6160ee5bf Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Thu, 14 Nov 2024 13:57:13 +0100
Subject: implement asset + more WIP audio system

---
 .crepe-root                     |  0
 .gitmodules                     |  4 ++++
 lib/whereami/CMakeLists.txt     | 38 ++++++++++++++++++++++++++++++++++
 lib/whereami/lib                |  1 +
 src/CMakeLists.txt              |  2 ++
 src/crepe/Asset.cpp             | 46 +++++++++++++++++++++++++++++++++++------
 src/crepe/Asset.h               | 32 ++++++++++++++--------------
 src/crepe/api/AudioSource.cpp   |  6 ++----
 src/crepe/api/AudioSource.h     |  5 +++--
 src/crepe/api/CMakeLists.txt    |  4 ++--
 src/crepe/api/Config.h          | 14 +++++++++++++
 src/crepe/api/Texture.cpp       |  2 +-
 src/crepe/facade/SDLContext.cpp |  2 +-
 src/crepe/facade/Sound.cpp      |  2 +-
 src/example/asset_manager.cpp   | 22 ++++++++++----------
 src/example/audio_internal.cpp  | 16 ++++++++++----
 src/example/particles.cpp       |  5 +++--
 src/example/rendering.cpp       |  6 +++---
 src/test/AssetTest.cpp          | 33 +++++++++++++++++++++++++++++
 src/test/AudioTest.cpp          |  4 ++--
 src/test/CMakeLists.txt         |  1 +
 src/test/ParticleTest.cpp       |  2 +-
 22 files changed, 192 insertions(+), 55 deletions(-)
 create mode 100644 .crepe-root
 create mode 100644 lib/whereami/CMakeLists.txt
 create mode 160000 lib/whereami/lib
 create mode 100644 src/test/AssetTest.cpp

(limited to 'src')

diff --git a/.crepe-root b/.crepe-root
new file mode 100644
index 0000000..e69de29
diff --git a/.gitmodules b/.gitmodules
index 2f64601..bd6e7f7 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -22,3 +22,7 @@
 	path = lib/libdb
 	url = https://github.com/berkeleydb/libdb
 	shallow = true
+[submodule "lib/whereami/whereami"]
+	path = lib/whereami/lib
+	url = https://github.com/gpakosz/whereami
+	shallow = true
diff --git a/lib/whereami/CMakeLists.txt b/lib/whereami/CMakeLists.txt
new file mode 100644
index 0000000..96d3a23
--- /dev/null
+++ b/lib/whereami/CMakeLists.txt
@@ -0,0 +1,38 @@
+cmake_minimum_required(VERSION 3.28)
+set(CMAKE_C_STANDARD 11)
+project(whereami C)
+
+include(CMakePackageConfigHelpers)
+
+add_library(whereami SHARED)
+
+target_include_directories(whereami PRIVATE SYSTEM lib/src)
+target_sources(whereami PRIVATE lib/src/whereami.c)
+
+install(
+	TARGETS whereami
+	EXPORT whereamiTargets
+	LIBRARY DESTINATION lib
+	ARCHIVE DESTINATION lib
+	RUNTIME DESTINATION lib
+	INCLUDES DESTINATION include
+)
+install(
+	FILES lib/src/whereami.h
+	DESTINATION include
+)
+write_basic_package_version_file(
+	"${CMAKE_CURRENT_BINARY_DIR}/whereami-config-version.cmake"
+	VERSION 0.0.0
+	COMPATIBILITY AnyNewerVersion
+)
+install(
+	FILES
+		"${CMAKE_CURRENT_BINARY_DIR}/whereami-config-version.cmake"
+	DESTINATION lib/cmake/whereami
+)
+install(
+	EXPORT whereamiTargets
+	FILE whereami-config.cmake
+	DESTINATION lib/cmake/whereami
+)
diff --git a/lib/whereami/lib b/lib/whereami/lib
new file mode 160000
index 0000000..dcb52a0
--- /dev/null
+++ b/lib/whereami/lib
@@ -0,0 +1 @@
+Subproject commit dcb52a058dc14530ba9ae05e4339bd3ddfae0e0e
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 445a8b2..c3f29da 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -11,6 +11,7 @@ find_package(SDL2 REQUIRED)
 find_package(SDL2_image REQUIRED)
 find_package(SoLoud REQUIRED)
 find_package(GTest REQUIRED)
+find_package(whereami REQUIRED)
 find_library(BERKELEY_DB db)
 
 add_library(crepe SHARED)
@@ -25,6 +26,7 @@ target_link_libraries(crepe
 	PUBLIC SDL2
 	PUBLIC SDL2_image
 	PUBLIC ${BERKELEY_DB}
+	PUBLIC whereami
 )
 
 add_subdirectory(crepe)
diff --git a/src/crepe/Asset.cpp b/src/crepe/Asset.cpp
index 9c41ecb..8692c6c 100644
--- a/src/crepe/Asset.cpp
+++ b/src/crepe/Asset.cpp
@@ -1,16 +1,50 @@
 #include <filesystem>
+#include <stdexcept>
+#include <whereami.h>
 
 #include "Asset.h"
+#include "api/Config.h"
 
 using namespace crepe;
 using namespace std;
 
-// FIXME: restore this
-// src(std::filesystem::canonical(src))
-Asset::Asset(const std::string & src) : src(src) {
-	this->file = std::ifstream(this->src, std::ios::in | std::ios::binary);
+Asset::Asset(const string & src) : src(find_asset(src)) { }
+Asset::Asset(const char * src) : src(find_asset(src)) { }
+
+const string & Asset::get_path() const noexcept { return this->src; }
+
+string Asset::find_asset(const string & src) const {
+	auto & cfg = Config::get_instance();
+	auto & root_pattern = cfg.asset.root_pattern;
+
+	// if root_pattern is empty, find_asset must return all paths as-is
+	if (root_pattern.empty()) return src;
+
+	// absolute paths do not need to be resolved, only canonicalized
+	filesystem::path path = src;
+	if (path.is_absolute())
+		return filesystem::canonical(path);
+
+	// find directory matching root_pattern
+	filesystem::path root = this->whereami();
+	while (1) {
+		if (filesystem::exists(root / root_pattern))
+			break;
+		if (!root.has_parent_path())
+			throw runtime_error(format("Asset: Cannot find root pattern ({})", root_pattern));
+		root = root.parent_path();
+	}
+
+	// join path to root (base directory) and canonicalize
+	return filesystem::canonical(root / path);
 }
 
-istream & Asset::get_stream() { return this->file; }
+string Asset::whereami() const noexcept {
+	string path;
+	size_t path_length = wai_getExecutablePath(NULL, 0, NULL);
+	path.resize(path_length + 1); // wai writes null byte
+	wai_getExecutablePath(path.data(), path_length, NULL);
+	path.resize(path_length);
+	return path;
+}
 
-const string & Asset::get_canonical() const { return this->src; }
diff --git a/src/crepe/Asset.h b/src/crepe/Asset.h
index cb413f4..f6e6782 100644
--- a/src/crepe/Asset.h
+++ b/src/crepe/Asset.h
@@ -1,7 +1,5 @@
 #pragma once
 
-#include <fstream>
-#include <iostream>
 #include <string>
 
 namespace crepe {
@@ -9,8 +7,8 @@ namespace crepe {
 /**
  * \brief Asset location helper
  *
- * This class is used to locate and canonicalize paths to game asset files, and
- * should *always* be used when retrieving files from disk.
+ * This class is used to locate game asset files, and should *always* be used
+ * instead of reading file paths directly.
  */
 class Asset {
 public:
@@ -18,24 +16,28 @@ public:
 	 * \param src  Unique identifier to asset
 	 */
 	Asset(const std::string & src);
-
-public:
 	/**
-	 * \brief Get an input stream to the contents of this asset
-	 * \return Input stream with file contents
+	 * \param src  Unique identifier to asset
 	 */
-	std::istream & get_stream();
+	Asset(const char * src);
+
+public:
 	/**
-	 * \brief Get the canonical path to this asset
-	 * \return Canonical path to this asset
+	 * \brief Get the path to this asset
+	 * \return path to this asset
 	 */
-	const std::string & get_canonical() const;
+	const std::string & get_path() const noexcept;
 
 private:
-	//! Canonical path to asset
+	//! path to asset
 	const std::string src;
-	//! File handle (stream)
-	std::ifstream file;
+
+private:
+	std::string find_asset(const std::string & src) const;
+	/**
+	 * \returns The path to the current executable
+	 */
+	std::string whereami() const noexcept;
 };
 
 } // namespace crepe
diff --git a/src/crepe/api/AudioSource.cpp b/src/crepe/api/AudioSource.cpp
index b0cf28c..4baac9a 100644
--- a/src/crepe/api/AudioSource.cpp
+++ b/src/crepe/api/AudioSource.cpp
@@ -1,13 +1,11 @@
-#include <memory>
-
 #include "AudioSource.h"
 
 using namespace crepe;
 using namespace std;
 
-AudioSource::AudioSource(game_object_id_t id, unique_ptr<Asset> audio_clip) :
+AudioSource::AudioSource(game_object_id_t id, const Asset & src) :
 	Component(id),
-	audio_clip(std::move(audio_clip))
+	source(src)
 { }
 
 void AudioSource::play(bool looping) {
diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h
index 5bc70f9..0748267 100644
--- a/src/crepe/api/AudioSource.h
+++ b/src/crepe/api/AudioSource.h
@@ -21,8 +21,6 @@ public:
 	void stop();
 
 public:
-	//! Sample file location
-	const std::unique_ptr<Asset> audio_clip;
 	//! Play when this component becomes active
 	bool play_on_awake = false;
 	//! Repeat the current audio clip during playback
@@ -31,6 +29,9 @@ public:
 	float volume = 1.0;
 
 private:
+	//! This audio source's clip
+	const Asset source;
+
 	//! If this source is playing audio
 	bool playing = false;
 	//! Rewind the sample location
diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt
index 85696c4..93a1fac 100644
--- a/src/crepe/api/CMakeLists.txt
+++ b/src/crepe/api/CMakeLists.txt
@@ -1,5 +1,5 @@
 target_sources(crepe PUBLIC
-	# AudioSource.cpp
+	AudioSource.cpp
 	BehaviorScript.cpp
 	Script.cpp
 	GameObject.cpp
@@ -23,7 +23,7 @@ target_sources(crepe PUBLIC
 )
 
 target_sources(crepe PUBLIC FILE_SET HEADERS FILES
-	# AudioSource.h
+	AudioSource.h
 	BehaviorScript.h
 	Config.h
 	Script.h
diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h
index e3f86bf..c3f9474 100644
--- a/src/crepe/api/Config.h
+++ b/src/crepe/api/Config.h
@@ -58,6 +58,20 @@ public:
 		 */
 		double gravity = 1;
 	} physics;
+
+	//! Asset loading options
+	struct {
+		/**
+		 * \brief Pattern to match for Asset base directory
+		 *
+		 * All non-absolute paths resolved using \c Asset will be made relative to
+		 * the first parent directory relative to the calling executable where
+		 * appending this pattern results in a path that exists. If this string is
+		 * empty, path resolution is disabled, and Asset will return all paths
+		 * as-is.
+		 */
+		std::string root_pattern = ".crepe-root";
+	} asset;
 };
 
 } // namespace crepe
diff --git a/src/crepe/api/Texture.cpp b/src/crepe/api/Texture.cpp
index de0d0ea..6a1e4d8 100644
--- a/src/crepe/api/Texture.cpp
+++ b/src/crepe/api/Texture.cpp
@@ -26,7 +26,7 @@ Texture::~Texture() {
 
 void Texture::load(unique_ptr<Asset> res) {
 	SDLContext & ctx = SDLContext::get_instance();
-	this->texture = std::move(ctx.texture_from_path(res->get_canonical()));
+	this->texture = std::move(ctx.texture_from_path(res->get_path()));
 }
 
 int Texture::get_width() const {
diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp
index a68d940..5527803 100644
--- a/src/crepe/facade/SDLContext.cpp
+++ b/src/crepe/facade/SDLContext.cpp
@@ -159,7 +159,7 @@ SDLContext::texture_from_path(const std::string & path) {
 
 	SDL_Surface * tmp = IMG_Load(path.c_str());
 	if (tmp == nullptr) {
-		tmp = IMG_Load("../asset/texture/ERROR.png");
+		tmp = IMG_Load("asset/texture/ERROR.png");
 	}
 
 	std::unique_ptr<SDL_Surface, std::function<void(SDL_Surface *)>>
diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp
index 5cd31e8..b7bfeab 100644
--- a/src/crepe/facade/Sound.cpp
+++ b/src/crepe/facade/Sound.cpp
@@ -13,7 +13,7 @@ Sound::Sound(SoundContext & ctx) : context(ctx) { dbg_trace(); }
 
 unique_ptr<Resource> Sound::clone(const Asset & src) const {
 	auto instance = make_unique<Sound>(*this);
-	instance->sample.load(src.get_canonical().c_str());
+	instance->sample.load(src.get_path().c_str());
 	return instance;
 }
 
diff --git a/src/example/asset_manager.cpp b/src/example/asset_manager.cpp
index cf64f89..a2ca8c3 100644
--- a/src/example/asset_manager.cpp
+++ b/src/example/asset_manager.cpp
@@ -8,7 +8,7 @@ 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"); }
+	{ 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).
@@ -18,20 +18,20 @@ int main() {
 	{
 		// 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 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 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 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 img = mgr.cache<Texture>("asset/texture/img.png");
+		auto img1 = mgr.cache<Texture>("asset/texture/second.png");
 	}
 }
diff --git a/src/example/audio_internal.cpp b/src/example/audio_internal.cpp
index ff55a59..9b60e6b 100644
--- a/src/example/audio_internal.cpp
+++ b/src/example/audio_internal.cpp
@@ -4,7 +4,9 @@
  */
 
 #include <crepe/api/Config.h>
+#include <crepe/facade/SoundContext.h>
 #include <crepe/facade/Sound.h>
+#include <crepe/Asset.h>
 #include <crepe/util/Log.h>
 
 #include <thread>
@@ -24,12 +26,18 @@ int _ = []() {
 }();
 
 int main() {
+	SoundContext ctx{};
+	Sound sound{ctx};
 	// Load a background track (Ogg Vorbis)
-	auto bgm = Sound("../mwe/audio/bgm.ogg");
+	auto _bgm = sound.clone(Asset{"mwe/audio/bgm.ogg"});
+	Sound & bgm = *dynamic_cast<Sound *>(_bgm.get());
 	// Load three short samples (WAV)
-	auto sfx1 = Sound("../mwe/audio/sfx1.wav");
-	auto sfx2 = Sound("../mwe/audio/sfx2.wav");
-	auto sfx3 = Sound("../mwe/audio/sfx3.wav");
+	auto _sfx1 = sound.clone(Asset{"mwe/audio/sfx1.wav"});
+	Sound & sfx1 = *dynamic_cast<Sound *>(_sfx1.get());
+	auto _sfx2 = sound.clone(Asset{"mwe/audio/sfx2.wav"});
+	Sound & sfx2 = *dynamic_cast<Sound *>(_sfx2.get());
+	auto _sfx3 = sound.clone(Asset{"mwe/audio/sfx3.wav"});
+	Sound & sfx3 = *dynamic_cast<Sound *>(_sfx3.get());
 
 	// Start the background track
 	bgm.play();
diff --git a/src/example/particles.cpp b/src/example/particles.cpp
index 6eab046..b65671a 100644
--- a/src/example/particles.cpp
+++ b/src/example/particles.cpp
@@ -14,10 +14,11 @@ using namespace crepe;
 using namespace std;
 
 int main(int argc, char * argv[]) {
-	GameObject game_object(0, "", "", Vector2{0, 0}, 0, 0);
+	ComponentManager mgr;
+	GameObject game_object = mgr.new_object("", "", Vector2{0, 0}, 0, 0);
 	Color color(0, 0, 0, 0);
 	Sprite test_sprite = game_object.add_component<Sprite>(
-		make_shared<Texture>("../asset/texture/img.png"), color,
+		make_shared<Texture>("asset/texture/img.png"), color,
 		FlipSettings{true, true});
 	game_object.add_component<ParticleEmitter>(ParticleEmitter::Data{
 		.position = {0, 0},
diff --git a/src/example/rendering.cpp b/src/example/rendering.cpp
index abd11b1..2157bdc 100644
--- a/src/example/rendering.cpp
+++ b/src/example/rendering.cpp
@@ -31,21 +31,21 @@ int main() {
 	{
 		Color color(0, 0, 0, 0);
 		obj.add_component<Sprite>(
-			make_shared<Texture>("../asset/texture/img.png"), color,
+			make_shared<Texture>("asset/texture/img.png"), color,
 			FlipSettings{false, false});
 		obj.add_component<Camera>(Color::get_red());
 	}
 	{
 		Color color(0, 0, 0, 0);
 		obj1.add_component<Sprite>(
-			make_shared<Texture>("../asset/texture/second.png"), color,
+			make_shared<Texture>("asset/texture/second.png"), color,
 			FlipSettings{true, true});
 	}
 
 	/*
 	{
 		Color color(0, 0, 0, 0);
-		auto img = mgr.cache<Texture>("../asset/texture/second.png");
+		auto img = mgr.cache<Texture>("asset/texture/second.png");
 		obj2.add_component<Sprite>(img, color, FlipSettings{true, true});
 	}
 	*/
diff --git a/src/test/AssetTest.cpp b/src/test/AssetTest.cpp
new file mode 100644
index 0000000..c3ff158
--- /dev/null
+++ b/src/test/AssetTest.cpp
@@ -0,0 +1,33 @@
+#include "api/Config.h"
+#include <gtest/gtest.h>
+
+#include <crepe/Asset.h>
+
+using namespace std;
+using namespace crepe;
+using namespace testing;
+
+class AssetTest : public Test {
+public:
+	Config & cfg = Config::get_instance();
+	void SetUp() override {
+		this->cfg.asset.root_pattern = ".crepe-root";
+	}
+};
+
+TEST_F(AssetTest, Existant) {
+	ASSERT_NO_THROW(Asset{"asset/texture/img.png"});
+}
+
+TEST_F(AssetTest, Nonexistant) {
+	ASSERT_ANY_THROW(Asset{"asset/nonexistant"});
+}
+
+TEST_F(AssetTest, Rootless) {
+	cfg.asset.root_pattern.clear();
+
+	string arbitrary = "\\/this is / /../passed through as-is";
+	Asset asset{arbitrary};
+	ASSERT_EQ(arbitrary, asset.get_path());
+}
+
diff --git a/src/test/AudioTest.cpp b/src/test/AudioTest.cpp
index 6e2706c..e181de9 100644
--- a/src/test/AudioTest.cpp
+++ b/src/test/AudioTest.cpp
@@ -1,9 +1,9 @@
-#include "system/AudioSystem.h"
 #include <gtest/gtest.h>
 
 #include <crepe/ComponentManager.h>
 #include <crepe/api/AudioSource.h>
 #include <crepe/api/GameObject.h>
+#include <crepe/system/AudioSystem.h>
 
 using namespace std;
 using namespace crepe;
@@ -17,7 +17,7 @@ public:
 	void SetUp() override {
 		auto & mgr = this->component_manager;
 		GameObject entity = mgr.new_object("name");
-		entity.add_component<AudioSource>("../mwe/audio/sfx1.wav");
+		entity.add_component<AudioSource>("mwe/audio/sfx1.wav");
 	}
 };
 
diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt
index 1a986bd..5ea90f7 100644
--- a/src/test/CMakeLists.txt
+++ b/src/test/CMakeLists.txt
@@ -4,5 +4,6 @@ target_sources(test_main PUBLIC
 	ScriptTest.cpp
 	ParticleTest.cpp
 	AudioTest.cpp
+	AssetTest.cpp
 )
 
diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp
index 1a89e3a..cd2ec2a 100644
--- a/src/test/ParticleTest.cpp
+++ b/src/test/ParticleTest.cpp
@@ -29,7 +29,7 @@ public:
 
 			Color color(0, 0, 0, 0);
 			Sprite test_sprite = game_object.add_component<Sprite>(
-				make_shared<Texture>("../asset/texture/img.png"), color,
+				make_shared<Texture>("asset/texture/img.png"), color,
 				FlipSettings{true, true});
 
 			game_object.add_component<ParticleEmitter>(ParticleEmitter::Data{
-- 
cgit v1.2.3


From 431b0bd7c6c502b42bb5be5488371d8c475e7024 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Thu, 14 Nov 2024 14:06:18 +0100
Subject: move some shit around

---
 src/crepe/Asset.cpp               | 50 ------------------------------
 src/crepe/Asset.h                 | 43 --------------------------
 src/crepe/CMakeLists.txt          |  2 --
 src/crepe/api/Asset.cpp           | 50 ++++++++++++++++++++++++++++++
 src/crepe/api/Asset.h             | 43 ++++++++++++++++++++++++++
 src/crepe/api/AssetManager.cpp    | 17 ----------
 src/crepe/api/AssetManager.h      | 65 ---------------------------------------
 src/crepe/api/AssetManager.hpp    | 24 ---------------
 src/crepe/api/AudioSource.h       |  5 ++-
 src/crepe/api/CMakeLists.txt      |  8 +++--
 src/crepe/api/ResourceManager.cpp | 17 ++++++++++
 src/crepe/api/ResourceManager.h   | 65 +++++++++++++++++++++++++++++++++++++++
 src/crepe/api/ResourceManager.hpp | 24 +++++++++++++++
 src/crepe/facade/Sound.cpp        |  2 +-
 src/test/AssetTest.cpp            |  2 +-
 15 files changed, 208 insertions(+), 209 deletions(-)
 delete mode 100644 src/crepe/Asset.cpp
 delete mode 100644 src/crepe/Asset.h
 create mode 100644 src/crepe/api/Asset.cpp
 create mode 100644 src/crepe/api/Asset.h
 delete mode 100644 src/crepe/api/AssetManager.cpp
 delete mode 100644 src/crepe/api/AssetManager.h
 delete mode 100644 src/crepe/api/AssetManager.hpp
 create mode 100644 src/crepe/api/ResourceManager.cpp
 create mode 100644 src/crepe/api/ResourceManager.h
 create mode 100644 src/crepe/api/ResourceManager.hpp

(limited to 'src')

diff --git a/src/crepe/Asset.cpp b/src/crepe/Asset.cpp
deleted file mode 100644
index 8692c6c..0000000
--- a/src/crepe/Asset.cpp
+++ /dev/null
@@ -1,50 +0,0 @@
-#include <filesystem>
-#include <stdexcept>
-#include <whereami.h>
-
-#include "Asset.h"
-#include "api/Config.h"
-
-using namespace crepe;
-using namespace std;
-
-Asset::Asset(const string & src) : src(find_asset(src)) { }
-Asset::Asset(const char * src) : src(find_asset(src)) { }
-
-const string & Asset::get_path() const noexcept { return this->src; }
-
-string Asset::find_asset(const string & src) const {
-	auto & cfg = Config::get_instance();
-	auto & root_pattern = cfg.asset.root_pattern;
-
-	// if root_pattern is empty, find_asset must return all paths as-is
-	if (root_pattern.empty()) return src;
-
-	// absolute paths do not need to be resolved, only canonicalized
-	filesystem::path path = src;
-	if (path.is_absolute())
-		return filesystem::canonical(path);
-
-	// find directory matching root_pattern
-	filesystem::path root = this->whereami();
-	while (1) {
-		if (filesystem::exists(root / root_pattern))
-			break;
-		if (!root.has_parent_path())
-			throw runtime_error(format("Asset: Cannot find root pattern ({})", root_pattern));
-		root = root.parent_path();
-	}
-
-	// join path to root (base directory) and canonicalize
-	return filesystem::canonical(root / path);
-}
-
-string Asset::whereami() const noexcept {
-	string path;
-	size_t path_length = wai_getExecutablePath(NULL, 0, NULL);
-	path.resize(path_length + 1); // wai writes null byte
-	wai_getExecutablePath(path.data(), path_length, NULL);
-	path.resize(path_length);
-	return path;
-}
-
diff --git a/src/crepe/Asset.h b/src/crepe/Asset.h
deleted file mode 100644
index f6e6782..0000000
--- a/src/crepe/Asset.h
+++ /dev/null
@@ -1,43 +0,0 @@
-#pragma once
-
-#include <string>
-
-namespace crepe {
-
-/**
- * \brief Asset location helper
- *
- * This class is used to locate game asset files, and should *always* be used
- * instead of reading file paths directly.
- */
-class Asset {
-public:
-	/**
-	 * \param src  Unique identifier to asset
-	 */
-	Asset(const std::string & src);
-	/**
-	 * \param src  Unique identifier to asset
-	 */
-	Asset(const char * src);
-
-public:
-	/**
-	 * \brief Get the path to this asset
-	 * \return path to this asset
-	 */
-	const std::string & get_path() const noexcept;
-
-private:
-	//! path to asset
-	const std::string src;
-
-private:
-	std::string find_asset(const std::string & src) const;
-	/**
-	 * \returns The path to the current executable
-	 */
-	std::string whereami() const noexcept;
-};
-
-} // namespace crepe
diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt
index 52a781e..05f86d7 100644
--- a/src/crepe/CMakeLists.txt
+++ b/src/crepe/CMakeLists.txt
@@ -1,5 +1,4 @@
 target_sources(crepe PUBLIC
-	Asset.cpp
 	Particle.cpp
 	ComponentManager.cpp
 	Component.cpp
@@ -8,7 +7,6 @@ target_sources(crepe PUBLIC
 )
 
 target_sources(crepe PUBLIC FILE_SET HEADERS FILES
-	Asset.h
 	ComponentManager.h
 	ComponentManager.hpp
 	Component.h
diff --git a/src/crepe/api/Asset.cpp b/src/crepe/api/Asset.cpp
new file mode 100644
index 0000000..8692c6c
--- /dev/null
+++ b/src/crepe/api/Asset.cpp
@@ -0,0 +1,50 @@
+#include <filesystem>
+#include <stdexcept>
+#include <whereami.h>
+
+#include "Asset.h"
+#include "api/Config.h"
+
+using namespace crepe;
+using namespace std;
+
+Asset::Asset(const string & src) : src(find_asset(src)) { }
+Asset::Asset(const char * src) : src(find_asset(src)) { }
+
+const string & Asset::get_path() const noexcept { return this->src; }
+
+string Asset::find_asset(const string & src) const {
+	auto & cfg = Config::get_instance();
+	auto & root_pattern = cfg.asset.root_pattern;
+
+	// if root_pattern is empty, find_asset must return all paths as-is
+	if (root_pattern.empty()) return src;
+
+	// absolute paths do not need to be resolved, only canonicalized
+	filesystem::path path = src;
+	if (path.is_absolute())
+		return filesystem::canonical(path);
+
+	// find directory matching root_pattern
+	filesystem::path root = this->whereami();
+	while (1) {
+		if (filesystem::exists(root / root_pattern))
+			break;
+		if (!root.has_parent_path())
+			throw runtime_error(format("Asset: Cannot find root pattern ({})", root_pattern));
+		root = root.parent_path();
+	}
+
+	// join path to root (base directory) and canonicalize
+	return filesystem::canonical(root / path);
+}
+
+string Asset::whereami() const noexcept {
+	string path;
+	size_t path_length = wai_getExecutablePath(NULL, 0, NULL);
+	path.resize(path_length + 1); // wai writes null byte
+	wai_getExecutablePath(path.data(), path_length, NULL);
+	path.resize(path_length);
+	return path;
+}
+
diff --git a/src/crepe/api/Asset.h b/src/crepe/api/Asset.h
new file mode 100644
index 0000000..f6e6782
--- /dev/null
+++ b/src/crepe/api/Asset.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include <string>
+
+namespace crepe {
+
+/**
+ * \brief Asset location helper
+ *
+ * This class is used to locate game asset files, and should *always* be used
+ * instead of reading file paths directly.
+ */
+class Asset {
+public:
+	/**
+	 * \param src  Unique identifier to asset
+	 */
+	Asset(const std::string & src);
+	/**
+	 * \param src  Unique identifier to asset
+	 */
+	Asset(const char * src);
+
+public:
+	/**
+	 * \brief Get the path to this asset
+	 * \return path to this asset
+	 */
+	const std::string & get_path() const noexcept;
+
+private:
+	//! path to asset
+	const std::string src;
+
+private:
+	std::string find_asset(const std::string & src) const;
+	/**
+	 * \returns The path to the current executable
+	 */
+	std::string whereami() const noexcept;
+};
+
+} // namespace crepe
diff --git a/src/crepe/api/AssetManager.cpp b/src/crepe/api/AssetManager.cpp
deleted file mode 100644
index 3925758..0000000
--- a/src/crepe/api/AssetManager.cpp
+++ /dev/null
@@ -1,17 +0,0 @@
-#include "util/Log.h"
-
-#include "AssetManager.h"
-
-using namespace crepe;
-
-AssetManager & AssetManager::get_instance() {
-	static AssetManager instance;
-	return instance;
-}
-
-AssetManager::~AssetManager() {
-	dbg_trace();
-	this->asset_cache.clear();
-}
-
-AssetManager::AssetManager() { dbg_trace(); }
diff --git a/src/crepe/api/AssetManager.h b/src/crepe/api/AssetManager.h
deleted file mode 100644
index 86a9902..0000000
--- a/src/crepe/api/AssetManager.h
+++ /dev/null
@@ -1,65 +0,0 @@
-#pragma once
-
-#include <any>
-#include <memory>
-#include <string>
-#include <unordered_map>
-
-namespace crepe {
-
-/**
- * \brief The AssetManager is responsible for storing and managing assets over
- * multiple scenes.
- * 
- * The AssetManager ensures that assets are loaded once and can be accessed
- * across different scenes. It caches assets to avoid reloading them every time
- * a scene is loaded. Assets are retained in memory until the AssetManager is
- * destroyed, at which point the cached assets are cleared.
- */
-class AssetManager {
-
-private:
-	//! A cache that holds all the assets, accessible by their file path, over multiple scenes.
-	std::unordered_map<std::string, std::any> asset_cache;
-
-private:
-	AssetManager();
-	virtual ~AssetManager();
-
-public:
-	AssetManager(const AssetManager &) = delete;
-	AssetManager(AssetManager &&) = delete;
-	AssetManager & operator=(const AssetManager &) = delete;
-	AssetManager & operator=(AssetManager &&) = delete;
-
-	/**
-	 * \brief Retrieves the singleton instance of the AssetManager.
-	 *
-	 * \return A reference to the single instance of the AssetManager.
-	 */
-	static AssetManager & get_instance();
-
-public:
-	/**
-	 * \brief Caches an asset by loading it from the given file path.
-	 *
-	 * \param file_path The path to the asset file to load.
-	 * \param reload If true, the asset will be reloaded from the file, even if
-	 * it is already cached.
-	 * \tparam T The type of asset to cache (e.g., texture, sound, etc.).
-	 * 
-	 * \return A shared pointer to the cached asset.
-	 * 
-	 * This template function caches the asset at the given file path. If the
-	 * asset is already cached and `reload` is false, the existing cached version
-	 * will be returned. Otherwise, the asset will be reloaded and added to the
-	 * cache.
-	 */
-	template <typename T>
-	std::shared_ptr<T> cache(const std::string & file_path,
-							 bool reload = false);
-};
-
-} // namespace crepe
-
-#include "AssetManager.hpp"
diff --git a/src/crepe/api/AssetManager.hpp b/src/crepe/api/AssetManager.hpp
deleted file mode 100644
index 977b4e1..0000000
--- a/src/crepe/api/AssetManager.hpp
+++ /dev/null
@@ -1,24 +0,0 @@
-#pragma once
-
-#include "AssetManager.h"
-
-namespace crepe {
-
-template <typename asset>
-std::shared_ptr<asset> AssetManager::cache(const std::string & file_path,
-										   bool reload) {
-	auto it = asset_cache.find(file_path);
-
-	if (!reload && it != asset_cache.end()) {
-		return std::any_cast<std::shared_ptr<asset>>(it->second);
-	}
-
-	std::shared_ptr<asset> new_asset
-		= std::make_shared<asset>(file_path.c_str());
-
-	asset_cache[file_path] = new_asset;
-
-	return new_asset;
-}
-
-} // namespace crepe
diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h
index 0748267..8a78927 100644
--- a/src/crepe/api/AudioSource.h
+++ b/src/crepe/api/AudioSource.h
@@ -1,11 +1,10 @@
 #pragma once
 
-#include <memory>
-
-#include "../Asset.h"
 #include "../Component.h"
 #include "../types.h"
 
+#include "Asset.h"
+
 namespace crepe {
 
 //! Audio source component
diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt
index 93a1fac..70f1527 100644
--- a/src/crepe/api/CMakeLists.txt
+++ b/src/crepe/api/CMakeLists.txt
@@ -8,7 +8,7 @@ target_sources(crepe PUBLIC
 	Transform.cpp
 	Color.cpp
 	Texture.cpp
-	AssetManager.cpp
+	ResourceManager.cpp
 	Sprite.cpp
 	SaveManager.cpp
 	Config.cpp
@@ -20,6 +20,7 @@ target_sources(crepe PUBLIC
 	Animator.cpp
 	LoopManager.cpp
 	LoopTimer.cpp
+	Asset.cpp
 )
 
 target_sources(crepe PUBLIC FILE_SET HEADERS FILES
@@ -35,8 +36,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
 	Vector2.h
 	Color.h
 	Texture.h 
-	AssetManager.h 
-	AssetManager.hpp
+	ResourceManager.h 
+	ResourceManager.hpp
 	SaveManager.h
 	Scene.h
 	Metadata.h
@@ -46,4 +47,5 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
 	Animator.h
 	LoopManager.h
 	LoopTimer.h
+	Asset.h
 )
diff --git a/src/crepe/api/ResourceManager.cpp b/src/crepe/api/ResourceManager.cpp
new file mode 100644
index 0000000..470e511
--- /dev/null
+++ b/src/crepe/api/ResourceManager.cpp
@@ -0,0 +1,17 @@
+#include "util/Log.h"
+
+#include "ResourceManager.h"
+
+using namespace crepe;
+
+ResourceManager & ResourceManager::get_instance() {
+	static ResourceManager instance;
+	return instance;
+}
+
+ResourceManager::~ResourceManager() {
+	dbg_trace();
+	this->asset_cache.clear();
+}
+
+ResourceManager::ResourceManager() { dbg_trace(); }
diff --git a/src/crepe/api/ResourceManager.h b/src/crepe/api/ResourceManager.h
new file mode 100644
index 0000000..7a45493
--- /dev/null
+++ b/src/crepe/api/ResourceManager.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#include <any>
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+namespace crepe {
+
+/**
+ * \brief The ResourceManager is responsible for storing and managing assets over
+ * multiple scenes.
+ * 
+ * The ResourceManager ensures that assets are loaded once and can be accessed
+ * across different scenes. It caches assets to avoid reloading them every time
+ * a scene is loaded. Assets are retained in memory until the ResourceManager is
+ * destroyed, at which point the cached assets are cleared.
+ */
+class ResourceManager {
+
+private:
+	//! A cache that holds all the assets, accessible by their file path, over multiple scenes.
+	std::unordered_map<std::string, std::any> asset_cache;
+
+private:
+	ResourceManager();
+	virtual ~ResourceManager();
+
+public:
+	ResourceManager(const ResourceManager &) = delete;
+	ResourceManager(ResourceManager &&) = delete;
+	ResourceManager & operator=(const ResourceManager &) = delete;
+	ResourceManager & operator=(ResourceManager &&) = delete;
+
+	/**
+	 * \brief Retrieves the singleton instance of the ResourceManager.
+	 *
+	 * \return A reference to the single instance of the ResourceManager.
+	 */
+	static ResourceManager & get_instance();
+
+public:
+	/**
+	 * \brief Caches an asset by loading it from the given file path.
+	 *
+	 * \param file_path The path to the asset file to load.
+	 * \param reload If true, the asset will be reloaded from the file, even if
+	 * it is already cached.
+	 * \tparam T The type of asset to cache (e.g., texture, sound, etc.).
+	 * 
+	 * \return A shared pointer to the cached asset.
+	 * 
+	 * This template function caches the asset at the given file path. If the
+	 * asset is already cached and `reload` is false, the existing cached version
+	 * will be returned. Otherwise, the asset will be reloaded and added to the
+	 * cache.
+	 */
+	template <typename T>
+	std::shared_ptr<T> cache(const std::string & file_path,
+							 bool reload = false);
+};
+
+} // namespace crepe
+
+#include "ResourceManager.hpp"
diff --git a/src/crepe/api/ResourceManager.hpp b/src/crepe/api/ResourceManager.hpp
new file mode 100644
index 0000000..9cd4bcb
--- /dev/null
+++ b/src/crepe/api/ResourceManager.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "ResourceManager.h"
+
+namespace crepe {
+
+template <typename asset>
+std::shared_ptr<asset> ResourceManager::cache(const std::string & file_path,
+										   bool reload) {
+	auto it = asset_cache.find(file_path);
+
+	if (!reload && it != asset_cache.end()) {
+		return std::any_cast<std::shared_ptr<asset>>(it->second);
+	}
+
+	std::shared_ptr<asset> new_asset
+		= std::make_shared<asset>(file_path.c_str());
+
+	asset_cache[file_path] = new_asset;
+
+	return new_asset;
+}
+
+} // namespace crepe
diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp
index b7bfeab..726f11f 100644
--- a/src/crepe/facade/Sound.cpp
+++ b/src/crepe/facade/Sound.cpp
@@ -1,6 +1,6 @@
 #include <memory>
 
-#include "../Asset.h"
+#include "../api/Asset.h"
 #include "../util/Log.h"
 
 #include "Sound.h"
diff --git a/src/test/AssetTest.cpp b/src/test/AssetTest.cpp
index c3ff158..324a3f1 100644
--- a/src/test/AssetTest.cpp
+++ b/src/test/AssetTest.cpp
@@ -1,7 +1,7 @@
 #include "api/Config.h"
 #include <gtest/gtest.h>
 
-#include <crepe/Asset.h>
+#include <crepe/api/Asset.h>
 
 using namespace std;
 using namespace crepe;
-- 
cgit v1.2.3


From 213f947d0907858cace470736c15f87caa934591 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Thu, 14 Nov 2024 17:01:10 +0100
Subject: fix resource manager

---
 src/crepe/api/Asset.cpp           |  4 ++++
 src/crepe/api/Asset.h             | 10 ++++++++++
 src/crepe/api/ResourceManager.cpp |  6 +-----
 src/crepe/api/ResourceManager.h   | 23 +++++++++++++++--------
 src/crepe/api/ResourceManager.hpp | 25 ++++++++++++++-----------
 5 files changed, 44 insertions(+), 24 deletions(-)

(limited to 'src')

diff --git a/src/crepe/api/Asset.cpp b/src/crepe/api/Asset.cpp
index 8692c6c..1887814 100644
--- a/src/crepe/api/Asset.cpp
+++ b/src/crepe/api/Asset.cpp
@@ -48,3 +48,7 @@ string Asset::whereami() const noexcept {
 	return path;
 }
 
+size_t std::hash<const Asset>::operator()(const Asset & asset) const noexcept {
+	return std::hash<string>{}(asset.get_path());
+};
+
diff --git a/src/crepe/api/Asset.h b/src/crepe/api/Asset.h
index f6e6782..0f6b0b3 100644
--- a/src/crepe/api/Asset.h
+++ b/src/crepe/api/Asset.h
@@ -1,6 +1,7 @@
 #pragma once
 
 #include <string>
+#include <unordered_map>
 
 namespace crepe {
 
@@ -41,3 +42,12 @@ private:
 };
 
 } // namespace crepe
+
+namespace std {
+
+template<> struct hash<const crepe::Asset> {
+	size_t operator()(const crepe::Asset & asset) const noexcept;
+};
+
+}
+
diff --git a/src/crepe/api/ResourceManager.cpp b/src/crepe/api/ResourceManager.cpp
index 470e511..17fbd9b 100644
--- a/src/crepe/api/ResourceManager.cpp
+++ b/src/crepe/api/ResourceManager.cpp
@@ -9,9 +9,5 @@ ResourceManager & ResourceManager::get_instance() {
 	return instance;
 }
 
-ResourceManager::~ResourceManager() {
-	dbg_trace();
-	this->asset_cache.clear();
-}
-
+ResourceManager::~ResourceManager() { dbg_trace(); }
 ResourceManager::ResourceManager() { dbg_trace(); }
diff --git a/src/crepe/api/ResourceManager.h b/src/crepe/api/ResourceManager.h
index 7a45493..a69a9fa 100644
--- a/src/crepe/api/ResourceManager.h
+++ b/src/crepe/api/ResourceManager.h
@@ -1,12 +1,15 @@
 #pragma once
 
-#include <any>
 #include <memory>
-#include <string>
 #include <unordered_map>
 
+#include "Asset.h"
+#include "Resource.h"
+
 namespace crepe {
 
+class Sound;
+
 /**
  * \brief The ResourceManager is responsible for storing and managing assets over
  * multiple scenes.
@@ -20,18 +23,18 @@ class ResourceManager {
 
 private:
 	//! A cache that holds all the assets, accessible by their file path, over multiple scenes.
-	std::unordered_map<std::string, std::any> asset_cache;
+	std::unordered_map<const Asset, std::unique_ptr<Resource>> resources;
 
 private:
-	ResourceManager();
-	virtual ~ResourceManager();
+	ResourceManager(); // dbg_trace
+	virtual ~ResourceManager(); // dbg_trace
 
-public:
 	ResourceManager(const ResourceManager &) = delete;
 	ResourceManager(ResourceManager &&) = delete;
 	ResourceManager & operator=(const ResourceManager &) = delete;
 	ResourceManager & operator=(ResourceManager &&) = delete;
 
+public:
 	/**
 	 * \brief Retrieves the singleton instance of the ResourceManager.
 	 *
@@ -56,8 +59,12 @@ public:
 	 * cache.
 	 */
 	template <typename T>
-	std::shared_ptr<T> cache(const std::string & file_path,
-							 bool reload = false);
+	T & cache(const Asset & asset);
+
+	/**
+	 * \brief Clear the resource cache
+	 */
+	void clear();
 };
 
 } // namespace crepe
diff --git a/src/crepe/api/ResourceManager.hpp b/src/crepe/api/ResourceManager.hpp
index 9cd4bcb..62cac20 100644
--- a/src/crepe/api/ResourceManager.hpp
+++ b/src/crepe/api/ResourceManager.hpp
@@ -1,24 +1,27 @@
 #pragma once
 
+#include <stdexcept>
+#include <format>
+
 #include "ResourceManager.h"
 
 namespace crepe {
 
-template <typename asset>
-std::shared_ptr<asset> ResourceManager::cache(const std::string & file_path,
-										   bool reload) {
-	auto it = asset_cache.find(file_path);
+template <typename T>
+T & ResourceManager::cache(const Asset & asset) {
+	using namespace std;
+	static_assert(is_base_of<Resource, T>::value, "cache must recieve a derivative class of Resource");
 
-	if (!reload && it != asset_cache.end()) {
-		return std::any_cast<std::shared_ptr<asset>>(it->second);
-	}
+	if (!this->resources.contains(asset))
+		this->resources[asset] = make_unique<T>(asset);
 
-	std::shared_ptr<asset> new_asset
-		= std::make_shared<asset>(file_path.c_str());
+	Resource * resource = this->resources.at(asset).get();
+	T * concrete_resource = dynamic_cast<T *>(resource);
 
-	asset_cache[file_path] = new_asset;
+	if (concrete_resource == nullptr)
+		throw runtime_error(format("ResourceManager: mismatch between requested type and actual type of resource ({})", asset.get_path()));
 
-	return new_asset;
+	return *concrete_resource;
 }
 
 } // namespace crepe
-- 
cgit v1.2.3


From ab0b4923c4f49e7a28f6d17e994d3e013ca344bb Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Thu, 14 Nov 2024 18:04:03 +0100
Subject: more WIP audio system + utilities

---
 src/crepe/CMakeLists.txt          |  2 ++
 src/crepe/Resource.cpp            |  6 ++++
 src/crepe/Resource.h              | 12 ++-----
 src/crepe/api/ResourceManager.cpp | 13 ++++++++
 src/crepe/api/ResourceManager.h   | 13 ++++----
 src/crepe/facade/Sound.cpp        | 22 ++++++-------
 src/crepe/facade/Sound.h          |  7 ++--
 src/crepe/util/CMakeLists.txt     |  2 ++
 src/crepe/util/OptionalRef.h      | 41 ++++++++++++++++++++++++
 src/crepe/util/OptionalRef.hpp    | 67 +++++++++++++++++++++++++++++++++++++++
 src/test/AssetTest.cpp            |  2 +-
 src/test/CMakeLists.txt           |  2 ++
 src/test/OptionalRefTest.cpp      | 16 ++++++++++
 src/test/ResourceManagerTest.cpp  | 21 ++++++++++++
 14 files changed, 194 insertions(+), 32 deletions(-)
 create mode 100644 src/crepe/Resource.cpp
 create mode 100644 src/crepe/util/OptionalRef.h
 create mode 100644 src/crepe/util/OptionalRef.hpp
 create mode 100644 src/test/OptionalRefTest.cpp
 create mode 100644 src/test/ResourceManagerTest.cpp

(limited to 'src')

diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt
index 05f86d7..d72d4a0 100644
--- a/src/crepe/CMakeLists.txt
+++ b/src/crepe/CMakeLists.txt
@@ -4,6 +4,7 @@ target_sources(crepe PUBLIC
 	Component.cpp
 	Collider.cpp
 	Exception.cpp
+	Resource.cpp
 )
 
 target_sources(crepe PUBLIC FILE_SET HEADERS FILES
@@ -15,6 +16,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
 	ValueBroker.hpp
 	Exception.h
 	Exception.hpp
+	Resource.h
 )
 
 add_subdirectory(api)
diff --git a/src/crepe/Resource.cpp b/src/crepe/Resource.cpp
new file mode 100644
index 0000000..e254695
--- /dev/null
+++ b/src/crepe/Resource.cpp
@@ -0,0 +1,6 @@
+#include "Resource.h"
+
+using namespace crepe;
+
+Resource::Resource(const Asset & asset) { }
+
diff --git a/src/crepe/Resource.h b/src/crepe/Resource.h
index dcf3dbd..95b4d06 100644
--- a/src/crepe/Resource.h
+++ b/src/crepe/Resource.h
@@ -12,16 +12,10 @@ class Asset;
  * resource (e.g. textures, sounds).
  */
 class Resource {
+public:
+	Resource(const Asset & src);
+
 private:
-	/**
-	 * \brief Prototype pattern clone function.
-	 *
-	 * \param src Source file of new resource (abstraction for file saved on
-	 * disk)
-	 *
-	 * \returns New instance of concrete resource
-	 */
-	virtual std::unique_ptr<Resource> clone(const Asset & src) const = 0;
 	/**
 	 * The resource manager uses \c clone to create new instances of the concrete
 	 * resource class. This may be used to inherit references to classes that
diff --git a/src/crepe/api/ResourceManager.cpp b/src/crepe/api/ResourceManager.cpp
index 17fbd9b..6eb4afd 100644
--- a/src/crepe/api/ResourceManager.cpp
+++ b/src/crepe/api/ResourceManager.cpp
@@ -2,6 +2,9 @@
 
 #include "ResourceManager.h"
 
+// default resource cache functions
+#include "../facade/Sound.h"
+
 using namespace crepe;
 
 ResourceManager & ResourceManager::get_instance() {
@@ -11,3 +14,13 @@ ResourceManager & ResourceManager::get_instance() {
 
 ResourceManager::~ResourceManager() { dbg_trace(); }
 ResourceManager::ResourceManager() { dbg_trace(); }
+
+void ResourceManager::clear() {
+	this->resources.clear();
+}
+
+template <>
+Sound & ResourceManager::cache<Sound>(const Asset & asset) {
+	return this->cache<Sound>(asset);
+}
+
diff --git a/src/crepe/api/ResourceManager.h b/src/crepe/api/ResourceManager.h
index a69a9fa..468af16 100644
--- a/src/crepe/api/ResourceManager.h
+++ b/src/crepe/api/ResourceManager.h
@@ -51,22 +51,23 @@ public:
 	 * it is already cached.
 	 * \tparam T The type of asset to cache (e.g., texture, sound, etc.).
 	 * 
-	 * \return A shared pointer to the cached asset.
+	 * \return A reference to the resource
 	 * 
 	 * This template function caches the asset at the given file path. If the
-	 * asset is already cached and `reload` is false, the existing cached version
-	 * will be returned. Otherwise, the asset will be reloaded and added to the
+	 * asset is already cached, the existing instance will be returned.
+	 * Otherwise, the concrete resource will be instantiated and added to the
 	 * cache.
 	 */
 	template <typename T>
 	T & cache(const Asset & asset);
 
-	/**
-	 * \brief Clear the resource cache
-	 */
+	//! Clear the resource cache
 	void clear();
 };
 
+template <>
+Sound & ResourceManager::cache<Sound>(const Asset & asset);
+
 } // namespace crepe
 
 #include "ResourceManager.hpp"
diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp
index 726f11f..4eefcda 100644
--- a/src/crepe/facade/Sound.cpp
+++ b/src/crepe/facade/Sound.cpp
@@ -1,5 +1,3 @@
-#include <memory>
-
 #include "../api/Asset.h"
 #include "../util/Log.h"
 
@@ -9,16 +7,13 @@
 using namespace crepe;
 using namespace std;
 
-Sound::Sound(SoundContext & ctx) : context(ctx) { dbg_trace(); }
-
-unique_ptr<Resource> Sound::clone(const Asset & src) const {
-	auto instance = make_unique<Sound>(*this);
-	instance->sample.load(src.get_path().c_str());
-	return instance;
+Sound::Sound(const Asset & src) : Resource(src) {
+	this->sample.load(src.get_path().c_str());
+	dbg_trace();
 }
 
 void Sound::play() {
-	SoundContext & ctx = this->context;
+	SoundContext & ctx = this->context.get();
 	if (ctx.engine.getPause(this->handle)) {
 		// resume if paused
 		ctx.engine.setPause(this->handle, false);
@@ -30,13 +25,13 @@ void Sound::play() {
 }
 
 void Sound::pause() {
-	SoundContext & ctx = this->context;
+	SoundContext & ctx = this->context.get();
 	if (ctx.engine.getPause(this->handle)) return;
 	ctx.engine.setPause(this->handle, true);
 }
 
 void Sound::rewind() {
-	SoundContext & ctx = this->context;
+	SoundContext & ctx = this->context.get();
 	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
 	ctx.engine.seek(this->handle, 0);
 }
@@ -44,7 +39,7 @@ void Sound::rewind() {
 void Sound::set_volume(float volume) {
 	this->volume = volume;
 
-	SoundContext & ctx = this->context;
+	SoundContext & ctx = this->context.get();
 	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
 	ctx.engine.setVolume(this->handle, this->volume);
 }
@@ -52,7 +47,8 @@ void Sound::set_volume(float volume) {
 void Sound::set_looping(bool looping) {
 	this->looping = looping;
 
-	SoundContext & ctx = this->context;
+	SoundContext & ctx = this->context.get();
 	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
 	ctx.engine.setLooping(this->handle, this->looping);
 }
+
diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h
index 8342b46..6f8462a 100644
--- a/src/crepe/facade/Sound.h
+++ b/src/crepe/facade/Sound.h
@@ -4,6 +4,7 @@
 #include <soloud/soloud.h>
 #include <soloud/soloud_wav.h>
 
+#include "../util/OptionalRef.h"
 #include "../Resource.h"
 
 namespace crepe {
@@ -18,6 +19,7 @@ class SoundContext;
  */
 class Sound : public Resource {
 public:
+	Sound(const Asset & src);
 	/**
 	 * \brief Pause this sample
 	 *
@@ -72,13 +74,12 @@ public:
 	bool get_looping() const { return this->looping; }
 
 public:
-	Sound(SoundContext & ctx);
-	std::unique_ptr<Resource> clone(const Asset & src) const override;
+	void set_context(SoundContext & ctx);
 
 private:
 	SoLoud::Wav sample;
 	SoLoud::handle handle;
-	SoundContext & context;
+	OptionalRef<SoundContext> context;
 
 	float volume = 1.0f;
 	bool looping = false;
diff --git a/src/crepe/util/CMakeLists.txt b/src/crepe/util/CMakeLists.txt
index 4be738a..94ed906 100644
--- a/src/crepe/util/CMakeLists.txt
+++ b/src/crepe/util/CMakeLists.txt
@@ -9,5 +9,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
 	Log.hpp
 	Proxy.h
 	Proxy.hpp
+	OptionalRef.h
+	OptionalRef.hpp
 )
 
diff --git a/src/crepe/util/OptionalRef.h b/src/crepe/util/OptionalRef.h
new file mode 100644
index 0000000..1ad3a6d
--- /dev/null
+++ b/src/crepe/util/OptionalRef.h
@@ -0,0 +1,41 @@
+#pragma once
+
+namespace crepe {
+
+/**
+ * \brief Optional reference utility
+ *
+ * This class doesn't need to know the full definition of \c T to be used.
+ *
+ * \tparam T Value type
+ */
+template <typename T>
+class OptionalRef {
+public:
+	OptionalRef() = default;
+	OptionalRef(T &);
+  OptionalRef<T> & operator=(T &);
+	explicit operator bool() const noexcept;
+
+	void set(T &) noexcept;
+	T & get() const;
+	void clear() noexcept;
+
+  OptionalRef(const OptionalRef<T> &);
+  OptionalRef(OptionalRef<T> &&);
+  OptionalRef<T> & operator=(const OptionalRef<T> &);
+  OptionalRef<T> & operator=(OptionalRef<T> &&);
+
+private:
+	/**
+	 * \brief Reference to the value of type \c T
+	 *
+	 * \note This raw pointer is *not* managed, and only used as a reference!
+	 */
+	T * ref = nullptr;
+};
+
+}
+
+#include "OptionalRef.hpp"
+
diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp
new file mode 100644
index 0000000..e603a25
--- /dev/null
+++ b/src/crepe/util/OptionalRef.hpp
@@ -0,0 +1,67 @@
+#pragma once
+
+#include <stdexcept>
+
+#include "OptionalRef.h"
+
+namespace crepe {
+
+template <typename T>
+OptionalRef<T>::OptionalRef(T & ref) {
+	this->set(ref);
+}
+
+template <typename T>
+OptionalRef<T>::OptionalRef(const OptionalRef<T> & other) {
+	this->ref = other.ref;
+}
+
+template <typename T>
+OptionalRef<T>::OptionalRef(OptionalRef<T> && other) {
+	this->ref = other.ref;
+	other.clear();
+}
+
+template <typename T>
+OptionalRef<T> & OptionalRef<T>::operator=(const OptionalRef<T> & other) {
+	this->ref = other.ref;
+	return *this;
+}
+
+template <typename T>
+OptionalRef<T> & OptionalRef<T>::operator=(OptionalRef<T> && other) {
+	this->ref = other.ref;
+	other.clear();
+	return *this;
+}
+
+template <typename T>
+T & OptionalRef<T>::get() const {
+	if (this->ref == nullptr)
+		throw std::runtime_error("OptionalRef: attempt to dereference nullptr");
+	return *this->ref;
+}
+
+template <typename T>
+void OptionalRef<T>::set(T & ref) noexcept {
+	this->ref = &ref;
+}
+
+template <typename T>
+void OptionalRef<T>::clear() noexcept {
+	this->ref = nullptr;
+}
+
+template <typename T>
+OptionalRef<T> & OptionalRef<T>::operator=(T & ref) {
+	this->set(ref);
+	return *this;
+}
+
+template <typename T>
+OptionalRef<T>::operator bool() const noexcept {
+	return this->ref == nullptr;
+}
+
+}
+
diff --git a/src/test/AssetTest.cpp b/src/test/AssetTest.cpp
index 324a3f1..563a253 100644
--- a/src/test/AssetTest.cpp
+++ b/src/test/AssetTest.cpp
@@ -1,7 +1,7 @@
-#include "api/Config.h"
 #include <gtest/gtest.h>
 
 #include <crepe/api/Asset.h>
+#include <crepe/api/Config.h>
 
 using namespace std;
 using namespace crepe;
diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt
index 5ea90f7..437c296 100644
--- a/src/test/CMakeLists.txt
+++ b/src/test/CMakeLists.txt
@@ -5,5 +5,7 @@ target_sources(test_main PUBLIC
 	ParticleTest.cpp
 	AudioTest.cpp
 	AssetTest.cpp
+	ResourceManagerTest.cpp
+	OptionalRefTest.cpp
 )
 
diff --git a/src/test/OptionalRefTest.cpp b/src/test/OptionalRefTest.cpp
new file mode 100644
index 0000000..65bd816
--- /dev/null
+++ b/src/test/OptionalRefTest.cpp
@@ -0,0 +1,16 @@
+#include <gtest/gtest.h>
+
+#include <crepe/util/OptionalRef.h>
+
+using namespace std;
+using namespace crepe;
+using namespace testing;
+
+TEST(OptionalRefTest, Explicit) {
+	string value = "foo";
+	OptionalRef<string> ref;
+
+	EXPECT_FALSE(bool(ref));
+	ASSERT_THROW(ref.get(), runtime_error);
+}
+
diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp
new file mode 100644
index 0000000..42b6b5d
--- /dev/null
+++ b/src/test/ResourceManagerTest.cpp
@@ -0,0 +1,21 @@
+#include <gtest/gtest.h>
+
+#include <crepe/api/ResourceManager.h>
+
+using namespace std;
+using namespace crepe;
+using namespace testing;
+
+class ResourceManagerTest : public Test {
+public:
+	ResourceManager & manager = ResourceManager::get_instance();
+
+	void SetUp() override {
+		this->manager.clear();
+	}
+};
+
+TEST_F(ResourceManagerTest, Main) {
+	Sound & sound = this->manager.cache<Sound>("mwe/audio/sfx1.wav");
+}
+
-- 
cgit v1.2.3


From add8724446fdeae1aaec9b07544cf7a5475a9bfe Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Thu, 14 Nov 2024 19:57:45 +0100
Subject: ResourceManager working + tested

---
 src/crepe/Resource.h              |  3 +--
 src/crepe/api/Asset.cpp           |  4 ++++
 src/crepe/api/Asset.h             |  7 +++++++
 src/crepe/api/CMakeLists.txt      |  1 -
 src/crepe/api/ResourceManager.cpp | 27 +++++++++++++++++++++------
 src/crepe/api/ResourceManager.h   |  4 ----
 src/crepe/api/ResourceManager.hpp | 27 ---------------------------
 src/crepe/facade/Sound.cpp        |  5 +++++
 src/crepe/facade/Sound.h          |  2 +-
 src/crepe/util/OptionalRef.hpp    |  2 +-
 src/test/OptionalRefTest.cpp      | 24 +++++++++++++++++++++++-
 src/test/ResourceManagerTest.cpp  | 36 +++++++++++++++++++++++++++++++++---
 src/test/main.cpp                 | 15 +++++++++++++--
 13 files changed, 109 insertions(+), 48 deletions(-)
 delete mode 100644 src/crepe/api/ResourceManager.hpp

(limited to 'src')

diff --git a/src/crepe/Resource.h b/src/crepe/Resource.h
index 95b4d06..a0c8859 100644
--- a/src/crepe/Resource.h
+++ b/src/crepe/Resource.h
@@ -1,7 +1,5 @@
 #pragma once
 
-#include <memory>
-
 namespace crepe {
 
 class ResourceManager;
@@ -14,6 +12,7 @@ class Asset;
 class Resource {
 public:
 	Resource(const Asset & src);
+	virtual ~Resource() = default;
 
 private:
 	/**
diff --git a/src/crepe/api/Asset.cpp b/src/crepe/api/Asset.cpp
index 1887814..5271cf7 100644
--- a/src/crepe/api/Asset.cpp
+++ b/src/crepe/api/Asset.cpp
@@ -48,6 +48,10 @@ string Asset::whereami() const noexcept {
 	return path;
 }
 
+bool Asset::operator==(const Asset & other) const noexcept {
+	return this->src == other.src;
+}
+
 size_t std::hash<const Asset>::operator()(const Asset & asset) const noexcept {
 	return std::hash<string>{}(asset.get_path());
 };
diff --git a/src/crepe/api/Asset.h b/src/crepe/api/Asset.h
index 0f6b0b3..05dccba 100644
--- a/src/crepe/api/Asset.h
+++ b/src/crepe/api/Asset.h
@@ -29,6 +29,13 @@ public:
 	 */
 	const std::string & get_path() const noexcept;
 
+	/**
+	 * \brief Comparison operator
+	 * \param other Possibly different instance of \c Asset to test equality against
+	 * \return True if \c this and \c other are equal
+	 */
+	bool operator == (const Asset & other) const noexcept;
+
 private:
 	//! path to asset
 	const std::string src;
diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt
index 70f1527..b452f37 100644
--- a/src/crepe/api/CMakeLists.txt
+++ b/src/crepe/api/CMakeLists.txt
@@ -37,7 +37,6 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
 	Color.h
 	Texture.h 
 	ResourceManager.h 
-	ResourceManager.hpp
 	SaveManager.h
 	Scene.h
 	Metadata.h
diff --git a/src/crepe/api/ResourceManager.cpp b/src/crepe/api/ResourceManager.cpp
index 6eb4afd..7877ed9 100644
--- a/src/crepe/api/ResourceManager.cpp
+++ b/src/crepe/api/ResourceManager.cpp
@@ -1,11 +1,11 @@
+#include <stdexcept>
+
 #include "util/Log.h"
 
 #include "ResourceManager.h"
 
-// default resource cache functions
-#include "../facade/Sound.h"
-
 using namespace crepe;
+using namespace std;
 
 ResourceManager & ResourceManager::get_instance() {
 	static ResourceManager instance;
@@ -19,8 +19,23 @@ void ResourceManager::clear() {
 	this->resources.clear();
 }
 
-template <>
-Sound & ResourceManager::cache<Sound>(const Asset & asset) {
-	return this->cache<Sound>(asset);
+template <typename T>
+T & ResourceManager::cache(const Asset & asset) {
+	dbg_trace();
+	static_assert(is_base_of<Resource, T>::value, "cache must recieve a derivative class of Resource");
+
+	if (!this->resources.contains(asset))
+		this->resources[asset] = make_unique<T>(asset);
+
+	Resource * resource = this->resources.at(asset).get();
+	T * concrete_resource = dynamic_cast<T *>(resource);
+
+	if (concrete_resource == nullptr)
+		throw runtime_error(format("ResourceManager: mismatch between requested type and actual type of resource ({})", asset.get_path()));
+
+	return *concrete_resource;
 }
 
+#include "../facade/Sound.h"
+template Sound & ResourceManager::cache(const Asset &);
+
diff --git a/src/crepe/api/ResourceManager.h b/src/crepe/api/ResourceManager.h
index 468af16..efdd5c5 100644
--- a/src/crepe/api/ResourceManager.h
+++ b/src/crepe/api/ResourceManager.h
@@ -65,9 +65,5 @@ public:
 	void clear();
 };
 
-template <>
-Sound & ResourceManager::cache<Sound>(const Asset & asset);
-
 } // namespace crepe
 
-#include "ResourceManager.hpp"
diff --git a/src/crepe/api/ResourceManager.hpp b/src/crepe/api/ResourceManager.hpp
deleted file mode 100644
index 62cac20..0000000
--- a/src/crepe/api/ResourceManager.hpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#pragma once
-
-#include <stdexcept>
-#include <format>
-
-#include "ResourceManager.h"
-
-namespace crepe {
-
-template <typename T>
-T & ResourceManager::cache(const Asset & asset) {
-	using namespace std;
-	static_assert(is_base_of<Resource, T>::value, "cache must recieve a derivative class of Resource");
-
-	if (!this->resources.contains(asset))
-		this->resources[asset] = make_unique<T>(asset);
-
-	Resource * resource = this->resources.at(asset).get();
-	T * concrete_resource = dynamic_cast<T *>(resource);
-
-	if (concrete_resource == nullptr)
-		throw runtime_error(format("ResourceManager: mismatch between requested type and actual type of resource ({})", asset.get_path()));
-
-	return *concrete_resource;
-}
-
-} // namespace crepe
diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp
index 4eefcda..b589759 100644
--- a/src/crepe/facade/Sound.cpp
+++ b/src/crepe/facade/Sound.cpp
@@ -11,6 +11,7 @@ Sound::Sound(const Asset & src) : Resource(src) {
 	this->sample.load(src.get_path().c_str());
 	dbg_trace();
 }
+Sound::~Sound() { dbg_trace(); }
 
 void Sound::play() {
 	SoundContext & ctx = this->context.get();
@@ -52,3 +53,7 @@ void Sound::set_looping(bool looping) {
 	ctx.engine.setLooping(this->handle, this->looping);
 }
 
+void Sound::set_context(SoundContext & ctx) {
+	this->context = ctx;
+}
+
diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h
index 6f8462a..94b1996 100644
--- a/src/crepe/facade/Sound.h
+++ b/src/crepe/facade/Sound.h
@@ -1,6 +1,5 @@
 #pragma once
 
-#include <memory>
 #include <soloud/soloud.h>
 #include <soloud/soloud_wav.h>
 
@@ -20,6 +19,7 @@ class SoundContext;
 class Sound : public Resource {
 public:
 	Sound(const Asset & src);
+	~Sound(); // dbg_trace
 	/**
 	 * \brief Pause this sample
 	 *
diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp
index e603a25..7b201b0 100644
--- a/src/crepe/util/OptionalRef.hpp
+++ b/src/crepe/util/OptionalRef.hpp
@@ -60,7 +60,7 @@ OptionalRef<T> & OptionalRef<T>::operator=(T & ref) {
 
 template <typename T>
 OptionalRef<T>::operator bool() const noexcept {
-	return this->ref == nullptr;
+	return this->ref != nullptr;
 }
 
 }
diff --git a/src/test/OptionalRefTest.cpp b/src/test/OptionalRefTest.cpp
index 65bd816..219ccca 100644
--- a/src/test/OptionalRefTest.cpp
+++ b/src/test/OptionalRefTest.cpp
@@ -9,8 +9,30 @@ using namespace testing;
 TEST(OptionalRefTest, Explicit) {
 	string value = "foo";
 	OptionalRef<string> ref;
+	EXPECT_FALSE(ref);
+	ASSERT_THROW(ref.get(), runtime_error);
+
+	ref.set(value);
+	EXPECT_TRUE(ref);
+	ASSERT_NO_THROW(ref.get());
+
+	ref.clear();
+	EXPECT_FALSE(ref);
+	ASSERT_THROW(ref.get(), runtime_error);
+}
+
+TEST(OptionalRefTest, Implicit) {
+	string value = "foo";
+	OptionalRef<string> ref = value;
+	EXPECT_TRUE(ref);
+	ASSERT_NO_THROW(ref.get());
 
-	EXPECT_FALSE(bool(ref));
+	ref.clear();
+	EXPECT_FALSE(ref);
 	ASSERT_THROW(ref.get(), runtime_error);
+
+	ref = value;
+	EXPECT_TRUE(ref);
+	ASSERT_NO_THROW(ref.get());
 }
 
diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp
index 42b6b5d..5d1ae7a 100644
--- a/src/test/ResourceManagerTest.cpp
+++ b/src/test/ResourceManagerTest.cpp
@@ -1,5 +1,7 @@
 #include <gtest/gtest.h>
 
+#include <crepe/util/Log.h>
+#include <crepe/api/Config.h>
 #include <crepe/api/ResourceManager.h>
 
 using namespace std;
@@ -8,14 +10,42 @@ using namespace testing;
 
 class ResourceManagerTest : public Test {
 public:
-	ResourceManager & manager = ResourceManager::get_instance();
+	ResourceManager & resman = ResourceManager::get_instance();
+	Config & cfg = Config::get_instance();
 
 	void SetUp() override {
-		this->manager.clear();
+		cfg.log.level = Log::Level::TRACE;
+		resman.clear();
 	}
 };
 
 TEST_F(ResourceManagerTest, Main) {
-	Sound & sound = this->manager.cache<Sound>("mwe/audio/sfx1.wav");
+	Asset path1 = "mwe/audio/sfx1.wav";
+	Asset path2 = "mwe/audio/sfx1.wav";
+	ASSERT_EQ(path1, path2);
+
+	Sound * ptr1 = nullptr;
+	Sound * ptr2 = nullptr;
+	{
+		Log::logf(Log::Level::DEBUG, "Get first sound (constructor call)");
+		Sound & sound = resman.cache<Sound>(path1);
+		ptr1 = &sound;
+	}
+	{
+		Log::logf(Log::Level::DEBUG, "Get same sound (NO constructor call)");
+		Sound & sound = resman.cache<Sound>(path2);
+		ptr2 = &sound;
+	}
+	EXPECT_EQ(ptr1, ptr2);
+
+	Log::logf(Log::Level::DEBUG, "Clear cache (destructor call)");
+	resman.clear();
+
+	Log::logf(Log::Level::DEBUG, "Get first sound again (constructor call)");
+	Sound & sound = resman.cache<Sound>(path1);
+
+	// NOTE: there is no way (that I know of) to ensure the above statement
+	// allocates the new Sound instance in a different location than the first,
+	// so this test was verified using the above print statements.
 }
 
diff --git a/src/test/main.cpp b/src/test/main.cpp
index 241015d..19a8d6e 100644
--- a/src/test/main.cpp
+++ b/src/test/main.cpp
@@ -5,11 +5,22 @@
 using namespace crepe;
 using namespace testing;
 
+class GlobalConfigReset : public EmptyTestEventListener {
+public:
+	Config & cfg = Config::get_instance();
+
+	// This function is called before each test
+	void OnTestStart(const TestInfo &) override {
+		cfg.log.level = Log::Level::WARNING;
+	}
+};
+
 int main(int argc, char ** argv) {
 	InitGoogleTest(&argc, argv);
 
-	auto & cfg = Config::get_instance();
-	cfg.log.level = Log::Level::ERROR;
+	UnitTest & ut = *UnitTest::GetInstance();
+	ut.listeners().Append(new GlobalConfigReset);
 
 	return RUN_ALL_TESTS();
 }
+
-- 
cgit v1.2.3


From d258fcc8efdb6a968a220c4590a204292a16ad42 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Thu, 14 Nov 2024 20:27:05 +0100
Subject: added thoughts

---
 src/crepe/Component.h            | 12 +++++++++---
 src/crepe/api/AudioSource.h      | 11 ++++++++++-
 src/crepe/system/AudioSystem.cpp | 37 ++++++++++++++++++++++++++++++++++++-
 src/crepe/system/AudioSystem.h   |  2 ++
 src/test/ResourceManagerTest.cpp | 11 ++++++-----
 5 files changed, 63 insertions(+), 10 deletions(-)

(limited to 'src')

diff --git a/src/crepe/Component.h b/src/crepe/Component.h
index 12c10cb..7734335 100644
--- a/src/crepe/Component.h
+++ b/src/crepe/Component.h
@@ -16,7 +16,12 @@ class Component {
 public:
 	//! Whether the component is active
 	bool active = true;
-	//! The id of the GameObject this component belongs to
+	/**
+	 * \brief The id of the GameObject this component belongs to
+	 *
+	 * \note Only systems are supposed to use this member, but since friend
+	 * relations aren't inherited this needs to be public.
+	 */
 	const game_object_id_t game_object_id;
 
 protected:
@@ -24,10 +29,9 @@ protected:
 	 * \param id The id of the GameObject this component belongs to
 	 */
 	Component(game_object_id_t id);
-	//! Only the ComponentManager can create components
+	//! Only ComponentManager can create components
 	friend class ComponentManager;
 
-public:
 	/**
 	 * \brief Get the maximum number of instances for this component
 	 *
@@ -38,6 +42,8 @@ public:
 	 * \return The maximum number of instances for this component
 	 */
 	virtual int get_instances_max() const { return -1; }
+	//! Only ComponentManager needs to know the max instance count
+	friend class ComponentManager;
 };
 
 } // namespace crepe
diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h
index 8a78927..1264790 100644
--- a/src/crepe/api/AudioSource.h
+++ b/src/crepe/api/AudioSource.h
@@ -7,10 +7,19 @@
 
 namespace crepe {
 
+class AudioSystem;
+
 //! Audio source component
 class AudioSource : public Component {
-public:
+	//! AudioSource components are handled by AudioSystem
+	friend class AudioSystem;
+
+protected:
 	AudioSource(game_object_id_t id, const Asset & source);
+	//! Only ComponentManager can create components
+	friend class ComponentManager;
+public:
+	// But std::unique_ptr needs to be able to destoy this component again
 	virtual ~AudioSource() = default;
 
 public:
diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
index 67967ef..c8dae9d 100644
--- a/src/crepe/system/AudioSystem.cpp
+++ b/src/crepe/system/AudioSystem.cpp
@@ -14,7 +14,42 @@ void AudioSystem::update() {
 		AudioSource & component = component_ref.get();
 		if (!component.active) continue;
 
-		// TODO: fetch Sound instance from resourcemanager
+		/**
+		 * How this is supposed to work:
+		 * - Get an instance of Sound for this resource/component combo (Sound
+		 *   instance is supposed to be unique per component, even if they use the
+		 *   same underlying asset).
+		 * OR
+		 * - Use the same instance of Sound if this is what the cache returns
+		 *   (= what the game programmer's wishes to do).
+		 *
+		 * NOT supposed to happen but still the case:
+		 * - Below function call causes assets to be cached unintentionally
+		 * - Cached assets are deleted at the end of a scene (i think?)
+		 * - I'm not sure if the ResourceManager is even supposed to have a public
+		 *   `.clear()` method since the control over resource lifetime is
+		 *   explicitly handed over to the game programmer by using ResourceManager
+		 *   to cache/uncache. I believe the proper methods are supposed to be:
+		 *
+		 *   - get()       get a reference to resource (used here)
+		 *   - clear()     clears NON-cached assets
+		 *   - cache()     marks asset as "do not delete at end of scene"
+		 *   - uncache()   undoes the above
+		 *
+		 *   I think somewhere in the above function calls a unique identifier for
+		 *   the Asset/GameObject should be given to make sure the unique instance
+		 *   shit works as intended. The resource manager is also used for things
+		 *   other than sounds.
+		 *
+		 * Also need to check:
+		 * - Is it an issue if there are multiple AudioSource components playing
+		 *   the same sample (= identical Asset), while they are all triggered
+		 *   using the same underlying instance of Sound (esp. w/
+		 *   play/pause/retrigger behavior).
+		 */
+		Sound & sound = this->resman.cache<Sound>(component.source);
+		sound.set_context(this->context);
+
 		// TODO: lots of state diffing
 	}
 }
diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h
index e037f51..d0b4f9a 100644
--- a/src/crepe/system/AudioSystem.h
+++ b/src/crepe/system/AudioSystem.h
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "../facade/SoundContext.h"
+#include "../api/ResourceManager.h"
 
 #include "System.h"
 
@@ -13,6 +14,7 @@ public:
 
 private:
 	SoundContext context {};
+	ResourceManager & resman = ResourceManager::get_instance();
 };
 
 } // namespace crepe
diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp
index 5d1ae7a..3fc9ebd 100644
--- a/src/test/ResourceManagerTest.cpp
+++ b/src/test/ResourceManagerTest.cpp
@@ -14,12 +14,17 @@ public:
 	Config & cfg = Config::get_instance();
 
 	void SetUp() override {
-		cfg.log.level = Log::Level::TRACE;
 		resman.clear();
 	}
 };
 
 TEST_F(ResourceManagerTest, Main) {
+	// NOTE: there is no way (that I know of) to ensure the last cache call
+	// allocates the new Sound instance in a different location than the first,
+	// so this test should be verified manually using these print statements and
+	// debug trace messages.
+	cfg.log.level = Log::Level::TRACE;
+
 	Asset path1 = "mwe/audio/sfx1.wav";
 	Asset path2 = "mwe/audio/sfx1.wav";
 	ASSERT_EQ(path1, path2);
@@ -43,9 +48,5 @@ TEST_F(ResourceManagerTest, Main) {
 
 	Log::logf(Log::Level::DEBUG, "Get first sound again (constructor call)");
 	Sound & sound = resman.cache<Sound>(path1);
-
-	// NOTE: there is no way (that I know of) to ensure the above statement
-	// allocates the new Sound instance in a different location than the first,
-	// so this test was verified using the above print statements.
 }
 
-- 
cgit v1.2.3


From a685e5f743786cc6499e7ce8973bb78a83d101f7 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Tue, 26 Nov 2024 20:03:41 +0100
Subject: big WIP

---
 src/crepe/CMakeLists.txt          |   2 +
 src/crepe/ResourceManager.cpp     |  36 ++++++++++++
 src/crepe/ResourceManager.h       |  76 +++++++++++++++++++++++++
 src/crepe/api/AudioSource.h       |   1 +
 src/crepe/api/CMakeLists.txt      |   2 -
 src/crepe/api/Config.h            |  27 +--------
 src/crepe/api/ResourceManager.cpp |  41 --------------
 src/crepe/api/ResourceManager.h   |  69 -----------------------
 src/crepe/system/AudioSystem.cpp  |   2 +-
 src/crepe/system/AudioSystem.h    |   4 +-
 src/crepe/util/Log.cpp            |   2 +-
 src/crepe/util/Log.h              |  16 ++++++
 src/test/EventTest.cpp            |   1 -
 src/test/ResourceManagerTest.cpp  | 115 +++++++++++++++++++++++++++-----------
 src/test/main.cpp                 |  12 ++--
 15 files changed, 224 insertions(+), 182 deletions(-)
 create mode 100644 src/crepe/ResourceManager.cpp
 create mode 100644 src/crepe/ResourceManager.h
 delete mode 100644 src/crepe/api/ResourceManager.cpp
 delete mode 100644 src/crepe/api/ResourceManager.h

(limited to 'src')

diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt
index df15b8f..0313dfa 100644
--- a/src/crepe/CMakeLists.txt
+++ b/src/crepe/CMakeLists.txt
@@ -3,6 +3,7 @@ target_sources(crepe PUBLIC
 	ComponentManager.cpp
 	Component.cpp
 	Collider.cpp
+	ResourceManager.cpp
 	Resource.cpp
 )
 
@@ -13,6 +14,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
 	Collider.h
 	ValueBroker.h
 	ValueBroker.hpp
+	ResourceManager.h
 	Resource.h
 )
 
diff --git a/src/crepe/ResourceManager.cpp b/src/crepe/ResourceManager.cpp
new file mode 100644
index 0000000..111b9e0
--- /dev/null
+++ b/src/crepe/ResourceManager.cpp
@@ -0,0 +1,36 @@
+#include <stdexcept>
+
+#include "util/Log.h"
+
+#include "ResourceManager.h"
+
+using namespace crepe;
+using namespace std;
+
+ResourceManager::~ResourceManager() { dbg_trace(); }
+ResourceManager::ResourceManager() { dbg_trace(); }
+
+void ResourceManager::clear() {
+	this->resources.clear();
+}
+
+// template <typename T>
+// T & ResourceManager::cache(const Asset & asset) {
+// 	dbg_trace();
+// 	static_assert(is_base_of<Resource, T>::value, "cache must recieve a derivative class of Resource");
+// 
+// 	if (!this->resources.contains(asset))
+// 		this->resources[asset] = make_unique<T>(asset);
+// 
+// 	Resource * resource = this->resources.at(asset).get();
+// 	T * concrete_resource = dynamic_cast<T *>(resource);
+// 
+// 	if (concrete_resource == nullptr)
+// 		throw runtime_error(format("ResourceManager: mismatch between requested type and actual type of resource ({})", asset.get_path()));
+// 
+// 	return *concrete_resource;
+// }
+// 
+// #include "facade/Sound.h"
+// template Sound & ResourceManager::cache(const Asset &);
+
diff --git a/src/crepe/ResourceManager.h b/src/crepe/ResourceManager.h
new file mode 100644
index 0000000..26a86a8
--- /dev/null
+++ b/src/crepe/ResourceManager.h
@@ -0,0 +1,76 @@
+#pragma once
+
+#include <memory>
+#include <unordered_map>
+
+#include "api/Asset.h"
+
+#include "Component.h"
+#include "Resource.h"
+
+namespace crepe {
+
+
+/**
+ * \brief The ResourceManager is responsible for storing and managing assets over
+ * multiple scenes.
+ * 
+ * The ResourceManager ensures that assets are loaded once and can be accessed
+ * across different scenes. It caches assets to avoid reloading them every time
+ * a scene is loaded. Assets are retained in memory until the ResourceManager is
+ * destroyed, at which point the cached assets are cleared.
+ */
+class ResourceManager {
+public:
+	ResourceManager(); // dbg_trace
+	virtual ~ResourceManager(); // dbg_trace
+
+private:
+	template <typename Resource>
+	Resource & get_internal(const Component & component, const Asset & asset);
+
+	template <typename Resource>
+	const Asset & get_source(const Component & component) const;
+
+	//! A cache that holds all the assets, accessible by their file path, over multiple scenes.
+	std::unordered_map<const Asset, std::unique_ptr<Resource>> resources;
+
+public:
+	/**
+	 * \brief Caches an asset by loading it from the given file path.
+	 *
+	 * \param file_path The path to the asset file to load.
+	 * \param reload If true, the asset will be reloaded from the file, even if
+	 * it is already cached.
+	 * \tparam T The type of asset to cache (e.g., texture, sound, etc.).
+	 * 
+	 * \return A reference to the resource
+	 * 
+	 * This template function caches the asset at the given file path. If the
+	 * asset is already cached, the existing instance will be returned.
+	 * Otherwise, the concrete resource will be instantiated and added to the
+	 * cache.
+	 */
+	template <typename Resource>
+	void cache(const Asset & asset, bool persistent = false);
+
+	template <typename Component>
+	void cache(const Component & component, bool persistent = false);
+
+	// void resman.cache<Resource>(Asset, Lifetime);
+	// void resman.cache(Component, Asset, Lifetime);
+
+	template <typename Resource, typename Component>
+	Resource & get(const Component & component);
+
+	//! Clear the resource cache
+	void clear();
+};
+
+class Sound;
+class AudioSource;
+template <>
+Sound & ResourceManager::get(const AudioSource & component);
+
+} // namespace crepe
+
diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h
index 1264790..0950129 100644
--- a/src/crepe/api/AudioSource.h
+++ b/src/crepe/api/AudioSource.h
@@ -3,6 +3,7 @@
 #include "../Component.h"
 #include "../types.h"
 
+#include "GameObject.h"
 #include "Asset.h"
 
 namespace crepe {
diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt
index a2e21fa..ad82924 100644
--- a/src/crepe/api/CMakeLists.txt
+++ b/src/crepe/api/CMakeLists.txt
@@ -7,7 +7,6 @@ target_sources(crepe PUBLIC
 	Transform.cpp
 	Color.cpp
 	Texture.cpp
-	ResourceManager.cpp
 	Sprite.cpp
 	SaveManager.cpp
 	Config.cpp
@@ -39,7 +38,6 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
 	Vector2.hpp
 	Color.h
 	Texture.h 
-	ResourceManager.h 
 	SaveManager.h
 	Scene.h
 	Metadata.h
diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h
index 0c9d116..5bd6913 100644
--- a/src/crepe/api/Config.h
+++ b/src/crepe/api/Config.h
@@ -11,35 +11,12 @@ namespace crepe {
  * modified *before* execution is handed over from the game programmer to the engine (i.e. the
  * main loop is started).
  */
-class Config final {
-public:
+struct Config final {
 	//! Retrieve handle to global Config instance
 	static Config & get_instance();
 
-private:
-	Config() = default;
-	~Config() = default;
-	Config(const Config &) = default;
-	Config(Config &&) = default;
-	Config & operator=(const Config &) = default;
-	Config & operator=(Config &&) = default;
-
-public:
 	//! Logging-related settings
-	struct {
-		/**
-		 * \brief Log level
-		 *
-		 * Only messages with equal or higher priority than this value will be logged.
-		 */
-		Log::Level level = Log::Level::INFO;
-		/**
-		 * \brief Colored log output
-		 *
-		 * Enables log coloring using ANSI escape codes.
-		 */
-		bool color = true;
-	} log;
+	Log::Config log;
 
 	//! Save manager
 	struct {
diff --git a/src/crepe/api/ResourceManager.cpp b/src/crepe/api/ResourceManager.cpp
deleted file mode 100644
index 7877ed9..0000000
--- a/src/crepe/api/ResourceManager.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-#include <stdexcept>
-
-#include "util/Log.h"
-
-#include "ResourceManager.h"
-
-using namespace crepe;
-using namespace std;
-
-ResourceManager & ResourceManager::get_instance() {
-	static ResourceManager instance;
-	return instance;
-}
-
-ResourceManager::~ResourceManager() { dbg_trace(); }
-ResourceManager::ResourceManager() { dbg_trace(); }
-
-void ResourceManager::clear() {
-	this->resources.clear();
-}
-
-template <typename T>
-T & ResourceManager::cache(const Asset & asset) {
-	dbg_trace();
-	static_assert(is_base_of<Resource, T>::value, "cache must recieve a derivative class of Resource");
-
-	if (!this->resources.contains(asset))
-		this->resources[asset] = make_unique<T>(asset);
-
-	Resource * resource = this->resources.at(asset).get();
-	T * concrete_resource = dynamic_cast<T *>(resource);
-
-	if (concrete_resource == nullptr)
-		throw runtime_error(format("ResourceManager: mismatch between requested type and actual type of resource ({})", asset.get_path()));
-
-	return *concrete_resource;
-}
-
-#include "../facade/Sound.h"
-template Sound & ResourceManager::cache(const Asset &);
-
diff --git a/src/crepe/api/ResourceManager.h b/src/crepe/api/ResourceManager.h
deleted file mode 100644
index efdd5c5..0000000
--- a/src/crepe/api/ResourceManager.h
+++ /dev/null
@@ -1,69 +0,0 @@
-#pragma once
-
-#include <memory>
-#include <unordered_map>
-
-#include "Asset.h"
-#include "Resource.h"
-
-namespace crepe {
-
-class Sound;
-
-/**
- * \brief The ResourceManager is responsible for storing and managing assets over
- * multiple scenes.
- * 
- * The ResourceManager ensures that assets are loaded once and can be accessed
- * across different scenes. It caches assets to avoid reloading them every time
- * a scene is loaded. Assets are retained in memory until the ResourceManager is
- * destroyed, at which point the cached assets are cleared.
- */
-class ResourceManager {
-
-private:
-	//! A cache that holds all the assets, accessible by their file path, over multiple scenes.
-	std::unordered_map<const Asset, std::unique_ptr<Resource>> resources;
-
-private:
-	ResourceManager(); // dbg_trace
-	virtual ~ResourceManager(); // dbg_trace
-
-	ResourceManager(const ResourceManager &) = delete;
-	ResourceManager(ResourceManager &&) = delete;
-	ResourceManager & operator=(const ResourceManager &) = delete;
-	ResourceManager & operator=(ResourceManager &&) = delete;
-
-public:
-	/**
-	 * \brief Retrieves the singleton instance of the ResourceManager.
-	 *
-	 * \return A reference to the single instance of the ResourceManager.
-	 */
-	static ResourceManager & get_instance();
-
-public:
-	/**
-	 * \brief Caches an asset by loading it from the given file path.
-	 *
-	 * \param file_path The path to the asset file to load.
-	 * \param reload If true, the asset will be reloaded from the file, even if
-	 * it is already cached.
-	 * \tparam T The type of asset to cache (e.g., texture, sound, etc.).
-	 * 
-	 * \return A reference to the resource
-	 * 
-	 * This template function caches the asset at the given file path. If the
-	 * asset is already cached, the existing instance will be returned.
-	 * Otherwise, the concrete resource will be instantiated and added to the
-	 * cache.
-	 */
-	template <typename T>
-	T & cache(const Asset & asset);
-
-	//! Clear the resource cache
-	void clear();
-};
-
-} // namespace crepe
-
diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
index b7ac1f2..6c30b85 100644
--- a/src/crepe/system/AudioSystem.cpp
+++ b/src/crepe/system/AudioSystem.cpp
@@ -47,7 +47,7 @@ void AudioSystem::update() {
 		 *   using the same underlying instance of Sound (esp. w/
 		 *   play/pause/retrigger behavior).
 		 */
-		Sound & sound = this->resman.cache<Sound>(component.source);
+		// Sound & sound = this->resource_manager.get<Sound>(component);
 
 		// TODO: lots of state diffing
 	}
diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h
index d0b4f9a..d3d5aeb 100644
--- a/src/crepe/system/AudioSystem.h
+++ b/src/crepe/system/AudioSystem.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #include "../facade/SoundContext.h"
-#include "../api/ResourceManager.h"
+#include "../ResourceManager.h"
 
 #include "System.h"
 
@@ -14,7 +14,7 @@ public:
 
 private:
 	SoundContext context {};
-	ResourceManager & resman = ResourceManager::get_instance();
+	ResourceManager resource_manager {};
 };
 
 } // namespace crepe
diff --git a/src/crepe/util/Log.cpp b/src/crepe/util/Log.cpp
index 84d80a8..bc86c7e 100644
--- a/src/crepe/util/Log.cpp
+++ b/src/crepe/util/Log.cpp
@@ -25,7 +25,7 @@ string Log::prefix(const Level & level) {
 }
 
 void Log::log(const Level & level, const string & msg) {
-	auto & cfg = Config::get_instance();
+	auto & cfg = crepe::Config::get_instance();
 	if (level < cfg.log.level) return;
 
 	string out = Log::prefix(level) + msg;
diff --git a/src/crepe/util/Log.h b/src/crepe/util/Log.h
index fc0bb3a..914145a 100644
--- a/src/crepe/util/Log.h
+++ b/src/crepe/util/Log.h
@@ -77,6 +77,22 @@ private:
 	 * \return Colored message severity prefix string
 	 */
 	static std::string prefix(const Level & level);
+
+public:
+	struct Config {
+		/**
+		 * \brief Log level
+		 *
+		 * Only messages with equal or higher priority than this value will be logged.
+		 */
+		Level level = INFO;
+		/**
+		 * \brief Colored log output
+		 *
+		 * Enables log coloring using ANSI escape codes.
+		 */
+		bool color = true;
+	};
 };
 
 } // namespace crepe
diff --git a/src/test/EventTest.cpp b/src/test/EventTest.cpp
index b0e6c9c..a21a851 100644
--- a/src/test/EventTest.cpp
+++ b/src/test/EventTest.cpp
@@ -37,7 +37,6 @@ public:
 
 TEST_F(EventManagerTest, EventSubscription) {
 	EventHandler<KeyPressEvent> key_handler = [](const KeyPressEvent & e) {
-		std::cout << "Key Event Triggered" << std::endl;
 		return true;
 	};
 
diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp
index 3fc9ebd..f57c419 100644
--- a/src/test/ResourceManagerTest.cpp
+++ b/src/test/ResourceManagerTest.cpp
@@ -1,52 +1,101 @@
 #include <gtest/gtest.h>
 
+#define private public
+#define protected public
+
 #include <crepe/util/Log.h>
-#include <crepe/api/Config.h>
-#include <crepe/api/ResourceManager.h>
+#include <crepe/ResourceManager.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/Component.h>
+#include <crepe/ComponentManager.h>
 
 using namespace std;
 using namespace crepe;
 using namespace testing;
 
+class TestComponent : public Component {
+public:
+	TestComponent(game_object_id_t id, const Asset & asset)
+		: Component(id),
+		  source(asset) {}
+	const Asset source;
+};
+
+class TestResource : public Resource {
+public:
+	static unsigned instances;
+
+public:
+	const unsigned instance;
+	TestResource(const Asset & src)
+		: Resource(src),
+		  instance(this->instances++) { }
+};
+unsigned TestResource::instances = 0;
+
+template <>
+TestResource & ResourceManager::get(const TestComponent & component) {
+	return this->get_internal<TestResource>(component, component.source);
+}
+
 class ResourceManagerTest : public Test {
 public:
-	ResourceManager & resman = ResourceManager::get_instance();
-	Config & cfg = Config::get_instance();
+	ResourceManager resource_manager{};
+
+	static constexpr const char * ASSET_LOCATION = "asset/texture/img.png";
+
+	TestComponent a{0, ASSET_LOCATION};
+	TestComponent b{1, ASSET_LOCATION};
 
+private:
 	void SetUp() override {
-		resman.clear();
+		TestResource::instances = 0;
 	}
 };
 
-TEST_F(ResourceManagerTest, Main) {
-	// NOTE: there is no way (that I know of) to ensure the last cache call
-	// allocates the new Sound instance in a different location than the first,
-	// so this test should be verified manually using these print statements and
-	// debug trace messages.
-	cfg.log.level = Log::Level::TRACE;
-
-	Asset path1 = "mwe/audio/sfx1.wav";
-	Asset path2 = "mwe/audio/sfx1.wav";
-	ASSERT_EQ(path1, path2);
-
-	Sound * ptr1 = nullptr;
-	Sound * ptr2 = nullptr;
-	{
-		Log::logf(Log::Level::DEBUG, "Get first sound (constructor call)");
-		Sound & sound = resman.cache<Sound>(path1);
-		ptr1 = &sound;
-	}
-	{
-		Log::logf(Log::Level::DEBUG, "Get same sound (NO constructor call)");
-		Sound & sound = resman.cache<Sound>(path2);
-		ptr2 = &sound;
-	}
-	EXPECT_EQ(ptr1, ptr2);
+TEST_F(ResourceManagerTest, Uncached) {
+	TestResource & res_1 = resource_manager.get<TestResource>(a); // 1
+	TestResource & res_2 = resource_manager.get<TestResource>(a); // 1
+	TestResource & res_3 = resource_manager.get<TestResource>(b); // 2
+	TestResource & res_4 = resource_manager.get<TestResource>(b); // 2
+
+	ASSERT_EQ(res_1, res_2);
+	ASSERT_EQ(res_3, res_4);
+	EXPECT_NE(res_1, res_3);
+
+	EXPECT_EQ(TestResource::instances, 2);
+}
+
+// TODO: per GameObject / Component
+TEST_F(ResourceManagerTest, PerComponent) {
+	resource_manager.cache(a);
+	resource_manager.cache(b);
+
+	TestResource & res_1 = resource_manager.get<TestResource>(a); // 1
+	TestResource & res_2 = resource_manager.get<TestResource>(a); // 1
+	TestResource & res_3 = resource_manager.get<TestResource>(b); // 2
+	TestResource & res_4 = resource_manager.get<TestResource>(b); // 2
+
+	ASSERT_EQ(res_1, res_2);
+	ASSERT_EQ(res_3, res_4);
+	EXPECT_NE(res_1, res_3);
+
+	EXPECT_EQ(TestResource::instances, 2);
+}
+
+TEST_F(ResourceManagerTest, PerAsset) {
+	resource_manager.cache(ASSET_LOCATION);
+	EXPECT_EQ(TestResource::instances, 1);
+
+	TestResource & res_1 = resource_manager.get<TestResource>(a); // 1
+	TestResource & res_2 = resource_manager.get<TestResource>(a); // 1
+	TestResource & res_3 = resource_manager.get<TestResource>(b); // 1
+	TestResource & res_4 = resource_manager.get<TestResource>(b); // 1
 
-	Log::logf(Log::Level::DEBUG, "Clear cache (destructor call)");
-	resman.clear();
+	EXPECT_EQ(res_1, res_2);
+	EXPECT_EQ(res_2, res_3);
+	EXPECT_EQ(res_3, res_4);
 
-	Log::logf(Log::Level::DEBUG, "Get first sound again (constructor call)");
-	Sound & sound = resman.cache<Sound>(path1);
+	EXPECT_EQ(TestResource::instances, 1);
 }
 
diff --git a/src/test/main.cpp b/src/test/main.cpp
index e03a989..54f74fd 100644
--- a/src/test/main.cpp
+++ b/src/test/main.cpp
@@ -1,8 +1,4 @@
 #include <gtest/gtest.h>
-
-#define protected public
-#define private public
-
 #include <crepe/api/Config.h>
 
 using namespace crepe;
@@ -11,12 +7,14 @@ using namespace testing;
 class GlobalConfigReset : public EmptyTestEventListener {
 public:
 	Config & cfg = Config::get_instance();
-	Config cfg_default = Config();
 
 	// This function is called before each test
 	void OnTestStart(const TestInfo &) override {
-		cfg = cfg_default;
-		cfg.log.level = Log::Level::WARNING;
+		cfg = {
+			.log = {
+				.level = Log::Level::WARNING,
+			},
+		};
 	}
 };
 
-- 
cgit v1.2.3


From 0e45d4835f65ff9127a16adcbe9a9f0a20370cfc Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Thu, 28 Nov 2024 18:18:13 +0100
Subject: implement resource manager

---
 src/crepe/ResourceManager.cpp    | 37 +++++++---------
 src/crepe/ResourceManager.h      | 51 +++++----------------
 src/crepe/ResourceManager.hpp    | 26 +++++++++++
 src/test/ResourceManagerTest.cpp | 95 ++++++++++++++--------------------------
 4 files changed, 87 insertions(+), 122 deletions(-)
 create mode 100644 src/crepe/ResourceManager.hpp

(limited to 'src')

diff --git a/src/crepe/ResourceManager.cpp b/src/crepe/ResourceManager.cpp
index 111b9e0..8b1fbf5 100644
--- a/src/crepe/ResourceManager.cpp
+++ b/src/crepe/ResourceManager.cpp
@@ -1,5 +1,3 @@
-#include <stdexcept>
-
 #include "util/Log.h"
 
 #include "ResourceManager.h"
@@ -11,26 +9,23 @@ ResourceManager::~ResourceManager() { dbg_trace(); }
 ResourceManager::ResourceManager() { dbg_trace(); }
 
 void ResourceManager::clear() {
+	std::erase_if(this->resources, [](const pair<const Asset, CacheEntry> & pair) {
+		const CacheEntry & entry = pair.second;
+		return entry.persistent == false;
+	});
+}
+
+void ResourceManager::clear_all() {
 	this->resources.clear();
 }
 
-// template <typename T>
-// T & ResourceManager::cache(const Asset & asset) {
-// 	dbg_trace();
-// 	static_assert(is_base_of<Resource, T>::value, "cache must recieve a derivative class of Resource");
-// 
-// 	if (!this->resources.contains(asset))
-// 		this->resources[asset] = make_unique<T>(asset);
-// 
-// 	Resource * resource = this->resources.at(asset).get();
-// 	T * concrete_resource = dynamic_cast<T *>(resource);
-// 
-// 	if (concrete_resource == nullptr)
-// 		throw runtime_error(format("ResourceManager: mismatch between requested type and actual type of resource ({})", asset.get_path()));
-// 
-// 	return *concrete_resource;
-// }
-// 
-// #include "facade/Sound.h"
-// template Sound & ResourceManager::cache(const Asset &);
+void ResourceManager::set_persistent(const Asset & asset, bool persistent) {
+	this->get_entry(asset).persistent = persistent;
+}
+
+ResourceManager::CacheEntry & ResourceManager::get_entry(const Asset & asset) {
+	if (!this->resources.contains(asset))
+		this->resources[asset] = {};
+	return this->resources.at(asset);
+}
 
diff --git a/src/crepe/ResourceManager.h b/src/crepe/ResourceManager.h
index 26a86a8..fc50b65 100644
--- a/src/crepe/ResourceManager.h
+++ b/src/crepe/ResourceManager.h
@@ -5,12 +5,10 @@
 
 #include "api/Asset.h"
 
-#include "Component.h"
 #include "Resource.h"
 
 namespace crepe {
 
-
 /**
  * \brief The ResourceManager is responsible for storing and managing assets over
  * multiple scenes.
@@ -26,51 +24,24 @@ public:
 	virtual ~ResourceManager(); // dbg_trace
 
 private:
-	template <typename Resource>
-	Resource & get_internal(const Component & component, const Asset & asset);
-
-	template <typename Resource>
-	const Asset & get_source(const Component & component) const;
-
+	struct CacheEntry {
+		std::unique_ptr<Resource> resource = nullptr;
+		bool persistent = false;
+	};
 	//! A cache that holds all the assets, accessible by their file path, over multiple scenes.
-	std::unordered_map<const Asset, std::unique_ptr<Resource>> resources;
+	std::unordered_map<const Asset, CacheEntry> resources;
+	CacheEntry & get_entry(const Asset & asset);
 
 public:
-	/**
-	 * \brief Caches an asset by loading it from the given file path.
-	 *
-	 * \param file_path The path to the asset file to load.
-	 * \param reload If true, the asset will be reloaded from the file, even if
-	 * it is already cached.
-	 * \tparam T The type of asset to cache (e.g., texture, sound, etc.).
-	 * 
-	 * \return A reference to the resource
-	 * 
-	 * This template function caches the asset at the given file path. If the
-	 * asset is already cached, the existing instance will be returned.
-	 * Otherwise, the concrete resource will be instantiated and added to the
-	 * cache.
-	 */
-	template <typename Resource>
-	void cache(const Asset & asset, bool persistent = false);
-
-	template <typename Component>
-	void cache(const Component & component, bool persistent = false);
-
-	// void resman.cache<Resource>(Asset, Lifetime);
-	// void resman.cache(Component, Asset, Lifetime);
+	void set_persistent(const Asset & asset, bool persistent);
 
-	template <typename Resource, typename Component>
-	Resource & get(const Component & component);
+	template <typename Resource>
+	Resource & get(const Asset & asset);
 
-	//! Clear the resource cache
 	void clear();
+	void clear_all();
 };
 
-class Sound;
-class AudioSource;
-template <>
-Sound & ResourceManager::get(const AudioSource & component);
-
 } // namespace crepe
 
+#include "ResourceManager.hpp"
diff --git a/src/crepe/ResourceManager.hpp b/src/crepe/ResourceManager.hpp
new file mode 100644
index 0000000..8270bc5
--- /dev/null
+++ b/src/crepe/ResourceManager.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <format>
+
+#include "ResourceManager.h"
+
+namespace crepe {
+
+template <typename T>
+T & ResourceManager::get(const Asset & asset) {
+	using namespace std;
+	static_assert(is_base_of<Resource, T>::value, "cache must recieve a derivative class of Resource");
+
+	CacheEntry & entry = this->get_entry(asset);
+	if (entry.resource == nullptr)
+		entry.resource = make_unique<T>(asset);
+
+	T * concrete_resource = dynamic_cast<T *>(entry.resource.get());
+	if (concrete_resource == nullptr)
+		throw runtime_error(format("ResourceManager: mismatch between requested type and actual type of resource ({})", asset.get_path()));
+
+	return *concrete_resource;
+}
+
+}
+
diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp
index f57c419..cc3b022 100644
--- a/src/test/ResourceManagerTest.cpp
+++ b/src/test/ResourceManagerTest.cpp
@@ -6,96 +6,69 @@
 #include <crepe/util/Log.h>
 #include <crepe/ResourceManager.h>
 #include <crepe/api/GameObject.h>
-#include <crepe/Component.h>
-#include <crepe/ComponentManager.h>
 
 using namespace std;
 using namespace crepe;
 using namespace testing;
 
-class TestComponent : public Component {
-public:
-	TestComponent(game_object_id_t id, const Asset & asset)
-		: Component(id),
-		  source(asset) {}
-	const Asset source;
-};
-
-class TestResource : public Resource {
-public:
-	static unsigned instances;
-
-public:
-	const unsigned instance;
-	TestResource(const Asset & src)
-		: Resource(src),
-		  instance(this->instances++) { }
-};
-unsigned TestResource::instances = 0;
-
-template <>
-TestResource & ResourceManager::get(const TestComponent & component) {
-	return this->get_internal<TestResource>(component, component.source);
-}
-
 class ResourceManagerTest : public Test {
 public:
 	ResourceManager resource_manager{};
 
-	static constexpr const char * ASSET_LOCATION = "asset/texture/img.png";
+	Asset asset_a{"asset/texture/img.png"};
+	Asset asset_b{"asset/texture/ERROR.png"};
+
+	class TestResource : public Resource {
+	public:
+		static unsigned instances;
 
-	TestComponent a{0, ASSET_LOCATION};
-	TestComponent b{1, ASSET_LOCATION};
+	public:
+		const unsigned instance;
+		TestResource(const Asset & src)
+			: Resource(src),
+				instance(this->instances++) { }
+		~TestResource() { this->instances--; }
+		bool operator == (const TestResource & other) const {
+			return this->instance == other.instance;
+		}
+	};
 
 private:
 	void SetUp() override {
 		TestResource::instances = 0;
 	}
 };
+unsigned ResourceManagerTest::TestResource::instances = 0;
 
-TEST_F(ResourceManagerTest, Uncached) {
-	TestResource & res_1 = resource_manager.get<TestResource>(a); // 1
-	TestResource & res_2 = resource_manager.get<TestResource>(a); // 1
-	TestResource & res_3 = resource_manager.get<TestResource>(b); // 2
-	TestResource & res_4 = resource_manager.get<TestResource>(b); // 2
+TEST_F(ResourceManagerTest, Default) {
+	TestResource & res_1 = resource_manager.get<TestResource>(asset_a);
+	TestResource & res_2 = resource_manager.get<TestResource>(asset_a);
+	TestResource & res_3 = resource_manager.get<TestResource>(asset_b);
+	TestResource & res_4 = resource_manager.get<TestResource>(asset_b);
 
 	ASSERT_EQ(res_1, res_2);
 	ASSERT_EQ(res_3, res_4);
 	EXPECT_NE(res_1, res_3);
 
 	EXPECT_EQ(TestResource::instances, 2);
-}
-
-// TODO: per GameObject / Component
-TEST_F(ResourceManagerTest, PerComponent) {
-	resource_manager.cache(a);
-	resource_manager.cache(b);
 
-	TestResource & res_1 = resource_manager.get<TestResource>(a); // 1
-	TestResource & res_2 = resource_manager.get<TestResource>(a); // 1
-	TestResource & res_3 = resource_manager.get<TestResource>(b); // 2
-	TestResource & res_4 = resource_manager.get<TestResource>(b); // 2
+	resource_manager.clear();
+}
 
-	ASSERT_EQ(res_1, res_2);
-	ASSERT_EQ(res_3, res_4);
-	EXPECT_NE(res_1, res_3);
+TEST_F(ResourceManagerTest, Persistent) {
+	resource_manager.set_persistent(asset_a, true);
+	EXPECT_EQ(TestResource::instances, 0);
 
+	resource_manager.get<TestResource>(asset_a);
+	resource_manager.get<TestResource>(asset_a);
+	resource_manager.get<TestResource>(asset_b);
+	resource_manager.get<TestResource>(asset_b);
 	EXPECT_EQ(TestResource::instances, 2);
-}
 
-TEST_F(ResourceManagerTest, PerAsset) {
-	resource_manager.cache(ASSET_LOCATION);
+	resource_manager.clear();
 	EXPECT_EQ(TestResource::instances, 1);
 
-	TestResource & res_1 = resource_manager.get<TestResource>(a); // 1
-	TestResource & res_2 = resource_manager.get<TestResource>(a); // 1
-	TestResource & res_3 = resource_manager.get<TestResource>(b); // 1
-	TestResource & res_4 = resource_manager.get<TestResource>(b); // 1
-
-	EXPECT_EQ(res_1, res_2);
-	EXPECT_EQ(res_2, res_3);
-	EXPECT_EQ(res_3, res_4);
-
-	EXPECT_EQ(TestResource::instances, 1);
+	resource_manager.clear_all();
+	EXPECT_EQ(TestResource::instances, 0);
 }
 
-- 
cgit v1.2.3


From 74bffd3e466c342ca80811146a536716fb6437cb Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Thu, 28 Nov 2024 18:19:51 +0100
Subject: remove large think

---
 src/crepe/system/AudioSystem.cpp | 36 +-----------------------------------
 1 file changed, 1 insertion(+), 35 deletions(-)

(limited to 'src')

diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
index 6c30b85..f90132a 100644
--- a/src/crepe/system/AudioSystem.cpp
+++ b/src/crepe/system/AudioSystem.cpp
@@ -14,41 +14,7 @@ void AudioSystem::update() {
 		AudioSource & component = component_ref.get();
 		if (!component.active) continue;
 
-		/**
-		 * How this is supposed to work:
-		 * - Get an instance of Sound for this resource/component combo (Sound
-		 *   instance is supposed to be unique per component, even if they use the
-		 *   same underlying asset).
-		 * OR
-		 * - Use the same instance of Sound if this is what the cache returns
-		 *   (= what the game programmer's wishes to do).
-		 *
-		 * NOT supposed to happen but still the case:
-		 * - Below function call causes assets to be cached unintentionally
-		 * - Cached assets are deleted at the end of a scene (i think?)
-		 * - I'm not sure if the ResourceManager is even supposed to have a public
-		 *   `.clear()` method since the control over resource lifetime is
-		 *   explicitly handed over to the game programmer by using ResourceManager
-		 *   to cache/uncache. I believe the proper methods are supposed to be:
-		 *
-		 *   - get()       get a reference to resource (used here)
-		 *   - clear()     clears NON-cached assets
-		 *   - cache()     marks asset as "do not delete at end of scene"
-		 *   - uncache()   undoes the above
-		 *
-		 *   I think somewhere in the above function calls a unique identifier for
-		 *   the Asset/GameObject should be given to make sure the unique instance
-		 *   shit works as intended. The resource manager is also used for things
-		 *   other than sounds.
-		 *
-		 * Also need to check:
-		 * - Is it an issue if there are multiple AudioSource components playing
-		 *   the same sample (= identical Asset), while they are all triggered
-		 *   using the same underlying instance of Sound (esp. w/
-		 *   play/pause/retrigger behavior).
-		 */
-		// Sound & sound = this->resource_manager.get<Sound>(component);
-
+		Sound & sound = this->resource_manager.get<Sound>(component.source);
 		// TODO: lots of state diffing
 	}
 }
-- 
cgit v1.2.3


From c59d460f12e1393e0ddbaaa1c6f5522eb12f8ff9 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Fri, 29 Nov 2024 16:23:52 +0100
Subject: more utility classes for Audio system

---
 src/crepe/api/AudioSource.h      | 11 ++-----
 src/crepe/facade/Sound.h         |  4 +++
 src/crepe/system/AudioSystem.cpp | 17 +++++++++--
 src/crepe/system/AudioSystem.h   | 21 +++++++++++++
 src/crepe/util/CMakeLists.txt    |  3 ++
 src/crepe/util/Private.cpp       | 29 ++++++++++++++++++
 src/crepe/util/Private.h         | 34 +++++++++++++++++++++
 src/crepe/util/Private.hpp       | 31 +++++++++++++++++++
 src/example/CMakeLists.txt       |  1 -
 src/example/asset_manager.cpp    | 36 ----------------------
 src/test/AudioTest.cpp           |  5 ++-
 src/test/CMakeLists.txt          |  1 +
 src/test/PrivateTest.cpp         | 66 ++++++++++++++++++++++++++++++++++++++++
 13 files changed, 210 insertions(+), 49 deletions(-)
 create mode 100644 src/crepe/util/Private.cpp
 create mode 100644 src/crepe/util/Private.h
 create mode 100644 src/crepe/util/Private.hpp
 delete mode 100644 src/example/asset_manager.cpp
 create mode 100644 src/test/PrivateTest.cpp

(limited to 'src')

diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h
index 0950129..9d76f0b 100644
--- a/src/crepe/api/AudioSource.h
+++ b/src/crepe/api/AudioSource.h
@@ -2,6 +2,7 @@
 
 #include "../Component.h"
 #include "../types.h"
+#include "../util/Private.h"
 
 #include "GameObject.h"
 #include "Asset.h"
@@ -47,14 +48,8 @@ private:
 	bool rewind = false;
 
 private:
-	//! Value of \c active after last system update
-	bool last_active = false;
-	//! Value of \c playing after last system update
-	bool last_playing = false;
-	//! Value of \c volume after last system update
-	float last_volume = 1.0;
-	//! Value of \c loop after last system update
-	bool last_loop = false;
+	//! AudioSystem::ComponentPrivate
+	Private private_data;
 };
 
 } // namespace crepe
diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h
index b0b80f8..f33ee58 100644
--- a/src/crepe/facade/Sound.h
+++ b/src/crepe/facade/Sound.h
@@ -9,6 +9,10 @@ namespace crepe {
 
 class SoundContext;
 
+struct SoundHandle {
+	SoLoud::handle handle;
+};
+
 /**
  * \brief Sound resource facade
  *
diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
index 97cf966..0f943be 100644
--- a/src/crepe/system/AudioSystem.cpp
+++ b/src/crepe/system/AudioSystem.cpp
@@ -1,6 +1,5 @@
 #include "AudioSystem.h"
 
-#include "../api/AudioSource.h"
 #include "../manager/ComponentManager.h"
 #include "../manager/ResourceManager.h"
 #include "../types.h"
@@ -13,12 +12,24 @@ void AudioSystem::update() {
 	ResourceManager & resource_manager = this->mediator.resource_manager;
 	RefVector<AudioSource> components = component_manager.get_components_by_type<AudioSource>();
 
-	for (auto component_ref : components) {
-		AudioSource & component = component_ref.get();
+	for (AudioSource & component : components) {
 		if (!component.active) continue;
 
 		Sound & sound = resource_manager.get<Sound>(component.source);
+		if (component.private_data.empty())
+			component.private_data.set<ComponentPrivate>();
+		auto & data = component.private_data.get<ComponentPrivate>();
 		// TODO: lots of state diffing
+
+
+		this->update_private(component, data);
 	}
 }
 
+void AudioSystem::update_private(const AudioSource & component, ComponentPrivate & data) {
+	data.last_active = component.active;
+	data.last_loop = component.loop;
+	data.last_playing = component.playing;
+	data.last_volume = component.volume;
+}
+
diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h
index e037f51..7f41fda 100644
--- a/src/crepe/system/AudioSystem.h
+++ b/src/crepe/system/AudioSystem.h
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "../facade/SoundContext.h"
+#include "../api/AudioSource.h"
 
 #include "System.h"
 
@@ -11,6 +12,26 @@ public:
 	using System::System;
 	void update() override;
 
+private:
+	/**
+	 * \brief Private data stored by AudioSystem on AudioSource component
+	 */
+	struct ComponentPrivate {
+		//! This sample's voice handle
+		SoLoud::handle handle;
+
+		//! Value of \c active after last system update
+		bool last_active = false;
+		//! Value of \c playing after last system update
+		bool last_playing = false;
+		//! Value of \c volume after last system update
+		float last_volume = 1.0;
+		//! Value of \c loop after last system update
+		bool last_loop = false;
+	};
+
+	void update_private(const AudioSource & component, ComponentPrivate & data);
+
 private:
 	SoundContext context {};
 };
diff --git a/src/crepe/util/CMakeLists.txt b/src/crepe/util/CMakeLists.txt
index 94ed906..f49d851 100644
--- a/src/crepe/util/CMakeLists.txt
+++ b/src/crepe/util/CMakeLists.txt
@@ -1,6 +1,7 @@
 target_sources(crepe PUBLIC
 	LogColor.cpp
 	Log.cpp
+	Private.cpp
 )
 
 target_sources(crepe PUBLIC FILE_SET HEADERS FILES
@@ -11,5 +12,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
 	Proxy.hpp
 	OptionalRef.h
 	OptionalRef.hpp
+	Private.h
+	Private.hpp
 )
 
diff --git a/src/crepe/util/Private.cpp b/src/crepe/util/Private.cpp
new file mode 100644
index 0000000..c5b5b30
--- /dev/null
+++ b/src/crepe/util/Private.cpp
@@ -0,0 +1,29 @@
+#include "Private.h"
+
+using namespace crepe;
+
+bool Private::empty() const noexcept {
+	return this->instance == nullptr;
+}
+
+Private::~Private() {
+	if (this->instance == nullptr) return;
+	this->destructor(this->instance);
+}
+
+Private::Private(Private && other) {
+	*this = std::move(other);
+}
+
+Private & Private::operator=(Private && other) {
+	// TODO: ideally this function checks for self-assignment
+	this->instance = other.instance;
+	this->destructor = other.destructor;
+	this->type = other.type;
+
+	other.instance = nullptr;
+	other.destructor = [](void*){};
+
+	return *this;
+}
+
diff --git a/src/crepe/util/Private.h b/src/crepe/util/Private.h
new file mode 100644
index 0000000..fc3728f
--- /dev/null
+++ b/src/crepe/util/Private.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <typeindex>
+#include <functional>
+
+namespace crepe {
+
+class Private {
+public:
+	Private() = default;
+	~Private();
+	Private(Private &&);
+	Private & operator=(Private &&);
+	Private(const Private &) = delete;
+	Private & operator=(const Private &) = delete;
+
+	template <typename T>
+	T & get();
+
+	template <typename T, typename... Args>
+	void set(Args &&... args);
+
+	bool empty() const noexcept;
+
+private:
+	std::function<void(void *)> destructor;
+	std::type_index type = typeid(void);
+	void * instance = nullptr;
+};
+
+}
+
+#include "Private.hpp"
+
diff --git a/src/crepe/util/Private.hpp b/src/crepe/util/Private.hpp
new file mode 100644
index 0000000..30c8146
--- /dev/null
+++ b/src/crepe/util/Private.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <stdexcept>
+#include <format>
+
+#include "Private.h"
+
+namespace crepe {
+
+template <typename T, typename... Args>
+void Private::set(Args &&... args) {
+	T * instance = new T(std::forward<Args>(args)...);
+	this->instance = static_cast<void*>(instance);
+	this->destructor = [](void * instance) {
+		delete static_cast<T*>(instance);
+	};
+	this->type = typeid(T);
+}
+
+template <typename T>
+T & Private::get() {
+	using namespace std;
+	if (this->empty())
+		throw out_of_range("Private: get() called on empty object");
+	type_index requested_type = typeid(T);
+	if (this->type != requested_type)
+		throw logic_error(format("Private: get() called with [T = {}] (actual is [T = {}])", requested_type.name(), this->type.name()));
+	return *static_cast<T*>(this->instance);
+}
+
+}
diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt
index 560e2bc..9c3c550 100644
--- a/src/example/CMakeLists.txt
+++ b/src/example/CMakeLists.txt
@@ -16,7 +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(gameloop)
diff --git a/src/example/asset_manager.cpp b/src/example/asset_manager.cpp
deleted file mode 100644
index 660b318..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/test/AudioTest.cpp b/src/test/AudioTest.cpp
index afd2672..3be5afa 100644
--- a/src/test/AudioTest.cpp
+++ b/src/test/AudioTest.cpp
@@ -1,6 +1,7 @@
 #include <gtest/gtest.h>
 
 #include <crepe/manager/ComponentManager.h>
+#include <crepe/manager/ResourceManager.h>
 #include <crepe/api/AudioSource.h>
 #include <crepe/api/GameObject.h>
 #include <crepe/system/AudioSystem.h>
@@ -13,15 +14,17 @@ class AudioTest : public Test {
 	Mediator mediator;
 public:
 	ComponentManager component_manager{mediator};
+	ResourceManager resource_manager{mediator};
 	AudioSystem system {mediator};
 
 	void SetUp() override {
 		auto & mgr = this->component_manager;
 		GameObject entity = mgr.new_object("name");
-		entity.add_component<AudioSource>("mwe/audio/sfx1.wav");
+		AudioSource & audio_source = entity.add_component<AudioSource>("mwe/audio/sfx1.wav");
 	}
 };
 
 TEST_F(AudioTest, Default) {
+	system.update();
 }
 
diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt
index 4174926..8c4b855 100644
--- a/src/test/CMakeLists.txt
+++ b/src/test/CMakeLists.txt
@@ -16,4 +16,5 @@ target_sources(test_main PUBLIC
 	Vector2Test.cpp
 	ScriptEventTest.cpp
 	ScriptSceneTest.cpp
+	PrivateTest.cpp
 )
diff --git a/src/test/PrivateTest.cpp b/src/test/PrivateTest.cpp
new file mode 100644
index 0000000..f0d2b1a
--- /dev/null
+++ b/src/test/PrivateTest.cpp
@@ -0,0 +1,66 @@
+#include <gtest/gtest.h>
+
+#include <crepe/util/Private.h>
+
+using namespace std;
+using namespace crepe;
+using namespace testing;
+
+class PrivateTest : public Test {
+public:
+	static unsigned constructors;
+	static unsigned destructors;
+
+	void SetUp() override {
+		PrivateTest::constructors = 0;
+		PrivateTest::destructors = 0;
+	}
+
+	class TestClass {
+	public:
+		TestClass() { PrivateTest::constructors++; }
+		~TestClass() { PrivateTest::destructors++; }
+	};
+	class Unrelated {};
+};
+unsigned PrivateTest::constructors;
+unsigned PrivateTest::destructors;
+
+TEST_F(PrivateTest, Empty) {
+	{
+		Private foo;
+	}
+
+	EXPECT_EQ(PrivateTest::constructors, 0);
+	EXPECT_EQ(PrivateTest::destructors, 0);
+}
+
+TEST_F(PrivateTest, WithObject) {
+	{
+		Private foo;
+		foo.set<TestClass>();
+
+		EXPECT_EQ(PrivateTest::constructors, 1);
+		EXPECT_EQ(PrivateTest::destructors, 0);
+	}
+
+	EXPECT_EQ(PrivateTest::constructors, 1);
+	EXPECT_EQ(PrivateTest::destructors, 1);
+}
+
+TEST_F(PrivateTest, EmptyException) {
+	Private foo;
+	EXPECT_THROW(foo.get<TestClass>(), std::out_of_range);
+
+	foo.set<TestClass>();
+	EXPECT_NO_THROW(foo.get<TestClass>());
+}
+
+TEST_F(PrivateTest, IncorrectTypeException) {
+	Private foo;
+	foo.set<TestClass>();
+
+	EXPECT_THROW(foo.get<Unrelated>(), std::logic_error);
+	EXPECT_NO_THROW(foo.get<TestClass>());
+}
+
-- 
cgit v1.2.3


From 693355f55193cb2ea4c29616073227e37665afc1 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Fri, 29 Nov 2024 17:30:45 +0100
Subject: more audio system WIP

---
 src/crepe/facade/Sound.cpp       | 64 ++++++++++++++--------------
 src/crepe/facade/Sound.h         | 66 +++++-----------------------
 src/crepe/facade/SoundContext.h  |  3 +-
 src/crepe/system/AudioSystem.cpp | 29 ++++++++++---
 src/crepe/system/AudioSystem.h   | 26 +++++++-----
 src/crepe/util/Private.cpp       |  5 +++
 src/crepe/util/Private.h         |  6 +--
 src/crepe/util/Private.hpp       |  4 +-
 src/test/AudioTest.cpp           | 51 +++++++++++++++++++---
 src/test/PrivateTest.cpp         | 92 ++++++++++++++++++++++++++++++++++++++++
 10 files changed, 229 insertions(+), 117 deletions(-)

(limited to 'src')

diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp
index 52496af..0df1f48 100644
--- a/src/crepe/facade/Sound.cpp
+++ b/src/crepe/facade/Sound.cpp
@@ -13,36 +13,36 @@ Sound::Sound(const Asset & src) : Resource(src) {
 }
 Sound::~Sound() { dbg_trace(); }
 
-void Sound::play(SoundContext & ctx) {
-	if (ctx.engine.getPause(this->handle)) {
-		// resume if paused
-		ctx.engine.setPause(this->handle, false);
-	} else {
-		// or start new sound
-		this->handle = ctx.engine.play(this->sample, this->volume);
-		ctx.engine.setLooping(this->handle, this->looping);
-	}
-}
-
-void Sound::pause(SoundContext & ctx) {
-	if (ctx.engine.getPause(this->handle)) return;
-	ctx.engine.setPause(this->handle, true);
-}
-
-void Sound::rewind(SoundContext & ctx) {
-	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
-	ctx.engine.seek(this->handle, 0);
-}
-
-void Sound::set_volume(SoundContext & ctx, float volume) {
-	this->volume = volume;
-	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
-	ctx.engine.setVolume(this->handle, this->volume);
-}
-
-void Sound::set_looping(SoundContext & ctx, bool looping) {
-	this->looping = looping;
-	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
-	ctx.engine.setLooping(this->handle, this->looping);
-}
+// void Sound::play(SoundContext & ctx) {
+// 	if (ctx.engine.getPause(this->handle)) {
+// 		// resume if paused
+// 		ctx.engine.setPause(this->handle, false);
+// 	} else {
+// 		// or start new sound
+// 		this->handle = ctx.engine.play(this->sample, this->volume);
+// 		ctx.engine.setLooping(this->handle, this->looping);
+// 	}
+// }
+// 
+// void Sound::pause(SoundContext & ctx) {
+// 	if (ctx.engine.getPause(this->handle)) return;
+// 	ctx.engine.setPause(this->handle, true);
+// }
+// 
+// void Sound::rewind(SoundContext & ctx) {
+// 	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
+// 	ctx.engine.seek(this->handle, 0);
+// }
+// 
+// void Sound::set_volume(SoundContext & ctx, float volume) {
+// 	this->volume = volume;
+// 	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
+// 	ctx.engine.setVolume(this->handle, this->volume);
+// }
+// 
+// void Sound::set_looping(SoundContext & ctx, bool looping) {
+// 	this->looping = looping;
+// 	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
+// 	ctx.engine.setLooping(this->handle, this->looping);
+// }
 
diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h
index f33ee58..10d7c3c 100644
--- a/src/crepe/facade/Sound.h
+++ b/src/crepe/facade/Sound.h
@@ -9,10 +9,6 @@ namespace crepe {
 
 class SoundContext;
 
-struct SoundHandle {
-	SoLoud::handle handle;
-};
-
 /**
  * \brief Sound resource facade
  *
@@ -23,62 +19,20 @@ class Sound : public Resource {
 public:
 	Sound(const Asset & src);
 	~Sound(); // dbg_trace
-	/**
-	 * \brief Pause this sample
-	 *
-	 * Pauses this sound if it is playing, or does nothing if it is already paused. The playhead
-	 * position is saved, such that calling \c play() after this function makes the sound resume.
-	 */
-	void pause(SoundContext & ctx);
-	/**
-	 * \brief Play this sample
-	 *
-	 * Resume playback if this sound is paused, or start from the beginning of the sample.
-	 *
-	 * \note This class only saves a reference to the most recent 'voice' of this sound. Calling
-	 * \c play() while the sound is already playing causes multiple instances of the sample to
-	 * play simultaniously. The sample started last is the one that is controlled afterwards.
-	 */
-	void play(SoundContext & ctx);
-	/**
-	 * \brief Reset playhead position
-	 * 
-	 * Resets the playhead position so that calling \c play() after this function makes it play
-	 * from the start of the sample. If the sound is not paused before calling this function,
-	 * this function will stop playback.
-	 */
-	void rewind(SoundContext & ctx);
-	/**
-	 * \brief Set playback volume / gain
-	 *
-	 * \param volume  Volume (0 = muted, 1 = full volume)
-	 */
-	void set_volume(SoundContext & ctx, float volume);
-	/**
-	 * \brief Get playback volume / gain
-	 *
-	 * \return Volume
-	 */
-	float get_volume() const { return this->volume; }
-	/**
-	 * \brief Set looping behavior for this sample
-	 *
-	 * \param looping  Looping behavior (false = one-shot, true = loop)
-	 */
-	void set_looping(SoundContext & ctx, bool looping);
-	/**
-	 * \brief Get looping behavior
-	 *
-	 * \return true if looping, false if one-shot
-	 */
-	bool get_looping() const { return this->looping; }
+
+	class Handle {
+	private:
+		SoLoud::handle handle;
+		float volume = 1.0f;
+		bool looping = false;
+
+		friend class SoundContext;
+	};
 
 private:
 	SoLoud::Wav sample;
-	SoLoud::handle handle;
 
-	float volume = 1.0f;
-	bool looping = false;
+	friend class SoundContext;
 };
 
 } // namespace crepe
diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h
index d22ff7a..286ced8 100644
--- a/src/crepe/facade/SoundContext.h
+++ b/src/crepe/facade/SoundContext.h
@@ -22,10 +22,9 @@ public:
 	SoundContext & operator=(const SoundContext &) = delete;
 	SoundContext & operator=(SoundContext &&) = delete;
 
+
 private:
 	SoLoud::Soloud engine;
-	//! Sound directly calls methods on \c engine
-	friend class Sound;
 };
 
 } // namespace crepe
diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
index 0f943be..191dbbb 100644
--- a/src/crepe/system/AudioSystem.cpp
+++ b/src/crepe/system/AudioSystem.cpp
@@ -15,18 +15,35 @@ void AudioSystem::update() {
 	for (AudioSource & component : components) {
 		if (!component.active) continue;
 
-		Sound & sound = resource_manager.get<Sound>(component.source);
-		if (component.private_data.empty())
-			component.private_data.set<ComponentPrivate>();
+		Sound & resource = resource_manager.get<Sound>(component.source);
+
+		if (component.private_data.empty()) {
+			auto & data = component.private_data.set<ComponentPrivate>();
+			this->update_last(component, data);
+		}
 		auto & data = component.private_data.get<ComponentPrivate>();
-		// TODO: lots of state diffing
 
+		this->diff_update(component, data, resource);
+
+		this->update_last(component, data);
+	}
+}
+
+void AudioSystem::diff_update(AudioSource & component, const ComponentPrivate & data, Sound & resource) {
+	bool update_playing = component.playing != data.last_playing;
+	bool update_volume = component.volume != data.last_volume;
+	bool update_loop = component.loop != data.last_loop;
+	bool update_active = component.active != data.last_active;
 
-		this->update_private(component, data);
+	if (update_active)
+	if (component.rewind) {
+		component.playing = false;
+		// this->context.rewind(resource, data.handle);
 	}
+
 }
 
-void AudioSystem::update_private(const AudioSource & component, ComponentPrivate & data) {
+void AudioSystem::update_last(const AudioSource & component, ComponentPrivate & data) {
 	data.last_active = component.active;
 	data.last_loop = component.loop;
 	data.last_playing = component.playing;
diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h
index 7f41fda..4650178 100644
--- a/src/crepe/system/AudioSystem.h
+++ b/src/crepe/system/AudioSystem.h
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "../facade/SoundContext.h"
+#include "../facade/Sound.h"
 #include "../api/AudioSource.h"
 
 #include "System.h"
@@ -18,19 +19,22 @@ private:
 	 */
 	struct ComponentPrivate {
 		//! This sample's voice handle
-		SoLoud::handle handle;
-
-		//! Value of \c active after last system update
-		bool last_active = false;
-		//! Value of \c playing after last system update
-		bool last_playing = false;
-		//! Value of \c volume after last system update
-		float last_volume = 1.0;
-		//! Value of \c loop after last system update
-		bool last_loop = false;
+		Sound::Handle handle;
+
+		/**
+		 * \name State diffing variables
+		 * \{
+		 */
+		typeof(AudioSource::active) last_active;
+		typeof(AudioSource::playing) last_playing;
+		typeof(AudioSource::volume) last_volume;
+		typeof(AudioSource::loop) last_loop;
+		//! \}
 	};
 
-	void update_private(const AudioSource & component, ComponentPrivate & data);
+	void update_last(const AudioSource & component, ComponentPrivate & data);
+
+	void diff_update(AudioSource & component, const ComponentPrivate & data, Sound & resource);
 
 private:
 	SoundContext context {};
diff --git a/src/crepe/util/Private.cpp b/src/crepe/util/Private.cpp
index c5b5b30..cb4cb5b 100644
--- a/src/crepe/util/Private.cpp
+++ b/src/crepe/util/Private.cpp
@@ -27,3 +27,8 @@ Private & Private::operator=(Private && other) {
 	return *this;
 }
 
+Private::Private(const Private & other) { }
+Private & Private::operator=(const Private & other) {
+	return *this;
+}
+
diff --git a/src/crepe/util/Private.h b/src/crepe/util/Private.h
index fc3728f..6dd28bb 100644
--- a/src/crepe/util/Private.h
+++ b/src/crepe/util/Private.h
@@ -9,16 +9,16 @@ class Private {
 public:
 	Private() = default;
 	~Private();
+	Private(const Private &);
 	Private(Private &&);
+	Private & operator=(const Private &);
 	Private & operator=(Private &&);
-	Private(const Private &) = delete;
-	Private & operator=(const Private &) = delete;
 
 	template <typename T>
 	T & get();
 
 	template <typename T, typename... Args>
-	void set(Args &&... args);
+	T & set(Args &&... args);
 
 	bool empty() const noexcept;
 
diff --git a/src/crepe/util/Private.hpp b/src/crepe/util/Private.hpp
index 30c8146..d6ab23f 100644
--- a/src/crepe/util/Private.hpp
+++ b/src/crepe/util/Private.hpp
@@ -8,13 +8,15 @@
 namespace crepe {
 
 template <typename T, typename... Args>
-void Private::set(Args &&... args) {
+T & Private::set(Args &&... args) {
+	if (!this->empty()) this->destructor(this->instance);
 	T * instance = new T(std::forward<Args>(args)...);
 	this->instance = static_cast<void*>(instance);
 	this->destructor = [](void * instance) {
 		delete static_cast<T*>(instance);
 	};
 	this->type = typeid(T);
+	return *instance;
 }
 
 template <typename T>
diff --git a/src/test/AudioTest.cpp b/src/test/AudioTest.cpp
index 3be5afa..c6f0097 100644
--- a/src/test/AudioTest.cpp
+++ b/src/test/AudioTest.cpp
@@ -1,4 +1,5 @@
 #include <gtest/gtest.h>
+#include <future>
 
 #include <crepe/manager/ComponentManager.h>
 #include <crepe/manager/ResourceManager.h>
@@ -7,6 +8,7 @@
 #include <crepe/system/AudioSystem.h>
 
 using namespace std;
+using namespace std::chrono_literals;
 using namespace crepe;
 using namespace testing;
 
@@ -17,14 +19,51 @@ public:
 	ResourceManager resource_manager{mediator};
 	AudioSystem system {mediator};
 
-	void SetUp() override {
-		auto & mgr = this->component_manager;
-		GameObject entity = mgr.new_object("name");
-		AudioSource & audio_source = entity.add_component<AudioSource>("mwe/audio/sfx1.wav");
-	}
+private:
+	GameObject entity = component_manager.new_object("name");
+public:
+	AudioSource & bgm = entity.add_component<AudioSource>("mwe/audio/bgm.ogg");
+	AudioSource & sfx1 = entity.add_component<AudioSource>("mwe/audio/sfx1.wav");
+	AudioSource & sfx2 = entity.add_component<AudioSource>("mwe/audio/sfx2.wav");
+	AudioSource & sfx3 = entity.add_component<AudioSource>("mwe/audio/sfx3.wav");
+
 };
 
 TEST_F(AudioTest, Default) {
-	system.update();
+	bool example_done = false;
+
+	future example = async([&](){
+		// Start the background track
+		bgm.play();
+
+		// Play each sample sequentially while pausing and resuming the background track
+		this_thread::sleep_for(500ms);
+		sfx1.play();
+		this_thread::sleep_for(500ms);
+		sfx2.play();
+		bgm.stop();
+		this_thread::sleep_for(500ms);
+		sfx3.play();
+		bgm.play();
+		this_thread::sleep_for(500ms);
+
+		// Play all samples simultaniously
+		sfx1.play();
+		sfx2.play();
+		sfx3.play();
+		this_thread::sleep_for(1000ms);
+	});
+
+	future system_loop = async([&](){
+		while (!example_done) {
+			auto next = chrono::steady_clock::now() + 25ms;
+			system.update();
+			this_thread::sleep_until(next);
+		}
+	});
+
+	example.wait();
+	example_done = true;
+	system_loop.wait();
 }
 
diff --git a/src/test/PrivateTest.cpp b/src/test/PrivateTest.cpp
index f0d2b1a..0ea67d6 100644
--- a/src/test/PrivateTest.cpp
+++ b/src/test/PrivateTest.cpp
@@ -64,3 +64,95 @@ TEST_F(PrivateTest, IncorrectTypeException) {
 	EXPECT_NO_THROW(foo.get<TestClass>());
 }
 
+TEST_F(PrivateTest, MoveConstructor) {
+	{
+		Private foo;
+		foo.set<TestClass>();
+
+		EXPECT_EQ(PrivateTest::constructors, 1);
+		EXPECT_EQ(PrivateTest::destructors, 0);
+
+		Private bar(std::move(foo));
+
+		EXPECT_EQ(PrivateTest::constructors, 1);
+		EXPECT_EQ(PrivateTest::destructors, 0);
+	}
+
+	EXPECT_EQ(PrivateTest::constructors, 1);
+	EXPECT_EQ(PrivateTest::destructors, 1);
+}
+
+TEST_F(PrivateTest, MoveOperator) {
+	{
+		Private foo;
+		foo.set<TestClass>();
+
+		EXPECT_EQ(PrivateTest::constructors, 1);
+		EXPECT_EQ(PrivateTest::destructors, 0);
+
+		Private bar = std::move(foo);
+
+		EXPECT_EQ(PrivateTest::constructors, 1);
+		EXPECT_EQ(PrivateTest::destructors, 0);
+	}
+
+	EXPECT_EQ(PrivateTest::constructors, 1);
+	EXPECT_EQ(PrivateTest::destructors, 1);
+}
+
+TEST_F(PrivateTest, CopyConstructor) {
+	{
+		Private foo;
+		foo.set<TestClass>();
+
+		EXPECT_EQ(PrivateTest::constructors, 1);
+		EXPECT_EQ(PrivateTest::destructors, 0);
+
+		Private bar(foo);
+
+		EXPECT_TRUE(bar.empty());
+		EXPECT_EQ(PrivateTest::constructors, 1);
+		EXPECT_EQ(PrivateTest::destructors, 0);
+	}
+
+	EXPECT_EQ(PrivateTest::constructors, 1);
+	EXPECT_EQ(PrivateTest::destructors, 1);
+}
+
+TEST_F(PrivateTest, CopyOperator) {
+	{
+		Private foo;
+		foo.set<TestClass>();
+
+		EXPECT_EQ(PrivateTest::constructors, 1);
+		EXPECT_EQ(PrivateTest::destructors, 0);
+
+		Private bar = foo;
+
+		EXPECT_TRUE(bar.empty());
+		EXPECT_EQ(PrivateTest::constructors, 1);
+		EXPECT_EQ(PrivateTest::destructors, 0);
+	}
+
+	EXPECT_EQ(PrivateTest::constructors, 1);
+	EXPECT_EQ(PrivateTest::destructors, 1);
+}
+
+TEST_F(PrivateTest, DoubleAssignment) {
+	{
+		Private foo;
+		foo.set<TestClass>();
+
+		EXPECT_EQ(PrivateTest::constructors, 1);
+		EXPECT_EQ(PrivateTest::destructors, 0);
+
+		foo.set<TestClass>();
+
+		EXPECT_EQ(PrivateTest::constructors, 2);
+		EXPECT_EQ(PrivateTest::destructors, 1);
+	}
+
+	EXPECT_EQ(PrivateTest::constructors, 2);
+	EXPECT_EQ(PrivateTest::destructors, 2);
+}
+
-- 
cgit v1.2.3


From e4be73051a68b552c44280bbe9836dd4f02972d8 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Fri, 29 Nov 2024 20:35:17 +0100
Subject: audio system kinda working

---
 src/crepe/api/AudioSource.cpp     |  1 -
 src/crepe/api/AudioSource.h       |  2 --
 src/crepe/api/Config.h            | 25 +++++++++++++++++++++++--
 src/crepe/facade/Sound.h          |  7 +------
 src/crepe/facade/SoundContext.cpp | 31 +++++++++++++++++++++++++++++--
 src/crepe/facade/SoundContext.h   | 10 ++++++++++
 src/crepe/system/AudioSystem.cpp  | 30 ++++++++++++++++++++++++------
 src/crepe/system/AudioSystem.h    |  2 +-
 src/crepe/util/Log.cpp            |  2 +-
 src/crepe/util/Log.h              | 16 ----------------
 10 files changed, 89 insertions(+), 37 deletions(-)

(limited to 'src')

diff --git a/src/crepe/api/AudioSource.cpp b/src/crepe/api/AudioSource.cpp
index 4baac9a..c646aeb 100644
--- a/src/crepe/api/AudioSource.cpp
+++ b/src/crepe/api/AudioSource.cpp
@@ -15,6 +15,5 @@ void AudioSource::play(bool looping) {
 
 void AudioSource::stop() {
 	this->playing = false;
-	this->rewind = true;
 }
 
diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h
index 9d76f0b..8dc1645 100644
--- a/src/crepe/api/AudioSource.h
+++ b/src/crepe/api/AudioSource.h
@@ -44,8 +44,6 @@ private:
 
 	//! If this source is playing audio
 	bool playing = false;
-	//! Rewind the sample location
-	bool rewind = false;
 
 private:
 	//! AudioSystem::ComponentPrivate
diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h
index 693400a..7be506e 100644
--- a/src/crepe/api/Config.h
+++ b/src/crepe/api/Config.h
@@ -1,8 +1,10 @@
 #pragma once
 
+#include <string>
+
 #include "../util/Log.h"
+
 #include "types.h"
-#include <string>
 
 namespace crepe {
 
@@ -18,7 +20,20 @@ struct Config final {
 	static Config & get_instance();
 
 	//! Logging-related settings
-	Log::Config log;
+	struct {
+		/**
+		 * \brief Log level
+		 *
+		 * Only messages with equal or higher priority than this value will be logged.
+		 */
+		Log::Level level = Log::Level::INFO;
+		/**
+		 * \brief Colored log output
+		 *
+		 * Enables log coloring using ANSI escape codes.
+		 */
+		bool color = true;
+	} log;
 
 	//! Save manager
 	struct {
@@ -62,6 +77,12 @@ struct Config final {
 		 */
 		std::string root_pattern = ".crepe-root";
 	} asset;
+
+	//! Audio system settings
+	struct {
+		//! Max amount of simultanious voices
+		unsigned int voices = 32;
+	} audio;
 };
 
 } // namespace crepe
diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h
index 10d7c3c..35bccdb 100644
--- a/src/crepe/facade/Sound.h
+++ b/src/crepe/facade/Sound.h
@@ -20,13 +20,8 @@ public:
 	Sound(const Asset & src);
 	~Sound(); // dbg_trace
 
-	class Handle {
-	private:
+	struct Handle {
 		SoLoud::handle handle;
-		float volume = 1.0f;
-		bool looping = false;
-
-		friend class SoundContext;
 	};
 
 private:
diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp
index b65dfb2..3e9a3d1 100644
--- a/src/crepe/facade/SoundContext.cpp
+++ b/src/crepe/facade/SoundContext.cpp
@@ -6,10 +6,37 @@ using namespace crepe;
 
 SoundContext::SoundContext() {
 	dbg_trace();
-	engine.init();
+	this->engine.init();
+	this->engine.setMaxActiveVoiceCount(this->config.audio.voices);
 }
 
 SoundContext::~SoundContext() {
 	dbg_trace();
-	engine.deinit();
+	this->engine.deinit();
 }
+
+Sound::Handle SoundContext::play(Sound & resource) {
+	return {
+		.handle = this->engine.play(resource.sample, this->default_volume),
+	};
+}
+
+void SoundContext::stop(Sound::Handle & handle) {
+	this->engine.stop(handle.handle);
+}
+
+void SoundContext::set_volume(Sound & resource, Sound::Handle & handle, float volume) {
+	this->engine.setVolume(handle.handle, volume);
+	this->default_volume = volume;
+}
+
+void SoundContext::set_loop(Sound & resource, Sound::Handle & handle, bool loop) {
+	this->engine.setLooping(handle.handle, loop);
+}
+
+bool SoundContext::get_playing(Sound::Handle & handle) {
+	// See Soloud::stopVoice_internal in soloud/src/core/soloud_core_voiceops.cpp for why this is
+	// the correct method to use here (samples are currently never paused)
+	return this->engine.isValidVoiceHandle(handle.handle);
+}
+
diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h
index 286ced8..e02977e 100644
--- a/src/crepe/facade/SoundContext.h
+++ b/src/crepe/facade/SoundContext.h
@@ -2,6 +2,8 @@
 
 #include <soloud/soloud.h>
 
+#include "../api/Config.h"
+
 #include "Sound.h"
 
 namespace crepe {
@@ -22,9 +24,17 @@ public:
 	SoundContext & operator=(const SoundContext &) = delete;
 	SoundContext & operator=(SoundContext &&) = delete;
 
+	Sound::Handle play(Sound & resource);
+	void stop(Sound::Handle &);
+	void set_volume(Sound &, Sound::Handle &, float);
+	void set_loop(Sound &, Sound::Handle &, bool);
+	bool get_playing(Sound::Handle &);
 
 private:
 	SoLoud::Soloud engine;
+	float default_volume = 1.0f;
+
+	Config & config = Config::get_instance();
 };
 
 } // namespace crepe
diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
index 191dbbb..98aff58 100644
--- a/src/crepe/system/AudioSystem.cpp
+++ b/src/crepe/system/AudioSystem.cpp
@@ -20,6 +20,7 @@ void AudioSystem::update() {
 		if (component.private_data.empty()) {
 			auto & data = component.private_data.set<ComponentPrivate>();
 			this->update_last(component, data);
+			data.last_playing = false; // always start
 		}
 		auto & data = component.private_data.get<ComponentPrivate>();
 
@@ -29,18 +30,35 @@ void AudioSystem::update() {
 	}
 }
 
-void AudioSystem::diff_update(AudioSource & component, const ComponentPrivate & data, Sound & resource) {
+void AudioSystem::diff_update(AudioSource & component, ComponentPrivate & data, Sound & resource) {
 	bool update_playing = component.playing != data.last_playing;
 	bool update_volume = component.volume != data.last_volume;
 	bool update_loop = component.loop != data.last_loop;
 	bool update_active = component.active != data.last_active;
 
-	if (update_active)
-	if (component.rewind) {
-		component.playing = false;
-		// this->context.rewind(resource, data.handle);
+	if (update_active) {
+		if (component.active) {
+			update_playing = true;
+			if (component.play_on_awake)
+				component.playing = true;
+		} else {
+			this->context.stop(data.handle);
+			return;
+		}
+	}
+	if (!component.active) return;
+	if (update_playing) {
+		if (component.playing) data.handle = this->context.play(resource);
+		else this->context.stop(data.handle);
+	} else {
+		component.playing = this->context.get_playing(data.handle);
+	}
+	if (update_volume) {
+		this->context.set_volume(resource, data.handle, component.volume);
+	}
+	if (update_loop) {
+		this->context.set_loop(resource, data.handle, component.loop);
 	}
-
 }
 
 void AudioSystem::update_last(const AudioSource & component, ComponentPrivate & data) {
diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h
index 4650178..3404878 100644
--- a/src/crepe/system/AudioSystem.h
+++ b/src/crepe/system/AudioSystem.h
@@ -34,7 +34,7 @@ private:
 
 	void update_last(const AudioSource & component, ComponentPrivate & data);
 
-	void diff_update(AudioSource & component, const ComponentPrivate & data, Sound & resource);
+	void diff_update(AudioSource & component, ComponentPrivate & data, Sound & resource);
 
 private:
 	SoundContext context {};
diff --git a/src/crepe/util/Log.cpp b/src/crepe/util/Log.cpp
index bc86c7e..84d80a8 100644
--- a/src/crepe/util/Log.cpp
+++ b/src/crepe/util/Log.cpp
@@ -25,7 +25,7 @@ string Log::prefix(const Level & level) {
 }
 
 void Log::log(const Level & level, const string & msg) {
-	auto & cfg = crepe::Config::get_instance();
+	auto & cfg = Config::get_instance();
 	if (level < cfg.log.level) return;
 
 	string out = Log::prefix(level) + msg;
diff --git a/src/crepe/util/Log.h b/src/crepe/util/Log.h
index 914145a..fc0bb3a 100644
--- a/src/crepe/util/Log.h
+++ b/src/crepe/util/Log.h
@@ -77,22 +77,6 @@ private:
 	 * \return Colored message severity prefix string
 	 */
 	static std::string prefix(const Level & level);
-
-public:
-	struct Config {
-		/**
-		 * \brief Log level
-		 *
-		 * Only messages with equal or higher priority than this value will be logged.
-		 */
-		Level level = INFO;
-		/**
-		 * \brief Colored log output
-		 *
-		 * Enables log coloring using ANSI escape codes.
-		 */
-		bool color = true;
-	};
 };
 
 } // namespace crepe
-- 
cgit v1.2.3


From 30e2b2b0cbb503d83a087d8d326940c3c4bc8fff Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Sat, 30 Nov 2024 15:08:57 +0100
Subject: fix audio system implementation

---
 src/crepe/api/AudioSource.cpp     |  4 ++--
 src/crepe/api/AudioSource.h       | 15 +++++++++++++--
 src/crepe/facade/SoundContext.cpp |  6 ------
 src/crepe/facade/SoundContext.h   |  1 -
 src/crepe/system/AudioSystem.cpp  | 23 ++++++++++-------------
 src/crepe/system/AudioSystem.h    |  1 -
 src/test/AudioTest.cpp            | 14 ++++++++++----
 7 files changed, 35 insertions(+), 29 deletions(-)

(limited to 'src')

diff --git a/src/crepe/api/AudioSource.cpp b/src/crepe/api/AudioSource.cpp
index c646aeb..cc70801 100644
--- a/src/crepe/api/AudioSource.cpp
+++ b/src/crepe/api/AudioSource.cpp
@@ -10,10 +10,10 @@ AudioSource::AudioSource(game_object_id_t id, const Asset & src) :
 
 void AudioSource::play(bool looping) {
 	this->loop = looping;
-	this->playing = true;
+	this->oneshot_play = true;
 }
 
 void AudioSource::stop() {
-	this->playing = false;
+	this->oneshot_stop = true;
 }
 
diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h
index 8dc1645..1899c22 100644
--- a/src/crepe/api/AudioSource.h
+++ b/src/crepe/api/AudioSource.h
@@ -42,8 +42,19 @@ private:
 	//! This audio source's clip
 	const Asset source;
 
-	//! If this source is playing audio
-	bool playing = false;
+	/**
+	 * \name One-shot state variables
+	 *
+	 * These variables trigger function calls when set to true, and are unconditionally reset on
+	 * every system update.
+	 *
+	 * \{
+	 */
+	//! Play this sample
+	bool oneshot_play = false;
+	//! Stop this sample
+	bool oneshot_stop = false;
+	//! \}
 
 private:
 	//! AudioSystem::ComponentPrivate
diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp
index 3e9a3d1..3ae5956 100644
--- a/src/crepe/facade/SoundContext.cpp
+++ b/src/crepe/facade/SoundContext.cpp
@@ -34,9 +34,3 @@ void SoundContext::set_loop(Sound & resource, Sound::Handle & handle, bool loop)
 	this->engine.setLooping(handle.handle, loop);
 }
 
-bool SoundContext::get_playing(Sound::Handle & handle) {
-	// See Soloud::stopVoice_internal in soloud/src/core/soloud_core_voiceops.cpp for why this is
-	// the correct method to use here (samples are currently never paused)
-	return this->engine.isValidVoiceHandle(handle.handle);
-}
-
diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h
index e02977e..91d3fe9 100644
--- a/src/crepe/facade/SoundContext.h
+++ b/src/crepe/facade/SoundContext.h
@@ -28,7 +28,6 @@ public:
 	void stop(Sound::Handle &);
 	void set_volume(Sound &, Sound::Handle &, float);
 	void set_loop(Sound &, Sound::Handle &, bool);
-	bool get_playing(Sound::Handle &);
 
 private:
 	SoLoud::Soloud engine;
diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
index 98aff58..b105a4d 100644
--- a/src/crepe/system/AudioSystem.cpp
+++ b/src/crepe/system/AudioSystem.cpp
@@ -13,14 +13,12 @@ void AudioSystem::update() {
 	RefVector<AudioSource> components = component_manager.get_components_by_type<AudioSource>();
 
 	for (AudioSource & component : components) {
-		if (!component.active) continue;
-
 		Sound & resource = resource_manager.get<Sound>(component.source);
 
 		if (component.private_data.empty()) {
 			auto & data = component.private_data.set<ComponentPrivate>();
 			this->update_last(component, data);
-			data.last_playing = false; // always start
+			data.last_active = false;
 		}
 		auto & data = component.private_data.get<ComponentPrivate>();
 
@@ -31,27 +29,26 @@ void AudioSystem::update() {
 }
 
 void AudioSystem::diff_update(AudioSource & component, ComponentPrivate & data, Sound & resource) {
-	bool update_playing = component.playing != data.last_playing;
 	bool update_volume = component.volume != data.last_volume;
 	bool update_loop = component.loop != data.last_loop;
 	bool update_active = component.active != data.last_active;
 
 	if (update_active) {
 		if (component.active) {
-			update_playing = true;
-			if (component.play_on_awake)
-				component.playing = true;
+			component.oneshot_play = component.play_on_awake;
 		} else {
 			this->context.stop(data.handle);
 			return;
 		}
 	}
 	if (!component.active) return;
-	if (update_playing) {
-		if (component.playing) data.handle = this->context.play(resource);
-		else this->context.stop(data.handle);
-	} else {
-		component.playing = this->context.get_playing(data.handle);
+	if (component.oneshot_play) {
+		data.handle = this->context.play(resource);
+		component.oneshot_play = false;
+	}
+	if (component.oneshot_stop) {
+		this->context.stop(data.handle);
+		component.oneshot_stop = false;
 	}
 	if (update_volume) {
 		this->context.set_volume(resource, data.handle, component.volume);
@@ -63,8 +60,8 @@ void AudioSystem::diff_update(AudioSource & component, ComponentPrivate & data,
 
 void AudioSystem::update_last(const AudioSource & component, ComponentPrivate & data) {
 	data.last_active = component.active;
+	if (!component.active) return;
 	data.last_loop = component.loop;
-	data.last_playing = component.playing;
 	data.last_volume = component.volume;
 }
 
diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h
index 3404878..dee82f6 100644
--- a/src/crepe/system/AudioSystem.h
+++ b/src/crepe/system/AudioSystem.h
@@ -26,7 +26,6 @@ private:
 		 * \{
 		 */
 		typeof(AudioSource::active) last_active;
-		typeof(AudioSource::playing) last_playing;
 		typeof(AudioSource::volume) last_volume;
 		typeof(AudioSource::loop) last_loop;
 		//! \}
diff --git a/src/test/AudioTest.cpp b/src/test/AudioTest.cpp
index c6f0097..2a2d0e1 100644
--- a/src/test/AudioTest.cpp
+++ b/src/test/AudioTest.cpp
@@ -1,3 +1,4 @@
+#include "util/Log.h"
 #include <gtest/gtest.h>
 #include <future>
 
@@ -27,24 +28,29 @@ public:
 	AudioSource & sfx2 = entity.add_component<AudioSource>("mwe/audio/sfx2.wav");
 	AudioSource & sfx3 = entity.add_component<AudioSource>("mwe/audio/sfx3.wav");
 
+	void SetUp() override {
+		bgm.play_on_awake = true;
+	}
 };
 
 TEST_F(AudioTest, Default) {
 	bool example_done = false;
 
 	future example = async([&](){
-		// Start the background track
-		bgm.play();
+		// Start the background track. This happens automatically due to the play_on_awake property
+		// being true. The following call is optional and doesn't start two simultanious voices if
+		// left in:
+		// bgm.play();
 
 		// Play each sample sequentially while pausing and resuming the background track
 		this_thread::sleep_for(500ms);
 		sfx1.play();
 		this_thread::sleep_for(500ms);
 		sfx2.play();
-		bgm.stop();
+		bgm.active = false;
 		this_thread::sleep_for(500ms);
 		sfx3.play();
-		bgm.play();
+		bgm.active = true;
 		this_thread::sleep_for(500ms);
 
 		// Play all samples simultaniously
-- 
cgit v1.2.3


From eefaeb807eb5d0b08353389070bf3d27c3d0d958 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Sat, 30 Nov 2024 16:32:13 +0100
Subject: test and debug audio system

---
 src/crepe/facade/SoundContext.h  |   8 +-
 src/crepe/system/AudioSystem.cpp |  28 ++++---
 src/crepe/system/AudioSystem.h   |   4 +-
 src/crepe/system/System.cpp      |   4 +-
 src/test/AudioTest.cpp           | 174 ++++++++++++++++++++++++++++-----------
 5 files changed, 150 insertions(+), 68 deletions(-)

(limited to 'src')

diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h
index 91d3fe9..c651cd5 100644
--- a/src/crepe/facade/SoundContext.h
+++ b/src/crepe/facade/SoundContext.h
@@ -24,10 +24,10 @@ public:
 	SoundContext & operator=(const SoundContext &) = delete;
 	SoundContext & operator=(SoundContext &&) = delete;
 
-	Sound::Handle play(Sound & resource);
-	void stop(Sound::Handle &);
-	void set_volume(Sound &, Sound::Handle &, float);
-	void set_loop(Sound &, Sound::Handle &, bool);
+	virtual Sound::Handle play(Sound & resource);
+	virtual void stop(Sound::Handle &);
+	virtual void set_volume(Sound &, Sound::Handle &, float);
+	virtual void set_loop(Sound &, Sound::Handle &, bool);
 
 private:
 	SoLoud::Soloud engine;
diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
index b105a4d..84a101a 100644
--- a/src/crepe/system/AudioSystem.cpp
+++ b/src/crepe/system/AudioSystem.cpp
@@ -29,39 +29,43 @@ void AudioSystem::update() {
 }
 
 void AudioSystem::diff_update(AudioSource & component, ComponentPrivate & data, Sound & resource) {
-	bool update_volume = component.volume != data.last_volume;
-	bool update_loop = component.loop != data.last_loop;
-	bool update_active = component.active != data.last_active;
+	SoundContext & context = this->get_context();
 
-	if (update_active) {
+	if (component.active != data.last_active) {
 		if (component.active) {
 			component.oneshot_play = component.play_on_awake;
 		} else {
-			this->context.stop(data.handle);
+			context.stop(data.handle);
 			return;
 		}
 	}
 	if (!component.active) return;
+
 	if (component.oneshot_play) {
-		data.handle = this->context.play(resource);
+		data.handle = context.play(resource);
 		component.oneshot_play = false;
 	}
 	if (component.oneshot_stop) {
-		this->context.stop(data.handle);
+		context.stop(data.handle);
 		component.oneshot_stop = false;
 	}
-	if (update_volume) {
-		this->context.set_volume(resource, data.handle, component.volume);
+	if (component.volume != data.last_volume) {
+		context.set_volume(resource, data.handle, component.volume);
 	}
-	if (update_loop) {
-		this->context.set_loop(resource, data.handle, component.loop);
+	if (component.loop != data.last_loop) {
+		context.set_loop(resource, data.handle, component.loop);
 	}
 }
 
 void AudioSystem::update_last(const AudioSource & component, ComponentPrivate & data) {
 	data.last_active = component.active;
-	if (!component.active) return;
 	data.last_loop = component.loop;
 	data.last_volume = component.volume;
 }
 
+SoundContext & AudioSystem::get_context() {
+	if (this->context.empty())
+		this->context.set<SoundContext>();
+	return this->context.get<SoundContext>();
+}
+
diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h
index dee82f6..a004c60 100644
--- a/src/crepe/system/AudioSystem.h
+++ b/src/crepe/system/AudioSystem.h
@@ -35,8 +35,10 @@ private:
 
 	void diff_update(AudioSource & component, ComponentPrivate & data, Sound & resource);
 
+protected:
+	virtual SoundContext & get_context();
 private:
-	SoundContext context {};
+	Private context;
 };
 
 } // namespace crepe
diff --git a/src/crepe/system/System.cpp b/src/crepe/system/System.cpp
index f68549b..ecc740d 100644
--- a/src/crepe/system/System.cpp
+++ b/src/crepe/system/System.cpp
@@ -1,7 +1,5 @@
-#include "../util/Log.h"
-
 #include "System.h"
 
 using namespace crepe;
 
-System::System(const Mediator & mediator) : mediator(mediator) { dbg_trace(); }
+System::System(const Mediator & mediator) : mediator(mediator) {}
diff --git a/src/test/AudioTest.cpp b/src/test/AudioTest.cpp
index 2a2d0e1..9c3cb9c 100644
--- a/src/test/AudioTest.cpp
+++ b/src/test/AudioTest.cpp
@@ -1,6 +1,5 @@
-#include "util/Log.h"
 #include <gtest/gtest.h>
-#include <future>
+#include <gmock/gmock.h>
 
 #include <crepe/manager/ComponentManager.h>
 #include <crepe/manager/ResourceManager.h>
@@ -14,62 +13,141 @@ using namespace crepe;
 using namespace testing;
 
 class AudioTest : public Test {
+private:
+	class TestSoundContext : public SoundContext {
+	public:
+		MOCK_METHOD(Sound::Handle, play, (Sound & resource), (override));
+		MOCK_METHOD(void, stop, (Sound::Handle &), (override));
+		MOCK_METHOD(void, set_volume, (Sound &, Sound::Handle &, float), (override));
+		MOCK_METHOD(void, set_loop, (Sound &, Sound::Handle &, bool), (override));
+	};
+
+	class TestAudioSystem : public AudioSystem {
+	public:
+		using AudioSystem::AudioSystem;
+		StrictMock<TestSoundContext> context;
+		virtual SoundContext & get_context() {
+			return this->context;
+		}
+	};
+
+private:
 	Mediator mediator;
-public:
 	ComponentManager component_manager{mediator};
 	ResourceManager resource_manager{mediator};
-	AudioSystem system {mediator};
+public:
+	TestAudioSystem system {mediator};
+	TestSoundContext & context = system.context;
 
 private:
 	GameObject entity = component_manager.new_object("name");
 public:
-	AudioSource & bgm = entity.add_component<AudioSource>("mwe/audio/bgm.ogg");
-	AudioSource & sfx1 = entity.add_component<AudioSource>("mwe/audio/sfx1.wav");
-	AudioSource & sfx2 = entity.add_component<AudioSource>("mwe/audio/sfx2.wav");
-	AudioSource & sfx3 = entity.add_component<AudioSource>("mwe/audio/sfx3.wav");
-
-	void SetUp() override {
-		bgm.play_on_awake = true;
-	}
+	AudioSource & component = entity.add_component<AudioSource>("mwe/audio/bgm.ogg");
 };
 
 TEST_F(AudioTest, Default) {
-	bool example_done = false;
-
-	future example = async([&](){
-		// Start the background track. This happens automatically due to the play_on_awake property
-		// being true. The following call is optional and doesn't start two simultanious voices if
-		// left in:
-		// bgm.play();
-
-		// Play each sample sequentially while pausing and resuming the background track
-		this_thread::sleep_for(500ms);
-		sfx1.play();
-		this_thread::sleep_for(500ms);
-		sfx2.play();
-		bgm.active = false;
-		this_thread::sleep_for(500ms);
-		sfx3.play();
-		bgm.active = true;
-		this_thread::sleep_for(500ms);
-
-		// Play all samples simultaniously
-		sfx1.play();
-		sfx2.play();
-		sfx3.play();
-		this_thread::sleep_for(1000ms);
-	});
-
-	future system_loop = async([&](){
-		while (!example_done) {
-			auto next = chrono::steady_clock::now() + 25ms;
-			system.update();
-			this_thread::sleep_until(next);
-		}
-	});
+	EXPECT_CALL(context, play(_)).Times(0);
+	EXPECT_CALL(context, stop(_)).Times(0);
+	EXPECT_CALL(context, set_volume(_, _, _)).Times(0);
+	EXPECT_CALL(context, set_loop(_, _, _)).Times(0);
+	system.update();
+}
+
+TEST_F(AudioTest, Play) {
+	system.update();
+
+	{
+		InSequence seq;
 
-	example.wait();
-	example_done = true;
-	system_loop.wait();
+		EXPECT_CALL(context, play(_)).Times(0);
+		component.play();
+	}
+
+	{
+		InSequence seq;
+
+		EXPECT_CALL(context, play(_)).Times(1);
+		system.update();
+	}
+}
+
+TEST_F(AudioTest, Stop) {
+	system.update();
+
+	{
+		InSequence seq;
+
+		EXPECT_CALL(context, stop(_)).Times(0);
+		component.stop();
+	}
+
+	{
+		InSequence seq;
+
+		EXPECT_CALL(context, stop(_)).Times(1);
+		system.update();
+	}
+}
+
+TEST_F(AudioTest, Volume) {
+	system.update();
+
+	{
+		InSequence seq;
+
+		EXPECT_CALL(context, set_volume(_, _, _)).Times(0);
+		component.volume += 0.2;
+	}
+
+	{
+		InSequence seq;
+
+		EXPECT_CALL(context, set_volume(_, _, component.volume)).Times(1);
+		system.update();
+	}
+}
+
+TEST_F(AudioTest, Looping) {
+	system.update();
+
+	{
+		InSequence seq;
+
+		EXPECT_CALL(context, set_loop(_, _, _)).Times(0);
+		component.loop = !component.loop;
+	}
+
+	{
+		InSequence seq;
+
+		EXPECT_CALL(context, set_loop(_, _, component.loop)).Times(1);
+		system.update();
+	}
+}
+
+TEST_F(AudioTest, StopOnDeactivate) {
+	system.update();
+
+	{
+		InSequence seq;
+
+		EXPECT_CALL(context, stop(_)).Times(1);
+		component.active = false;
+		system.update();
+	}
+}
+
+TEST_F(AudioTest, PlayOnActive) {
+	component.active = false;
+	component.play_on_awake = true;
+	system.update();
+
+	{
+		InSequence seq;
+
+		EXPECT_CALL(context, play(_)).Times(1);
+		component.active = true;
+		system.update();
+	}
 }
 
-- 
cgit v1.2.3


From 9eff2e24fa4cf0ffad2b47cc922a6558bc1a9fa1 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Sat, 30 Nov 2024 16:42:21 +0100
Subject: `make format`

---
 src/crepe/Resource.cpp                 |  3 +--
 src/crepe/api/AudioSource.cpp          | 12 ++++--------
 src/crepe/api/AudioSource.h            |  4 ++--
 src/crepe/api/LoopManager.cpp          |  5 +----
 src/crepe/api/LoopManager.h            |  2 +-
 src/crepe/api/Scene.h                  |  3 ++-
 src/crepe/api/Script.cpp               |  1 -
 src/crepe/api/Script.h                 |  4 ++--
 src/crepe/facade/Sound.cpp             |  9 ++++-----
 src/crepe/facade/SoundContext.cpp      |  5 +----
 src/crepe/manager/ComponentManager.cpp |  2 +-
 src/crepe/manager/Manager.cpp          |  3 +--
 src/crepe/manager/Manager.h            |  3 +--
 src/crepe/manager/Mediator.h           |  4 ++--
 src/crepe/manager/ResourceManager.cpp  |  8 ++------
 src/crepe/manager/ResourceManager.hpp  | 13 +++++++------
 src/crepe/manager/SaveManager.cpp      |  4 ++--
 src/crepe/system/AudioSystem.cpp       | 10 +++++-----
 src/crepe/system/AudioSystem.h         |  6 +++---
 src/crepe/system/PhysicsSystem.cpp     |  2 +-
 src/crepe/system/RenderSystem.cpp      |  2 +-
 src/crepe/system/ScriptSystem.cpp      |  2 +-
 src/crepe/util/Private.cpp             | 17 +++++------------
 src/crepe/util/Private.h               |  5 ++---
 src/crepe/util/Private.hpp             | 18 ++++++++----------
 src/test/AudioTest.cpp                 | 15 +++++++--------
 src/test/ECSTest.cpp                   |  3 ++-
 src/test/EventTest.cpp                 |  6 ++----
 src/test/ParticleTest.cpp              |  3 ++-
 src/test/PhysicsTest.cpp               |  3 ++-
 src/test/PrivateTest.cpp               |  5 +----
 src/test/RenderSystemTest.cpp          |  3 ++-
 src/test/ResourceManagerTest.cpp       | 16 ++++++----------
 src/test/SceneManagerTest.cpp          |  7 ++++---
 src/test/ScriptEventTest.cpp           |  7 +++----
 src/test/ScriptSceneTest.cpp           |  3 +--
 src/test/ScriptTest.cpp                |  3 +--
 src/test/ScriptTest.h                  |  8 +++++---
 src/test/main.cpp                      |  3 +--
 39 files changed, 99 insertions(+), 133 deletions(-)

(limited to 'src')

diff --git a/src/crepe/Resource.cpp b/src/crepe/Resource.cpp
index e254695..27b4c4b 100644
--- a/src/crepe/Resource.cpp
+++ b/src/crepe/Resource.cpp
@@ -2,5 +2,4 @@
 
 using namespace crepe;
 
-Resource::Resource(const Asset & asset) { }
-
+Resource::Resource(const Asset & asset) {}
diff --git a/src/crepe/api/AudioSource.cpp b/src/crepe/api/AudioSource.cpp
index cc70801..7b05cb1 100644
--- a/src/crepe/api/AudioSource.cpp
+++ b/src/crepe/api/AudioSource.cpp
@@ -3,17 +3,13 @@
 using namespace crepe;
 using namespace std;
 
-AudioSource::AudioSource(game_object_id_t id, const Asset & src) :
-	Component(id),
-	source(src)
-{ }
+AudioSource::AudioSource(game_object_id_t id, const Asset & src)
+	: Component(id),
+	  source(src) {}
 
 void AudioSource::play(bool looping) {
 	this->loop = looping;
 	this->oneshot_play = true;
 }
 
-void AudioSource::stop() {
-	this->oneshot_stop = true;
-}
-
+void AudioSource::stop() { this->oneshot_stop = true; }
diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h
index 1899c22..63b4bc4 100644
--- a/src/crepe/api/AudioSource.h
+++ b/src/crepe/api/AudioSource.h
@@ -4,8 +4,8 @@
 #include "../types.h"
 #include "../util/Private.h"
 
-#include "GameObject.h"
 #include "Asset.h"
+#include "GameObject.h"
 
 namespace crepe {
 
@@ -20,6 +20,7 @@ protected:
 	AudioSource(game_object_id_t id, const Asset & source);
 	//! Only ComponentManager can create components
 	friend class ComponentManager;
+
 public:
 	// But std::unique_ptr needs to be able to destoy this component again
 	virtual ~AudioSource() = default;
@@ -62,4 +63,3 @@ private:
 };
 
 } // namespace crepe
-
diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp
index b277185..731cfb7 100644
--- a/src/crepe/api/LoopManager.cpp
+++ b/src/crepe/api/LoopManager.cpp
@@ -22,9 +22,7 @@ LoopManager::LoopManager() {
 	this->load_system<ScriptSystem>();
 }
 
-void LoopManager::process_input() {
-	this->sdl_context.handle_events(this->game_running);
-}
+void LoopManager::process_input() { this->sdl_context.handle_events(this->game_running); }
 
 void LoopManager::start() {
 	this->setup();
@@ -69,4 +67,3 @@ void LoopManager::render() {
 }
 
 void LoopManager::update() {}
-
diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h
index 6ea5ccc..d8910a0 100644
--- a/src/crepe/api/LoopManager.h
+++ b/src/crepe/api/LoopManager.h
@@ -2,10 +2,10 @@
 
 #include <memory>
 
+#include "../facade/SDLContext.h"
 #include "../manager/ComponentManager.h"
 #include "../manager/SceneManager.h"
 #include "../system/System.h"
-#include "../facade/SDLContext.h"
 
 #include "LoopTimer.h"
 
diff --git a/src/crepe/api/Scene.h b/src/crepe/api/Scene.h
index 66dad17..9f1e8ce 100644
--- a/src/crepe/api/Scene.h
+++ b/src/crepe/api/Scene.h
@@ -2,8 +2,8 @@
 
 #include <string>
 
-#include "../util/OptionalRef.h"
 #include "../manager/Mediator.h"
+#include "../util/OptionalRef.h"
 
 namespace crepe {
 
@@ -37,6 +37,7 @@ public:
 
 	// TODO: Late references should ALWAYS be private! This is currently kept as-is so unit tests
 	// keep passing, but this reference should not be directly accessible by the user!!!
+
 protected:
 	/**
 	 * \name Late references
diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp
index a27838e..4091fd4 100644
--- a/src/crepe/api/Script.cpp
+++ b/src/crepe/api/Script.cpp
@@ -25,4 +25,3 @@ void Script::set_next_scene(const string & name) {
 	SceneManager & mgr = mediator.scene_manager;
 	mgr.set_next_scene(name);
 }
-
diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h
index e1f86b2..1b339b0 100644
--- a/src/crepe/api/Script.h
+++ b/src/crepe/api/Script.h
@@ -2,10 +2,10 @@
 
 #include <vector>
 
+#include "../manager/EventManager.h"
+#include "../manager/Mediator.h"
 #include "../types.h"
 #include "../util/OptionalRef.h"
-#include "../manager/Mediator.h"
-#include "../manager/EventManager.h"
 
 namespace crepe {
 
diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp
index 0df1f48..33a0c47 100644
--- a/src/crepe/facade/Sound.cpp
+++ b/src/crepe/facade/Sound.cpp
@@ -23,26 +23,25 @@ Sound::~Sound() { dbg_trace(); }
 // 		ctx.engine.setLooping(this->handle, this->looping);
 // 	}
 // }
-// 
+//
 // void Sound::pause(SoundContext & ctx) {
 // 	if (ctx.engine.getPause(this->handle)) return;
 // 	ctx.engine.setPause(this->handle, true);
 // }
-// 
+//
 // void Sound::rewind(SoundContext & ctx) {
 // 	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
 // 	ctx.engine.seek(this->handle, 0);
 // }
-// 
+//
 // void Sound::set_volume(SoundContext & ctx, float volume) {
 // 	this->volume = volume;
 // 	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
 // 	ctx.engine.setVolume(this->handle, this->volume);
 // }
-// 
+//
 // void Sound::set_looping(SoundContext & ctx, bool looping) {
 // 	this->looping = looping;
 // 	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
 // 	ctx.engine.setLooping(this->handle, this->looping);
 // }
-
diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp
index 3ae5956..470b3cc 100644
--- a/src/crepe/facade/SoundContext.cpp
+++ b/src/crepe/facade/SoundContext.cpp
@@ -21,9 +21,7 @@ Sound::Handle SoundContext::play(Sound & resource) {
 	};
 }
 
-void SoundContext::stop(Sound::Handle & handle) {
-	this->engine.stop(handle.handle);
-}
+void SoundContext::stop(Sound::Handle & handle) { this->engine.stop(handle.handle); }
 
 void SoundContext::set_volume(Sound & resource, Sound::Handle & handle, float volume) {
 	this->engine.setVolume(handle.handle, volume);
@@ -33,4 +31,3 @@ void SoundContext::set_volume(Sound & resource, Sound::Handle & handle, float vo
 void SoundContext::set_loop(Sound & resource, Sound::Handle & handle, bool loop) {
 	this->engine.setLooping(handle.handle, loop);
 }
-
diff --git a/src/crepe/manager/ComponentManager.cpp b/src/crepe/manager/ComponentManager.cpp
index 5a96158..80cf8b4 100644
--- a/src/crepe/manager/ComponentManager.cpp
+++ b/src/crepe/manager/ComponentManager.cpp
@@ -1,6 +1,6 @@
 #include "../api/GameObject.h"
-#include "../util/Log.h"
 #include "../types.h"
+#include "../util/Log.h"
 
 #include "ComponentManager.h"
 
diff --git a/src/crepe/manager/Manager.cpp b/src/crepe/manager/Manager.cpp
index fe7c936..1182785 100644
--- a/src/crepe/manager/Manager.cpp
+++ b/src/crepe/manager/Manager.cpp
@@ -2,5 +2,4 @@
 
 using namespace crepe;
 
-Manager::Manager(Mediator & mediator) : mediator(mediator) { }
-
+Manager::Manager(Mediator & mediator) : mediator(mediator) {}
diff --git a/src/crepe/manager/Manager.h b/src/crepe/manager/Manager.h
index 9adfd0b..4f21ef4 100644
--- a/src/crepe/manager/Manager.h
+++ b/src/crepe/manager/Manager.h
@@ -13,5 +13,4 @@ protected:
 	Mediator & mediator;
 };
 
-}
-
+} // namespace crepe
diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h
index 475aed9..e9c10b1 100644
--- a/src/crepe/manager/Mediator.h
+++ b/src/crepe/manager/Mediator.h
@@ -3,8 +3,8 @@
 #include "../util/OptionalRef.h"
 
 // TODO: remove these singletons:
-#include "SaveManager.h"
 #include "EventManager.h"
+#include "SaveManager.h"
 
 namespace crepe {
 
@@ -32,4 +32,4 @@ struct Mediator {
 	OptionalRef<ResourceManager> resource_manager;
 };
 
-}
+} // namespace crepe
diff --git a/src/crepe/manager/ResourceManager.cpp b/src/crepe/manager/ResourceManager.cpp
index 87585ad..7c01808 100644
--- a/src/crepe/manager/ResourceManager.cpp
+++ b/src/crepe/manager/ResourceManager.cpp
@@ -18,17 +18,13 @@ void ResourceManager::clear() {
 	});
 }
 
-void ResourceManager::clear_all() {
-	this->resources.clear();
-}
+void ResourceManager::clear_all() { this->resources.clear(); }
 
 void ResourceManager::set_persistent(const Asset & asset, bool persistent) {
 	this->get_entry(asset).persistent = persistent;
 }
 
 ResourceManager::CacheEntry & ResourceManager::get_entry(const Asset & asset) {
-	if (!this->resources.contains(asset))
-		this->resources[asset] = {};
+	if (!this->resources.contains(asset)) this->resources[asset] = {};
 	return this->resources.at(asset);
 }
-
diff --git a/src/crepe/manager/ResourceManager.hpp b/src/crepe/manager/ResourceManager.hpp
index 8270bc5..5167d71 100644
--- a/src/crepe/manager/ResourceManager.hpp
+++ b/src/crepe/manager/ResourceManager.hpp
@@ -9,18 +9,19 @@ namespace crepe {
 template <typename T>
 T & ResourceManager::get(const Asset & asset) {
 	using namespace std;
-	static_assert(is_base_of<Resource, T>::value, "cache must recieve a derivative class of Resource");
+	static_assert(is_base_of<Resource, T>::value,
+				  "cache must recieve a derivative class of Resource");
 
 	CacheEntry & entry = this->get_entry(asset);
-	if (entry.resource == nullptr)
-		entry.resource = make_unique<T>(asset);
+	if (entry.resource == nullptr) entry.resource = make_unique<T>(asset);
 
 	T * concrete_resource = dynamic_cast<T *>(entry.resource.get());
 	if (concrete_resource == nullptr)
-		throw runtime_error(format("ResourceManager: mismatch between requested type and actual type of resource ({})", asset.get_path()));
+		throw runtime_error(format("ResourceManager: mismatch between requested type and "
+								   "actual type of resource ({})",
+								   asset.get_path()));
 
 	return *concrete_resource;
 }
 
-}
-
+} // namespace crepe
diff --git a/src/crepe/manager/SaveManager.cpp b/src/crepe/manager/SaveManager.cpp
index 121d017..d4ed1c1 100644
--- a/src/crepe/manager/SaveManager.cpp
+++ b/src/crepe/manager/SaveManager.cpp
@@ -1,7 +1,7 @@
+#include "../ValueBroker.h"
+#include "../api/Config.h"
 #include "../facade/DB.h"
 #include "../util/Log.h"
-#include "../api/Config.h"
-#include "../ValueBroker.h"
 
 #include "SaveManager.h"
 
diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
index 84a101a..0696b34 100644
--- a/src/crepe/system/AudioSystem.cpp
+++ b/src/crepe/system/AudioSystem.cpp
@@ -10,7 +10,8 @@ using namespace std;
 void AudioSystem::update() {
 	ComponentManager & component_manager = this->mediator.component_manager;
 	ResourceManager & resource_manager = this->mediator.resource_manager;
-	RefVector<AudioSource> components = component_manager.get_components_by_type<AudioSource>();
+	RefVector<AudioSource> components
+		= component_manager.get_components_by_type<AudioSource>();
 
 	for (AudioSource & component : components) {
 		Sound & resource = resource_manager.get<Sound>(component.source);
@@ -28,7 +29,8 @@ void AudioSystem::update() {
 	}
 }
 
-void AudioSystem::diff_update(AudioSource & component, ComponentPrivate & data, Sound & resource) {
+void AudioSystem::diff_update(AudioSource & component, ComponentPrivate & data,
+							  Sound & resource) {
 	SoundContext & context = this->get_context();
 
 	if (component.active != data.last_active) {
@@ -64,8 +66,6 @@ void AudioSystem::update_last(const AudioSource & component, ComponentPrivate &
 }
 
 SoundContext & AudioSystem::get_context() {
-	if (this->context.empty())
-		this->context.set<SoundContext>();
+	if (this->context.empty()) this->context.set<SoundContext>();
 	return this->context.get<SoundContext>();
 }
-
diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h
index a004c60..c941470 100644
--- a/src/crepe/system/AudioSystem.h
+++ b/src/crepe/system/AudioSystem.h
@@ -1,8 +1,8 @@
 #pragma once
 
-#include "../facade/SoundContext.h"
-#include "../facade/Sound.h"
 #include "../api/AudioSource.h"
+#include "../facade/Sound.h"
+#include "../facade/SoundContext.h"
 
 #include "System.h"
 
@@ -37,9 +37,9 @@ private:
 
 protected:
 	virtual SoundContext & get_context();
+
 private:
 	Private context;
 };
 
 } // namespace crepe
-
diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp
index eba9dfa..bebcf3d 100644
--- a/src/crepe/system/PhysicsSystem.cpp
+++ b/src/crepe/system/PhysicsSystem.cpp
@@ -1,10 +1,10 @@
 #include <cmath>
 
-#include "../manager/ComponentManager.h"
 #include "../api/Config.h"
 #include "../api/Rigidbody.h"
 #include "../api/Transform.h"
 #include "../api/Vector2.h"
+#include "../manager/ComponentManager.h"
 
 #include "PhysicsSystem.h"
 
diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp
index 4e97b3e..0ad685c 100644
--- a/src/crepe/system/RenderSystem.cpp
+++ b/src/crepe/system/RenderSystem.cpp
@@ -5,12 +5,12 @@
 #include <stdexcept>
 #include <vector>
 
-#include "../manager/ComponentManager.h"
 #include "../api/Camera.h"
 #include "../api/ParticleEmitter.h"
 #include "../api/Sprite.h"
 #include "../api/Transform.h"
 #include "../facade/SDLContext.h"
+#include "../manager/ComponentManager.h"
 
 #include "RenderSystem.h"
 
diff --git a/src/crepe/system/ScriptSystem.cpp b/src/crepe/system/ScriptSystem.cpp
index 2e16eb0..d6b2ca1 100644
--- a/src/crepe/system/ScriptSystem.cpp
+++ b/src/crepe/system/ScriptSystem.cpp
@@ -1,6 +1,6 @@
-#include "../manager/ComponentManager.h"
 #include "../api/BehaviorScript.h"
 #include "../api/Script.h"
+#include "../manager/ComponentManager.h"
 
 #include "ScriptSystem.h"
 
diff --git a/src/crepe/util/Private.cpp b/src/crepe/util/Private.cpp
index cb4cb5b..262620d 100644
--- a/src/crepe/util/Private.cpp
+++ b/src/crepe/util/Private.cpp
@@ -2,18 +2,14 @@
 
 using namespace crepe;
 
-bool Private::empty() const noexcept {
-	return this->instance == nullptr;
-}
+bool Private::empty() const noexcept { return this->instance == nullptr; }
 
 Private::~Private() {
 	if (this->instance == nullptr) return;
 	this->destructor(this->instance);
 }
 
-Private::Private(Private && other) {
-	*this = std::move(other);
-}
+Private::Private(Private && other) { *this = std::move(other); }
 
 Private & Private::operator=(Private && other) {
 	// TODO: ideally this function checks for self-assignment
@@ -22,13 +18,10 @@ Private & Private::operator=(Private && other) {
 	this->type = other.type;
 
 	other.instance = nullptr;
-	other.destructor = [](void*){};
-
-	return *this;
-}
+	other.destructor = [](void *) {};
 
-Private::Private(const Private & other) { }
-Private & Private::operator=(const Private & other) {
 	return *this;
 }
 
+Private::Private(const Private & other) {}
+Private & Private::operator=(const Private & other) { return *this; }
diff --git a/src/crepe/util/Private.h b/src/crepe/util/Private.h
index 6dd28bb..62a2e1a 100644
--- a/src/crepe/util/Private.h
+++ b/src/crepe/util/Private.h
@@ -1,7 +1,7 @@
 #pragma once
 
-#include <typeindex>
 #include <functional>
+#include <typeindex>
 
 namespace crepe {
 
@@ -28,7 +28,6 @@ private:
 	void * instance = nullptr;
 };
 
-}
+} // namespace crepe
 
 #include "Private.hpp"
-
diff --git a/src/crepe/util/Private.hpp b/src/crepe/util/Private.hpp
index d6ab23f..3a87a9f 100644
--- a/src/crepe/util/Private.hpp
+++ b/src/crepe/util/Private.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
-#include <stdexcept>
 #include <format>
+#include <stdexcept>
 
 #include "Private.h"
 
@@ -11,10 +11,8 @@ template <typename T, typename... Args>
 T & Private::set(Args &&... args) {
 	if (!this->empty()) this->destructor(this->instance);
 	T * instance = new T(std::forward<Args>(args)...);
-	this->instance = static_cast<void*>(instance);
-	this->destructor = [](void * instance) {
-		delete static_cast<T*>(instance);
-	};
+	this->instance = static_cast<void *>(instance);
+	this->destructor = [](void * instance) { delete static_cast<T *>(instance); };
 	this->type = typeid(T);
 	return *instance;
 }
@@ -22,12 +20,12 @@ T & Private::set(Args &&... args) {
 template <typename T>
 T & Private::get() {
 	using namespace std;
-	if (this->empty())
-		throw out_of_range("Private: get() called on empty object");
+	if (this->empty()) throw out_of_range("Private: get() called on empty object");
 	type_index requested_type = typeid(T);
 	if (this->type != requested_type)
-		throw logic_error(format("Private: get() called with [T = {}] (actual is [T = {}])", requested_type.name(), this->type.name()));
-	return *static_cast<T*>(this->instance);
+		throw logic_error(format("Private: get() called with [T = {}] (actual is [T = {}])",
+								 requested_type.name(), this->type.name()));
+	return *static_cast<T *>(this->instance);
 }
 
-}
+} // namespace crepe
diff --git a/src/test/AudioTest.cpp b/src/test/AudioTest.cpp
index 9c3cb9c..14f57bd 100644
--- a/src/test/AudioTest.cpp
+++ b/src/test/AudioTest.cpp
@@ -1,10 +1,10 @@
-#include <gtest/gtest.h>
 #include <gmock/gmock.h>
+#include <gtest/gtest.h>
 
-#include <crepe/manager/ComponentManager.h>
-#include <crepe/manager/ResourceManager.h>
 #include <crepe/api/AudioSource.h>
 #include <crepe/api/GameObject.h>
+#include <crepe/manager/ComponentManager.h>
+#include <crepe/manager/ResourceManager.h>
 #include <crepe/system/AudioSystem.h>
 
 using namespace std;
@@ -26,21 +26,21 @@ private:
 	public:
 		using AudioSystem::AudioSystem;
 		StrictMock<TestSoundContext> context;
-		virtual SoundContext & get_context() {
-			return this->context;
-		}
+		virtual SoundContext & get_context() { return this->context; }
 	};
 
 private:
 	Mediator mediator;
 	ComponentManager component_manager{mediator};
 	ResourceManager resource_manager{mediator};
+
 public:
-	TestAudioSystem system {mediator};
+	TestAudioSystem system{mediator};
 	TestSoundContext & context = system.context;
 
 private:
 	GameObject entity = component_manager.new_object("name");
+
 public:
 	AudioSource & component = entity.add_component<AudioSource>("mwe/audio/bgm.ogg");
 };
@@ -150,4 +150,3 @@ TEST_F(AudioTest, PlayOnActive) {
 		system.update();
 	}
 }
-
diff --git a/src/test/ECSTest.cpp b/src/test/ECSTest.cpp
index 22c4fe7..3e6c61c 100644
--- a/src/test/ECSTest.cpp
+++ b/src/test/ECSTest.cpp
@@ -2,17 +2,18 @@
 
 #define protected public
 
-#include <crepe/manager/ComponentManager.h>
 #include <crepe/api/GameObject.h>
 #include <crepe/api/Metadata.h>
 #include <crepe/api/Transform.h>
 #include <crepe/api/Vector2.h>
+#include <crepe/manager/ComponentManager.h>
 
 using namespace std;
 using namespace crepe;
 
 class ECSTest : public ::testing::Test {
 	Mediator m;
+
 public:
 	ComponentManager mgr{m};
 };
diff --git a/src/test/EventTest.cpp b/src/test/EventTest.cpp
index 350dd07..dccd554 100644
--- a/src/test/EventTest.cpp
+++ b/src/test/EventTest.cpp
@@ -2,9 +2,9 @@
 #include <gtest/gtest.h>
 
 #include <crepe/api/Event.h>
-#include <crepe/manager/EventManager.h>
 #include <crepe/api/IKeyListener.h>
 #include <crepe/api/IMouseListener.h>
+#include <crepe/manager/EventManager.h>
 
 using namespace std;
 using namespace std::chrono_literals;
@@ -37,9 +37,7 @@ public:
 };
 
 TEST_F(EventManagerTest, EventSubscription) {
-	EventHandler<KeyPressEvent> key_handler = [](const KeyPressEvent & e) {
-		return true;
-	};
+	EventHandler<KeyPressEvent> key_handler = [](const KeyPressEvent & e) { return true; };
 
 	// Subscribe to KeyPressEvent
 	EventManager::get_instance().subscribe<KeyPressEvent>(key_handler, 1);
diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp
index 4e9fa4e..a659fe5 100644
--- a/src/test/ParticleTest.cpp
+++ b/src/test/ParticleTest.cpp
@@ -1,4 +1,3 @@
-#include <crepe/manager/ComponentManager.h>
 #include <crepe/Particle.h>
 #include <crepe/api/Config.h>
 #include <crepe/api/GameObject.h>
@@ -7,6 +6,7 @@
 #include <crepe/api/Sprite.h>
 #include <crepe/api/Texture.h>
 #include <crepe/api/Transform.h>
+#include <crepe/manager/ComponentManager.h>
 #include <crepe/system/ParticleSystem.h>
 #include <gtest/gtest.h>
 #include <math.h>
@@ -17,6 +17,7 @@ using namespace crepe;
 
 class ParticlesTest : public ::testing::Test {
 	Mediator m;
+
 public:
 	ComponentManager component_manager{m};
 	ParticleSystem particle_system{m};
diff --git a/src/test/PhysicsTest.cpp b/src/test/PhysicsTest.cpp
index 01b7c51..43af8e4 100644
--- a/src/test/PhysicsTest.cpp
+++ b/src/test/PhysicsTest.cpp
@@ -1,8 +1,8 @@
-#include <crepe/manager/ComponentManager.h>
 #include <crepe/api/Config.h>
 #include <crepe/api/GameObject.h>
 #include <crepe/api/Rigidbody.h>
 #include <crepe/api/Transform.h>
+#include <crepe/manager/ComponentManager.h>
 #include <crepe/system/PhysicsSystem.h>
 #include <gtest/gtest.h>
 
@@ -12,6 +12,7 @@ using namespace crepe;
 
 class PhysicsTest : public ::testing::Test {
 	Mediator m;
+
 public:
 	ComponentManager component_manager{m};
 	PhysicsSystem system{m};
diff --git a/src/test/PrivateTest.cpp b/src/test/PrivateTest.cpp
index 0ea67d6..454789e 100644
--- a/src/test/PrivateTest.cpp
+++ b/src/test/PrivateTest.cpp
@@ -27,9 +27,7 @@ unsigned PrivateTest::constructors;
 unsigned PrivateTest::destructors;
 
 TEST_F(PrivateTest, Empty) {
-	{
-		Private foo;
-	}
+	{ Private foo; }
 
 	EXPECT_EQ(PrivateTest::constructors, 0);
 	EXPECT_EQ(PrivateTest::destructors, 0);
@@ -155,4 +153,3 @@ TEST_F(PrivateTest, DoubleAssignment) {
 	EXPECT_EQ(PrivateTest::constructors, 2);
 	EXPECT_EQ(PrivateTest::destructors, 2);
 }
-
diff --git a/src/test/RenderSystemTest.cpp b/src/test/RenderSystemTest.cpp
index 3528e46..c105dcb 100644
--- a/src/test/RenderSystemTest.cpp
+++ b/src/test/RenderSystemTest.cpp
@@ -7,11 +7,11 @@
 #define protected public
 
 #include <crepe/api/Camera.h>
-#include <crepe/manager/ComponentManager.h>
 #include <crepe/api/Color.h>
 #include <crepe/api/GameObject.h>
 #include <crepe/api/Sprite.h>
 #include <crepe/api/Texture.h>
+#include <crepe/manager/ComponentManager.h>
 
 #include <crepe/system/RenderSystem.h>
 
@@ -21,6 +21,7 @@ using namespace testing;
 
 class RenderSystemTest : public Test {
 	Mediator m;
+
 public:
 	ComponentManager mgr{m};
 	RenderSystem sys{m};
diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp
index 1f56e23..b6be3c0 100644
--- a/src/test/ResourceManagerTest.cpp
+++ b/src/test/ResourceManagerTest.cpp
@@ -3,9 +3,9 @@
 #define private public
 #define protected public
 
-#include <crepe/util/Log.h>
-#include <crepe/manager/ResourceManager.h>
 #include <crepe/api/GameObject.h>
+#include <crepe/manager/ResourceManager.h>
+#include <crepe/util/Log.h>
 
 using namespace std;
 using namespace crepe;
@@ -13,6 +13,7 @@ using namespace testing;
 
 class ResourceManagerTest : public Test {
 	Mediator mediator;
+
 public:
 	ResourceManager resource_manager{mediator};
 
@@ -25,19 +26,15 @@ public:
 
 	public:
 		const unsigned instance;
-		TestResource(const Asset & src)
-			: Resource(src),
-				instance(this->instances++) { }
+		TestResource(const Asset & src) : Resource(src), instance(this->instances++) {}
 		~TestResource() { this->instances--; }
-		bool operator == (const TestResource & other) const {
+		bool operator==(const TestResource & other) const {
 			return this->instance == other.instance;
 		}
 	};
 
 private:
-	void SetUp() override {
-		TestResource::instances = 0;
-	}
+	void SetUp() override { TestResource::instances = 0; }
 };
 unsigned ResourceManagerTest::TestResource::instances = 0;
 
@@ -72,4 +69,3 @@ TEST_F(ResourceManagerTest, Persistent) {
 	resource_manager.clear_all();
 	EXPECT_EQ(TestResource::instances, 0);
 }
-
diff --git a/src/test/SceneManagerTest.cpp b/src/test/SceneManagerTest.cpp
index d027d89..9bb260c 100644
--- a/src/test/SceneManagerTest.cpp
+++ b/src/test/SceneManagerTest.cpp
@@ -1,13 +1,13 @@
 #include <gtest/gtest.h>
 
-#include <crepe/types.h>
-#include <crepe/manager/SceneManager.h>
-#include <crepe/manager/ComponentManager.h>
 #include <crepe/api/GameObject.h>
 #include <crepe/api/Metadata.h>
 #include <crepe/api/Scene.h>
 #include <crepe/api/Transform.h>
 #include <crepe/api/Vector2.h>
+#include <crepe/manager/ComponentManager.h>
+#include <crepe/manager/SceneManager.h>
+#include <crepe/types.h>
 
 using namespace std;
 using namespace crepe;
@@ -57,6 +57,7 @@ private:
 
 class SceneManagerTest : public ::testing::Test {
 	Mediator m;
+
 public:
 	ComponentManager component_mgr{m};
 	SceneManager scene_mgr{m};
diff --git a/src/test/ScriptEventTest.cpp b/src/test/ScriptEventTest.cpp
index 7a9abbb..5da31e7 100644
--- a/src/test/ScriptEventTest.cpp
+++ b/src/test/ScriptEventTest.cpp
@@ -4,13 +4,13 @@
 #define private public
 #define protected public
 
-#include <crepe/manager/ComponentManager.h>
-#include <crepe/manager/EventManager.h>
 #include <crepe/api/BehaviorScript.h>
 #include <crepe/api/Event.h>
 #include <crepe/api/GameObject.h>
 #include <crepe/api/Script.h>
 #include <crepe/api/Vector2.h>
+#include <crepe/manager/ComponentManager.h>
+#include <crepe/manager/EventManager.h>
 #include <crepe/system/ScriptSystem.h>
 
 #include "ScriptTest.h"
@@ -32,7 +32,7 @@ TEST_F(ScriptEventTest, Inactive) {
 	EventManager & evmgr = this->event_manager;
 
 	unsigned event_count = 0;
-	script.subscribe<MyEvent>([&](const MyEvent &){
+	script.subscribe<MyEvent>([&](const MyEvent &) {
 		event_count++;
 		return true;
 	});
@@ -48,4 +48,3 @@ TEST_F(ScriptEventTest, Inactive) {
 	evmgr.trigger_event<MyEvent>();
 	EXPECT_EQ(1, event_count);
 }
-
diff --git a/src/test/ScriptSceneTest.cpp b/src/test/ScriptSceneTest.cpp
index f96ae8b..9ee1e52 100644
--- a/src/test/ScriptSceneTest.cpp
+++ b/src/test/ScriptSceneTest.cpp
@@ -4,8 +4,8 @@
 #define private public
 #define protected public
 
-#include <crepe/manager/SceneManager.h>
 #include "ScriptTest.h"
+#include <crepe/manager/SceneManager.h>
 
 using namespace std;
 using namespace crepe;
@@ -28,4 +28,3 @@ TEST_F(ScriptSceneTest, Inactive) {
 	script.set_next_scene(non_default_value);
 	EXPECT_EQ(non_default_value, scene_manager.next_scene);
 }
-
diff --git a/src/test/ScriptTest.cpp b/src/test/ScriptTest.cpp
index 6d0d5fb..1d2d6dd 100644
--- a/src/test/ScriptTest.cpp
+++ b/src/test/ScriptTest.cpp
@@ -1,5 +1,5 @@
-#include <gtest/gtest.h>
 #include <gmock/gmock.h>
+#include <gtest/gtest.h>
 
 // stupid hack to allow access to private/protected members under test
 #define private public
@@ -75,4 +75,3 @@ TEST_F(ScriptTest, UpdateInactive) {
 		system.update();
 	}
 }
-
diff --git a/src/test/ScriptTest.h b/src/test/ScriptTest.h
index 9a71ba7..1bbfdd3 100644
--- a/src/test/ScriptTest.h
+++ b/src/test/ScriptTest.h
@@ -1,22 +1,24 @@
 #pragma once
 
-#include <gtest/gtest.h>
 #include <gmock/gmock.h>
+#include <gtest/gtest.h>
 
-#include <crepe/manager/ComponentManager.h>
-#include <crepe/system/ScriptSystem.h>
 #include <crepe/api/BehaviorScript.h>
 #include <crepe/api/Script.h>
+#include <crepe/manager/ComponentManager.h>
+#include <crepe/system/ScriptSystem.h>
 
 class ScriptTest : public testing::Test {
 protected:
 	crepe::Mediator mediator;
+
 public:
 	crepe::ComponentManager component_manager{mediator};
 	crepe::ScriptSystem system{mediator};
 
 	class MyScript : public crepe::Script {
 		// NOTE: explicitly stating `public:` is not required on actual scripts
+
 	public:
 		MOCK_METHOD(void, init, (), (override));
 		MOCK_METHOD(void, update, (), (override));
diff --git a/src/test/main.cpp b/src/test/main.cpp
index 54f74fd..ed2aed5 100644
--- a/src/test/main.cpp
+++ b/src/test/main.cpp
@@ -1,5 +1,5 @@
-#include <gtest/gtest.h>
 #include <crepe/api/Config.h>
+#include <gtest/gtest.h>
 
 using namespace crepe;
 using namespace testing;
@@ -26,4 +26,3 @@ int main(int argc, char ** argv) {
 
 	return RUN_ALL_TESTS();
 }
-
-- 
cgit v1.2.3


From b8194e02679dc88f5c0a240da83a4700ec5200cf Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Sat, 30 Nov 2024 17:27:16 +0100
Subject: add doxygen comments + clean up

---
 src/crepe/Resource.h                | 18 +++++------
 src/crepe/api/AudioSource.h         |  9 ++++--
 src/crepe/facade/Sound.cpp          | 34 ---------------------
 src/crepe/facade/Sound.h            |  9 ++++--
 src/crepe/facade/SoundContext.cpp   |  7 ++---
 src/crepe/facade/SoundContext.h     | 48 +++++++++++++++++++++++++----
 src/crepe/manager/ResourceManager.h | 44 ++++++++++++++++++++++-----
 src/crepe/system/AudioSystem.cpp    |  4 +--
 src/crepe/system/AudioSystem.h      | 29 ++++++++++++++++--
 src/crepe/util/Private.h            | 60 +++++++++++++++++++++++++++++++++++--
 src/crepe/util/Private.hpp          |  2 +-
 src/test/AudioTest.cpp              | 16 +++++-----
 12 files changed, 197 insertions(+), 83 deletions(-)

(limited to 'src')

diff --git a/src/crepe/Resource.h b/src/crepe/Resource.h
index a0c8859..a2d65df 100644
--- a/src/crepe/Resource.h
+++ b/src/crepe/Resource.h
@@ -6,21 +6,19 @@ class ResourceManager;
 class Asset;
 
 /**
- * Resource is an interface class used to represent a (deserialized) game
- * resource (e.g. textures, sounds).
+ * \brief Resource interface
+ *
+ * Resource is an interface class used to represent a (deserialized) game resource (e.g.
+ * textures, sounds). Resources are always created from \ref Asset "assets" by ResourceManager.
+ *
+ * The game programmer has the ability to use the ResourceManager to keep instances of concrete
+ * resources between scenes, preventing them from being reinstantiated during a scene
+ * transition.
  */
 class Resource {
 public:
 	Resource(const Asset & src);
 	virtual ~Resource() = default;
-
-private:
-	/**
-	 * The resource manager uses \c clone to create new instances of the concrete
-	 * resource class. This may be used to inherit references to classes that
-	 * would otherwise need to be implemented as singletons.
-	 */
-	friend class ResourceManager;
 };
 
 } // namespace crepe
diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h
index 63b4bc4..330e8e1 100644
--- a/src/crepe/api/AudioSource.h
+++ b/src/crepe/api/AudioSource.h
@@ -17,16 +17,19 @@ class AudioSource : public Component {
 	friend class AudioSystem;
 
 protected:
+	/**
+	 * \param source Sound sample to load
+	 */
 	AudioSource(game_object_id_t id, const Asset & source);
-	//! Only ComponentManager can create components
+	//! Only ComponentManager creates components
 	friend class ComponentManager;
 
 public:
-	// But std::unique_ptr needs to be able to destoy this component again
+	// std::unique_ptr needs to be able to destoy this component
 	virtual ~AudioSource() = default;
 
 public:
-	//! Start or resume this audio source
+	//! Start this audio source
 	void play(bool looping = false);
 	//! Stop this audio source
 	void stop();
diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp
index 33a0c47..ad50637 100644
--- a/src/crepe/facade/Sound.cpp
+++ b/src/crepe/facade/Sound.cpp
@@ -2,7 +2,6 @@
 #include "../util/Log.h"
 
 #include "Sound.h"
-#include "SoundContext.h"
 
 using namespace crepe;
 using namespace std;
@@ -12,36 +11,3 @@ Sound::Sound(const Asset & src) : Resource(src) {
 	dbg_trace();
 }
 Sound::~Sound() { dbg_trace(); }
-
-// void Sound::play(SoundContext & ctx) {
-// 	if (ctx.engine.getPause(this->handle)) {
-// 		// resume if paused
-// 		ctx.engine.setPause(this->handle, false);
-// 	} else {
-// 		// or start new sound
-// 		this->handle = ctx.engine.play(this->sample, this->volume);
-// 		ctx.engine.setLooping(this->handle, this->looping);
-// 	}
-// }
-//
-// void Sound::pause(SoundContext & ctx) {
-// 	if (ctx.engine.getPause(this->handle)) return;
-// 	ctx.engine.setPause(this->handle, true);
-// }
-//
-// void Sound::rewind(SoundContext & ctx) {
-// 	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
-// 	ctx.engine.seek(this->handle, 0);
-// }
-//
-// void Sound::set_volume(SoundContext & ctx, float volume) {
-// 	this->volume = volume;
-// 	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
-// 	ctx.engine.setVolume(this->handle, this->volume);
-// }
-//
-// void Sound::set_looping(SoundContext & ctx, bool looping) {
-// 	this->looping = looping;
-// 	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
-// 	ctx.engine.setLooping(this->handle, this->looping);
-// }
diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h
index 35bccdb..a78a2a7 100644
--- a/src/crepe/facade/Sound.h
+++ b/src/crepe/facade/Sound.h
@@ -12,21 +12,24 @@ class SoundContext;
 /**
  * \brief Sound resource facade
  *
- * This class is a wrapper around a \c SoLoud::Wav instance, which holds a
- * single sample. It is part of the sound facade.
+ * This class is a wrapper around a \c SoLoud::Wav instance, which holds a single sample. It is
+ * part of the sound facade.
  */
 class Sound : public Resource {
 public:
 	Sound(const Asset & src);
 	~Sound(); // dbg_trace
 
+	//! Voice handle wrapper
 	struct Handle {
+		//! Voice handle (soloud), used by SoundContext
 		SoLoud::handle handle;
 	};
 
 private:
+	//! Deserialized resource (soloud)
 	SoLoud::Wav sample;
-
+	//! SoundContext uses \c sample
 	friend class SoundContext;
 };
 
diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp
index 470b3cc..8bd7e74 100644
--- a/src/crepe/facade/SoundContext.cpp
+++ b/src/crepe/facade/SoundContext.cpp
@@ -17,17 +17,16 @@ SoundContext::~SoundContext() {
 
 Sound::Handle SoundContext::play(Sound & resource) {
 	return {
-		.handle = this->engine.play(resource.sample, this->default_volume),
+		.handle = this->engine.play(resource.sample, 1.0f),
 	};
 }
 
 void SoundContext::stop(Sound::Handle & handle) { this->engine.stop(handle.handle); }
 
-void SoundContext::set_volume(Sound & resource, Sound::Handle & handle, float volume) {
+void SoundContext::set_volume(Sound::Handle & handle, float volume) {
 	this->engine.setVolume(handle.handle, volume);
-	this->default_volume = volume;
 }
 
-void SoundContext::set_loop(Sound & resource, Sound::Handle & handle, bool loop) {
+void SoundContext::set_loop(Sound::Handle & handle, bool loop) {
 	this->engine.setLooping(handle.handle, loop);
 }
diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h
index c651cd5..3bc8be5 100644
--- a/src/crepe/facade/SoundContext.h
+++ b/src/crepe/facade/SoundContext.h
@@ -11,8 +11,8 @@ namespace crepe {
 /**
  * \brief Sound engine facade
  *
- * This class is a wrapper around a \c SoLoud::Soloud instance, which provides
- * the methods for playing \c Sound instances. It is part of the sound facade.
+ * This class is a wrapper around a \c SoLoud::Soloud instance, which provides the methods for
+ * playing \c Sound instances. It is part of the sound facade.
  */
 class SoundContext {
 public:
@@ -24,15 +24,51 @@ public:
 	SoundContext & operator=(const SoundContext &) = delete;
 	SoundContext & operator=(SoundContext &&) = delete;
 
+	/**
+	 * \brief Play a sample
+	 *
+	 * Plays a Sound from the beginning of the sample and returns a handle to control it later.
+	 *
+	 * \param resource Sound instance to play
+	 *
+	 * \returns Handle to control this voice
+	 */
 	virtual Sound::Handle play(Sound & resource);
-	virtual void stop(Sound::Handle &);
-	virtual void set_volume(Sound &, Sound::Handle &, float);
-	virtual void set_loop(Sound &, Sound::Handle &, bool);
+	/**
+	 * \brief Stop a voice immediately if it is still playing
+	 *
+	 * \note This function does nothing if the handle is invalid or if the sound is already
+	 * stopped / finished playing.
+	 *
+	 * \param handle Voice handle returned by SoundContext::play
+	 */
+	virtual void stop(Sound::Handle & handle);
+	/**
+	 * \brief Change the volume of a voice
+	 *
+	 * \note This function does nothing if the handle is invalid or if the sound is already
+	 * stopped / finished playing.
+	 *
+	 * \param handle Voice handle returned by SoundContext::play
+	 * \param volume New gain value (0=silent, 1=default)
+	 */
+	virtual void set_volume(Sound::Handle & handle, float volume);
+	/**
+	 * \brief Set the looping behavior of a voice
+	 *
+	 * \note This function does nothing if the handle is invalid or if the sound is already
+	 * stopped / finished playing.
+	 *
+	 * \param handle Voice handle returned by SoundContext::play
+	 * \param loop Looping behavior (false=oneshot, true=loop)
+	 */
+	virtual void set_loop(Sound::Handle & handle, bool loop);
 
 private:
+	//! Abstracted class
 	SoLoud::Soloud engine;
-	float default_volume = 1.0f;
 
+	//! Config reference
 	Config & config = Config::get_instance();
 };
 
diff --git a/src/crepe/manager/ResourceManager.h b/src/crepe/manager/ResourceManager.h
index e7e6abc..84b275d 100644
--- a/src/crepe/manager/ResourceManager.h
+++ b/src/crepe/manager/ResourceManager.h
@@ -11,13 +11,11 @@
 namespace crepe {
 
 /**
- * \brief The ResourceManager is responsible for storing and managing assets over
- * multiple scenes.
+ * \brief Owner of concrete Resource instances
  * 
- * The ResourceManager ensures that assets are loaded once and can be accessed
- * across different scenes. It caches assets to avoid reloading them every time
- * a scene is loaded. Assets are retained in memory until the ResourceManager is
- * destroyed, at which point the cached assets are cleared.
+ * ResourceManager caches concrete Resource instances per Asset. Concrete resources are
+ * destroyed at the end of scenes by default, unless the game programmer marks them as
+ * persistent.
  */
 class ResourceManager : public Manager {
 public:
@@ -25,21 +23,53 @@ public:
 	virtual ~ResourceManager(); // dbg_trace
 
 private:
+	//! Cache entry
 	struct CacheEntry {
+		//! Concrete resource instance
 		std::unique_ptr<Resource> resource = nullptr;
+		//! Prevent ResourceManager::clear from removing this entry
 		bool persistent = false;
 	};
-	//! A cache that holds all the assets, accessible by their file path, over multiple scenes.
+	//! Internal cache
 	std::unordered_map<const Asset, CacheEntry> resources;
+	/**
+	 * \brief Ensure a cache entry exists for this asset and return a mutable reference to it
+	 *
+	 * \param asset Asset the concrete resource is instantiated from
+	 *
+	 * \returns Mutable reference to cache entry
+	 */
 	CacheEntry & get_entry(const Asset & asset);
 
 public:
+	/**
+	 * \brief Mark a resource as persistent (i.e. used across multiple scenes)
+	 *
+	 * \param asset Asset the concrete resource is instantiated from
+	 * \param persistent Whether this resource is persistent (true=keep, false=destroy)
+	 */
 	void set_persistent(const Asset & asset, bool persistent);
 
+	/**
+	 * \brief Retrieve reference to concrete Resource by Asset
+	 *
+	 * \param asset Asset the concrete resource is instantiated from
+	 * \tparam Resource Concrete derivative of Resource
+	 *
+	 * This class instantiates the concrete resource if it is not yet stored in the internal
+	 * cache, or returns a reference to the cached resource if it already exists.
+	 *
+	 * \returns Reference to concrete resource
+	 *
+	 * \throws std::runtime_error if the \c Resource parameter does not match with the actual
+	 * type of the resource stored in the cache for this Asset
+	 */
 	template <typename Resource>
 	Resource & get(const Asset & asset);
 
+	//! Clear non-persistent resources from cache
 	void clear();
+	//! Clear all resources from cache regardless of persistence
 	void clear_all();
 };
 
diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
index 0696b34..26913c0 100644
--- a/src/crepe/system/AudioSystem.cpp
+++ b/src/crepe/system/AudioSystem.cpp
@@ -52,10 +52,10 @@ void AudioSystem::diff_update(AudioSource & component, ComponentPrivate & data,
 		component.oneshot_stop = false;
 	}
 	if (component.volume != data.last_volume) {
-		context.set_volume(resource, data.handle, component.volume);
+		context.set_volume(data.handle, component.volume);
 	}
 	if (component.loop != data.last_loop) {
-		context.set_loop(resource, data.handle, component.loop);
+		context.set_loop(data.handle, component.loop);
 	}
 }
 
diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h
index c941470..4d21883 100644
--- a/src/crepe/system/AudioSystem.h
+++ b/src/crepe/system/AudioSystem.h
@@ -14,9 +14,7 @@ public:
 	void update() override;
 
 private:
-	/**
-	 * \brief Private data stored by AudioSystem on AudioSource component
-	 */
+	//! Private data stored by AudioSystem on AudioSource component
 	struct ComponentPrivate {
 		//! This sample's voice handle
 		Sound::Handle handle;
@@ -31,14 +29,39 @@ private:
 		//! \}
 	};
 
+	/**
+	 * \brief Update `last_*` members of \c data
+	 *
+	 * Copies all component properties stored for comparison between AudioSystem::update() calls
+	 *
+	 * \param component Source properties
+	 * \param data Destination properties
+	 */
 	void update_last(const AudioSource & component, ComponentPrivate & data);
 
+	/**
+	 * \brief Compare update component
+	 *
+	 * Compares properties of \c component and \c data, and calls SoundContext functions where
+	 * applicable.
+	 *
+	 * \param component AudioSource component to update
+	 * \param data AudioSource's private data
+	 * \param resource Sound instance for AudioSource's Asset
+	 */
 	void diff_update(AudioSource & component, ComponentPrivate & data, Sound & resource);
 
 protected:
+	/**
+	 * \brief Get SoundContext
+	 *
+	 * SoundContext is retrieved through this function instead of being a direct member of
+	 * AudioSystem to aid with testability.
+	 */
 	virtual SoundContext & get_context();
 
 private:
+	//! Actually stores SoundContext if the base AudioSystem::get_context implementation is used
 	Private context;
 };
 
diff --git a/src/crepe/util/Private.h b/src/crepe/util/Private.h
index 62a2e1a..d725a5e 100644
--- a/src/crepe/util/Private.h
+++ b/src/crepe/util/Private.h
@@ -5,26 +5,82 @@
 
 namespace crepe {
 
+/**
+ * \brief Utility for storing type hidden from user
+ *
+ * This class can be used to store types which cannot be used in the API directly due to header
+ * distribution limitations. This class is similar to `std::any`, but provides a method for
+ * retrieving a mutable reference to the stored object.
+ */
 class Private {
 public:
 	Private() = default;
 	~Private();
+	/**
+	 * \name Copy
+	 *
+	 * \note These functions do not do anything, resulting in `*this` being an empty (default)
+	 * instance.
+	 *
+	 * \{
+	 */
 	Private(const Private &);
-	Private(Private &&);
 	Private & operator=(const Private &);
+	//! \}
+	/**
+	 * \name Move
+	 *
+	 * These functions actually move the stored type if present.
+	 *
+	 * \{
+	 */
+	Private(Private &&);
 	Private & operator=(Private &&);
+	//! \}
 
+	/**
+	 * \brief Get the stored object
+	 *
+	 * \tparam T Type of stored object
+	 *
+	 * \returns Mutable reference to stored object
+	 *
+	 * \throws std::out_of_range if this instance does not contain any object
+	 * \throws std::logic_error if the stored type and requested type differ
+	 */
 	template <typename T>
-	T & get();
+	T & get() const;
 
+	/**
+	 * \brief Create and store an arbitrary object
+	 *
+	 * \tparam T Type of object
+	 * \tparam Args Perfect forwarding arguments
+	 * \param args Perfect forwarding arguments
+	 *
+	 * All arguments to this function are forwarded using `std::forward` to the constructor of T.
+	 *
+	 * \returns Mutable reference to stored object
+	 *
+	 * \note If this instance already contained an object, this function implicitly destroys the
+	 * previous object.
+	 */
 	template <typename T, typename... Args>
 	T & set(Args &&... args);
 
+	/**
+	 * \brief Check if this instance contains an object
+	 *
+	 * \returns `true` if this instance is empty, `false` if it contains an object
+	 */
 	bool empty() const noexcept;
 
 private:
+	//! Wrapper for destructor call of stored object type
 	std::function<void(void *)> destructor;
+	//! Stored object's type
 	std::type_index type = typeid(void);
+	//! Stored object
 	void * instance = nullptr;
 };
 
diff --git a/src/crepe/util/Private.hpp b/src/crepe/util/Private.hpp
index 3a87a9f..b2174c0 100644
--- a/src/crepe/util/Private.hpp
+++ b/src/crepe/util/Private.hpp
@@ -18,7 +18,7 @@ T & Private::set(Args &&... args) {
 }
 
 template <typename T>
-T & Private::get() {
+T & Private::get() const {
 	using namespace std;
 	if (this->empty()) throw out_of_range("Private: get() called on empty object");
 	type_index requested_type = typeid(T);
diff --git a/src/test/AudioTest.cpp b/src/test/AudioTest.cpp
index 14f57bd..7644ab7 100644
--- a/src/test/AudioTest.cpp
+++ b/src/test/AudioTest.cpp
@@ -18,8 +18,8 @@ private:
 	public:
 		MOCK_METHOD(Sound::Handle, play, (Sound & resource), (override));
 		MOCK_METHOD(void, stop, (Sound::Handle &), (override));
-		MOCK_METHOD(void, set_volume, (Sound &, Sound::Handle &, float), (override));
-		MOCK_METHOD(void, set_loop, (Sound &, Sound::Handle &, bool), (override));
+		MOCK_METHOD(void, set_volume, (Sound::Handle &, float), (override));
+		MOCK_METHOD(void, set_loop, (Sound::Handle &, bool), (override));
 	};
 
 	class TestAudioSystem : public AudioSystem {
@@ -48,8 +48,8 @@ public:
 TEST_F(AudioTest, Default) {
 	EXPECT_CALL(context, play(_)).Times(0);
 	EXPECT_CALL(context, stop(_)).Times(0);
-	EXPECT_CALL(context, set_volume(_, _, _)).Times(0);
-	EXPECT_CALL(context, set_loop(_, _, _)).Times(0);
+	EXPECT_CALL(context, set_volume(_, _)).Times(0);
+	EXPECT_CALL(context, set_loop(_, _)).Times(0);
 	system.update();
 }
 
@@ -95,14 +95,14 @@ TEST_F(AudioTest, Volume) {
 	{
 		InSequence seq;
 
-		EXPECT_CALL(context, set_volume(_, _, _)).Times(0);
+		EXPECT_CALL(context, set_volume(_, _)).Times(0);
 		component.volume += 0.2;
 	}
 
 	{
 		InSequence seq;
 
-		EXPECT_CALL(context, set_volume(_, _, component.volume)).Times(1);
+		EXPECT_CALL(context, set_volume(_, component.volume)).Times(1);
 		system.update();
 	}
 }
@@ -113,14 +113,14 @@ TEST_F(AudioTest, Looping) {
 	{
 		InSequence seq;
 
-		EXPECT_CALL(context, set_loop(_, _, _)).Times(0);
+		EXPECT_CALL(context, set_loop(_, _)).Times(0);
 		component.loop = !component.loop;
 	}
 
 	{
 		InSequence seq;
 
-		EXPECT_CALL(context, set_loop(_, _, component.loop)).Times(1);
+		EXPECT_CALL(context, set_loop(_, component.loop)).Times(1);
 		system.update();
 	}
 }
-- 
cgit v1.2.3


From 7a8657dfe019104aced61a5b63e63f61ad919f7a Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Mon, 2 Dec 2024 16:13:08 +0100
Subject: remove `Private`

---
 src/crepe/api/AudioSource.h       |  15 +++-
 src/crepe/facade/Sound.h          |   6 --
 src/crepe/facade/SoundContext.cpp |  23 +++---
 src/crepe/facade/SoundContext.h   |  15 +++-
 src/crepe/facade/SoundHandle.h    |  13 ++++
 src/crepe/system/AudioSystem.cpp  |  44 +++++------
 src/crepe/system/AudioSystem.h    |  29 ++-----
 src/crepe/util/CMakeLists.txt     |   3 -
 src/crepe/util/Private.cpp        |  27 -------
 src/crepe/util/Private.h          |  89 ----------------------
 src/crepe/util/Private.hpp        |  31 --------
 src/test/AudioTest.cpp            |   8 +-
 src/test/CMakeLists.txt           |   1 -
 src/test/PrivateTest.cpp          | 155 --------------------------------------
 14 files changed, 78 insertions(+), 381 deletions(-)
 create mode 100644 src/crepe/facade/SoundHandle.h
 delete mode 100644 src/crepe/util/Private.cpp
 delete mode 100644 src/crepe/util/Private.h
 delete mode 100644 src/crepe/util/Private.hpp
 delete mode 100644 src/test/PrivateTest.cpp

(limited to 'src')

diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h
index 330e8e1..7c1f161 100644
--- a/src/crepe/api/AudioSource.h
+++ b/src/crepe/api/AudioSource.h
@@ -2,7 +2,7 @@
 
 #include "../Component.h"
 #include "../types.h"
-#include "../util/Private.h"
+#include "../facade/SoundHandle.h"
 
 #include "Asset.h"
 #include "GameObject.h"
@@ -59,10 +59,17 @@ private:
 	//! Stop this sample
 	bool oneshot_stop = false;
 	//! \}
+	/**
+	 * \name State diffing variables
+	 * \{
+	 */
+	typeof(active) last_active = false;
+	typeof(volume) last_volume = volume;
+	typeof(loop) last_loop = loop;
+	//! \}
+	//! This source's voice handle
+	SoundHandle voice{};
 
-private:
-	//! AudioSystem::ComponentPrivate
-	Private private_data;
 };
 
 } // namespace crepe
diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h
index a78a2a7..85d141b 100644
--- a/src/crepe/facade/Sound.h
+++ b/src/crepe/facade/Sound.h
@@ -20,12 +20,6 @@ public:
 	Sound(const Asset & src);
 	~Sound(); // dbg_trace
 
-	//! Voice handle wrapper
-	struct Handle {
-		//! Voice handle (soloud), used by SoundContext
-		SoLoud::handle handle;
-	};
-
 private:
 	//! Deserialized resource (soloud)
 	SoLoud::Wav sample;
diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp
index 8bd7e74..d18afc6 100644
--- a/src/crepe/facade/SoundContext.cpp
+++ b/src/crepe/facade/SoundContext.cpp
@@ -15,18 +15,23 @@ SoundContext::~SoundContext() {
 	this->engine.deinit();
 }
 
-Sound::Handle SoundContext::play(Sound & resource) {
-	return {
-		.handle = this->engine.play(resource.sample, 1.0f),
-	};
+SoundHandle SoundContext::play(Sound & resource) {
+	SoLoud::handle real_handle = this->engine.play(resource.sample, 1.0f);
+	SoundHandle handle = this->next_handle;
+	this->registry[handle] = real_handle;
+	this->next_handle++;
+	return handle;
 }
 
-void SoundContext::stop(Sound::Handle & handle) { this->engine.stop(handle.handle); }
+void SoundContext::stop(const SoundHandle & handle) {
+	this->engine.stop(this->registry[handle]);
+}
 
-void SoundContext::set_volume(Sound::Handle & handle, float volume) {
-	this->engine.setVolume(handle.handle, volume);
+void SoundContext::set_volume(const SoundHandle & handle, float volume) {
+	this->engine.setVolume(this->registry[handle], volume);
 }
 
-void SoundContext::set_loop(Sound::Handle & handle, bool loop) {
-	this->engine.setLooping(handle.handle, loop);
+void SoundContext::set_loop(const SoundHandle & handle, bool loop) {
+	this->engine.setLooping(this->registry[handle], loop);
 }
+
diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h
index 3bc8be5..102f928 100644
--- a/src/crepe/facade/SoundContext.h
+++ b/src/crepe/facade/SoundContext.h
@@ -4,6 +4,7 @@
 
 #include "../api/Config.h"
 
+#include "SoundHandle.h"
 #include "Sound.h"
 
 namespace crepe {
@@ -33,7 +34,7 @@ public:
 	 *
 	 * \returns Handle to control this voice
 	 */
-	virtual Sound::Handle play(Sound & resource);
+	virtual SoundHandle play(Sound & resource);
 	/**
 	 * \brief Stop a voice immediately if it is still playing
 	 *
@@ -42,7 +43,7 @@ public:
 	 *
 	 * \param handle Voice handle returned by SoundContext::play
 	 */
-	virtual void stop(Sound::Handle & handle);
+	virtual void stop(const SoundHandle & handle);
 	/**
 	 * \brief Change the volume of a voice
 	 *
@@ -52,7 +53,7 @@ public:
 	 * \param handle Voice handle returned by SoundContext::play
 	 * \param volume New gain value (0=silent, 1=default)
 	 */
-	virtual void set_volume(Sound::Handle & handle, float volume);
+	virtual void set_volume(const SoundHandle & handle, float volume);
 	/**
 	 * \brief Set the looping behavior of a voice
 	 *
@@ -62,7 +63,7 @@ public:
 	 * \param handle Voice handle returned by SoundContext::play
 	 * \param loop Looping behavior (false=oneshot, true=loop)
 	 */
-	virtual void set_loop(Sound::Handle & handle, bool loop);
+	virtual void set_loop(const SoundHandle & handle, bool loop);
 
 private:
 	//! Abstracted class
@@ -70,6 +71,12 @@ private:
 
 	//! Config reference
 	Config & config = Config::get_instance();
+
+	//! Sound handle registry
+	std::unordered_map<SoundHandle, SoLoud::handle> registry;
+	//! Unique handle counter
+	SoundHandle next_handle = 0;
+
 };
 
 } // namespace crepe
diff --git a/src/crepe/facade/SoundHandle.h b/src/crepe/facade/SoundHandle.h
new file mode 100644
index 0000000..131d28c
--- /dev/null
+++ b/src/crepe/facade/SoundHandle.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <cstddef>
+
+namespace crepe {
+
+/**
+ * \brief Voice handle returned by
+ */
+typedef size_t SoundHandle;
+
+}
+
diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
index 26913c0..c1cde8b 100644
--- a/src/crepe/system/AudioSystem.cpp
+++ b/src/crepe/system/AudioSystem.cpp
@@ -16,56 +16,50 @@ void AudioSystem::update() {
 	for (AudioSource & component : components) {
 		Sound & resource = resource_manager.get<Sound>(component.source);
 
-		if (component.private_data.empty()) {
-			auto & data = component.private_data.set<ComponentPrivate>();
-			this->update_last(component, data);
-			data.last_active = false;
-		}
-		auto & data = component.private_data.get<ComponentPrivate>();
-
-		this->diff_update(component, data, resource);
+		this->diff_update(component, resource);
 
-		this->update_last(component, data);
+		this->update_last(component);
 	}
 }
 
-void AudioSystem::diff_update(AudioSource & component, ComponentPrivate & data,
-							  Sound & resource) {
+void AudioSystem::diff_update(AudioSource & component, Sound & resource) {
 	SoundContext & context = this->get_context();
 
-	if (component.active != data.last_active) {
+	if (component.active != component.last_active) {
 		if (component.active) {
 			component.oneshot_play = component.play_on_awake;
 		} else {
-			context.stop(data.handle);
+			context.stop(component.voice);
 			return;
 		}
 	}
 	if (!component.active) return;
 
 	if (component.oneshot_play) {
-		data.handle = context.play(resource);
+		component.voice = context.play(resource);
 		component.oneshot_play = false;
 	}
 	if (component.oneshot_stop) {
-		context.stop(data.handle);
+		context.stop(component.voice);
 		component.oneshot_stop = false;
 	}
-	if (component.volume != data.last_volume) {
-		context.set_volume(data.handle, component.volume);
+	if (component.volume != component.last_volume) {
+		context.set_volume(component.voice, component.volume);
 	}
-	if (component.loop != data.last_loop) {
-		context.set_loop(data.handle, component.loop);
+	if (component.loop != component.last_loop) {
+		context.set_loop(component.voice, component.loop);
 	}
 }
 
-void AudioSystem::update_last(const AudioSource & component, ComponentPrivate & data) {
-	data.last_active = component.active;
-	data.last_loop = component.loop;
-	data.last_volume = component.volume;
+void AudioSystem::update_last(AudioSource & component) {
+	component.last_active = component.active;
+	component.last_loop = component.loop;
+	component.last_volume = component.volume;
 }
 
 SoundContext & AudioSystem::get_context() {
-	if (this->context.empty()) this->context.set<SoundContext>();
-	return this->context.get<SoundContext>();
+	if (this->context == nullptr)
+		this->context = make_unique<SoundContext>();
+	return *this->context.get();
 }
+
diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h
index 4d21883..2ddc443 100644
--- a/src/crepe/system/AudioSystem.h
+++ b/src/crepe/system/AudioSystem.h
@@ -14,30 +14,14 @@ public:
 	void update() override;
 
 private:
-	//! Private data stored by AudioSystem on AudioSource component
-	struct ComponentPrivate {
-		//! This sample's voice handle
-		Sound::Handle handle;
-
-		/**
-		 * \name State diffing variables
-		 * \{
-		 */
-		typeof(AudioSource::active) last_active;
-		typeof(AudioSource::volume) last_volume;
-		typeof(AudioSource::loop) last_loop;
-		//! \}
-	};
-
 	/**
-	 * \brief Update `last_*` members of \c data
+	 * \brief Update `last_*` members of \c component
 	 *
 	 * Copies all component properties stored for comparison between AudioSystem::update() calls
 	 *
-	 * \param component Source properties
-	 * \param data Destination properties
+	 * \param component AudioSource component to update
 	 */
-	void update_last(const AudioSource & component, ComponentPrivate & data);
+	void update_last(AudioSource & component);
 
 	/**
 	 * \brief Compare update component
@@ -46,10 +30,9 @@ private:
 	 * applicable.
 	 *
 	 * \param component AudioSource component to update
-	 * \param data AudioSource's private data
 	 * \param resource Sound instance for AudioSource's Asset
 	 */
-	void diff_update(AudioSource & component, ComponentPrivate & data, Sound & resource);
+	void diff_update(AudioSource & component, Sound & resource);
 
 protected:
 	/**
@@ -61,8 +44,8 @@ protected:
 	virtual SoundContext & get_context();
 
 private:
-	//! Actually stores SoundContext if the base AudioSystem::get_context implementation is used
-	Private context;
+	//! SoundContext
+	std::unique_ptr<SoundContext> context = nullptr;
 };
 
 } // namespace crepe
diff --git a/src/crepe/util/CMakeLists.txt b/src/crepe/util/CMakeLists.txt
index f49d851..94ed906 100644
--- a/src/crepe/util/CMakeLists.txt
+++ b/src/crepe/util/CMakeLists.txt
@@ -1,7 +1,6 @@
 target_sources(crepe PUBLIC
 	LogColor.cpp
 	Log.cpp
-	Private.cpp
 )
 
 target_sources(crepe PUBLIC FILE_SET HEADERS FILES
@@ -12,7 +11,5 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
 	Proxy.hpp
 	OptionalRef.h
 	OptionalRef.hpp
-	Private.h
-	Private.hpp
 )
 
diff --git a/src/crepe/util/Private.cpp b/src/crepe/util/Private.cpp
deleted file mode 100644
index 262620d..0000000
--- a/src/crepe/util/Private.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#include "Private.h"
-
-using namespace crepe;
-
-bool Private::empty() const noexcept { return this->instance == nullptr; }
-
-Private::~Private() {
-	if (this->instance == nullptr) return;
-	this->destructor(this->instance);
-}
-
-Private::Private(Private && other) { *this = std::move(other); }
-
-Private & Private::operator=(Private && other) {
-	// TODO: ideally this function checks for self-assignment
-	this->instance = other.instance;
-	this->destructor = other.destructor;
-	this->type = other.type;
-
-	other.instance = nullptr;
-	other.destructor = [](void *) {};
-
-	return *this;
-}
-
-Private::Private(const Private & other) {}
-Private & Private::operator=(const Private & other) { return *this; }
diff --git a/src/crepe/util/Private.h b/src/crepe/util/Private.h
deleted file mode 100644
index d725a5e..0000000
--- a/src/crepe/util/Private.h
+++ /dev/null
@@ -1,89 +0,0 @@
-#pragma once
-
-#include <functional>
-#include <typeindex>
-
-namespace crepe {
-
-/**
- * \brief Utility for storing type hidden from user
- *
- * This class can be used to store types which cannot be used in the API directly due to header
- * distribution limitations. This class is similar to `std::any`, but provides a method for
- * retrieving a mutable reference to the stored object.
- */
-class Private {
-public:
-	Private() = default;
-	~Private();
-	/**
-	 * \name Copy
-	 *
-	 * \note These functions do not do anything, resulting in `*this` being an empty (default)
-	 * instance.
-	 *
-	 * \{
-	 */
-	Private(const Private &);
-	Private & operator=(const Private &);
-	//! \}
-	/**
-	 * \name Move
-	 *
-	 * These functions actually move the stored type if present.
-	 *
-	 * \{
-	 */
-	Private(Private &&);
-	Private & operator=(Private &&);
-	//! \}
-
-	/**
-	 * \brief Get the stored object
-	 *
-	 * \tparam T Type of stored object
-	 *
-	 * \returns Mutable reference to stored object
-	 *
-	 * \throws std::out_of_range if this instance does not contain any object
-	 * \throws std::logic_error if the stored type and requested type differ
-	 */
-	template <typename T>
-	T & get() const;
-
-	/**
-	 * \brief Create and store an arbitrary object
-	 *
-	 * \tparam T Type of object
-	 * \tparam Args Perfect forwarding arguments
-	 * \param args Perfect forwarding arguments
-	 *
-	 * All arguments to this function are forwarded using `std::forward` to the constructor of T.
-	 *
-	 * \returns Mutable reference to stored object
-	 *
-	 * \note If this instance already contained an object, this function implicitly destroys the
-	 * previous object.
-	 */
-	template <typename T, typename... Args>
-	T & set(Args &&... args);
-
-	/**
-	 * \brief Check if this instance contains an object
-	 *
-	 * \returns `true` if this instance is empty, `false` if it contains an object
-	 */
-	bool empty() const noexcept;
-
-private:
-	//! Wrapper for destructor call of stored object type
-	std::function<void(void *)> destructor;
-	//! Stored object's type
-	std::type_index type = typeid(void);
-	//! Stored object
-	void * instance = nullptr;
-};
-
-} // namespace crepe
-
-#include "Private.hpp"
diff --git a/src/crepe/util/Private.hpp b/src/crepe/util/Private.hpp
deleted file mode 100644
index b2174c0..0000000
--- a/src/crepe/util/Private.hpp
+++ /dev/null
@@ -1,31 +0,0 @@
-#pragma once
-
-#include <format>
-#include <stdexcept>
-
-#include "Private.h"
-
-namespace crepe {
-
-template <typename T, typename... Args>
-T & Private::set(Args &&... args) {
-	if (!this->empty()) this->destructor(this->instance);
-	T * instance = new T(std::forward<Args>(args)...);
-	this->instance = static_cast<void *>(instance);
-	this->destructor = [](void * instance) { delete static_cast<T *>(instance); };
-	this->type = typeid(T);
-	return *instance;
-}
-
-template <typename T>
-T & Private::get() const {
-	using namespace std;
-	if (this->empty()) throw out_of_range("Private: get() called on empty object");
-	type_index requested_type = typeid(T);
-	if (this->type != requested_type)
-		throw logic_error(format("Private: get() called with [T = {}] (actual is [T = {}])",
-								 requested_type.name(), this->type.name()));
-	return *static_cast<T *>(this->instance);
-}
-
-} // namespace crepe
diff --git a/src/test/AudioTest.cpp b/src/test/AudioTest.cpp
index 7644ab7..774fdb8 100644
--- a/src/test/AudioTest.cpp
+++ b/src/test/AudioTest.cpp
@@ -16,10 +16,10 @@ class AudioTest : public Test {
 private:
 	class TestSoundContext : public SoundContext {
 	public:
-		MOCK_METHOD(Sound::Handle, play, (Sound & resource), (override));
-		MOCK_METHOD(void, stop, (Sound::Handle &), (override));
-		MOCK_METHOD(void, set_volume, (Sound::Handle &, float), (override));
-		MOCK_METHOD(void, set_loop, (Sound::Handle &, bool), (override));
+		MOCK_METHOD(SoundHandle, play, (Sound & resource), (override));
+		MOCK_METHOD(void, stop, (const SoundHandle &), (override));
+		MOCK_METHOD(void, set_volume, (const SoundHandle &, float), (override));
+		MOCK_METHOD(void, set_loop, (const SoundHandle &, bool), (override));
 	};
 
 	class TestAudioSystem : public AudioSystem {
diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt
index 8c4b855..4174926 100644
--- a/src/test/CMakeLists.txt
+++ b/src/test/CMakeLists.txt
@@ -16,5 +16,4 @@ target_sources(test_main PUBLIC
 	Vector2Test.cpp
 	ScriptEventTest.cpp
 	ScriptSceneTest.cpp
-	PrivateTest.cpp
 )
diff --git a/src/test/PrivateTest.cpp b/src/test/PrivateTest.cpp
deleted file mode 100644
index 454789e..0000000
--- a/src/test/PrivateTest.cpp
+++ /dev/null
@@ -1,155 +0,0 @@
-#include <gtest/gtest.h>
-
-#include <crepe/util/Private.h>
-
-using namespace std;
-using namespace crepe;
-using namespace testing;
-
-class PrivateTest : public Test {
-public:
-	static unsigned constructors;
-	static unsigned destructors;
-
-	void SetUp() override {
-		PrivateTest::constructors = 0;
-		PrivateTest::destructors = 0;
-	}
-
-	class TestClass {
-	public:
-		TestClass() { PrivateTest::constructors++; }
-		~TestClass() { PrivateTest::destructors++; }
-	};
-	class Unrelated {};
-};
-unsigned PrivateTest::constructors;
-unsigned PrivateTest::destructors;
-
-TEST_F(PrivateTest, Empty) {
-	{ Private foo; }
-
-	EXPECT_EQ(PrivateTest::constructors, 0);
-	EXPECT_EQ(PrivateTest::destructors, 0);
-}
-
-TEST_F(PrivateTest, WithObject) {
-	{
-		Private foo;
-		foo.set<TestClass>();
-
-		EXPECT_EQ(PrivateTest::constructors, 1);
-		EXPECT_EQ(PrivateTest::destructors, 0);
-	}
-
-	EXPECT_EQ(PrivateTest::constructors, 1);
-	EXPECT_EQ(PrivateTest::destructors, 1);
-}
-
-TEST_F(PrivateTest, EmptyException) {
-	Private foo;
-	EXPECT_THROW(foo.get<TestClass>(), std::out_of_range);
-
-	foo.set<TestClass>();
-	EXPECT_NO_THROW(foo.get<TestClass>());
-}
-
-TEST_F(PrivateTest, IncorrectTypeException) {
-	Private foo;
-	foo.set<TestClass>();
-
-	EXPECT_THROW(foo.get<Unrelated>(), std::logic_error);
-	EXPECT_NO_THROW(foo.get<TestClass>());
-}
-
-TEST_F(PrivateTest, MoveConstructor) {
-	{
-		Private foo;
-		foo.set<TestClass>();
-
-		EXPECT_EQ(PrivateTest::constructors, 1);
-		EXPECT_EQ(PrivateTest::destructors, 0);
-
-		Private bar(std::move(foo));
-
-		EXPECT_EQ(PrivateTest::constructors, 1);
-		EXPECT_EQ(PrivateTest::destructors, 0);
-	}
-
-	EXPECT_EQ(PrivateTest::constructors, 1);
-	EXPECT_EQ(PrivateTest::destructors, 1);
-}
-
-TEST_F(PrivateTest, MoveOperator) {
-	{
-		Private foo;
-		foo.set<TestClass>();
-
-		EXPECT_EQ(PrivateTest::constructors, 1);
-		EXPECT_EQ(PrivateTest::destructors, 0);
-
-		Private bar = std::move(foo);
-
-		EXPECT_EQ(PrivateTest::constructors, 1);
-		EXPECT_EQ(PrivateTest::destructors, 0);
-	}
-
-	EXPECT_EQ(PrivateTest::constructors, 1);
-	EXPECT_EQ(PrivateTest::destructors, 1);
-}
-
-TEST_F(PrivateTest, CopyConstructor) {
-	{
-		Private foo;
-		foo.set<TestClass>();
-
-		EXPECT_EQ(PrivateTest::constructors, 1);
-		EXPECT_EQ(PrivateTest::destructors, 0);
-
-		Private bar(foo);
-
-		EXPECT_TRUE(bar.empty());
-		EXPECT_EQ(PrivateTest::constructors, 1);
-		EXPECT_EQ(PrivateTest::destructors, 0);
-	}
-
-	EXPECT_EQ(PrivateTest::constructors, 1);
-	EXPECT_EQ(PrivateTest::destructors, 1);
-}
-
-TEST_F(PrivateTest, CopyOperator) {
-	{
-		Private foo;
-		foo.set<TestClass>();
-
-		EXPECT_EQ(PrivateTest::constructors, 1);
-		EXPECT_EQ(PrivateTest::destructors, 0);
-
-		Private bar = foo;
-
-		EXPECT_TRUE(bar.empty());
-		EXPECT_EQ(PrivateTest::constructors, 1);
-		EXPECT_EQ(PrivateTest::destructors, 0);
-	}
-
-	EXPECT_EQ(PrivateTest::constructors, 1);
-	EXPECT_EQ(PrivateTest::destructors, 1);
-}
-
-TEST_F(PrivateTest, DoubleAssignment) {
-	{
-		Private foo;
-		foo.set<TestClass>();
-
-		EXPECT_EQ(PrivateTest::constructors, 1);
-		EXPECT_EQ(PrivateTest::destructors, 0);
-
-		foo.set<TestClass>();
-
-		EXPECT_EQ(PrivateTest::constructors, 2);
-		EXPECT_EQ(PrivateTest::destructors, 1);
-	}
-
-	EXPECT_EQ(PrivateTest::constructors, 2);
-	EXPECT_EQ(PrivateTest::destructors, 2);
-}
-- 
cgit v1.2.3


From 84c5900445cc0ce8ab2fe8befc5050ff99def01e Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Wed, 4 Dec 2024 21:47:42 +0100
Subject: remove copy/move constructor/operator from base Resource

---
 src/crepe/Resource.h | 5 +++++
 1 file changed, 5 insertions(+)

(limited to 'src')

diff --git a/src/crepe/Resource.h b/src/crepe/Resource.h
index a2d65df..eceb15b 100644
--- a/src/crepe/Resource.h
+++ b/src/crepe/Resource.h
@@ -19,6 +19,11 @@ class Resource {
 public:
 	Resource(const Asset & src);
 	virtual ~Resource() = default;
+
+	Resource(const Resource &) = delete;
+	Resource(Resource &&) = delete;
+	Resource & operator=(const Resource &) = delete;
+	Resource & operator=(Resource &&) = delete;
 };
 
 } // namespace crepe
-- 
cgit v1.2.3


From 803771dfc4fb5b9144d551a91b77a5a4ec8f21b6 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Thu, 5 Dec 2024 09:51:36 +0100
Subject: add unit test

---
 src/test/ResourceManagerTest.cpp | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

(limited to 'src')

diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp
index b6be3c0..0789ef0 100644
--- a/src/test/ResourceManagerTest.cpp
+++ b/src/test/ResourceManagerTest.cpp
@@ -17,6 +17,10 @@ class ResourceManagerTest : public Test {
 public:
 	ResourceManager resource_manager{mediator};
 
+	class Unrelated : public Resource {
+		using Resource::Resource;
+	};
+
 	Asset asset_a{"asset/texture/img.png"};
 	Asset asset_b{"asset/texture/ERROR.png"};
 
@@ -69,3 +73,14 @@ TEST_F(ResourceManagerTest, Persistent) {
 	resource_manager.clear_all();
 	EXPECT_EQ(TestResource::instances, 0);
 }
+
+TEST_F(ResourceManagerTest, UnmatchedType) {
+	EXPECT_NO_THROW({
+		resource_manager.get<TestResource>(asset_a);
+	});
+
+	EXPECT_THROW({
+		resource_manager.get<Unrelated>(asset_a);
+	}, runtime_error);
+}
+
-- 
cgit v1.2.3


From 1e9e564f3806d07c7b0dc445c4ae2e738350fc83 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Thu, 5 Dec 2024 17:12:56 +0100
Subject: `make format`

---
 src/crepe/api/AudioSource.h       | 3 +--
 src/crepe/facade/SoundContext.cpp | 1 -
 src/crepe/facade/SoundContext.h   | 3 +--
 src/crepe/facade/SoundHandle.h    | 3 +--
 src/crepe/system/AudioSystem.cpp  | 4 +---
 src/test/ResourceManagerTest.cpp  | 9 ++-------
 6 files changed, 6 insertions(+), 17 deletions(-)

(limited to 'src')

diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h
index 7c1f161..b20e490 100644
--- a/src/crepe/api/AudioSource.h
+++ b/src/crepe/api/AudioSource.h
@@ -1,8 +1,8 @@
 #pragma once
 
 #include "../Component.h"
-#include "../types.h"
 #include "../facade/SoundHandle.h"
+#include "../types.h"
 
 #include "Asset.h"
 #include "GameObject.h"
@@ -69,7 +69,6 @@ private:
 	//! \}
 	//! This source's voice handle
 	SoundHandle voice{};
-
 };
 
 } // namespace crepe
diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp
index d18afc6..b1f8cb3 100644
--- a/src/crepe/facade/SoundContext.cpp
+++ b/src/crepe/facade/SoundContext.cpp
@@ -34,4 +34,3 @@ void SoundContext::set_volume(const SoundHandle & handle, float volume) {
 void SoundContext::set_loop(const SoundHandle & handle, bool loop) {
 	this->engine.setLooping(this->registry[handle], loop);
 }
-
diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h
index 102f928..d986c59 100644
--- a/src/crepe/facade/SoundContext.h
+++ b/src/crepe/facade/SoundContext.h
@@ -4,8 +4,8 @@
 
 #include "../api/Config.h"
 
-#include "SoundHandle.h"
 #include "Sound.h"
+#include "SoundHandle.h"
 
 namespace crepe {
 
@@ -76,7 +76,6 @@ private:
 	std::unordered_map<SoundHandle, SoLoud::handle> registry;
 	//! Unique handle counter
 	SoundHandle next_handle = 0;
-
 };
 
 } // namespace crepe
diff --git a/src/crepe/facade/SoundHandle.h b/src/crepe/facade/SoundHandle.h
index 131d28c..b7925fc 100644
--- a/src/crepe/facade/SoundHandle.h
+++ b/src/crepe/facade/SoundHandle.h
@@ -9,5 +9,4 @@ namespace crepe {
  */
 typedef size_t SoundHandle;
 
-}
-
+} // namespace crepe
diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
index c1cde8b..b2c1dc6 100644
--- a/src/crepe/system/AudioSystem.cpp
+++ b/src/crepe/system/AudioSystem.cpp
@@ -58,8 +58,6 @@ void AudioSystem::update_last(AudioSource & component) {
 }
 
 SoundContext & AudioSystem::get_context() {
-	if (this->context == nullptr)
-		this->context = make_unique<SoundContext>();
+	if (this->context == nullptr) this->context = make_unique<SoundContext>();
 	return *this->context.get();
 }
-
diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp
index 0789ef0..44a5921 100644
--- a/src/test/ResourceManagerTest.cpp
+++ b/src/test/ResourceManagerTest.cpp
@@ -75,12 +75,7 @@ TEST_F(ResourceManagerTest, Persistent) {
 }
 
 TEST_F(ResourceManagerTest, UnmatchedType) {
-	EXPECT_NO_THROW({
-		resource_manager.get<TestResource>(asset_a);
-	});
+	EXPECT_NO_THROW({ resource_manager.get<TestResource>(asset_a); });
 
-	EXPECT_THROW({
-		resource_manager.get<Unrelated>(asset_a);
-	}, runtime_error);
+	EXPECT_THROW({ resource_manager.get<Unrelated>(asset_a); }, runtime_error);
 }
-
-- 
cgit v1.2.3


From 55f245cb5cbd1cba54e0dc5f0cbceaf3f526c8b1 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Tue, 10 Dec 2024 14:13:42 +0100
Subject: process feedback on #60

---
 src/crepe/Component.h         | 2 --
 src/crepe/api/LoopManager.cpp | 3 +++
 2 files changed, 3 insertions(+), 2 deletions(-)

(limited to 'src')

diff --git a/src/crepe/Component.h b/src/crepe/Component.h
index 060ff00..eff5a58 100644
--- a/src/crepe/Component.h
+++ b/src/crepe/Component.h
@@ -50,8 +50,6 @@ public:
 	 * \return The maximum number of instances for this component
 	 */
 	virtual int get_instances_max() const { return -1; }
-	//! Only ComponentManager needs to know the max instance count
-	friend class ComponentManager;
 };
 
 } // namespace crepe
diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp
index 88243c4..3e9e21c 100644
--- a/src/crepe/api/LoopManager.cpp
+++ b/src/crepe/api/LoopManager.cpp
@@ -1,4 +1,5 @@
 #include "../system/AnimatorSystem.h"
+#include "../system/AudioSystem.h"
 #include "../system/CollisionSystem.h"
 #include "../system/InputSystem.h"
 #include "../system/ParticleSystem.h"
@@ -20,6 +21,7 @@ LoopManager::LoopManager() {
 	this->load_system<RenderSystem>();
 	this->load_system<ScriptSystem>();
 	this->load_system<InputSystem>();
+	this->load_system<AudioSystem>();
 }
 
 void LoopManager::process_input() { this->get_system<InputSystem>().update(); }
@@ -37,6 +39,7 @@ void LoopManager::fixed_update() {
 	this->get_system<ScriptSystem>().update();
 	this->get_system<PhysicsSystem>().update();
 	this->get_system<CollisionSystem>().update();
+	this->get_system<AudioSystem>().update();
 }
 
 void LoopManager::loop() {
-- 
cgit v1.2.3


From fe865eef33eb78b6dab6a177b463554f2ffe7387 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Tue, 10 Dec 2024 15:19:17 +0100
Subject: add ResourceManager to LoopManager

---
 src/crepe/api/LoopManager.h | 3 +++
 1 file changed, 3 insertions(+)

(limited to 'src')

diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h
index d8910a0..0947f94 100644
--- a/src/crepe/api/LoopManager.h
+++ b/src/crepe/api/LoopManager.h
@@ -4,6 +4,7 @@
 
 #include "../facade/SDLContext.h"
 #include "../manager/ComponentManager.h"
+#include "../manager/ResourceManager.h"
 #include "../manager/SceneManager.h"
 #include "../system/System.h"
 
@@ -95,6 +96,8 @@ private:
 	ComponentManager component_manager{mediator};
 	//! Scene manager instance
 	SceneManager scene_manager{mediator};
+	//! Resource manager instance
+	ResourceManager resource_manager{mediator};
 
 	//! SDL context \todo no more singletons!
 	SDLContext & sdl_context = SDLContext::get_instance();
-- 
cgit v1.2.3


From 7cbc577e94ed048f2a8146fab6972ae6ff290be7 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Tue, 10 Dec 2024 18:57:03 +0100
Subject: fix AudioSystem bug + add regression test

---
 src/crepe/system/AudioSystem.cpp | 6 +++---
 src/test/AudioTest.cpp           | 9 +++++++++
 2 files changed, 12 insertions(+), 3 deletions(-)

(limited to 'src')

diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
index b2c1dc6..ddba268 100644
--- a/src/crepe/system/AudioSystem.cpp
+++ b/src/crepe/system/AudioSystem.cpp
@@ -26,12 +26,12 @@ void AudioSystem::diff_update(AudioSource & component, Sound & resource) {
 	SoundContext & context = this->get_context();
 
 	if (component.active != component.last_active) {
-		if (component.active) {
-			component.oneshot_play = component.play_on_awake;
-		} else {
+		if (!component.active) {
 			context.stop(component.voice);
 			return;
 		}
+		if (component.play_on_awake)
+			component.oneshot_play = true;
 	}
 	if (!component.active) return;
 
diff --git a/src/test/AudioTest.cpp b/src/test/AudioTest.cpp
index 774fdb8..48bba1b 100644
--- a/src/test/AudioTest.cpp
+++ b/src/test/AudioTest.cpp
@@ -150,3 +150,12 @@ TEST_F(AudioTest, PlayOnActive) {
 		system.update();
 	}
 }
+
+TEST_F(AudioTest, PlayImmediately) {
+	component.play_on_awake = false;
+	component.play();
+
+	EXPECT_CALL(context, play(_)).Times(1);
+
+	system.update();
+}
-- 
cgit v1.2.3