aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/crepe/Resource.h18
-rw-r--r--src/crepe/api/AudioSource.h9
-rw-r--r--src/crepe/facade/Sound.cpp34
-rw-r--r--src/crepe/facade/Sound.h9
-rw-r--r--src/crepe/facade/SoundContext.cpp7
-rw-r--r--src/crepe/facade/SoundContext.h48
-rw-r--r--src/crepe/manager/ResourceManager.h44
-rw-r--r--src/crepe/system/AudioSystem.cpp4
-rw-r--r--src/crepe/system/AudioSystem.h29
-rw-r--r--src/crepe/util/Private.h60
-rw-r--r--src/crepe/util/Private.hpp2
-rw-r--r--src/test/AudioTest.cpp16
12 files changed, 197 insertions, 83 deletions
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> 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<const Asset, CacheEntry> 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 <typename Resource>
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 <typename T>
- 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 <typename T, typename... Args>
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<void(void *)> 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 <typename T>
-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();
}
}