From 8d78727d6e7badca16ba7a1328643928a0039569 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch 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 - -#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 -#include -#include - -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 +#include +#include + +#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::operator()(const Asset & asset) const noexcept { + return std::hash{}(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 +#include + +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 { + 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 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> 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(src)); } -void Sound::load(unique_ptr res) { this->sample.load(res->get_canonical().c_str()); } +void Sound::load(unique_ptr 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 #include -#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 +class OptionalRef { +public: + OptionalRef() = default; + OptionalRef(T &); + OptionalRef & operator=(T &); + explicit operator bool() const noexcept; + + void set(T &) noexcept; + T & get() const; + void clear() noexcept; + + OptionalRef(const OptionalRef &); + OptionalRef(OptionalRef &&); + OptionalRef & operator=(const OptionalRef &); + OptionalRef & operator=(OptionalRef &&); + +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 + +#include "OptionalRef.h" + +namespace crepe { + +template +OptionalRef::OptionalRef(T & ref) { + this->set(ref); +} + +template +OptionalRef::OptionalRef(const OptionalRef & other) { + this->ref = other.ref; +} + +template +OptionalRef::OptionalRef(OptionalRef && other) { + this->ref = other.ref; + other.clear(); +} + +template +OptionalRef & OptionalRef::operator=(const OptionalRef & other) { + this->ref = other.ref; + return *this; +} + +template +OptionalRef & OptionalRef::operator=(OptionalRef && other) { + this->ref = other.ref; + other.clear(); + return *this; +} + +template +T & OptionalRef::get() const { + if (this->ref == nullptr) + throw std::runtime_error("OptionalRef: attempt to dereference nullptr"); + return *this->ref; +} + +template +void OptionalRef::set(T & ref) noexcept { + this->ref = &ref; +} + +template +void OptionalRef::clear() noexcept { + this->ref = nullptr; +} + +template +OptionalRef & OptionalRef::operator=(T & ref) { + this->set(ref); + return *this; +} + +template +OptionalRef::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( - make_shared("../asset/texture/img.png"), color, FlipSettings{true, true}); + make_shared("asset/texture/img.png"), color, FlipSettings{true, true}); game_object.add_component(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(make_shared("../asset/texture/img.png"), color, - FlipSettings{false, false}); + obj.add_component( + make_shared("asset/texture/img.png"), color, FlipSettings{false, false}); obj.add_component(Color::get_red()); } { Color color(0, 0, 0, 0); - obj1.add_component(make_shared("../asset/texture/second.png"), color, - FlipSettings{true, true}); + obj1.add_component( + make_shared("asset/texture/second.png"), color, FlipSettings{true, true}); } /* { Color color(0, 0, 0, 0); - auto img = mgr.cache("../asset/texture/second.png"); + auto img = mgr.cache("asset/texture/second.png"); obj2.add_component(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 + +#include +#include + +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 + +#include + +using namespace std; +using namespace crepe; +using namespace testing; + +TEST(OptionalRefTest, Explicit) { + string value = "foo"; + OptionalRef 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 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