From c58fbbefd5a426c38b1182e9e760f149f0091670 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Wed, 13 Nov 2024 12:31:59 +0100 Subject: move some files from `loek/tests` to `loek/audio` --- src/crepe/Resource.h | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/crepe/Resource.h (limited to 'src/crepe/Resource.h') diff --git a/src/crepe/Resource.h b/src/crepe/Resource.h new file mode 100644 index 0000000..dcf3dbd --- /dev/null +++ b/src/crepe/Resource.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +namespace crepe { + +class ResourceManager; +class Asset; + +/** + * Resource is an interface class used to represent a (deserialized) game + * resource (e.g. textures, sounds). + */ +class Resource { +private: + /** + * \brief Prototype pattern clone function. + * + * \param src Source file of new resource (abstraction for file saved on + * disk) + * + * \returns New instance of concrete resource + */ + virtual std::unique_ptr clone(const Asset & src) const = 0; + /** + * The resource manager uses \c clone to create new instances of the concrete + * resource class. This may be used to inherit references to classes that + * would otherwise need to be implemented as singletons. + */ + friend class ResourceManager; +}; + +} // namespace crepe -- cgit v1.2.3 From ab0b4923c4f49e7a28f6d17e994d3e013ca344bb Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Thu, 14 Nov 2024 18:04:03 +0100 Subject: more WIP audio system + utilities --- src/crepe/CMakeLists.txt | 2 ++ src/crepe/Resource.cpp | 6 ++++ src/crepe/Resource.h | 12 ++----- src/crepe/api/ResourceManager.cpp | 13 ++++++++ src/crepe/api/ResourceManager.h | 13 ++++---- src/crepe/facade/Sound.cpp | 22 ++++++------- src/crepe/facade/Sound.h | 7 ++-- src/crepe/util/CMakeLists.txt | 2 ++ src/crepe/util/OptionalRef.h | 41 ++++++++++++++++++++++++ src/crepe/util/OptionalRef.hpp | 67 +++++++++++++++++++++++++++++++++++++++ src/test/AssetTest.cpp | 2 +- src/test/CMakeLists.txt | 2 ++ src/test/OptionalRefTest.cpp | 16 ++++++++++ src/test/ResourceManagerTest.cpp | 21 ++++++++++++ 14 files changed, 194 insertions(+), 32 deletions(-) create mode 100644 src/crepe/Resource.cpp create mode 100644 src/crepe/util/OptionalRef.h create mode 100644 src/crepe/util/OptionalRef.hpp create mode 100644 src/test/OptionalRefTest.cpp create mode 100644 src/test/ResourceManagerTest.cpp (limited to 'src/crepe/Resource.h') diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt index 05f86d7..d72d4a0 100644 --- a/src/crepe/CMakeLists.txt +++ b/src/crepe/CMakeLists.txt @@ -4,6 +4,7 @@ target_sources(crepe PUBLIC Component.cpp Collider.cpp Exception.cpp + Resource.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -15,6 +16,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES ValueBroker.hpp Exception.h Exception.hpp + Resource.h ) add_subdirectory(api) diff --git a/src/crepe/Resource.cpp b/src/crepe/Resource.cpp new file mode 100644 index 0000000..e254695 --- /dev/null +++ b/src/crepe/Resource.cpp @@ -0,0 +1,6 @@ +#include "Resource.h" + +using namespace crepe; + +Resource::Resource(const Asset & asset) { } + diff --git a/src/crepe/Resource.h b/src/crepe/Resource.h index dcf3dbd..95b4d06 100644 --- a/src/crepe/Resource.h +++ b/src/crepe/Resource.h @@ -12,16 +12,10 @@ class Asset; * resource (e.g. textures, sounds). */ class Resource { +public: + Resource(const Asset & src); + private: - /** - * \brief Prototype pattern clone function. - * - * \param src Source file of new resource (abstraction for file saved on - * disk) - * - * \returns New instance of concrete resource - */ - virtual std::unique_ptr clone(const Asset & src) const = 0; /** * The resource manager uses \c clone to create new instances of the concrete * resource class. This may be used to inherit references to classes that diff --git a/src/crepe/api/ResourceManager.cpp b/src/crepe/api/ResourceManager.cpp index 17fbd9b..6eb4afd 100644 --- a/src/crepe/api/ResourceManager.cpp +++ b/src/crepe/api/ResourceManager.cpp @@ -2,6 +2,9 @@ #include "ResourceManager.h" +// default resource cache functions +#include "../facade/Sound.h" + using namespace crepe; ResourceManager & ResourceManager::get_instance() { @@ -11,3 +14,13 @@ ResourceManager & ResourceManager::get_instance() { ResourceManager::~ResourceManager() { dbg_trace(); } ResourceManager::ResourceManager() { dbg_trace(); } + +void ResourceManager::clear() { + this->resources.clear(); +} + +template <> +Sound & ResourceManager::cache(const Asset & asset) { + return this->cache(asset); +} + diff --git a/src/crepe/api/ResourceManager.h b/src/crepe/api/ResourceManager.h index a69a9fa..468af16 100644 --- a/src/crepe/api/ResourceManager.h +++ b/src/crepe/api/ResourceManager.h @@ -51,22 +51,23 @@ public: * it is already cached. * \tparam T The type of asset to cache (e.g., texture, sound, etc.). * - * \return A shared pointer to the cached asset. + * \return A reference to the resource * * This template function caches the asset at the given file path. If the - * asset is already cached and `reload` is false, the existing cached version - * will be returned. Otherwise, the asset will be reloaded and added to the + * asset is already cached, the existing instance will be returned. + * Otherwise, the concrete resource will be instantiated and added to the * cache. */ template T & cache(const Asset & asset); - /** - * \brief Clear the resource cache - */ + //! Clear the resource cache void clear(); }; +template <> +Sound & ResourceManager::cache(const Asset & asset); + } // namespace crepe #include "ResourceManager.hpp" diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp index 726f11f..4eefcda 100644 --- a/src/crepe/facade/Sound.cpp +++ b/src/crepe/facade/Sound.cpp @@ -1,5 +1,3 @@ -#include - #include "../api/Asset.h" #include "../util/Log.h" @@ -9,16 +7,13 @@ using namespace crepe; using namespace std; -Sound::Sound(SoundContext & ctx) : context(ctx) { dbg_trace(); } - -unique_ptr Sound::clone(const Asset & src) const { - auto instance = make_unique(*this); - instance->sample.load(src.get_path().c_str()); - return instance; +Sound::Sound(const Asset & src) : Resource(src) { + this->sample.load(src.get_path().c_str()); + dbg_trace(); } void Sound::play() { - SoundContext & ctx = this->context; + SoundContext & ctx = this->context.get(); if (ctx.engine.getPause(this->handle)) { // resume if paused ctx.engine.setPause(this->handle, false); @@ -30,13 +25,13 @@ void Sound::play() { } void Sound::pause() { - SoundContext & ctx = this->context; + SoundContext & ctx = this->context.get(); if (ctx.engine.getPause(this->handle)) return; ctx.engine.setPause(this->handle, true); } void Sound::rewind() { - SoundContext & ctx = this->context; + SoundContext & ctx = this->context.get(); if (!ctx.engine.isValidVoiceHandle(this->handle)) return; ctx.engine.seek(this->handle, 0); } @@ -44,7 +39,7 @@ void Sound::rewind() { void Sound::set_volume(float volume) { this->volume = volume; - SoundContext & ctx = this->context; + SoundContext & ctx = this->context.get(); if (!ctx.engine.isValidVoiceHandle(this->handle)) return; ctx.engine.setVolume(this->handle, this->volume); } @@ -52,7 +47,8 @@ void Sound::set_volume(float volume) { void Sound::set_looping(bool looping) { this->looping = looping; - SoundContext & ctx = this->context; + SoundContext & ctx = this->context.get(); if (!ctx.engine.isValidVoiceHandle(this->handle)) return; ctx.engine.setLooping(this->handle, this->looping); } + diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h index 8342b46..6f8462a 100644 --- a/src/crepe/facade/Sound.h +++ b/src/crepe/facade/Sound.h @@ -4,6 +4,7 @@ #include #include +#include "../util/OptionalRef.h" #include "../Resource.h" namespace crepe { @@ -18,6 +19,7 @@ class SoundContext; */ class Sound : public Resource { public: + Sound(const Asset & src); /** * \brief Pause this sample * @@ -72,13 +74,12 @@ public: bool get_looping() const { return this->looping; } public: - Sound(SoundContext & ctx); - std::unique_ptr clone(const Asset & src) const override; + void set_context(SoundContext & ctx); private: SoLoud::Wav sample; SoLoud::handle handle; - SoundContext & context; + OptionalRef context; float volume = 1.0f; bool looping = false; diff --git a/src/crepe/util/CMakeLists.txt b/src/crepe/util/CMakeLists.txt index 4be738a..94ed906 100644 --- a/src/crepe/util/CMakeLists.txt +++ b/src/crepe/util/CMakeLists.txt @@ -9,5 +9,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Log.hpp Proxy.h Proxy.hpp + OptionalRef.h + OptionalRef.hpp ) diff --git a/src/crepe/util/OptionalRef.h b/src/crepe/util/OptionalRef.h new file mode 100644 index 0000000..1ad3a6d --- /dev/null +++ b/src/crepe/util/OptionalRef.h @@ -0,0 +1,41 @@ +#pragma once + +namespace crepe { + +/** + * \brief Optional reference utility + * + * This class doesn't need to know the full definition of \c T to be used. + * + * \tparam T Value type + */ +template +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..e603a25 --- /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/test/AssetTest.cpp b/src/test/AssetTest.cpp index 324a3f1..563a253 100644 --- a/src/test/AssetTest.cpp +++ b/src/test/AssetTest.cpp @@ -1,7 +1,7 @@ -#include "api/Config.h" #include #include +#include using namespace std; using namespace crepe; diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 5ea90f7..437c296 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -5,5 +5,7 @@ target_sources(test_main PUBLIC ParticleTest.cpp AudioTest.cpp AssetTest.cpp + ResourceManagerTest.cpp + OptionalRefTest.cpp ) diff --git a/src/test/OptionalRefTest.cpp b/src/test/OptionalRefTest.cpp new file mode 100644 index 0000000..65bd816 --- /dev/null +++ b/src/test/OptionalRefTest.cpp @@ -0,0 +1,16 @@ +#include + +#include + +using namespace std; +using namespace crepe; +using namespace testing; + +TEST(OptionalRefTest, Explicit) { + string value = "foo"; + OptionalRef ref; + + EXPECT_FALSE(bool(ref)); + ASSERT_THROW(ref.get(), runtime_error); +} + diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp new file mode 100644 index 0000000..42b6b5d --- /dev/null +++ b/src/test/ResourceManagerTest.cpp @@ -0,0 +1,21 @@ +#include + +#include + +using namespace std; +using namespace crepe; +using namespace testing; + +class ResourceManagerTest : public Test { +public: + ResourceManager & manager = ResourceManager::get_instance(); + + void SetUp() override { + this->manager.clear(); + } +}; + +TEST_F(ResourceManagerTest, Main) { + Sound & sound = this->manager.cache("mwe/audio/sfx1.wav"); +} + -- cgit v1.2.3 From add8724446fdeae1aaec9b07544cf7a5475a9bfe Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Thu, 14 Nov 2024 19:57:45 +0100 Subject: ResourceManager working + tested --- src/crepe/Resource.h | 3 +-- src/crepe/api/Asset.cpp | 4 ++++ src/crepe/api/Asset.h | 7 +++++++ src/crepe/api/CMakeLists.txt | 1 - src/crepe/api/ResourceManager.cpp | 27 +++++++++++++++++++++------ src/crepe/api/ResourceManager.h | 4 ---- src/crepe/api/ResourceManager.hpp | 27 --------------------------- src/crepe/facade/Sound.cpp | 5 +++++ src/crepe/facade/Sound.h | 2 +- src/crepe/util/OptionalRef.hpp | 2 +- src/test/OptionalRefTest.cpp | 24 +++++++++++++++++++++++- src/test/ResourceManagerTest.cpp | 36 +++++++++++++++++++++++++++++++++--- src/test/main.cpp | 15 +++++++++++++-- 13 files changed, 109 insertions(+), 48 deletions(-) delete mode 100644 src/crepe/api/ResourceManager.hpp (limited to 'src/crepe/Resource.h') diff --git a/src/crepe/Resource.h b/src/crepe/Resource.h index 95b4d06..a0c8859 100644 --- a/src/crepe/Resource.h +++ b/src/crepe/Resource.h @@ -1,7 +1,5 @@ #pragma once -#include - namespace crepe { class ResourceManager; @@ -14,6 +12,7 @@ class Asset; class Resource { public: Resource(const Asset & src); + virtual ~Resource() = default; private: /** diff --git a/src/crepe/api/Asset.cpp b/src/crepe/api/Asset.cpp index 1887814..5271cf7 100644 --- a/src/crepe/api/Asset.cpp +++ b/src/crepe/api/Asset.cpp @@ -48,6 +48,10 @@ string Asset::whereami() const noexcept { return path; } +bool Asset::operator==(const Asset & other) const noexcept { + return this->src == other.src; +} + size_t std::hash::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 index 0f6b0b3..05dccba 100644 --- a/src/crepe/api/Asset.h +++ b/src/crepe/api/Asset.h @@ -29,6 +29,13 @@ public: */ const std::string & get_path() const noexcept; + /** + * \brief Comparison operator + * \param other Possibly different instance of \c Asset to test equality against + * \return True if \c this and \c other are equal + */ + bool operator == (const Asset & other) const noexcept; + private: //! path to asset const std::string src; diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 70f1527..b452f37 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -37,7 +37,6 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Color.h Texture.h ResourceManager.h - ResourceManager.hpp SaveManager.h Scene.h Metadata.h diff --git a/src/crepe/api/ResourceManager.cpp b/src/crepe/api/ResourceManager.cpp index 6eb4afd..7877ed9 100644 --- a/src/crepe/api/ResourceManager.cpp +++ b/src/crepe/api/ResourceManager.cpp @@ -1,11 +1,11 @@ +#include + #include "util/Log.h" #include "ResourceManager.h" -// default resource cache functions -#include "../facade/Sound.h" - using namespace crepe; +using namespace std; ResourceManager & ResourceManager::get_instance() { static ResourceManager instance; @@ -19,8 +19,23 @@ void ResourceManager::clear() { this->resources.clear(); } -template <> -Sound & ResourceManager::cache(const Asset & asset) { - return this->cache(asset); +template +T & ResourceManager::cache(const Asset & asset) { + dbg_trace(); + static_assert(is_base_of::value, "cache must recieve a derivative class of Resource"); + + if (!this->resources.contains(asset)) + this->resources[asset] = make_unique(asset); + + Resource * resource = this->resources.at(asset).get(); + T * concrete_resource = dynamic_cast(resource); + + if (concrete_resource == nullptr) + throw runtime_error(format("ResourceManager: mismatch between requested type and actual type of resource ({})", asset.get_path())); + + return *concrete_resource; } +#include "../facade/Sound.h" +template Sound & ResourceManager::cache(const Asset &); + diff --git a/src/crepe/api/ResourceManager.h b/src/crepe/api/ResourceManager.h index 468af16..efdd5c5 100644 --- a/src/crepe/api/ResourceManager.h +++ b/src/crepe/api/ResourceManager.h @@ -65,9 +65,5 @@ public: void clear(); }; -template <> -Sound & ResourceManager::cache(const Asset & asset); - } // namespace crepe -#include "ResourceManager.hpp" diff --git a/src/crepe/api/ResourceManager.hpp b/src/crepe/api/ResourceManager.hpp deleted file mode 100644 index 62cac20..0000000 --- a/src/crepe/api/ResourceManager.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include -#include - -#include "ResourceManager.h" - -namespace crepe { - -template -T & ResourceManager::cache(const Asset & asset) { - using namespace std; - static_assert(is_base_of::value, "cache must recieve a derivative class of Resource"); - - if (!this->resources.contains(asset)) - this->resources[asset] = make_unique(asset); - - Resource * resource = this->resources.at(asset).get(); - T * concrete_resource = dynamic_cast(resource); - - if (concrete_resource == nullptr) - throw runtime_error(format("ResourceManager: mismatch between requested type and actual type of resource ({})", asset.get_path())); - - return *concrete_resource; -} - -} // namespace crepe diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp index 4eefcda..b589759 100644 --- a/src/crepe/facade/Sound.cpp +++ b/src/crepe/facade/Sound.cpp @@ -11,6 +11,7 @@ Sound::Sound(const Asset & src) : Resource(src) { this->sample.load(src.get_path().c_str()); dbg_trace(); } +Sound::~Sound() { dbg_trace(); } void Sound::play() { SoundContext & ctx = this->context.get(); @@ -52,3 +53,7 @@ void Sound::set_looping(bool looping) { ctx.engine.setLooping(this->handle, this->looping); } +void Sound::set_context(SoundContext & ctx) { + this->context = ctx; +} + diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h index 6f8462a..94b1996 100644 --- a/src/crepe/facade/Sound.h +++ b/src/crepe/facade/Sound.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -20,6 +19,7 @@ class SoundContext; class Sound : public Resource { public: Sound(const Asset & src); + ~Sound(); // dbg_trace /** * \brief Pause this sample * diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp index e603a25..7b201b0 100644 --- a/src/crepe/util/OptionalRef.hpp +++ b/src/crepe/util/OptionalRef.hpp @@ -60,7 +60,7 @@ OptionalRef & OptionalRef::operator=(T & ref) { template OptionalRef::operator bool() const noexcept { - return this->ref == nullptr; + return this->ref != nullptr; } } diff --git a/src/test/OptionalRefTest.cpp b/src/test/OptionalRefTest.cpp index 65bd816..219ccca 100644 --- a/src/test/OptionalRefTest.cpp +++ b/src/test/OptionalRefTest.cpp @@ -9,8 +9,30 @@ using namespace testing; TEST(OptionalRefTest, Explicit) { string value = "foo"; OptionalRef 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()); - EXPECT_FALSE(bool(ref)); + ref.clear(); + EXPECT_FALSE(ref); ASSERT_THROW(ref.get(), runtime_error); + + ref = value; + EXPECT_TRUE(ref); + ASSERT_NO_THROW(ref.get()); } diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp index 42b6b5d..5d1ae7a 100644 --- a/src/test/ResourceManagerTest.cpp +++ b/src/test/ResourceManagerTest.cpp @@ -1,5 +1,7 @@ #include +#include +#include #include using namespace std; @@ -8,14 +10,42 @@ using namespace testing; class ResourceManagerTest : public Test { public: - ResourceManager & manager = ResourceManager::get_instance(); + ResourceManager & resman = ResourceManager::get_instance(); + Config & cfg = Config::get_instance(); void SetUp() override { - this->manager.clear(); + cfg.log.level = Log::Level::TRACE; + resman.clear(); } }; TEST_F(ResourceManagerTest, Main) { - Sound & sound = this->manager.cache("mwe/audio/sfx1.wav"); + Asset path1 = "mwe/audio/sfx1.wav"; + Asset path2 = "mwe/audio/sfx1.wav"; + ASSERT_EQ(path1, path2); + + Sound * ptr1 = nullptr; + Sound * ptr2 = nullptr; + { + Log::logf(Log::Level::DEBUG, "Get first sound (constructor call)"); + Sound & sound = resman.cache(path1); + ptr1 = &sound; + } + { + Log::logf(Log::Level::DEBUG, "Get same sound (NO constructor call)"); + Sound & sound = resman.cache(path2); + ptr2 = &sound; + } + EXPECT_EQ(ptr1, ptr2); + + Log::logf(Log::Level::DEBUG, "Clear cache (destructor call)"); + resman.clear(); + + Log::logf(Log::Level::DEBUG, "Get first sound again (constructor call)"); + Sound & sound = resman.cache(path1); + + // NOTE: there is no way (that I know of) to ensure the above statement + // allocates the new Sound instance in a different location than the first, + // so this test was verified using the above print statements. } diff --git a/src/test/main.cpp b/src/test/main.cpp index 241015d..19a8d6e 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -5,11 +5,22 @@ using namespace crepe; using namespace testing; +class GlobalConfigReset : public EmptyTestEventListener { +public: + Config & cfg = Config::get_instance(); + + // This function is called before each test + void OnTestStart(const TestInfo &) override { + cfg.log.level = Log::Level::WARNING; + } +}; + int main(int argc, char ** argv) { InitGoogleTest(&argc, argv); - auto & cfg = Config::get_instance(); - cfg.log.level = Log::Level::ERROR; + UnitTest & ut = *UnitTest::GetInstance(); + ut.listeners().Append(new GlobalConfigReset); return RUN_ALL_TESTS(); } + -- cgit v1.2.3 From b8194e02679dc88f5c0a240da83a4700ec5200cf Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sat, 30 Nov 2024 17:27:16 +0100 Subject: add doxygen comments + clean up --- src/crepe/Resource.h | 18 +++++------ src/crepe/api/AudioSource.h | 9 ++++-- src/crepe/facade/Sound.cpp | 34 --------------------- src/crepe/facade/Sound.h | 9 ++++-- src/crepe/facade/SoundContext.cpp | 7 ++--- src/crepe/facade/SoundContext.h | 48 +++++++++++++++++++++++++---- src/crepe/manager/ResourceManager.h | 44 ++++++++++++++++++++++----- src/crepe/system/AudioSystem.cpp | 4 +-- src/crepe/system/AudioSystem.h | 29 ++++++++++++++++-- src/crepe/util/Private.h | 60 +++++++++++++++++++++++++++++++++++-- src/crepe/util/Private.hpp | 2 +- src/test/AudioTest.cpp | 16 +++++----- 12 files changed, 197 insertions(+), 83 deletions(-) (limited to 'src/crepe/Resource.h') diff --git a/src/crepe/Resource.h b/src/crepe/Resource.h index a0c8859..a2d65df 100644 --- a/src/crepe/Resource.h +++ b/src/crepe/Resource.h @@ -6,21 +6,19 @@ class ResourceManager; class Asset; /** - * Resource is an interface class used to represent a (deserialized) game - * resource (e.g. textures, sounds). + * \brief Resource interface + * + * Resource is an interface class used to represent a (deserialized) game resource (e.g. + * textures, sounds). Resources are always created from \ref Asset "assets" by ResourceManager. + * + * The game programmer has the ability to use the ResourceManager to keep instances of concrete + * resources between scenes, preventing them from being reinstantiated during a scene + * transition. */ class Resource { public: Resource(const Asset & src); virtual ~Resource() = default; - -private: - /** - * The resource manager uses \c clone to create new instances of the concrete - * resource class. This may be used to inherit references to classes that - * would otherwise need to be implemented as singletons. - */ - friend class ResourceManager; }; } // namespace crepe diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h index 63b4bc4..330e8e1 100644 --- a/src/crepe/api/AudioSource.h +++ b/src/crepe/api/AudioSource.h @@ -17,16 +17,19 @@ class AudioSource : public Component { friend class AudioSystem; protected: + /** + * \param source Sound sample to load + */ AudioSource(game_object_id_t id, const Asset & source); - //! Only ComponentManager can create components + //! Only ComponentManager creates components friend class ComponentManager; public: - // But std::unique_ptr needs to be able to destoy this component again + // std::unique_ptr needs to be able to destoy this component virtual ~AudioSource() = default; public: - //! Start or resume this audio source + //! Start this audio source void play(bool looping = false); //! Stop this audio source void stop(); diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp index 33a0c47..ad50637 100644 --- a/src/crepe/facade/Sound.cpp +++ b/src/crepe/facade/Sound.cpp @@ -2,7 +2,6 @@ #include "../util/Log.h" #include "Sound.h" -#include "SoundContext.h" using namespace crepe; using namespace std; @@ -12,36 +11,3 @@ Sound::Sound(const Asset & src) : Resource(src) { dbg_trace(); } Sound::~Sound() { dbg_trace(); } - -// void Sound::play(SoundContext & ctx) { -// if (ctx.engine.getPause(this->handle)) { -// // resume if paused -// ctx.engine.setPause(this->handle, false); -// } else { -// // or start new sound -// this->handle = ctx.engine.play(this->sample, this->volume); -// ctx.engine.setLooping(this->handle, this->looping); -// } -// } -// -// void Sound::pause(SoundContext & ctx) { -// if (ctx.engine.getPause(this->handle)) return; -// ctx.engine.setPause(this->handle, true); -// } -// -// void Sound::rewind(SoundContext & ctx) { -// if (!ctx.engine.isValidVoiceHandle(this->handle)) return; -// ctx.engine.seek(this->handle, 0); -// } -// -// void Sound::set_volume(SoundContext & ctx, float volume) { -// this->volume = volume; -// if (!ctx.engine.isValidVoiceHandle(this->handle)) return; -// ctx.engine.setVolume(this->handle, this->volume); -// } -// -// void Sound::set_looping(SoundContext & ctx, bool looping) { -// this->looping = looping; -// if (!ctx.engine.isValidVoiceHandle(this->handle)) return; -// ctx.engine.setLooping(this->handle, this->looping); -// } diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h index 35bccdb..a78a2a7 100644 --- a/src/crepe/facade/Sound.h +++ b/src/crepe/facade/Sound.h @@ -12,21 +12,24 @@ class SoundContext; /** * \brief Sound resource facade * - * This class is a wrapper around a \c SoLoud::Wav instance, which holds a - * single sample. It is part of the sound facade. + * This class is a wrapper around a \c SoLoud::Wav instance, which holds a single sample. It is + * part of the sound facade. */ class Sound : public Resource { public: Sound(const Asset & src); ~Sound(); // dbg_trace + //! Voice handle wrapper struct Handle { + //! Voice handle (soloud), used by SoundContext SoLoud::handle handle; }; private: + //! Deserialized resource (soloud) SoLoud::Wav sample; - + //! SoundContext uses \c sample friend class SoundContext; }; diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp index 470b3cc..8bd7e74 100644 --- a/src/crepe/facade/SoundContext.cpp +++ b/src/crepe/facade/SoundContext.cpp @@ -17,17 +17,16 @@ SoundContext::~SoundContext() { Sound::Handle SoundContext::play(Sound & resource) { return { - .handle = this->engine.play(resource.sample, this->default_volume), + .handle = this->engine.play(resource.sample, 1.0f), }; } void SoundContext::stop(Sound::Handle & handle) { this->engine.stop(handle.handle); } -void SoundContext::set_volume(Sound & resource, Sound::Handle & handle, float volume) { +void SoundContext::set_volume(Sound::Handle & handle, float volume) { this->engine.setVolume(handle.handle, volume); - this->default_volume = volume; } -void SoundContext::set_loop(Sound & resource, Sound::Handle & handle, bool loop) { +void SoundContext::set_loop(Sound::Handle & handle, bool loop) { this->engine.setLooping(handle.handle, loop); } diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h index c651cd5..3bc8be5 100644 --- a/src/crepe/facade/SoundContext.h +++ b/src/crepe/facade/SoundContext.h @@ -11,8 +11,8 @@ namespace crepe { /** * \brief Sound engine facade * - * This class is a wrapper around a \c SoLoud::Soloud instance, which provides - * the methods for playing \c Sound instances. It is part of the sound facade. + * This class is a wrapper around a \c SoLoud::Soloud instance, which provides the methods for + * playing \c Sound instances. It is part of the sound facade. */ class SoundContext { public: @@ -24,15 +24,51 @@ public: SoundContext & operator=(const SoundContext &) = delete; SoundContext & operator=(SoundContext &&) = delete; + /** + * \brief Play a sample + * + * Plays a Sound from the beginning of the sample and returns a handle to control it later. + * + * \param resource Sound instance to play + * + * \returns Handle to control this voice + */ virtual Sound::Handle play(Sound & resource); - virtual void stop(Sound::Handle &); - virtual void set_volume(Sound &, Sound::Handle &, float); - virtual void set_loop(Sound &, Sound::Handle &, bool); + /** + * \brief Stop a voice immediately if it is still playing + * + * \note This function does nothing if the handle is invalid or if the sound is already + * stopped / finished playing. + * + * \param handle Voice handle returned by SoundContext::play + */ + virtual void stop(Sound::Handle & handle); + /** + * \brief Change the volume of a voice + * + * \note This function does nothing if the handle is invalid or if the sound is already + * stopped / finished playing. + * + * \param handle Voice handle returned by SoundContext::play + * \param volume New gain value (0=silent, 1=default) + */ + virtual void set_volume(Sound::Handle & handle, float volume); + /** + * \brief Set the looping behavior of a voice + * + * \note This function does nothing if the handle is invalid or if the sound is already + * stopped / finished playing. + * + * \param handle Voice handle returned by SoundContext::play + * \param loop Looping behavior (false=oneshot, true=loop) + */ + virtual void set_loop(Sound::Handle & handle, bool loop); private: + //! Abstracted class SoLoud::Soloud engine; - float default_volume = 1.0f; + //! Config reference Config & config = Config::get_instance(); }; diff --git a/src/crepe/manager/ResourceManager.h b/src/crepe/manager/ResourceManager.h index e7e6abc..84b275d 100644 --- a/src/crepe/manager/ResourceManager.h +++ b/src/crepe/manager/ResourceManager.h @@ -11,13 +11,11 @@ namespace crepe { /** - * \brief The ResourceManager is responsible for storing and managing assets over - * multiple scenes. + * \brief Owner of concrete Resource instances * - * The ResourceManager ensures that assets are loaded once and can be accessed - * across different scenes. It caches assets to avoid reloading them every time - * a scene is loaded. Assets are retained in memory until the ResourceManager is - * destroyed, at which point the cached assets are cleared. + * ResourceManager caches concrete Resource instances per Asset. Concrete resources are + * destroyed at the end of scenes by default, unless the game programmer marks them as + * persistent. */ class ResourceManager : public Manager { public: @@ -25,21 +23,53 @@ public: virtual ~ResourceManager(); // dbg_trace private: + //! Cache entry struct CacheEntry { + //! Concrete resource instance std::unique_ptr resource = nullptr; + //! Prevent ResourceManager::clear from removing this entry bool persistent = false; }; - //! A cache that holds all the assets, accessible by their file path, over multiple scenes. + //! Internal cache std::unordered_map resources; + /** + * \brief Ensure a cache entry exists for this asset and return a mutable reference to it + * + * \param asset Asset the concrete resource is instantiated from + * + * \returns Mutable reference to cache entry + */ CacheEntry & get_entry(const Asset & asset); public: + /** + * \brief Mark a resource as persistent (i.e. used across multiple scenes) + * + * \param asset Asset the concrete resource is instantiated from + * \param persistent Whether this resource is persistent (true=keep, false=destroy) + */ void set_persistent(const Asset & asset, bool persistent); + /** + * \brief Retrieve reference to concrete Resource by Asset + * + * \param asset Asset the concrete resource is instantiated from + * \tparam Resource Concrete derivative of Resource + * + * This class instantiates the concrete resource if it is not yet stored in the internal + * cache, or returns a reference to the cached resource if it already exists. + * + * \returns Reference to concrete resource + * + * \throws std::runtime_error if the \c Resource parameter does not match with the actual + * type of the resource stored in the cache for this Asset + */ template Resource & get(const Asset & asset); + //! Clear non-persistent resources from cache void clear(); + //! Clear all resources from cache regardless of persistence void clear_all(); }; diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp index 0696b34..26913c0 100644 --- a/src/crepe/system/AudioSystem.cpp +++ b/src/crepe/system/AudioSystem.cpp @@ -52,10 +52,10 @@ void AudioSystem::diff_update(AudioSource & component, ComponentPrivate & data, component.oneshot_stop = false; } if (component.volume != data.last_volume) { - context.set_volume(resource, data.handle, component.volume); + context.set_volume(data.handle, component.volume); } if (component.loop != data.last_loop) { - context.set_loop(resource, data.handle, component.loop); + context.set_loop(data.handle, component.loop); } } diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h index c941470..4d21883 100644 --- a/src/crepe/system/AudioSystem.h +++ b/src/crepe/system/AudioSystem.h @@ -14,9 +14,7 @@ public: void update() override; private: - /** - * \brief Private data stored by AudioSystem on AudioSource component - */ + //! Private data stored by AudioSystem on AudioSource component struct ComponentPrivate { //! This sample's voice handle Sound::Handle handle; @@ -31,14 +29,39 @@ private: //! \} }; + /** + * \brief Update `last_*` members of \c data + * + * Copies all component properties stored for comparison between AudioSystem::update() calls + * + * \param component Source properties + * \param data Destination properties + */ void update_last(const AudioSource & component, ComponentPrivate & data); + /** + * \brief Compare update component + * + * Compares properties of \c component and \c data, and calls SoundContext functions where + * applicable. + * + * \param component AudioSource component to update + * \param data AudioSource's private data + * \param resource Sound instance for AudioSource's Asset + */ void diff_update(AudioSource & component, ComponentPrivate & data, Sound & resource); protected: + /** + * \brief Get SoundContext + * + * SoundContext is retrieved through this function instead of being a direct member of + * AudioSystem to aid with testability. + */ virtual SoundContext & get_context(); private: + //! Actually stores SoundContext if the base AudioSystem::get_context implementation is used Private context; }; diff --git a/src/crepe/util/Private.h b/src/crepe/util/Private.h index 62a2e1a..d725a5e 100644 --- a/src/crepe/util/Private.h +++ b/src/crepe/util/Private.h @@ -5,26 +5,82 @@ namespace crepe { +/** + * \brief Utility for storing type hidden from user + * + * This class can be used to store types which cannot be used in the API directly due to header + * distribution limitations. This class is similar to `std::any`, but provides a method for + * retrieving a mutable reference to the stored object. + */ class Private { public: Private() = default; ~Private(); + /** + * \name Copy + * + * \note These functions do not do anything, resulting in `*this` being an empty (default) + * instance. + * + * \{ + */ Private(const Private &); - Private(Private &&); Private & operator=(const Private &); + //! \} + /** + * \name Move + * + * These functions actually move the stored type if present. + * + * \{ + */ + Private(Private &&); Private & operator=(Private &&); + //! \} + /** + * \brief Get the stored object + * + * \tparam T Type of stored object + * + * \returns Mutable reference to stored object + * + * \throws std::out_of_range if this instance does not contain any object + * \throws std::logic_error if the stored type and requested type differ + */ template - T & get(); + T & get() const; + /** + * \brief Create and store an arbitrary object + * + * \tparam T Type of object + * \tparam Args Perfect forwarding arguments + * \param args Perfect forwarding arguments + * + * All arguments to this function are forwarded using `std::forward` to the constructor of T. + * + * \returns Mutable reference to stored object + * + * \note If this instance already contained an object, this function implicitly destroys the + * previous object. + */ template T & set(Args &&... args); + /** + * \brief Check if this instance contains an object + * + * \returns `true` if this instance is empty, `false` if it contains an object + */ bool empty() const noexcept; private: + //! Wrapper for destructor call of stored object type std::function destructor; + //! Stored object's type std::type_index type = typeid(void); + //! Stored object void * instance = nullptr; }; diff --git a/src/crepe/util/Private.hpp b/src/crepe/util/Private.hpp index 3a87a9f..b2174c0 100644 --- a/src/crepe/util/Private.hpp +++ b/src/crepe/util/Private.hpp @@ -18,7 +18,7 @@ T & Private::set(Args &&... args) { } template -T & Private::get() { +T & Private::get() const { using namespace std; if (this->empty()) throw out_of_range("Private: get() called on empty object"); type_index requested_type = typeid(T); diff --git a/src/test/AudioTest.cpp b/src/test/AudioTest.cpp index 14f57bd..7644ab7 100644 --- a/src/test/AudioTest.cpp +++ b/src/test/AudioTest.cpp @@ -18,8 +18,8 @@ private: public: MOCK_METHOD(Sound::Handle, play, (Sound & resource), (override)); MOCK_METHOD(void, stop, (Sound::Handle &), (override)); - MOCK_METHOD(void, set_volume, (Sound &, Sound::Handle &, float), (override)); - MOCK_METHOD(void, set_loop, (Sound &, Sound::Handle &, bool), (override)); + MOCK_METHOD(void, set_volume, (Sound::Handle &, float), (override)); + MOCK_METHOD(void, set_loop, (Sound::Handle &, bool), (override)); }; class TestAudioSystem : public AudioSystem { @@ -48,8 +48,8 @@ public: TEST_F(AudioTest, Default) { EXPECT_CALL(context, play(_)).Times(0); EXPECT_CALL(context, stop(_)).Times(0); - EXPECT_CALL(context, set_volume(_, _, _)).Times(0); - EXPECT_CALL(context, set_loop(_, _, _)).Times(0); + EXPECT_CALL(context, set_volume(_, _)).Times(0); + EXPECT_CALL(context, set_loop(_, _)).Times(0); system.update(); } @@ -95,14 +95,14 @@ TEST_F(AudioTest, Volume) { { InSequence seq; - EXPECT_CALL(context, set_volume(_, _, _)).Times(0); + EXPECT_CALL(context, set_volume(_, _)).Times(0); component.volume += 0.2; } { InSequence seq; - EXPECT_CALL(context, set_volume(_, _, component.volume)).Times(1); + EXPECT_CALL(context, set_volume(_, component.volume)).Times(1); system.update(); } } @@ -113,14 +113,14 @@ TEST_F(AudioTest, Looping) { { InSequence seq; - EXPECT_CALL(context, set_loop(_, _, _)).Times(0); + EXPECT_CALL(context, set_loop(_, _)).Times(0); component.loop = !component.loop; } { InSequence seq; - EXPECT_CALL(context, set_loop(_, _, component.loop)).Times(1); + EXPECT_CALL(context, set_loop(_, component.loop)).Times(1); system.update(); } } -- cgit v1.2.3 From 84c5900445cc0ce8ab2fe8befc5050ff99def01e Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Wed, 4 Dec 2024 21:47:42 +0100 Subject: remove copy/move constructor/operator from base Resource --- src/crepe/Resource.h | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/crepe/Resource.h') diff --git a/src/crepe/Resource.h b/src/crepe/Resource.h index a2d65df..eceb15b 100644 --- a/src/crepe/Resource.h +++ b/src/crepe/Resource.h @@ -19,6 +19,11 @@ class Resource { public: Resource(const Asset & src); virtual ~Resource() = default; + + Resource(const Resource &) = delete; + Resource(Resource &&) = delete; + Resource & operator=(const Resource &) = delete; + Resource & operator=(Resource &&) = delete; }; } // namespace crepe -- cgit v1.2.3