aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.crepe-root0
-rw-r--r--.gitmodules4
-rw-r--r--lib/whereami/CMakeLists.txt38
m---------lib/whereami/lib0
-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/api/Asset.cpp58
-rw-r--r--src/crepe/api/Asset.h60
-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/system/System.h2
-rw-r--r--src/crepe/util/CMakeLists.txt2
-rw-r--r--src/crepe/util/OptionalRef.h41
-rw-r--r--src/crepe/util/OptionalRef.hpp67
-rw-r--r--src/example/audio_internal.cpp8
-rw-r--r--src/example/particles.cpp2
-rw-r--r--src/example/rendering.cpp10
-rw-r--r--src/test/AssetTest.cpp33
-rw-r--r--src/test/CMakeLists.txt2
-rw-r--r--src/test/OptionalRefTest.cpp38
26 files changed, 377 insertions, 73 deletions
diff --git a/.crepe-root b/.crepe-root
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/.crepe-root
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
+Subproject dcb52a058dc14530ba9ae05e4339bd3ddfae0e0
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());
+}
+