aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWBoerenkamps <wrj.boerenkamps@student.avans.nl>2024-12-10 13:40:42 +0100
committerWBoerenkamps <wrj.boerenkamps@student.avans.nl>2024-12-10 13:40:42 +0100
commitd80c98ee03e59e0c38e025a7fe83ab4c39115fb8 (patch)
treec31c861b82d384e64071b4e7f64dfafe99b888c0
parentb2c72ee8ce282bb13b0fbeb2ddb01fdfd6ad1280 (diff)
parent7a8657dfe019104aced61a5b63e63f61ad919f7a (diff)
font progress
-rw-r--r--src/crepe/CMakeLists.txt2
-rw-r--r--src/crepe/Component.h11
-rw-r--r--src/crepe/Resource.cpp5
-rw-r--r--src/crepe/Resource.h24
-rw-r--r--src/crepe/api/AssetManager.cpp17
-rw-r--r--src/crepe/api/AssetManager.hpp22
-rw-r--r--src/crepe/api/AudioSource.cpp15
-rw-r--r--src/crepe/api/AudioSource.h75
-rw-r--r--src/crepe/api/CMakeLists.txt9
-rw-r--r--src/crepe/api/Config.h18
-rw-r--r--src/crepe/api/Text.cpp0
-rw-r--r--src/crepe/api/Text.h12
-rw-r--r--src/crepe/facade/Sound.cpp54
-rw-r--r--src/crepe/facade/Sound.h76
-rw-r--r--src/crepe/facade/SoundContext.cpp31
-rw-r--r--src/crepe/facade/SoundContext.h63
-rw-r--r--src/crepe/facade/SoundHandle.h13
-rw-r--r--src/crepe/facade/font.h24
-rw-r--r--src/crepe/manager/CMakeLists.txt3
-rw-r--r--src/crepe/manager/Mediator.h4
-rw-r--r--src/crepe/manager/ResourceManager.cpp30
-rw-r--r--src/crepe/manager/ResourceManager.h78
-rw-r--r--src/crepe/manager/ResourceManager.hpp27
-rw-r--r--src/crepe/system/AudioSystem.cpp65
-rw-r--r--src/crepe/system/AudioSystem.h51
-rw-r--r--src/crepe/system/CMakeLists.txt2
-rw-r--r--src/crepe/system/System.cpp4
-rw-r--r--src/example/CMakeLists.txt1
-rw-r--r--src/example/asset_manager.cpp36
-rw-r--r--src/test/AudioTest.cpp152
-rw-r--r--src/test/CMakeLists.txt2
-rw-r--r--src/test/ResourceManagerTest.cpp71
-rw-r--r--src/test/main.cpp14
33 files changed, 757 insertions, 254 deletions
diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt
index da9d492..6cbb9fe 100644
--- a/src/crepe/CMakeLists.txt
+++ b/src/crepe/CMakeLists.txt
@@ -2,6 +2,7 @@ target_sources(crepe PUBLIC
Particle.cpp
Component.cpp
Collider.cpp
+ Resource.cpp
)
target_sources(crepe PUBLIC FILE_SET HEADERS FILES
@@ -9,6 +10,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
Collider.h
ValueBroker.h
ValueBroker.hpp
+ Resource.h
)
add_subdirectory(api)
diff --git a/src/crepe/Component.h b/src/crepe/Component.h
index c30419d..060ff00 100644
--- a/src/crepe/Component.h
+++ b/src/crepe/Component.h
@@ -16,7 +16,12 @@ class Component {
public:
//! Whether the component is active
bool active = true;
- //! The id of the GameObject this component belongs to
+ /**
+ * \brief The id of the GameObject this component belongs to
+ *
+ * \note Only systems are supposed to use this member, but since friend
+ * relations aren't inherited this needs to be public.
+ */
const game_object_id_t game_object_id;
protected:
@@ -24,7 +29,7 @@ protected:
* \param id The id of the GameObject this component belongs to
*/
Component(game_object_id_t id);
- //! Only the ComponentManager can create components
+ //! Only ComponentManager can create components
friend class ComponentManager;
Component(const Component &) = delete;
@@ -45,6 +50,8 @@ public:
* \return The maximum number of instances for this component
*/
virtual int get_instances_max() const { return -1; }
+ //! Only ComponentManager needs to know the max instance count
+ friend class ComponentManager;
};
} // namespace crepe
diff --git a/src/crepe/Resource.cpp b/src/crepe/Resource.cpp
new file mode 100644
index 0000000..27b4c4b
--- /dev/null
+++ b/src/crepe/Resource.cpp
@@ -0,0 +1,5 @@
+#include "Resource.h"
+
+using namespace crepe;
+
+Resource::Resource(const Asset & asset) {}
diff --git a/src/crepe/Resource.h b/src/crepe/Resource.h
new file mode 100644
index 0000000..a2d65df
--- /dev/null
+++ b/src/crepe/Resource.h
@@ -0,0 +1,24 @@
+#pragma once
+
+namespace crepe {
+
+class ResourceManager;
+class Asset;
+
+/**
+ * \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;
+};
+
+} // namespace crepe
diff --git a/src/crepe/api/AssetManager.cpp b/src/crepe/api/AssetManager.cpp
deleted file mode 100644
index 3925758..0000000
--- a/src/crepe/api/AssetManager.cpp
+++ /dev/null
@@ -1,17 +0,0 @@
-#include "util/Log.h"
-
-#include "AssetManager.h"
-
-using namespace crepe;
-
-AssetManager & AssetManager::get_instance() {
- static AssetManager instance;
- return instance;
-}
-
-AssetManager::~AssetManager() {
- dbg_trace();
- this->asset_cache.clear();
-}
-
-AssetManager::AssetManager() { dbg_trace(); }
diff --git a/src/crepe/api/AssetManager.hpp b/src/crepe/api/AssetManager.hpp
deleted file mode 100644
index 1c0e978..0000000
--- a/src/crepe/api/AssetManager.hpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#pragma once
-
-#include "AssetManager.h"
-
-namespace crepe {
-
-template <typename asset>
-std::shared_ptr<asset> AssetManager::cache(const std::string & file_path, bool reload) {
- auto it = asset_cache.find(file_path);
-
- if (!reload && it != asset_cache.end()) {
- return std::any_cast<std::shared_ptr<asset>>(it->second);
- }
-
- std::shared_ptr<asset> new_asset = std::make_shared<asset>(file_path.c_str());
-
- asset_cache[file_path] = new_asset;
-
- return new_asset;
-}
-
-} // namespace crepe
diff --git a/src/crepe/api/AudioSource.cpp b/src/crepe/api/AudioSource.cpp
new file mode 100644
index 0000000..7b05cb1
--- /dev/null
+++ b/src/crepe/api/AudioSource.cpp
@@ -0,0 +1,15 @@
+#include "AudioSource.h"
+
+using namespace crepe;
+using namespace std;
+
+AudioSource::AudioSource(game_object_id_t id, const Asset & src)
+ : Component(id),
+ source(src) {}
+
+void AudioSource::play(bool looping) {
+ this->loop = looping;
+ this->oneshot_play = true;
+}
+
+void AudioSource::stop() { this->oneshot_stop = true; }
diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h
new file mode 100644
index 0000000..7c1f161
--- /dev/null
+++ b/src/crepe/api/AudioSource.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include "../Component.h"
+#include "../types.h"
+#include "../facade/SoundHandle.h"
+
+#include "Asset.h"
+#include "GameObject.h"
+
+namespace crepe {
+
+class AudioSystem;
+
+//! Audio source component
+class AudioSource : public Component {
+ //! AudioSource components are handled by AudioSystem
+ friend class AudioSystem;
+
+protected:
+ /**
+ * \param source Sound sample to load
+ */
+ AudioSource(game_object_id_t id, const Asset & source);
+ //! Only ComponentManager creates components
+ friend class ComponentManager;
+
+public:
+ // std::unique_ptr needs to be able to destoy this component
+ virtual ~AudioSource() = default;
+
+public:
+ //! Start this audio source
+ void play(bool looping = false);
+ //! Stop this audio source
+ void stop();
+
+public:
+ //! Play when this component becomes active
+ bool play_on_awake = false;
+ //! Repeat the current audio clip during playback
+ bool loop = false;
+ //! Normalized volume (0.0 - 1.0)
+ float volume = 1.0;
+
+private:
+ //! This audio source's clip
+ const Asset source;
+
+ /**
+ * \name One-shot state variables
+ *
+ * These variables trigger function calls when set to true, and are unconditionally reset on
+ * every system update.
+ *
+ * \{
+ */
+ //! Play this sample
+ bool oneshot_play = false;
+ //! Stop this sample
+ bool oneshot_stop = false;
+ //! \}
+ /**
+ * \name State diffing variables
+ * \{
+ */
+ typeof(active) last_active = false;
+ typeof(volume) last_volume = volume;
+ typeof(loop) last_loop = loop;
+ //! \}
+ //! This source's voice handle
+ SoundHandle voice{};
+
+};
+
+} // namespace crepe
diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt
index 593c4e6..44ea243 100644
--- a/src/crepe/api/CMakeLists.txt
+++ b/src/crepe/api/CMakeLists.txt
@@ -1,5 +1,5 @@
target_sources(crepe PUBLIC
- # AudioSource.cpp
+ AudioSource.cpp
BehaviorScript.cpp
GameObject.cpp
Rigidbody.cpp
@@ -7,7 +7,6 @@ target_sources(crepe PUBLIC
Transform.cpp
Color.cpp
Texture.cpp
- AssetManager.cpp
Sprite.cpp
Config.cpp
Metadata.cpp
@@ -27,7 +26,7 @@ target_sources(crepe PUBLIC
)
target_sources(crepe PUBLIC FILE_SET HEADERS FILES
- # AudioSource.h
+ AudioSource.h
BehaviorScript.h
Config.h
Script.h
@@ -39,9 +38,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
Vector2.h
Vector2.hpp
Color.h
- Texture.h
- AssetManager.h
- AssetManager.hpp
+ Texture.h
Scene.h
Metadata.h
Camera.h
diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h
index e4808fe..def4c49 100644
--- a/src/crepe/api/Config.h
+++ b/src/crepe/api/Config.h
@@ -15,20 +15,10 @@ namespace crepe {
* modified *before* execution is handed over from the game programmer to the engine (i.e. the
* main loop is started).
*/
-class Config final {
-public:
+struct Config final {
//! Retrieve handle to global Config instance
static Config & get_instance();
-private:
- Config() = default;
- ~Config() = default;
- Config(const Config &) = default;
- Config(Config &&) = default;
- Config & operator=(const Config &) = default;
- Config & operator=(Config &&) = default;
-
-public:
//! Logging-related settings
struct {
/**
@@ -97,6 +87,12 @@ public:
*/
int font_size = 16;
} font;
+
+ //! Audio system settings
+ struct {
+ //! Max amount of simultanious voices
+ unsigned int voices = 32;
+ } audio;
};
} // namespace crepe
diff --git a/src/crepe/api/Text.cpp b/src/crepe/api/Text.cpp
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/crepe/api/Text.cpp
diff --git a/src/crepe/api/Text.h b/src/crepe/api/Text.h
new file mode 100644
index 0000000..5c527af
--- /dev/null
+++ b/src/crepe/api/Text.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "Asset.h"
+#include "../Component.h"
+class Text : public Component{
+public:
+ Text(game_object_id_t id, const Asset & font, int font_size);
+
+ int font_size = 16;
+ const Asset source;
+private:
+};
diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp
index 4d3abf5..ad50637 100644
--- a/src/crepe/facade/Sound.cpp
+++ b/src/crepe/facade/Sound.cpp
@@ -1,59 +1,13 @@
+#include "../api/Asset.h"
#include "../util/Log.h"
#include "Sound.h"
-#include "SoundContext.h"
using namespace crepe;
using namespace std;
-Sound::Sound(unique_ptr<Asset> res) {
+Sound::Sound(const Asset & src) : Resource(src) {
+ this->sample.load(src.get_path().c_str());
dbg_trace();
- this->load(std::move(res));
-}
-
-Sound::Sound(const char * src) {
- dbg_trace();
- this->load(make_unique<Asset>(src));
-}
-
-void Sound::load(unique_ptr<Asset> res) { this->sample.load(res->get_path().c_str()); }
-
-void Sound::play() {
- SoundContext & ctx = SoundContext::get_instance();
- 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 = SoundContext::get_instance();
- if (ctx.engine.getPause(this->handle)) return;
- ctx.engine.setPause(this->handle, true);
-}
-
-void Sound::rewind() {
- SoundContext & ctx = SoundContext::get_instance();
- if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
- ctx.engine.seek(this->handle, 0);
-}
-
-void Sound::set_volume(float volume) {
- this->volume = volume;
-
- SoundContext & ctx = SoundContext::get_instance();
- if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
- ctx.engine.setVolume(this->handle, this->volume);
-}
-
-void Sound::set_looping(bool looping) {
- this->looping = looping;
-
- SoundContext & ctx = SoundContext::get_instance();
- if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
- ctx.engine.setLooping(this->handle, this->looping);
}
+Sound::~Sound() { dbg_trace(); }
diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h
index ee43d94..85d141b 100644
--- a/src/crepe/facade/Sound.h
+++ b/src/crepe/facade/Sound.h
@@ -1,84 +1,30 @@
#pragma once
-#include <memory>
#include <soloud/soloud.h>
#include <soloud/soloud_wav.h>
-#include "../api/Asset.h"
+#include "../Resource.h"
namespace crepe {
+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:
- /**
- * \brief Pause this sample
- *
- * Pauses this sound if it is playing, or does nothing if it is already paused. The playhead
- * position is saved, such that calling \c play() after this function makes the sound resume.
- */
- void pause();
- /**
- * \brief Play this sample
- *
- * Resume playback if this sound is paused, or start from the beginning of the sample.
- *
- * \note This class only saves a reference to the most recent 'voice' of this sound. Calling
- * \c play() while the sound is already playing causes multiple instances of the sample to
- * play simultaniously. The sample started last is the one that is controlled afterwards.
- */
- void play();
- /**
- * \brief Reset playhead position
- *
- * Resets the playhead position so that calling \c play() after this function makes it play
- * from the start of the sample. If the sound is not paused before calling this function,
- * this function will stop playback.
- */
- void rewind();
- /**
- * \brief Set playback volume / gain
- *
- * \param volume Volume (0 = muted, 1 = full volume)
- */
- void set_volume(float volume);
- /**
- * \brief Get playback volume / gain
- *
- * \return Volume
- */
- float get_volume() const { return this->volume; }
- /**
- * \brief Set looping behavior for this sample
- *
- * \param looping Looping behavior (false = one-shot, true = loop)
- */
- void set_looping(bool looping);
- /**
- * \brief Get looping behavior
- *
- * \return true if looping, false if one-shot
- */
- bool get_looping() const { return this->looping; }
-
+class Sound : public Resource {
public:
- Sound(const char * src);
- Sound(std::unique_ptr<Asset> res);
-
-private:
- void load(std::unique_ptr<Asset> res);
+ Sound(const Asset & src);
+ ~Sound(); // dbg_trace
private:
+ //! Deserialized resource (soloud)
SoLoud::Wav sample;
- SoLoud::handle handle;
-
- float volume = 1.0f;
- bool looping = false;
+ //! SoundContext uses \c sample
+ friend class SoundContext;
};
} // namespace crepe
diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp
index deb2b62..d18afc6 100644
--- a/src/crepe/facade/SoundContext.cpp
+++ b/src/crepe/facade/SoundContext.cpp
@@ -4,17 +4,34 @@
using namespace crepe;
-SoundContext & SoundContext::get_instance() {
- static SoundContext instance;
- return instance;
-}
-
SoundContext::SoundContext() {
dbg_trace();
- engine.init();
+ this->engine.init();
+ this->engine.setMaxActiveVoiceCount(this->config.audio.voices);
}
SoundContext::~SoundContext() {
dbg_trace();
- engine.deinit();
+ this->engine.deinit();
+}
+
+SoundHandle SoundContext::play(Sound & resource) {
+ SoLoud::handle real_handle = this->engine.play(resource.sample, 1.0f);
+ SoundHandle handle = this->next_handle;
+ this->registry[handle] = real_handle;
+ this->next_handle++;
+ return handle;
+}
+
+void SoundContext::stop(const SoundHandle & handle) {
+ this->engine.stop(this->registry[handle]);
}
+
+void SoundContext::set_volume(const SoundHandle & handle, float volume) {
+ this->engine.setVolume(this->registry[handle], volume);
+}
+
+void SoundContext::set_loop(const SoundHandle & handle, bool loop) {
+ this->engine.setLooping(this->registry[handle], loop);
+}
+
diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h
index d703c16..102f928 100644
--- a/src/crepe/facade/SoundContext.h
+++ b/src/crepe/facade/SoundContext.h
@@ -2,6 +2,9 @@
#include <soloud/soloud.h>
+#include "../api/Config.h"
+
+#include "SoundHandle.h"
#include "Sound.h"
namespace crepe {
@@ -9,23 +12,71 @@ 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 {
-private:
- // singleton
+public:
SoundContext();
virtual ~SoundContext();
+
SoundContext(const SoundContext &) = delete;
SoundContext(SoundContext &&) = delete;
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 SoundHandle play(Sound & resource);
+ /**
+ * \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(const SoundHandle & 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(const SoundHandle & 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(const SoundHandle & handle, bool loop);
+
private:
- static SoundContext & get_instance();
+ //! Abstracted class
SoLoud::Soloud engine;
- friend class Sound;
+
+ //! Config reference
+ Config & config = Config::get_instance();
+
+ //! Sound handle registry
+ std::unordered_map<SoundHandle, SoLoud::handle> registry;
+ //! Unique handle counter
+ SoundHandle next_handle = 0;
+
};
} // namespace crepe
diff --git a/src/crepe/facade/SoundHandle.h b/src/crepe/facade/SoundHandle.h
new file mode 100644
index 0000000..131d28c
--- /dev/null
+++ b/src/crepe/facade/SoundHandle.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <cstddef>
+
+namespace crepe {
+
+/**
+ * \brief Voice handle returned by
+ */
+typedef size_t SoundHandle;
+
+}
+
diff --git a/src/crepe/facade/font.h b/src/crepe/facade/font.h
index f5c7785..a8d8040 100644
--- a/src/crepe/facade/font.h
+++ b/src/crepe/facade/font.h
@@ -12,36 +12,20 @@ namespace crepe {
*
* This class is a wrapper around an SDL_ttf font instance, encapsulating font loading and usage.
*/
-class Font {
+class Font : public Resource{
public:
/**
- * \param src The file path to the font file.
- */
- Font(const char* src);
-
- /**
* \param res A unique pointer to an Asset holding the font resource.
*/
- Font(std::unique_ptr<Asset> res);
+ Font(const Asset & src, Mediator & mediator);
/**
* \brief Destructor to clean up font resources.
*/
~Font() = default;
-
- void draw(const vec2& pos, const vec2&)
-private:
- /**
- * \brief Load the font from the given resource.
- *
- * This method is used by both constructors to load the font resource.
- *
- * \param res A unique pointer to an Asset holding the font resource.
- */
- void load(std::unique_ptr<Asset> res);
-
private:
- std::unique_ptr<TTF_Font, decltype(&TTF_CloseFont)> font; ///< The SDL_ttf font object with custom deleter.
+ //! The SDL_ttf font object with custom deleter.
+ std::unique_ptr<TTF_Font, decltype(&TTF_CloseFont)> font;
};
} // namespace crepe
diff --git a/src/crepe/manager/CMakeLists.txt b/src/crepe/manager/CMakeLists.txt
index 517b8a2..480c8ee 100644
--- a/src/crepe/manager/CMakeLists.txt
+++ b/src/crepe/manager/CMakeLists.txt
@@ -4,6 +4,7 @@ target_sources(crepe PUBLIC
Manager.cpp
SaveManager.cpp
SceneManager.cpp
+ ResourceManager.cpp
)
target_sources(crepe PUBLIC FILE_SET HEADERS FILES
@@ -16,5 +17,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
SaveManager.h
SceneManager.h
SceneManager.hpp
+ ResourceManager.h
+ ResourceManager.hpp
)
diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h
index 8094d80..1d6f307 100644
--- a/src/crepe/manager/Mediator.h
+++ b/src/crepe/manager/Mediator.h
@@ -7,11 +7,14 @@
#include "EventManager.h"
#include "SaveManager.h"
#include "api/LoopTimer.h"
+#include "EventManager.h"
+#include "SaveManager.h"
namespace crepe {
class ComponentManager;
class SceneManager;
+class ResourceManager;
/**
* Struct to pass references to classes that would otherwise need to be singletons down to
@@ -32,6 +35,7 @@ struct Mediator {
OptionalRef<EventManager> event_manager = EventManager::get_instance();
OptionalRef<SDLContext> sdl_context = SDLContext::get_instance();
OptionalRef<LoopTimer> timer = LoopTimer::get_instance();
+ OptionalRef<ResourceManager> resource_manager;
};
} // namespace crepe
diff --git a/src/crepe/manager/ResourceManager.cpp b/src/crepe/manager/ResourceManager.cpp
new file mode 100644
index 0000000..7c01808
--- /dev/null
+++ b/src/crepe/manager/ResourceManager.cpp
@@ -0,0 +1,30 @@
+#include "util/Log.h"
+
+#include "ResourceManager.h"
+
+using namespace crepe;
+using namespace std;
+
+ResourceManager::ResourceManager(Mediator & mediator) : Manager(mediator) {
+ mediator.resource_manager = *this;
+ dbg_trace();
+}
+ResourceManager::~ResourceManager() { dbg_trace(); }
+
+void ResourceManager::clear() {
+ std::erase_if(this->resources, [](const pair<const Asset, CacheEntry> & pair) {
+ const CacheEntry & entry = pair.second;
+ return entry.persistent == false;
+ });
+}
+
+void ResourceManager::clear_all() { this->resources.clear(); }
+
+void ResourceManager::set_persistent(const Asset & asset, bool persistent) {
+ this->get_entry(asset).persistent = persistent;
+}
+
+ResourceManager::CacheEntry & ResourceManager::get_entry(const Asset & asset) {
+ if (!this->resources.contains(asset)) this->resources[asset] = {};
+ return this->resources.at(asset);
+}
diff --git a/src/crepe/manager/ResourceManager.h b/src/crepe/manager/ResourceManager.h
new file mode 100644
index 0000000..84b275d
--- /dev/null
+++ b/src/crepe/manager/ResourceManager.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include <memory>
+#include <unordered_map>
+
+#include "../Resource.h"
+#include "../api/Asset.h"
+
+#include "Manager.h"
+
+namespace crepe {
+
+/**
+ * \brief Owner of concrete Resource instances
+ *
+ * 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:
+ ResourceManager(Mediator & mediator);
+ 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;
+ };
+ //! 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();
+};
+
+} // namespace crepe
+
+#include "ResourceManager.hpp"
diff --git a/src/crepe/manager/ResourceManager.hpp b/src/crepe/manager/ResourceManager.hpp
new file mode 100644
index 0000000..5167d71
--- /dev/null
+++ b/src/crepe/manager/ResourceManager.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <format>
+
+#include "ResourceManager.h"
+
+namespace crepe {
+
+template <typename T>
+T & ResourceManager::get(const Asset & asset) {
+ using namespace std;
+ static_assert(is_base_of<Resource, T>::value,
+ "cache must recieve a derivative class of Resource");
+
+ CacheEntry & entry = this->get_entry(asset);
+ if (entry.resource == nullptr) entry.resource = make_unique<T>(asset);
+
+ T * concrete_resource = dynamic_cast<T *>(entry.resource.get());
+ 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/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
new file mode 100644
index 0000000..c1cde8b
--- /dev/null
+++ b/src/crepe/system/AudioSystem.cpp
@@ -0,0 +1,65 @@
+#include "AudioSystem.h"
+
+#include "../manager/ComponentManager.h"
+#include "../manager/ResourceManager.h"
+#include "../types.h"
+
+using namespace crepe;
+using namespace std;
+
+void AudioSystem::update() {
+ ComponentManager & component_manager = this->mediator.component_manager;
+ ResourceManager & resource_manager = this->mediator.resource_manager;
+ RefVector<AudioSource> components
+ = component_manager.get_components_by_type<AudioSource>();
+
+ for (AudioSource & component : components) {
+ Sound & resource = resource_manager.get<Sound>(component.source);
+
+ this->diff_update(component, resource);
+
+ this->update_last(component);
+ }
+}
+
+void AudioSystem::diff_update(AudioSource & component, Sound & resource) {
+ SoundContext & context = this->get_context();
+
+ if (component.active != component.last_active) {
+ if (component.active) {
+ component.oneshot_play = component.play_on_awake;
+ } else {
+ context.stop(component.voice);
+ return;
+ }
+ }
+ if (!component.active) return;
+
+ if (component.oneshot_play) {
+ component.voice = context.play(resource);
+ component.oneshot_play = false;
+ }
+ if (component.oneshot_stop) {
+ context.stop(component.voice);
+ component.oneshot_stop = false;
+ }
+ if (component.volume != component.last_volume) {
+ context.set_volume(component.voice, component.volume);
+ }
+ if (component.loop != component.last_loop) {
+ context.set_loop(component.voice, component.loop);
+ }
+}
+
+void AudioSystem::update_last(AudioSource & component) {
+ component.last_active = component.active;
+ component.last_loop = component.loop;
+ component.last_volume = component.volume;
+}
+
+SoundContext & AudioSystem::get_context() {
+ if (this->context == nullptr)
+ this->context = make_unique<SoundContext>();
+ return *this->context.get();
+}
+
diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h
new file mode 100644
index 0000000..2ddc443
--- /dev/null
+++ b/src/crepe/system/AudioSystem.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "../api/AudioSource.h"
+#include "../facade/Sound.h"
+#include "../facade/SoundContext.h"
+
+#include "System.h"
+
+namespace crepe {
+
+class AudioSystem : public System {
+public:
+ using System::System;
+ void update() override;
+
+private:
+ /**
+ * \brief Update `last_*` members of \c component
+ *
+ * Copies all component properties stored for comparison between AudioSystem::update() calls
+ *
+ * \param component AudioSource component to update
+ */
+ void update_last(AudioSource & component);
+
+ /**
+ * \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 resource Sound instance for AudioSource's Asset
+ */
+ void diff_update(AudioSource & component, 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:
+ //! SoundContext
+ std::unique_ptr<SoundContext> context = nullptr;
+};
+
+} // namespace crepe
diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt
index 95f6e33..6b2e099 100644
--- a/src/crepe/system/CMakeLists.txt
+++ b/src/crepe/system/CMakeLists.txt
@@ -5,6 +5,7 @@ target_sources(crepe PUBLIC
PhysicsSystem.cpp
CollisionSystem.cpp
RenderSystem.cpp
+ AudioSystem.cpp
AnimatorSystem.cpp
InputSystem.cpp
)
@@ -15,6 +16,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
PhysicsSystem.h
CollisionSystem.h
RenderSystem.h
+ AudioSystem.h
AnimatorSystem.h
InputSystem.h
)
diff --git a/src/crepe/system/System.cpp b/src/crepe/system/System.cpp
index f68549b..ecc740d 100644
--- a/src/crepe/system/System.cpp
+++ b/src/crepe/system/System.cpp
@@ -1,7 +1,5 @@
-#include "../util/Log.h"
-
#include "System.h"
using namespace crepe;
-System::System(const Mediator & mediator) : mediator(mediator) { dbg_trace(); }
+System::System(const Mediator & mediator) : mediator(mediator) {}
diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt
index 7fc81d7..4203866 100644
--- a/src/example/CMakeLists.txt
+++ b/src/example/CMakeLists.txt
@@ -16,7 +16,6 @@ function(add_example target_name)
add_dependencies(examples ${target_name})
endfunction()
-add_example(asset_manager)
add_example(savemgr)
add_example(rendering_particle)
add_example(game)
diff --git a/src/example/asset_manager.cpp b/src/example/asset_manager.cpp
deleted file mode 100644
index 917b547..0000000
--- a/src/example/asset_manager.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-#include <crepe/api/AssetManager.h>
-#include <crepe/api/Texture.h>
-#include <crepe/facade/Sound.h>
-
-using namespace crepe;
-
-int main() {
-
- // this needs to be called before the asset manager otherwise the destructor of sdl is not in
- // the right order
- { Texture test("../asset/texture/img.png"); }
- // FIXME: make it so the issue described by the above comment is not possible (i.e. the order
- // in which internal classes are instantiated should not impact the way the engine works).
-
- auto & mgr = AssetManager::get_instance();
-
- {
- // TODO: [design] the Sound class can't be directly included by the user as it includes
- // SoLoud headers.
- auto bgm = mgr.cache<Sound>("../mwe/audio/bgm.ogg");
- auto sfx1 = mgr.cache<Sound>("../mwe/audio/sfx1.wav");
- auto sfx2 = mgr.cache<Sound>("../mwe/audio/sfx2.wav");
-
- auto img = mgr.cache<Texture>("../asset/texture/img.png");
- auto img1 = mgr.cache<Texture>("../asset/texture/second.png");
- }
-
- {
- auto bgm = mgr.cache<Sound>("../mwe/audio/bgm.ogg");
- auto sfx1 = mgr.cache<Sound>("../mwe/audio/sfx1.wav");
- auto sfx2 = mgr.cache<Sound>("../mwe/audio/sfx2.wav");
-
- auto img = mgr.cache<Texture>("../asset/texture/img.png");
- auto img1 = mgr.cache<Texture>("../asset/texture/second.png");
- }
-}
diff --git a/src/test/AudioTest.cpp b/src/test/AudioTest.cpp
new file mode 100644
index 0000000..774fdb8
--- /dev/null
+++ b/src/test/AudioTest.cpp
@@ -0,0 +1,152 @@
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <crepe/api/AudioSource.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/manager/ComponentManager.h>
+#include <crepe/manager/ResourceManager.h>
+#include <crepe/system/AudioSystem.h>
+
+using namespace std;
+using namespace std::chrono_literals;
+using namespace crepe;
+using namespace testing;
+
+class AudioTest : public Test {
+private:
+ class TestSoundContext : public SoundContext {
+ public:
+ MOCK_METHOD(SoundHandle, play, (Sound & resource), (override));
+ MOCK_METHOD(void, stop, (const SoundHandle &), (override));
+ MOCK_METHOD(void, set_volume, (const SoundHandle &, float), (override));
+ MOCK_METHOD(void, set_loop, (const SoundHandle &, bool), (override));
+ };
+
+ class TestAudioSystem : public AudioSystem {
+ public:
+ using AudioSystem::AudioSystem;
+ StrictMock<TestSoundContext> context;
+ virtual SoundContext & get_context() { return this->context; }
+ };
+
+private:
+ Mediator mediator;
+ ComponentManager component_manager{mediator};
+ ResourceManager resource_manager{mediator};
+
+public:
+ TestAudioSystem system{mediator};
+ TestSoundContext & context = system.context;
+
+private:
+ GameObject entity = component_manager.new_object("name");
+
+public:
+ AudioSource & component = entity.add_component<AudioSource>("mwe/audio/bgm.ogg");
+};
+
+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);
+ system.update();
+}
+
+TEST_F(AudioTest, Play) {
+ system.update();
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, play(_)).Times(0);
+ component.play();
+ }
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, play(_)).Times(1);
+ system.update();
+ }
+}
+
+TEST_F(AudioTest, Stop) {
+ system.update();
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, stop(_)).Times(0);
+ component.stop();
+ }
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, stop(_)).Times(1);
+ system.update();
+ }
+}
+
+TEST_F(AudioTest, Volume) {
+ system.update();
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, set_volume(_, _)).Times(0);
+ component.volume += 0.2;
+ }
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, set_volume(_, component.volume)).Times(1);
+ system.update();
+ }
+}
+
+TEST_F(AudioTest, Looping) {
+ system.update();
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, set_loop(_, _)).Times(0);
+ component.loop = !component.loop;
+ }
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, set_loop(_, component.loop)).Times(1);
+ system.update();
+ }
+}
+
+TEST_F(AudioTest, StopOnDeactivate) {
+ system.update();
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, stop(_)).Times(1);
+ component.active = false;
+ system.update();
+ }
+}
+
+TEST_F(AudioTest, PlayOnActive) {
+ component.active = false;
+ component.play_on_awake = true;
+ system.update();
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, play(_)).Times(1);
+ component.active = true;
+ system.update();
+ }
+}
diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt
index c9cbac5..9f986a0 100644
--- a/src/test/CMakeLists.txt
+++ b/src/test/CMakeLists.txt
@@ -4,7 +4,9 @@ target_sources(test_main PUBLIC
PhysicsTest.cpp
ScriptTest.cpp
ParticleTest.cpp
+ AudioTest.cpp
AssetTest.cpp
+ ResourceManagerTest.cpp
OptionalRefTest.cpp
RenderSystemTest.cpp
EventTest.cpp
diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp
new file mode 100644
index 0000000..b6be3c0
--- /dev/null
+++ b/src/test/ResourceManagerTest.cpp
@@ -0,0 +1,71 @@
+#include <gtest/gtest.h>
+
+#define private public
+#define protected public
+
+#include <crepe/api/GameObject.h>
+#include <crepe/manager/ResourceManager.h>
+#include <crepe/util/Log.h>
+
+using namespace std;
+using namespace crepe;
+using namespace testing;
+
+class ResourceManagerTest : public Test {
+ Mediator mediator;
+
+public:
+ ResourceManager resource_manager{mediator};
+
+ Asset asset_a{"asset/texture/img.png"};
+ Asset asset_b{"asset/texture/ERROR.png"};
+
+ class TestResource : public Resource {
+ public:
+ static unsigned instances;
+
+ public:
+ const unsigned instance;
+ TestResource(const Asset & src) : Resource(src), instance(this->instances++) {}
+ ~TestResource() { this->instances--; }
+ bool operator==(const TestResource & other) const {
+ return this->instance == other.instance;
+ }
+ };
+
+private:
+ void SetUp() override { TestResource::instances = 0; }
+};
+unsigned ResourceManagerTest::TestResource::instances = 0;
+
+TEST_F(ResourceManagerTest, Default) {
+ TestResource & res_1 = resource_manager.get<TestResource>(asset_a);
+ TestResource & res_2 = resource_manager.get<TestResource>(asset_a);
+ TestResource & res_3 = resource_manager.get<TestResource>(asset_b);
+ TestResource & res_4 = resource_manager.get<TestResource>(asset_b);
+
+ ASSERT_EQ(res_1, res_2);
+ ASSERT_EQ(res_3, res_4);
+ EXPECT_NE(res_1, res_3);
+
+ EXPECT_EQ(TestResource::instances, 2);
+
+ resource_manager.clear();
+}
+
+TEST_F(ResourceManagerTest, Persistent) {
+ resource_manager.set_persistent(asset_a, true);
+ EXPECT_EQ(TestResource::instances, 0);
+
+ resource_manager.get<TestResource>(asset_a);
+ resource_manager.get<TestResource>(asset_a);
+ resource_manager.get<TestResource>(asset_b);
+ resource_manager.get<TestResource>(asset_b);
+ EXPECT_EQ(TestResource::instances, 2);
+
+ resource_manager.clear();
+ EXPECT_EQ(TestResource::instances, 1);
+
+ resource_manager.clear_all();
+ EXPECT_EQ(TestResource::instances, 0);
+}
diff --git a/src/test/main.cpp b/src/test/main.cpp
index aece72d..ed2aed5 100644
--- a/src/test/main.cpp
+++ b/src/test/main.cpp
@@ -1,9 +1,5 @@
-#include <gtest/gtest.h>
-
-#define protected public
-#define private public
-
#include <crepe/api/Config.h>
+#include <gtest/gtest.h>
using namespace crepe;
using namespace testing;
@@ -11,12 +7,14 @@ using namespace testing;
class GlobalConfigReset : public EmptyTestEventListener {
public:
Config & cfg = Config::get_instance();
- Config cfg_default = Config();
// This function is called before each test
void OnTestStart(const TestInfo &) override {
- cfg = cfg_default;
- cfg.log.level = Log::Level::WARNING;
+ cfg = {
+ .log = {
+ .level = Log::Level::WARNING,
+ },
+ };
}
};