aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/crepe/Asset.cpp16
-rw-r--r--src/crepe/Asset.h41
-rw-r--r--src/crepe/CMakeLists.txt2
-rw-r--r--src/crepe/ComponentManager.h5
-rw-r--r--src/crepe/ComponentManager.hpp9
-rw-r--r--src/crepe/api/Asset.cpp54
-rw-r--r--src/crepe/api/Asset.h84
-rw-r--r--src/crepe/api/CMakeLists.txt2
-rw-r--r--src/crepe/api/Config.h14
-rw-r--r--src/crepe/api/Texture.cpp2
-rw-r--r--src/crepe/facade/SDLContext.cpp2
-rw-r--r--src/crepe/facade/Sound.cpp2
-rw-r--r--src/crepe/facade/Sound.h2
-rw-r--r--src/crepe/types.h9
-rw-r--r--src/crepe/util/CMakeLists.txt2
-rw-r--r--src/crepe/util/OptionalRef.h64
-rw-r--r--src/crepe/util/OptionalRef.hpp42
-rw-r--r--src/example/audio_internal.cpp8
-rw-r--r--src/example/particles.cpp2
-rw-r--r--src/example/rendering.cpp13
-rw-r--r--src/test/AssetTest.cpp26
-rw-r--r--src/test/CMakeLists.txt3
-rw-r--r--src/test/OptionalRefTest.cpp37
-rw-r--r--src/test/ParticleTest.cpp2
25 files changed, 361 insertions, 84 deletions
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/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
new file mode 100644
index 0000000..e148367
--- /dev/null
+++ b/src/crepe/api/Asset.cpp
@@ -0,0 +1,54 @@
+#include <filesystem>
+#include <stdexcept>
+#include <whereami.h>
+
+#include "api/Config.h"
+
+#include "Asset.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();
+ 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;
+
+ // 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..bfd0ac7
--- /dev/null
+++ b/src/crepe/api/Asset.h
@@ -0,0 +1,84 @@
+#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;
+
+ /**
+ * \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:
+ /**
+ * \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
+ */
+ std::string whereami() const noexcept;
+};
+
+} // namespace crepe
+
+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;
+};
+
+} // namespace std
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/types.h b/src/crepe/types.h
index 0d459e8..914c76c 100644
--- a/src/crepe/types.h
+++ b/src/crepe/types.h
@@ -1,9 +1,16 @@
#pragma once
#include <cstdint>
+#include <functional>
+#include <vector>
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>>;
+
+} // namespace crepe
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..57f9635
--- /dev/null
+++ b/src/crepe/util/OptionalRef.h
@@ -0,0 +1,64 @@
+#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:
+ //! Initialize empty (nonexistant) reference
+ OptionalRef() = default;
+ //! 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 & ref) 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;
+
+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;
+};
+
+} // namespace crepe
+
+#include "OptionalRef.hpp"
diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp
new file mode 100644
index 0000000..71e2a39
--- /dev/null
+++ b/src/crepe/util/OptionalRef.hpp
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <stdexcept>
+
+#include "OptionalRef.h"
+
+namespace crepe {
+
+template <typename T>
+OptionalRef<T>::OptionalRef(T & ref) {
+ this->set(ref);
+}
+
+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;
+}
+
+} // namespace crepe
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 ecd3f6a..01794f8 100644
--- a/src/example/rendering.cpp
+++ b/src/example/rendering.cpp
@@ -30,17 +30,16 @@ 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::get_red());
+ obj.add_component<Camera>(Color::RED);
}
{
Color color(0, 0, 0, 0);
Sprite & sprite = obj1.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});
sprite.sorting_in_layer = 2;
sprite.order_in_layer = 2;
}
@@ -48,7 +47,7 @@ int main() {
{
Color color(0, 0, 0, 0);
Sprite & sprite = obj2.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});
sprite.sorting_in_layer = 1;
sprite.order_in_layer = 2;
}
@@ -56,7 +55,7 @@ int main() {
/*
{
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..8aa7629
--- /dev/null
+++ b/src/test/AssetTest.cpp
@@ -0,0 +1,26 @@
+#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 d56d80f..f189e3d 100644
--- a/src/test/CMakeLists.txt
+++ b/src/test/CMakeLists.txt
@@ -3,6 +3,9 @@ target_sources(test_main PUBLIC
PhysicsTest.cpp
ScriptTest.cpp
ParticleTest.cpp
+ AssetTest.cpp
+ OptionalRefTest.cpp
+ RenderSystemTest.cpp
ECSTest.cpp
SceneManagerTest.cpp
)
diff --git a/src/test/OptionalRefTest.cpp b/src/test/OptionalRefTest.cpp
new file mode 100644
index 0000000..2072d56
--- /dev/null
+++ b/src/test/OptionalRefTest.cpp
@@ -0,0 +1,37 @@
+#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());
+}
diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp
index 4e655a9..cfbbc0e 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{