From 8d78727d6e7badca16ba7a1328643928a0039569 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Mon, 18 Nov 2024 18:02:09 +0100
Subject: move utilities from loek/audio

---
 .crepe-root                     |  0
 .gitmodules                     |  4 +++
 lib/whereami/CMakeLists.txt     | 38 +++++++++++++++++++++++
 lib/whereami/lib                |  1 +
 src/CMakeLists.txt              |  2 ++
 src/crepe/Asset.cpp             | 16 ----------
 src/crepe/Asset.h               | 41 -------------------------
 src/crepe/CMakeLists.txt        |  2 --
 src/crepe/api/Asset.cpp         | 58 +++++++++++++++++++++++++++++++++++
 src/crepe/api/Asset.h           | 60 ++++++++++++++++++++++++++++++++++++
 src/crepe/api/CMakeLists.txt    |  2 ++
 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/crepe/facade/Sound.h        |  2 +-
 src/crepe/system/System.h       |  2 ++
 src/crepe/util/CMakeLists.txt   |  2 ++
 src/crepe/util/OptionalRef.h    | 41 +++++++++++++++++++++++++
 src/crepe/util/OptionalRef.hpp  | 67 +++++++++++++++++++++++++++++++++++++++++
 src/example/audio_internal.cpp  |  8 ++---
 src/example/particles.cpp       |  2 +-
 src/example/rendering.cpp       | 10 +++---
 src/test/AssetTest.cpp          | 33 ++++++++++++++++++++
 src/test/CMakeLists.txt         |  2 ++
 src/test/OptionalRefTest.cpp    | 38 +++++++++++++++++++++++
 26 files changed, 378 insertions(+), 73 deletions(-)
 create mode 100644 .crepe-root
 create mode 100644 lib/whereami/CMakeLists.txt
 create mode 160000 lib/whereami/lib
 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
 create mode 100644 src/crepe/util/OptionalRef.h
 create mode 100644 src/crepe/util/OptionalRef.hpp
 create mode 100644 src/test/AssetTest.cpp
 create mode 100644 src/test/OptionalRefTest.cpp

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
deleted file mode 100644
index 9c41ecb..0000000
--- a/src/crepe/Asset.cpp
+++ /dev/null
@@ -1,16 +0,0 @@
-#include <filesystem>
-
-#include "Asset.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);
-}
-
-istream & Asset::get_stream() { return this->file; }
-
-const string & Asset::get_canonical() const { return this->src; }
diff --git a/src/crepe/Asset.h b/src/crepe/Asset.h
deleted file mode 100644
index 9051c5e..0000000
--- a/src/crepe/Asset.h
+++ /dev/null
@@ -1,41 +0,0 @@
-#pragma once
-
-#include <fstream>
-#include <iostream>
-#include <string>
-
-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.
- */
-class Asset {
-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
-	 */
-	std::istream & get_stream();
-	/**
-	 * \brief Get the canonical path to this asset
-	 * \return Canonical path to this asset
-	 */
-	const std::string & get_canonical() const;
-
-private:
-	//! Canonical path to asset
-	const std::string src;
-	//! File handle (stream)
-	std::ifstream file;
-};
-
-} // namespace crepe
diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt
index 3b05742..7e176e7 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
@@ -7,7 +6,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..5271cf7
--- /dev/null
+++ b/src/crepe/api/Asset.cpp
@@ -0,0 +1,58 @@
+#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;
+}
+
+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
new file mode 100644
index 0000000..05dccba
--- /dev/null
+++ b/src/crepe/api/Asset.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include <string>
+#include <unordered_map>
+
+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;
+
+	/**
+	 * \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;
+
+private:
+	std::string find_asset(const std::string & src) const;
+	/**
+	 * \returns The path to the current executable
+	 */
+	std::string whereami() const noexcept;
+};
+
+} // 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/CMakeLists.txt b/src/crepe/api/CMakeLists.txt
index f9b370f..6557656 100644
--- a/src/crepe/api/CMakeLists.txt
+++ b/src/crepe/api/CMakeLists.txt
@@ -19,6 +19,7 @@ target_sources(crepe PUBLIC
 	Animator.cpp
 	LoopManager.cpp
 	LoopTimer.cpp
+	Asset.cpp
 )
 
 target_sources(crepe PUBLIC FILE_SET HEADERS FILES
@@ -45,4 +46,5 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
 	Animator.h
 	LoopManager.h
 	LoopTimer.h
+	Asset.h
 )
diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h
index 3ab877a..13eabd1 100644
--- a/src/crepe/api/Config.h
+++ b/src/crepe/api/Config.h
@@ -62,6 +62,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 83e91f8..f2daada 100644
--- a/src/crepe/facade/SDLContext.cpp
+++ b/src/crepe/facade/SDLContext.cpp
@@ -149,7 +149,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 *)>> img_surface;
diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp
index 7aa89a9..4d3abf5 100644
--- a/src/crepe/facade/Sound.cpp
+++ b/src/crepe/facade/Sound.cpp
@@ -16,7 +16,7 @@ Sound::Sound(const char * src) {
 	this->load(make_unique<Asset>(src));
 }
 
-void Sound::load(unique_ptr<Asset> res) { this->sample.load(res->get_canonical().c_str()); }
+void Sound::load(unique_ptr<Asset> res) { this->sample.load(res->get_path().c_str()); }
 
 void Sound::play() {
 	SoundContext & ctx = SoundContext::get_instance();
diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h
index 32b6478..4c68f32 100644
--- a/src/crepe/facade/Sound.h
+++ b/src/crepe/facade/Sound.h
@@ -4,7 +4,7 @@
 #include <soloud/soloud.h>
 #include <soloud/soloud_wav.h>
 
-#include "../Asset.h"
+#include "../api/Asset.h"
 
 namespace crepe {
 
diff --git a/src/crepe/system/System.h b/src/crepe/system/System.h
index 28ea20e..36f7edc 100644
--- a/src/crepe/system/System.h
+++ b/src/crepe/system/System.h
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "../ComponentManager.h"
+
 namespace crepe {
 
 class ComponentManager;
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..7b201b0
--- /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/example/audio_internal.cpp b/src/example/audio_internal.cpp
index 661161a..1647f20 100644
--- a/src/example/audio_internal.cpp
+++ b/src/example/audio_internal.cpp
@@ -25,11 +25,11 @@ int _ = []() {
 
 int main() {
 	// Load a background track (Ogg Vorbis)
-	auto bgm = Sound("../mwe/audio/bgm.ogg");
+	auto bgm = Sound("mwe/audio/bgm.ogg");
 	// 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("mwe/audio/sfx1.wav");
+	auto sfx2 = Sound("mwe/audio/sfx2.wav");
+	auto sfx3 = Sound("mwe/audio/sfx3.wav");
 
 	// Start the background track
 	bgm.play();
diff --git a/src/example/particles.cpp b/src/example/particles.cpp
index 3d5f676..d4638a2 100644
--- a/src/example/particles.cpp
+++ b/src/example/particles.cpp
@@ -18,7 +18,7 @@ int main(int argc, char * argv[]) {
 	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, FlipSettings{true, true});
+		make_shared<Texture>("asset/texture/img.png"), color, FlipSettings{true, true});
 	game_object.add_component<ParticleEmitter>(ParticleEmitter::Data{
 		.position = {0, 0},
 		.max_particles = 100,
diff --git a/src/example/rendering.cpp b/src/example/rendering.cpp
index c9e62f1..c813524 100644
--- a/src/example/rendering.cpp
+++ b/src/example/rendering.cpp
@@ -30,20 +30,20 @@ int main() {
 	// Normal adding components
 	{
 		Color color(0, 0, 0, 0);
-		obj.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), color,
-								  FlipSettings{false, false});
+		obj.add_component<Sprite>(
+			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,
-								   FlipSettings{true, true});
+		obj1.add_component<Sprite>(
+			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..563a253
--- /dev/null
+++ b/src/test/AssetTest.cpp
@@ -0,0 +1,33 @@
+#include <gtest/gtest.h>
+
+#include <crepe/api/Asset.h>
+#include <crepe/api/Config.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/CMakeLists.txt b/src/test/CMakeLists.txt
index 49c8151..f722082 100644
--- a/src/test/CMakeLists.txt
+++ b/src/test/CMakeLists.txt
@@ -3,5 +3,7 @@ target_sources(test_main PUBLIC
 	PhysicsTest.cpp
 	ScriptTest.cpp
 	ParticleTest.cpp
+	AssetTest.cpp
+	OptionalRefTest.cpp
 )
 
diff --git a/src/test/OptionalRefTest.cpp b/src/test/OptionalRefTest.cpp
new file mode 100644
index 0000000..219ccca
--- /dev/null
+++ b/src/test/OptionalRefTest.cpp
@@ -0,0 +1,38 @@
+#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(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());
+
+	ref.clear();
+	EXPECT_FALSE(ref);
+	ASSERT_THROW(ref.get(), runtime_error);
+
+	ref = value;
+	EXPECT_TRUE(ref);
+	ASSERT_NO_THROW(ref.get());
+}
+
-- 
cgit v1.2.3


From 9770b548c5619821d7b6ea7a017df2b5a6898d0a Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Mon, 18 Nov 2024 18:10:31 +0100
Subject: add doxygen comments

---
 src/crepe/api/Asset.h        | 10 +++++++++-
 src/crepe/util/OptionalRef.h | 37 +++++++++++++++++++++++++++++++++++--
 2 files changed, 44 insertions(+), 3 deletions(-)

diff --git a/src/crepe/api/Asset.h b/src/crepe/api/Asset.h
index 05dccba..685dd3a 100644
--- a/src/crepe/api/Asset.h
+++ b/src/crepe/api/Asset.h
@@ -1,7 +1,6 @@
 #pragma once
 
 #include <string>
-#include <unordered_map>
 
 namespace crepe {
 
@@ -52,7 +51,16 @@ private:
 
 namespace std {
 
+//! Hash helper struct
 template<> struct hash<const crepe::Asset> {
+	/**
+	 * \brief Hash operator for crepe::Asset
+	 *
+	 * This function hashes a crepe::Asset instance, allowing it to be used as a key in an \c
+	 * std::unordered_map.
+	 *
+	 * \returns Hash value
+	 */
 	size_t operator()(const crepe::Asset & asset) const noexcept;
 };
 
diff --git a/src/crepe/util/OptionalRef.h b/src/crepe/util/OptionalRef.h
index 1ad3a6d..8417a25 100644
--- a/src/crepe/util/OptionalRef.h
+++ b/src/crepe/util/OptionalRef.h
@@ -12,18 +12,51 @@ namespace crepe {
 template <typename T>
 class OptionalRef {
 public:
+	//! Initialize empty (nonexistant) reference
 	OptionalRef() = default;
-	OptionalRef(T &);
-  OptionalRef<T> & operator=(T &);
+	//! Initialize reference with value
+	OptionalRef(T & ref);
+	/**
+	 * \brief Assign new reference
+	 *
+	 * \param ref Reference to assign
+	 *
+	 * \return Reference to this (required for operator)
+	 */
+  OptionalRef<T> & operator=(T & ref);
+	/**
+	 * \brief Check if this reference is not empty
+	 *
+	 * \returns `true` if reference is set, or `false` if it is not
+	 */
 	explicit operator bool() const noexcept;
 
+	/**
+	 * \brief Assign new reference
+	 *
+	 * \param ref Reference to assign
+	 */
 	void set(T &) noexcept;
+	/**
+	 * \brief Retrieve this reference
+	 *
+	 * \returns Internal reference if it is set
+	 *
+	 * \throws std::runtime_error if this function is called while the reference it not set
+	 */
 	T & get() const;
+	/**
+	 * \brief Make this reference empty
+	 */
 	void clear() noexcept;
 
+	//! Copy constructor
   OptionalRef(const OptionalRef<T> &);
+	//! Move constructor
   OptionalRef(OptionalRef<T> &&);
+	//! Copy assignment
   OptionalRef<T> & operator=(const OptionalRef<T> &);
+	//! Move assignment
   OptionalRef<T> & operator=(OptionalRef<T> &&);
 
 private:
-- 
cgit v1.2.3


From 5f39dc386cce357a7c71a81c523a90496f7b1e67 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Mon, 18 Nov 2024 18:18:27 +0100
Subject: update contributing.md

---
 contributing.md | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/contributing.md b/contributing.md
index 5b0c79d..e4f075f 100644
--- a/contributing.md
+++ b/contributing.md
@@ -15,7 +15,11 @@ that you can click on to open them.
   `name/feature` (i.e. `loek/dll-so-poc` or `jaro/class2`)
 - The master branch is considered stable, and should always contain a
   working/compiling version of the project
+- Pull requests for new code include either automated tests for the new code or
+  an explanation as to why the code can not (reliably) be tested
+<!--
 - TODO: tagging / versions
+-->
 
 # Code style
 
@@ -790,6 +794,7 @@ that you can click on to open them.
   }
   ```
   </td></tr></table></details>
+- Do not implement new classes as singletons
 
 ## CMakeLists-specific
 
-- 
cgit v1.2.3


From 4d1c6f1831e0a95029ff7af4cf8097e837dc2a5d Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Tue, 19 Nov 2024 15:05:30 +0100
Subject: add RefVector

---
 src/crepe/types.h | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/src/crepe/types.h b/src/crepe/types.h
index 0d459e8..86730cc 100644
--- a/src/crepe/types.h
+++ b/src/crepe/types.h
@@ -1,9 +1,16 @@
 #pragma once
 
 #include <cstdint>
+#include <vector>
+#include <functional>
 
 namespace crepe {
 
+//! GameObject ID
 typedef uint32_t game_object_id_t;
 
+//! vector of reference_wrapper
+template <typename T>
+using RefVector = std::vector<std::reference_wrapper<T>>;
+
 }
-- 
cgit v1.2.3


From 02845c3d25130e9473604cb2eeee42a7a7a8eadf Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Wed, 20 Nov 2024 14:01:31 +0100
Subject: `make format`

---
 src/crepe/api/Asset.cpp        | 15 +++++----------
 src/crepe/api/Asset.h          |  8 ++++----
 src/crepe/types.h              |  4 ++--
 src/crepe/util/OptionalRef.h   | 13 ++++++-------
 src/crepe/util/OptionalRef.hpp |  3 +--
 src/example/rendering.cpp      |  5 ++---
 src/test/AssetTest.cpp         | 13 +++----------
 src/test/OptionalRefTest.cpp   |  1 -
 8 files changed, 23 insertions(+), 39 deletions(-)

diff --git a/src/crepe/api/Asset.cpp b/src/crepe/api/Asset.cpp
index 5271cf7..3fe3ceb 100644
--- a/src/crepe/api/Asset.cpp
+++ b/src/crepe/api/Asset.cpp
@@ -8,8 +8,8 @@
 using namespace crepe;
 using namespace std;
 
-Asset::Asset(const string & src) : src(find_asset(src)) { }
-Asset::Asset(const char * src) : src(find_asset(src)) { }
+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; }
 
@@ -22,14 +22,12 @@ string Asset::find_asset(const string & src) const {
 
 	// absolute paths do not need to be resolved, only canonicalized
 	filesystem::path path = src;
-	if (path.is_absolute())
-		return filesystem::canonical(path);
+	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 (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();
@@ -48,11 +46,8 @@ string Asset::whereami() const noexcept {
 	return path;
 }
 
-bool Asset::operator==(const Asset & other) const noexcept {
-	return this->src == other.src;
-}
+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 685dd3a..77596ac 100644
--- a/src/crepe/api/Asset.h
+++ b/src/crepe/api/Asset.h
@@ -33,7 +33,7 @@ public:
 	 * \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;
+	bool operator==(const Asset & other) const noexcept;
 
 private:
 	//! path to asset
@@ -52,7 +52,8 @@ private:
 namespace std {
 
 //! Hash helper struct
-template<> struct hash<const crepe::Asset> {
+template <>
+struct hash<const crepe::Asset> {
 	/**
 	 * \brief Hash operator for crepe::Asset
 	 *
@@ -64,5 +65,4 @@ template<> struct hash<const crepe::Asset> {
 	size_t operator()(const crepe::Asset & asset) const noexcept;
 };
 
-}
-
+} // namespace std
diff --git a/src/crepe/types.h b/src/crepe/types.h
index 86730cc..914c76c 100644
--- a/src/crepe/types.h
+++ b/src/crepe/types.h
@@ -1,8 +1,8 @@
 #pragma once
 
 #include <cstdint>
-#include <vector>
 #include <functional>
+#include <vector>
 
 namespace crepe {
 
@@ -13,4 +13,4 @@ typedef uint32_t game_object_id_t;
 template <typename T>
 using RefVector = std::vector<std::reference_wrapper<T>>;
 
-}
+} // namespace crepe
diff --git a/src/crepe/util/OptionalRef.h b/src/crepe/util/OptionalRef.h
index 8417a25..0a94ae5 100644
--- a/src/crepe/util/OptionalRef.h
+++ b/src/crepe/util/OptionalRef.h
@@ -23,7 +23,7 @@ public:
 	 *
 	 * \return Reference to this (required for operator)
 	 */
-  OptionalRef<T> & operator=(T & ref);
+	OptionalRef<T> & operator=(T & ref);
 	/**
 	 * \brief Check if this reference is not empty
 	 *
@@ -51,13 +51,13 @@ public:
 	void clear() noexcept;
 
 	//! Copy constructor
-  OptionalRef(const OptionalRef<T> &);
+	OptionalRef(const OptionalRef<T> &);
 	//! Move constructor
-  OptionalRef(OptionalRef<T> &&);
+	OptionalRef(OptionalRef<T> &&);
 	//! Copy assignment
-  OptionalRef<T> & operator=(const OptionalRef<T> &);
+	OptionalRef<T> & operator=(const OptionalRef<T> &);
 	//! Move assignment
-  OptionalRef<T> & operator=(OptionalRef<T> &&);
+	OptionalRef<T> & operator=(OptionalRef<T> &&);
 
 private:
 	/**
@@ -68,7 +68,6 @@ private:
 	T * ref = nullptr;
 };
 
-}
+} // namespace crepe
 
 #include "OptionalRef.hpp"
-
diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp
index 7b201b0..ee41f61 100644
--- a/src/crepe/util/OptionalRef.hpp
+++ b/src/crepe/util/OptionalRef.hpp
@@ -63,5 +63,4 @@ OptionalRef<T>::operator bool() const noexcept {
 	return this->ref != nullptr;
 }
 
-}
-
+} // namespace crepe
diff --git a/src/example/rendering.cpp b/src/example/rendering.cpp
index 522ec0f..01794f8 100644
--- a/src/example/rendering.cpp
+++ b/src/example/rendering.cpp
@@ -30,9 +30,8 @@ int main() {
 	// Normal adding components
 	{
 		Color color(0, 0, 0, 0);
-		Sprite & sprite
-			= obj.add_component<Sprite>(make_shared<Texture>("asset/texture/img.png"),
-										color, FlipSettings{false, false});
+		Sprite & sprite = obj.add_component<Sprite>(
+			make_shared<Texture>("asset/texture/img.png"), color, FlipSettings{false, false});
 		sprite.sorting_in_layer = 2;
 		sprite.order_in_layer = 1;
 		obj.add_component<Camera>(Color::RED);
diff --git a/src/test/AssetTest.cpp b/src/test/AssetTest.cpp
index 563a253..8aa7629 100644
--- a/src/test/AssetTest.cpp
+++ b/src/test/AssetTest.cpp
@@ -10,18 +10,12 @@ using namespace testing;
 class AssetTest : public Test {
 public:
 	Config & cfg = Config::get_instance();
-	void SetUp() override {
-		this->cfg.asset.root_pattern = ".crepe-root";
-	}
+	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, Existant) { ASSERT_NO_THROW(Asset{"asset/texture/img.png"}); }
 
-TEST_F(AssetTest, Nonexistant) {
-	ASSERT_ANY_THROW(Asset{"asset/nonexistant"});
-}
+TEST_F(AssetTest, Nonexistant) { ASSERT_ANY_THROW(Asset{"asset/nonexistant"}); }
 
 TEST_F(AssetTest, Rootless) {
 	cfg.asset.root_pattern.clear();
@@ -30,4 +24,3 @@ TEST_F(AssetTest, Rootless) {
 	Asset asset{arbitrary};
 	ASSERT_EQ(arbitrary, asset.get_path());
 }
-
diff --git a/src/test/OptionalRefTest.cpp b/src/test/OptionalRefTest.cpp
index 219ccca..2072d56 100644
--- a/src/test/OptionalRefTest.cpp
+++ b/src/test/OptionalRefTest.cpp
@@ -35,4 +35,3 @@ TEST(OptionalRefTest, Implicit) {
 	EXPECT_TRUE(ref);
 	ASSERT_NO_THROW(ref.get());
 }
-
-- 
cgit v1.2.3


From 22a7e9f3c40b4b6eb68a5343e4870e76c4bfcf63 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Wed, 20 Nov 2024 14:24:08 +0100
Subject: process feedback on #39

---
 readme.md                      |  7 +++++++
 src/crepe/ComponentManager.h   |  5 +++--
 src/crepe/ComponentManager.hpp |  9 ++++-----
 src/crepe/api/Asset.cpp        |  5 +++--
 src/crepe/api/Asset.h          | 16 ++++++++++++++++
 src/crepe/system/System.h      |  2 --
 src/crepe/util/OptionalRef.h   | 11 +----------
 src/crepe/util/OptionalRef.hpp | 24 ------------------------
 8 files changed, 34 insertions(+), 45 deletions(-)

diff --git a/readme.md b/readme.md
index c83853d..d309b30 100644
--- a/readme.md
+++ b/readme.md
@@ -32,6 +32,7 @@ This project uses the following libraries
 |`SoLoud`|(latest git `master` version)|
 |Google Test (`GTest`)|1.15.2|
 |Berkeley DB (`libdb`)|5.3.21|
+|Where Am I?|(latest git `master` version)
 
 > [!NOTE]
 > Most of these libraries are likely available from your package manager if you
@@ -49,6 +50,11 @@ $ git submodule update --init --recursive --depth 1
 
 Then, follow these steps for each library you want to install:
 
+> [!IMPORTANT]
+> A dollar sign prompt (`$`) indicates commands to be run as a regular user,
+> while a hashtag (`#`) is used to denote commands that must be run with
+> privileges (e.g. as root or using `sudo`).
+
 1. Change into the library folder (run **one** of these):
    ```
    $ cd lib/googletest
@@ -56,6 +62,7 @@ Then, follow these steps for each library you want to install:
    $ cd lib/soloud/contrib
    $ cd lib/sdl_image
    $ cd lib/sdl_ttf
+   $ cd lib/whereami
    ```
 2. Use CMake to configure the build, run the build and install (run **all** of
    these):
diff --git a/src/crepe/ComponentManager.h b/src/crepe/ComponentManager.h
index 2107453..0956d1e 100644
--- a/src/crepe/ComponentManager.h
+++ b/src/crepe/ComponentManager.h
@@ -8,6 +8,7 @@
 #include "api/Vector2.h"
 
 #include "Component.h"
+#include "types.h"
 
 namespace crepe {
 
@@ -112,7 +113,7 @@ public:
 	 * \return A vector of all components of the specific type and id
 	 */
 	template <typename T>
-	std::vector<std::reference_wrapper<T>> get_components_by_id(game_object_id_t id) const;
+	RefVector<T> get_components_by_id(game_object_id_t id) const;
 	/**
 	 * \brief Get all components of a specific type
 	 * 
@@ -122,7 +123,7 @@ public:
 	 * \return A vector of all components of the specific type
 	 */
 	template <typename T>
-	std::vector<std::reference_wrapper<T>> get_components_by_type() const;
+	RefVector<T> get_components_by_type() const;
 
 private:
 	/**
diff --git a/src/crepe/ComponentManager.hpp b/src/crepe/ComponentManager.hpp
index be99cac..4d5eaf4 100644
--- a/src/crepe/ComponentManager.hpp
+++ b/src/crepe/ComponentManager.hpp
@@ -81,15 +81,14 @@ void ComponentManager::delete_components() {
 }
 
 template <typename T>
-std::vector<std::reference_wrapper<T>>
-ComponentManager::get_components_by_id(game_object_id_t id) const {
+RefVector<T> ComponentManager::get_components_by_id(game_object_id_t id) const {
 	using namespace std;
 
 	// Determine the type of T (this is used as the key of the unordered_map<>)
 	type_index type = typeid(T);
 
 	// Create an empty vector<>
-	vector<reference_wrapper<T>> component_vector;
+	RefVector<T> component_vector;
 
 	if (this->components.find(type) == this->components.end()) return component_vector;
 
@@ -114,14 +113,14 @@ ComponentManager::get_components_by_id(game_object_id_t id) const {
 }
 
 template <typename T>
-std::vector<std::reference_wrapper<T>> ComponentManager::get_components_by_type() const {
+RefVector<T> ComponentManager::get_components_by_type() const {
 	using namespace std;
 
 	// Determine the type of T (this is used as the key of the unordered_map<>)
 	type_index type = typeid(T);
 
 	// Create an empty vector<>
-	vector<reference_wrapper<T>> component_vector;
+	RefVector<T> component_vector;
 
 	// Find the type (in the unordered_map<>)
 	if (this->components.find(type) == this->components.end()) return component_vector;
diff --git a/src/crepe/api/Asset.cpp b/src/crepe/api/Asset.cpp
index 3fe3ceb..e148367 100644
--- a/src/crepe/api/Asset.cpp
+++ b/src/crepe/api/Asset.cpp
@@ -2,9 +2,10 @@
 #include <stdexcept>
 #include <whereami.h>
 
-#include "Asset.h"
 #include "api/Config.h"
 
+#include "Asset.h"
+
 using namespace crepe;
 using namespace std;
 
@@ -15,7 +16,7 @@ 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;
+	string & 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;
diff --git a/src/crepe/api/Asset.h b/src/crepe/api/Asset.h
index 77596ac..bfd0ac7 100644
--- a/src/crepe/api/Asset.h
+++ b/src/crepe/api/Asset.h
@@ -40,6 +40,22 @@ private:
 	const std::string src;
 
 private:
+	/**
+	 * \brief Locate asset path, or throw exception if it cannot be found
+	 *
+	 * This function resolves asset locations relative to crepe::Config::root_pattern if it is
+	 * set and \p src is a relative path. If \p src is an absolute path, it is canonicalized.
+	 * This function only returns if the file can be found.
+	 *
+	 * \param src Arbitrary path to resource file
+	 *
+	 * \returns \p src if crepe::Config::root_pattern is empty
+	 * \returns Canonical path to \p src
+	 *
+	 * \throws std::runtime_error if root_pattern cannot be found
+	 * \throws std::filesystem::filesystem_error if the resolved path does not exist
+	 * \throws std::filesystem::filesystem_error if the path cannot be canonicalized
+	 */
 	std::string find_asset(const std::string & src) const;
 	/**
 	 * \returns The path to the current executable
diff --git a/src/crepe/system/System.h b/src/crepe/system/System.h
index 36f7edc..28ea20e 100644
--- a/src/crepe/system/System.h
+++ b/src/crepe/system/System.h
@@ -1,7 +1,5 @@
 #pragma once
 
-#include "../ComponentManager.h"
-
 namespace crepe {
 
 class ComponentManager;
diff --git a/src/crepe/util/OptionalRef.h b/src/crepe/util/OptionalRef.h
index 0a94ae5..57f9635 100644
--- a/src/crepe/util/OptionalRef.h
+++ b/src/crepe/util/OptionalRef.h
@@ -36,7 +36,7 @@ public:
 	 *
 	 * \param ref Reference to assign
 	 */
-	void set(T &) noexcept;
+	void set(T & ref) noexcept;
 	/**
 	 * \brief Retrieve this reference
 	 *
@@ -50,15 +50,6 @@ public:
 	 */
 	void clear() noexcept;
 
-	//! Copy constructor
-	OptionalRef(const OptionalRef<T> &);
-	//! Move constructor
-	OptionalRef(OptionalRef<T> &&);
-	//! Copy assignment
-	OptionalRef<T> & operator=(const OptionalRef<T> &);
-	//! Move assignment
-	OptionalRef<T> & operator=(OptionalRef<T> &&);
-
 private:
 	/**
 	 * \brief Reference to the value of type \c T
diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp
index ee41f61..71e2a39 100644
--- a/src/crepe/util/OptionalRef.hpp
+++ b/src/crepe/util/OptionalRef.hpp
@@ -11,30 +11,6 @@ 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)
-- 
cgit v1.2.3


From e2c27d8c99a71e5d94ffe49027ec13afeb74b021 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Wed, 20 Nov 2024 14:59:34 +0100
Subject: replace more vector<reference_wrapper<T>> with RefVector

---
 src/crepe/api/GameObject.cpp        |  6 ++----
 src/crepe/api/Script.h              |  2 +-
 src/crepe/api/Script.hpp            |  5 ++---
 src/crepe/system/AnimatorSystem.cpp |  5 +----
 src/crepe/system/ParticleSystem.cpp |  3 +--
 src/crepe/system/PhysicsSystem.cpp  |  6 ++----
 src/crepe/system/RenderSystem.cpp   | 13 +++++--------
 src/crepe/system/RenderSystem.h     |  3 +--
 src/crepe/system/ScriptSystem.cpp   | 13 +++++--------
 src/crepe/system/ScriptSystem.h     |  6 +++---
 10 files changed, 23 insertions(+), 39 deletions(-)

diff --git a/src/crepe/api/GameObject.cpp b/src/crepe/api/GameObject.cpp
index 287e81d..4874426 100644
--- a/src/crepe/api/GameObject.cpp
+++ b/src/crepe/api/GameObject.cpp
@@ -23,12 +23,10 @@ void GameObject::set_parent(const GameObject & parent) {
 	ComponentManager & mgr = this->component_manager;
 
 	// Set parent on own Metadata component
-	vector<reference_wrapper<Metadata>> this_metadata
-		= mgr.get_components_by_id<Metadata>(this->id);
+	RefVector<Metadata> this_metadata = mgr.get_components_by_id<Metadata>(this->id);
 	this_metadata.at(0).get().parent = parent.id;
 
 	// Add own id to children list of parent's Metadata component
-	vector<reference_wrapper<Metadata>> parent_metadata
-		= mgr.get_components_by_id<Metadata>(parent.id);
+	RefVector<Metadata> parent_metadata = mgr.get_components_by_id<Metadata>(parent.id);
 	parent_metadata.at(0).get().children.push_back(this->id);
 }
diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h
index 2b70379..839d937 100644
--- a/src/crepe/api/Script.h
+++ b/src/crepe/api/Script.h
@@ -62,7 +62,7 @@ protected:
 	 * \returns List of component references
 	 */
 	template <typename T>
-	std::vector<std::reference_wrapper<T>> get_components() const;
+	RefVector<T> get_components() const;
 
 protected:
 	// NOTE: Script must have a constructor without arguments so the game programmer doesn't need
diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp
index a064a90..a85d814 100644
--- a/src/crepe/api/Script.hpp
+++ b/src/crepe/api/Script.hpp
@@ -10,7 +10,7 @@ namespace crepe {
 template <typename T>
 T & Script::get_component() const {
 	using namespace std;
-	vector<reference_wrapper<T>> all_components = this->get_components<T>();
+	RefVector<T> all_components = this->get_components<T>();
 	if (all_components.size() < 1)
 		throw runtime_error(
 			format("Script: no component found with type = {}", typeid(T).name()));
@@ -19,9 +19,8 @@ T & Script::get_component() const {
 }
 
 template <typename T>
-std::vector<std::reference_wrapper<T>> Script::get_components() const {
+RefVector<T> Script::get_components() const {
 	auto & mgr = *this->component_manager_ref;
-
 	return mgr.get_components_by_id<T>(this->game_object_id);
 }
 
diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp
index 9d18873..676e485 100644
--- a/src/crepe/system/AnimatorSystem.cpp
+++ b/src/crepe/system/AnimatorSystem.cpp
@@ -1,6 +1,4 @@
 #include <cstdint>
-#include <functional>
-#include <vector>
 
 #include "api/Animator.h"
 #include "facade/SDLContext.h"
@@ -13,8 +11,7 @@ using namespace crepe;
 void AnimatorSystem::update() {
 	ComponentManager & mgr = this->component_manager;
 
-	std::vector<std::reference_wrapper<Animator>> animations
-		= mgr.get_components_by_type<Animator>();
+	RefVector<Animator> animations = mgr.get_components_by_type<Animator>();
 
 	uint64_t tick = SDLContext::get_instance().get_ticks();
 	for (Animator & a : animations) {
diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp
index 7316309..fcf7522 100644
--- a/src/crepe/system/ParticleSystem.cpp
+++ b/src/crepe/system/ParticleSystem.cpp
@@ -14,8 +14,7 @@ using namespace crepe;
 void ParticleSystem::update() {
 	// Get all emitters
 	ComponentManager & mgr = this->component_manager;
-	std::vector<std::reference_wrapper<ParticleEmitter>> emitters
-		= mgr.get_components_by_type<ParticleEmitter>();
+	RefVector<ParticleEmitter> emitters = mgr.get_components_by_type<ParticleEmitter>();
 
 	for (ParticleEmitter & emitter : emitters) {
 		// Get transform linked to emitter
diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp
index 4a7dbfb..bcde431 100644
--- a/src/crepe/system/PhysicsSystem.cpp
+++ b/src/crepe/system/PhysicsSystem.cpp
@@ -12,10 +12,8 @@ using namespace crepe;
 
 void PhysicsSystem::update() {
 	ComponentManager & mgr = this->component_manager;
-	std::vector<std::reference_wrapper<Rigidbody>> rigidbodies
-		= mgr.get_components_by_type<Rigidbody>();
-	std::vector<std::reference_wrapper<Transform>> transforms
-		= mgr.get_components_by_type<Transform>();
+	RefVector<Rigidbody> rigidbodies = mgr.get_components_by_type<Rigidbody>();
+	RefVector<Transform> transforms = mgr.get_components_by_type<Transform>();
 
 	double gravity = Config::get_instance().physics.gravity;
 	for (Rigidbody & rigidbody : rigidbodies) {
diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp
index 96c5f27..7ee03e5 100644
--- a/src/crepe/system/RenderSystem.cpp
+++ b/src/crepe/system/RenderSystem.cpp
@@ -1,6 +1,5 @@
 #include <algorithm>
 #include <cassert>
-#include <functional>
 #include <stdexcept>
 #include <vector>
 
@@ -20,7 +19,7 @@ void RenderSystem::present_screen() { this->context.present_screen(); }
 void RenderSystem::update_camera() {
 	ComponentManager & mgr = this->component_manager;
 
-	std::vector<std::reference_wrapper<Camera>> cameras = mgr.get_components_by_type<Camera>();
+	RefVector<Camera> cameras = mgr.get_components_by_type<Camera>();
 
 	if (cameras.size() == 0) throw std::runtime_error("No cameras in current scene");
 
@@ -37,10 +36,8 @@ bool sorting_comparison(const Sprite & a, const Sprite & b) {
 	return false;
 }
 
-std::vector<std::reference_wrapper<Sprite>>
-RenderSystem::sort(std::vector<std::reference_wrapper<Sprite>> & objs) {
-
-	std::vector<std::reference_wrapper<Sprite>> sorted_objs(objs);
+RefVector<Sprite> RenderSystem::sort(RefVector<Sprite> & objs) {
+	RefVector<Sprite> sorted_objs(objs);
 	std::sort(sorted_objs.begin(), sorted_objs.end(), sorting_comparison);
 
 	return sorted_objs;
@@ -48,8 +45,8 @@ RenderSystem::sort(std::vector<std::reference_wrapper<Sprite>> & objs) {
 
 void RenderSystem::render_sprites() {
 	ComponentManager & mgr = this->component_manager;
-	vector<reference_wrapper<Sprite>> sprites = mgr.get_components_by_type<Sprite>();
-	vector<reference_wrapper<Sprite>> sorted_sprites = this->sort(sprites);
+	RefVector<Sprite> sprites = mgr.get_components_by_type<Sprite>();
+	RefVector<Sprite> sorted_sprites = this->sort(sprites);
 
 	for (const Sprite & sprite : sorted_sprites) {
 		auto transforms = mgr.get_components_by_id<Transform>(sprite.game_object_id);
diff --git a/src/crepe/system/RenderSystem.h b/src/crepe/system/RenderSystem.h
index 57b9c73..40978ae 100644
--- a/src/crepe/system/RenderSystem.h
+++ b/src/crepe/system/RenderSystem.h
@@ -47,8 +47,7 @@ private:
 	 * \param objs the vector that will do a sorting algorithm on 
 	 * \return returns a sorted reference vector
 	 */
-	std::vector<std::reference_wrapper<Sprite>>
-	sort(std::vector<std::reference_wrapper<Sprite>> & objs);
+	RefVector<Sprite> sort(RefVector<Sprite> & objs);
 
 	/**
 	 * \todo Include color handling for sprites.
diff --git a/src/crepe/system/ScriptSystem.cpp b/src/crepe/system/ScriptSystem.cpp
index c4d724c..c33309c 100644
--- a/src/crepe/system/ScriptSystem.cpp
+++ b/src/crepe/system/ScriptSystem.cpp
@@ -1,6 +1,4 @@
-#include <forward_list>
 #include <functional>
-#include <vector>
 
 #include "../ComponentManager.h"
 #include "../api/BehaviorScript.h"
@@ -14,7 +12,7 @@ using namespace crepe;
 void ScriptSystem::update() {
 	dbg_trace();
 
-	forward_list<reference_wrapper<Script>> scripts = this->get_scripts();
+	RefVector<Script> scripts = this->get_scripts();
 
 	for (auto & script_ref : scripts) {
 		Script & script = script_ref.get();
@@ -26,18 +24,17 @@ void ScriptSystem::update() {
 	}
 }
 
-forward_list<reference_wrapper<Script>> ScriptSystem::get_scripts() const {
-	forward_list<reference_wrapper<Script>> scripts = {};
+RefVector<Script> ScriptSystem::get_scripts() const {
+	RefVector<Script> scripts = {};
 	ComponentManager & mgr = this->component_manager;
-	vector<reference_wrapper<BehaviorScript>> behavior_scripts
-		= mgr.get_components_by_type<BehaviorScript>();
+	RefVector<BehaviorScript> behavior_scripts = mgr.get_components_by_type<BehaviorScript>();
 
 	for (auto behavior_script_ref : behavior_scripts) {
 		BehaviorScript & behavior_script = behavior_script_ref.get();
 		if (!behavior_script.active) continue;
 		Script * script = behavior_script.script.get();
 		if (script == nullptr) continue;
-		scripts.push_front(*script);
+		scripts.push_back(*script);
 	}
 
 	return scripts;
diff --git a/src/crepe/system/ScriptSystem.h b/src/crepe/system/ScriptSystem.h
index deb89cb..32e1fcd 100644
--- a/src/crepe/system/ScriptSystem.h
+++ b/src/crepe/system/ScriptSystem.h
@@ -1,9 +1,9 @@
 #pragma once
 
-#include <forward_list>
-
 #include "System.h"
 
+#include "../types.h"
+
 namespace crepe {
 
 class Script;
@@ -33,7 +33,7 @@ private:
 	 *
 	 * \returns List of active \c Script instances
 	 */
-	std::forward_list<std::reference_wrapper<Script>> get_scripts() const;
+	RefVector<Script> get_scripts() const;
 };
 
 } // namespace crepe
-- 
cgit v1.2.3


From 9d66b6cfccd15a1600c30af5e744e8b0710eeae6 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Thu, 21 Nov 2024 09:29:15 +0100
Subject: remove OptionalRef::set and OptionalRef::get

---
 src/crepe/util/OptionalRef.h   | 22 ++++++++--------------
 src/crepe/util/OptionalRef.hpp | 21 ++++++++-------------
 src/test/OptionalRefTest.cpp   | 42 ++++++++++++++++++++++++++----------------
 3 files changed, 42 insertions(+), 43 deletions(-)

diff --git a/src/crepe/util/OptionalRef.h b/src/crepe/util/OptionalRef.h
index 57f9635..253bc07 100644
--- a/src/crepe/util/OptionalRef.h
+++ b/src/crepe/util/OptionalRef.h
@@ -24,19 +24,6 @@ public:
 	 * \return Reference to this (required for operator)
 	 */
 	OptionalRef<T> & operator=(T & ref);
-	/**
-	 * \brief Check if this reference is not empty
-	 *
-	 * \returns `true` if reference is set, or `false` if it is not
-	 */
-	explicit operator bool() const noexcept;
-
-	/**
-	 * \brief Assign new reference
-	 *
-	 * \param ref Reference to assign
-	 */
-	void set(T & ref) noexcept;
 	/**
 	 * \brief Retrieve this reference
 	 *
@@ -44,7 +31,14 @@ public:
 	 *
 	 * \throws std::runtime_error if this function is called while the reference it not set
 	 */
-	T & get() const;
+	operator T & () const;
+	/**
+	 * \brief Check if this reference is not empty
+	 *
+	 * \returns `true` if reference is set, or `false` if it is not
+	 */
+	explicit operator bool() const noexcept;
+
 	/**
 	 * \brief Make this reference empty
 	 */
diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp
index 71e2a39..ae7c73e 100644
--- a/src/crepe/util/OptionalRef.hpp
+++ b/src/crepe/util/OptionalRef.hpp
@@ -8,29 +8,19 @@ namespace crepe {
 
 template <typename T>
 OptionalRef<T>::OptionalRef(T & ref) {
-	this->set(ref);
+	this->ref = &ref;
 }
 
 template <typename T>
-T & OptionalRef<T>::get() const {
+OptionalRef<T>::operator T & () 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);
+	this->ref = &ref;
 	return *this;
 }
 
@@ -39,4 +29,9 @@ OptionalRef<T>::operator bool() const noexcept {
 	return this->ref != nullptr;
 }
 
+template <typename T>
+void OptionalRef<T>::clear() noexcept {
+	this->ref = nullptr;
+}
+
 } // namespace crepe
diff --git a/src/test/OptionalRefTest.cpp b/src/test/OptionalRefTest.cpp
index 2072d56..1c69348 100644
--- a/src/test/OptionalRefTest.cpp
+++ b/src/test/OptionalRefTest.cpp
@@ -6,32 +6,42 @@ using namespace std;
 using namespace crepe;
 using namespace testing;
 
-TEST(OptionalRefTest, Explicit) {
+TEST(OptionalRefTest, Normal) {
 	string value = "foo";
-	OptionalRef<string> ref;
-	EXPECT_FALSE(ref);
-	ASSERT_THROW(ref.get(), runtime_error);
+	OptionalRef<string> ref = value;
 
-	ref.set(value);
 	EXPECT_TRUE(ref);
-	ASSERT_NO_THROW(ref.get());
+	ASSERT_NO_THROW({
+		string & value_ref = ref;
+		EXPECT_EQ(value_ref, value);
+	});
 
 	ref.clear();
 	EXPECT_FALSE(ref);
-	ASSERT_THROW(ref.get(), runtime_error);
+	ASSERT_THROW({
+		string & value_ref = ref;
+	}, runtime_error);
 }
 
-TEST(OptionalRefTest, Implicit) {
+TEST(OptionalRefTest, Empty) {
 	string value = "foo";
-	OptionalRef<string> ref = value;
-	EXPECT_TRUE(ref);
-	ASSERT_NO_THROW(ref.get());
+	OptionalRef<string> ref;
 
-	ref.clear();
 	EXPECT_FALSE(ref);
-	ASSERT_THROW(ref.get(), runtime_error);
+	ASSERT_THROW({
+		string & value_ref = ref;
+	}, runtime_error);
+}
 
-	ref = value;
-	EXPECT_TRUE(ref);
-	ASSERT_NO_THROW(ref.get());
+TEST(OptionalRefTest, Chain) {
+	string value = "foo";
+	OptionalRef<string> ref1 = value;
+	OptionalRef<string> ref2 = ref1;
+
+	EXPECT_TRUE(ref2);
+	string & value_ref = ref2;
+	EXPECT_EQ(value_ref, value);
+	value_ref = "bar";
+	EXPECT_EQ(value_ref, value);
 }
+
-- 
cgit v1.2.3


From 115d6f50152dc018073345800ca90b85846ebaa9 Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Thu, 21 Nov 2024 10:01:04 +0100
Subject: `make format`

---
 src/crepe/util/OptionalRef.h   | 2 +-
 src/crepe/util/OptionalRef.hpp | 2 +-
 src/test/OptionalRefTest.cpp   | 9 ++-------
 src/test/SceneManagerTest.cpp  | 4 ++--
 4 files changed, 6 insertions(+), 11 deletions(-)

diff --git a/src/crepe/util/OptionalRef.h b/src/crepe/util/OptionalRef.h
index 253bc07..3201667 100644
--- a/src/crepe/util/OptionalRef.h
+++ b/src/crepe/util/OptionalRef.h
@@ -31,7 +31,7 @@ public:
 	 *
 	 * \throws std::runtime_error if this function is called while the reference it not set
 	 */
-	operator T & () const;
+	operator T &() const;
 	/**
 	 * \brief Check if this reference is not empty
 	 *
diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp
index ae7c73e..4608c9e 100644
--- a/src/crepe/util/OptionalRef.hpp
+++ b/src/crepe/util/OptionalRef.hpp
@@ -12,7 +12,7 @@ OptionalRef<T>::OptionalRef(T & ref) {
 }
 
 template <typename T>
-OptionalRef<T>::operator T & () const {
+OptionalRef<T>::operator T &() const {
 	if (this->ref == nullptr)
 		throw std::runtime_error("OptionalRef: attempt to dereference nullptr");
 	return *this->ref;
diff --git a/src/test/OptionalRefTest.cpp b/src/test/OptionalRefTest.cpp
index 1c69348..83f7b23 100644
--- a/src/test/OptionalRefTest.cpp
+++ b/src/test/OptionalRefTest.cpp
@@ -18,9 +18,7 @@ TEST(OptionalRefTest, Normal) {
 
 	ref.clear();
 	EXPECT_FALSE(ref);
-	ASSERT_THROW({
-		string & value_ref = ref;
-	}, runtime_error);
+	ASSERT_THROW({ string & value_ref = ref; }, runtime_error);
 }
 
 TEST(OptionalRefTest, Empty) {
@@ -28,9 +26,7 @@ TEST(OptionalRefTest, Empty) {
 	OptionalRef<string> ref;
 
 	EXPECT_FALSE(ref);
-	ASSERT_THROW({
-		string & value_ref = ref;
-	}, runtime_error);
+	ASSERT_THROW({ string & value_ref = ref; }, runtime_error);
 }
 
 TEST(OptionalRefTest, Chain) {
@@ -44,4 +40,3 @@ TEST(OptionalRefTest, Chain) {
 	value_ref = "bar";
 	EXPECT_EQ(value_ref, value);
 }
-
diff --git a/src/test/SceneManagerTest.cpp b/src/test/SceneManagerTest.cpp
index dab2ce9..1efcfb2 100644
--- a/src/test/SceneManagerTest.cpp
+++ b/src/test/SceneManagerTest.cpp
@@ -21,7 +21,7 @@ public:
 		GameObject object3 = mgr.new_object("scene_1", "tag_scene_1", Vector2{2, 0}, 0, 1);
 	}
 
-	string get_name() const { return "scene1";}
+	string get_name() const { return "scene1"; }
 };
 
 class ConcreteScene2 : public Scene {
@@ -36,7 +36,7 @@ public:
 		GameObject object4 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 3}, 0, 1);
 	}
 
-	string get_name() const { return "scene2";}
+	string get_name() const { return "scene2"; }
 };
 
 class SceneManagerTest : public ::testing::Test {
-- 
cgit v1.2.3