diff options
79 files changed, 1363 insertions, 513 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c3f29da..97b21f0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -40,5 +40,6 @@ install( target_link_libraries(test_main PRIVATE gtest + PRIVATE gmock PUBLIC crepe ) diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt index 7e176e7..6cbb9fe 100644 --- a/src/crepe/CMakeLists.txt +++ b/src/crepe/CMakeLists.txt @@ -1,21 +1,21 @@ target_sources(crepe PUBLIC Particle.cpp - ComponentManager.cpp Component.cpp Collider.cpp + Resource.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES - ComponentManager.h - ComponentManager.hpp Component.h Collider.h ValueBroker.h ValueBroker.hpp + Resource.h ) add_subdirectory(api) add_subdirectory(facade) +add_subdirectory(manager) add_subdirectory(system) add_subdirectory(util) diff --git a/src/crepe/Component.h b/src/crepe/Component.h index dc17721..67bcc68 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..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 new file mode 100644 index 0000000..a0c8859 --- /dev/null +++ b/src/crepe/Resource.h @@ -0,0 +1,26 @@ +#pragma once + +namespace crepe { + +class ResourceManager; +class Asset; + +/** + * Resource is an interface class used to represent a (deserialized) game + * resource (e.g. textures, sounds). + */ +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/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.h b/src/crepe/api/AssetManager.h deleted file mode 100644 index fee6780..0000000 --- a/src/crepe/api/AssetManager.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include <any> -#include <memory> -#include <string> -#include <unordered_map> - -namespace crepe { - -/** - * \brief The AssetManager is responsible for storing and managing assets over multiple scenes. - * - * The AssetManager 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 AssetManager is destroyed, at which point the cached assets are - * cleared. - */ -class AssetManager { - -private: - //! A cache that holds all the assets, accessible by their file path, over multiple scenes. - std::unordered_map<std::string, std::any> asset_cache; - -private: - AssetManager(); - virtual ~AssetManager(); - -public: - AssetManager(const AssetManager &) = delete; - AssetManager(AssetManager &&) = delete; - AssetManager & operator=(const AssetManager &) = delete; - AssetManager & operator=(AssetManager &&) = delete; - - /** - * \brief Retrieves the singleton instance of the AssetManager. - * - * \return A reference to the single instance of the AssetManager. - */ - static AssetManager & get_instance(); - -public: - /** - * \brief Caches an asset by loading it from the given file path. - * - * \param file_path The path to the asset file to load. - * \param reload If true, the asset will be reloaded from the file, even if 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. - * - * 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 cache. - */ - template <typename T> - std::shared_ptr<T> cache(const std::string & file_path, bool reload = false); -}; - -} // namespace crepe - -#include "AssetManager.hpp" 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..cc70801 --- /dev/null +++ b/src/crepe/api/AudioSource.cpp @@ -0,0 +1,19 @@ +#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..1899c22 --- /dev/null +++ b/src/crepe/api/AudioSource.h @@ -0,0 +1,65 @@ +#pragma once + +#include "../Component.h" +#include "../types.h" +#include "../util/Private.h" + +#include "GameObject.h" +#include "Asset.h" + +namespace crepe { + +class AudioSystem; + +//! Audio source component +class AudioSource : public Component { + //! AudioSource components are handled by AudioSystem + friend class AudioSystem; + +protected: + AudioSource(game_object_id_t id, const Asset & source); + //! Only ComponentManager can create components + friend class ComponentManager; +public: + // But std::unique_ptr needs to be able to destoy this component again + virtual ~AudioSource() = default; + +public: + //! Start or resume 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; + //! \} + +private: + //! AudioSystem::ComponentPrivate + Private private_data; +}; + +} // namespace crepe + diff --git a/src/crepe/api/BehaviorScript.cpp b/src/crepe/api/BehaviorScript.cpp index 7bbace0..d22afdf 100644 --- a/src/crepe/api/BehaviorScript.cpp +++ b/src/crepe/api/BehaviorScript.cpp @@ -4,12 +4,12 @@ using namespace crepe; -BehaviorScript::BehaviorScript(game_object_id_t id, ComponentManager & mgr) +BehaviorScript::BehaviorScript(game_object_id_t id, Mediator & mediator) : Component(id), - component_manager(mgr) {} + mediator(mediator) {} template <> BehaviorScript & GameObject::add_component<BehaviorScript>() { ComponentManager & mgr = this->component_manager; - return mgr.add_component<BehaviorScript>(this->id, mgr); + return mgr.add_component<BehaviorScript>(this->id, mgr.mediator); } diff --git a/src/crepe/api/BehaviorScript.h b/src/crepe/api/BehaviorScript.h index d556fe5..3909b96 100644 --- a/src/crepe/api/BehaviorScript.h +++ b/src/crepe/api/BehaviorScript.h @@ -23,14 +23,13 @@ class BehaviorScript : public Component { protected: /** * \param id Parent \c GameObject id - * \param component_manager Reference to component manager (passed through to \c Script - * instance) + * \param mediator Mediator reference * * \note Calls to this constructor (should) always pass through \c GameObject::add_component, * which has an exception for this specific component type. This was done so the user does * not have to pass references used within \c Script to each \c BehaviorScript instance. */ - BehaviorScript(game_object_id_t id, ComponentManager & component_manager); + BehaviorScript(game_object_id_t id, Mediator & mediator); //! Only ComponentManager is allowed to instantiate BehaviorScript friend class ComponentManager; @@ -55,8 +54,8 @@ protected: friend class ScriptSystem; protected: - //! Reference to component manager (passed to Script) - ComponentManager & component_manager; + //! Reference mediator + Mediator & mediator; }; /** diff --git a/src/crepe/api/BehaviorScript.hpp b/src/crepe/api/BehaviorScript.hpp index bd59337..6de0157 100644 --- a/src/crepe/api/BehaviorScript.hpp +++ b/src/crepe/api/BehaviorScript.hpp @@ -14,11 +14,11 @@ BehaviorScript & BehaviorScript::set_script(Args &&... args) { dbg_trace(); static_assert(std::is_base_of<Script, T>::value); Script * s = new T(std::forward<Args>(args)...); + Mediator & mediator = this->mediator; s->game_object_id = this->game_object_id; s->active = this->active; - s->component_manager = this->component_manager; - s->event_manager = EventManager::get_instance(); + s->mediator = mediator; this->script = std::unique_ptr<Script>(s); return *this; diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 50c51ed..0808612 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,15 +7,11 @@ target_sources(crepe PUBLIC Transform.cpp Color.cpp Texture.cpp - AssetManager.cpp Sprite.cpp - SaveManager.cpp Config.cpp Metadata.cpp - SceneManager.cpp Camera.cpp Animator.cpp - EventManager.cpp IKeyListener.cpp IMouseListener.cpp LoopManager.cpp @@ -26,7 +22,7 @@ target_sources(crepe PUBLIC ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES - # AudioSource.h + AudioSource.h BehaviorScript.h Config.h Script.h @@ -39,17 +35,10 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Vector2.hpp Color.h Texture.h - AssetManager.h - AssetManager.hpp - SaveManager.h Scene.h Metadata.h - SceneManager.h - SceneManager.hpp Camera.h Animator.h - EventManager.h - EventManager.hpp EventHandler.h EventHandler.hpp Event.h diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h index 225e9b9..7be506e 100644 --- a/src/crepe/api/Config.h +++ b/src/crepe/api/Config.h @@ -1,8 +1,10 @@ #pragma once +#include <string> + #include "../util/Log.h" + #include "types.h" -#include <string> namespace crepe { @@ -13,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 { /** @@ -85,6 +77,12 @@ public: */ std::string root_pattern = ".crepe-root"; } asset; + + //! Audio system settings + struct { + //! Max amount of simultanious voices + unsigned int voices = 32; + } audio; }; } // namespace crepe diff --git a/src/crepe/api/GameObject.hpp b/src/crepe/api/GameObject.hpp index 17b17d7..a6b45b0 100644 --- a/src/crepe/api/GameObject.hpp +++ b/src/crepe/api/GameObject.hpp @@ -1,6 +1,6 @@ #pragma once -#include "../ComponentManager.h" +#include "../manager/ComponentManager.h" #include "GameObject.h" diff --git a/src/crepe/api/IKeyListener.h b/src/crepe/api/IKeyListener.h index 328a4c2..6ded107 100644 --- a/src/crepe/api/IKeyListener.h +++ b/src/crepe/api/IKeyListener.h @@ -1,8 +1,9 @@ #pragma once +#include "../manager/EventManager.h" + #include "Event.h" #include "EventHandler.h" -#include "EventManager.h" namespace crepe { diff --git a/src/crepe/api/IMouseListener.h b/src/crepe/api/IMouseListener.h index 15e1619..9e4fdf7 100644 --- a/src/crepe/api/IMouseListener.h +++ b/src/crepe/api/IMouseListener.h @@ -1,8 +1,9 @@ #pragma once +#include "../manager/EventManager.h" + #include "Event.h" #include "EventHandler.h" -#include "EventManager.h" namespace crepe { diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp index 7edf4d1..b277185 100644 --- a/src/crepe/api/LoopManager.cpp +++ b/src/crepe/api/LoopManager.cpp @@ -1,5 +1,3 @@ -#include "../facade/SDLContext.h" - #include "../system/AnimatorSystem.h" #include "../system/CollisionSystem.h" #include "../system/ParticleSystem.h" @@ -8,12 +6,14 @@ #include "../system/ScriptSystem.h" #include "LoopManager.h" -#include "LoopTimer.h" using namespace crepe; using namespace std; LoopManager::LoopManager() { + this->mediator.component_manager = this->component_manager; + this->mediator.scene_manager = this->scene_manager; + this->load_system<AnimatorSystem>(); this->load_system<CollisionSystem>(); this->load_system<ParticleSystem>(); @@ -23,7 +23,7 @@ LoopManager::LoopManager() { } void LoopManager::process_input() { - SDLContext::get_instance().handle_events(this->game_running); + this->sdl_context.handle_events(this->game_running); } void LoopManager::start() { @@ -35,7 +35,7 @@ void LoopManager::set_running(bool running) { this->game_running = running; } void LoopManager::fixed_update() {} void LoopManager::loop() { - LoopTimer & timer = LoopTimer::get_instance(); + LoopTimer & timer = this->loop_timer; timer.start(); while (game_running) { @@ -55,15 +55,18 @@ void LoopManager::loop() { } void LoopManager::setup() { + LoopTimer & timer = this->loop_timer; + this->game_running = true; - LoopTimer::get_instance().start(); - LoopTimer::get_instance().set_fps(200); + timer.start(); + timer.set_fps(200); } void LoopManager::render() { - if (this->game_running) { - this->get_system<RenderSystem>().update(); - } + if (!this->game_running) return; + + this->get_system<RenderSystem>().update(); } -void LoopManager::update() { LoopTimer & timer = LoopTimer::get_instance(); } +void LoopManager::update() {} + diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h index 13e6dac..6ea5ccc 100644 --- a/src/crepe/api/LoopManager.h +++ b/src/crepe/api/LoopManager.h @@ -2,9 +2,12 @@ #include <memory> -#include "../ComponentManager.h" +#include "../manager/ComponentManager.h" +#include "../manager/SceneManager.h" #include "../system/System.h" -#include "api/SceneManager.h" +#include "../facade/SDLContext.h" + +#include "LoopTimer.h" namespace crepe { @@ -85,10 +88,18 @@ private: bool game_running = false; private: + //! Global context + Mediator mediator; + //! Component manager instance - ComponentManager component_manager{}; + ComponentManager component_manager{mediator}; //! Scene manager instance - SceneManager scene_manager{component_manager}; + SceneManager scene_manager{mediator}; + + //! SDL context \todo no more singletons! + SDLContext & sdl_context = SDLContext::get_instance(); + //! Loop timer \todo no more singletons! + LoopTimer & loop_timer = LoopTimer::get_instance(); private: /** diff --git a/src/crepe/api/LoopManager.hpp b/src/crepe/api/LoopManager.hpp index 9cf470b..cd6bd02 100644 --- a/src/crepe/api/LoopManager.hpp +++ b/src/crepe/api/LoopManager.hpp @@ -38,8 +38,11 @@ void LoopManager::load_system() { static_assert(is_base_of<System, T>::value, "load_system must recieve a derivative class of System"); - System * system = new T(this->component_manager); - this->systems[typeid(T)] = unique_ptr<System>(system); + System * system = new T(this->mediator); + const type_info & type = typeid(T); + if (this->systems.contains(type)) + throw runtime_error(format("LoopManager: {} is already initialized", type.name())); + this->systems[type] = unique_ptr<System>(system); } } // namespace crepe diff --git a/src/crepe/api/Scene.h b/src/crepe/api/Scene.h index f6fdb2a..66dad17 100644 --- a/src/crepe/api/Scene.h +++ b/src/crepe/api/Scene.h @@ -3,6 +3,7 @@ #include <string> #include "../util/OptionalRef.h" +#include "../manager/Mediator.h" namespace crepe { @@ -34,6 +35,8 @@ public: */ virtual std::string get_name() const = 0; + // TODO: Late references should ALWAYS be private! This is currently kept as-is so unit tests + // keep passing, but this reference should not be directly accessible by the user!!! protected: /** * \name Late references @@ -46,8 +49,8 @@ protected: * * \{ */ - //! Reference to the ComponentManager - OptionalRef<ComponentManager> component_manager; + //! Mediator reference + OptionalRef<Mediator> mediator; //! \} }; diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp index fcbe4c7..a27838e 100644 --- a/src/crepe/api/Script.cpp +++ b/src/crepe/api/Script.cpp @@ -1,11 +1,17 @@ +#include <string> + +#include "../manager/SceneManager.h" + #include "Script.h" using namespace crepe; +using namespace std; Script::~Script() { - EventManager & evmgr = this->event_manager; + Mediator & mediator = this->mediator; + EventManager & mgr = mediator.event_manager; for (auto id : this->listeners) { - evmgr.unsubscribe(id); + mgr.unsubscribe(id); } } @@ -13,3 +19,10 @@ template <> void Script::subscribe(const EventHandler<CollisionEvent> & callback) { this->subscribe_internal(callback, this->game_object_id); } + +void Script::set_next_scene(const string & name) { + Mediator & mediator = this->mediator; + SceneManager & mgr = mediator.scene_manager; + mgr.set_next_scene(name); +} + diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h index a0870cb..e1f86b2 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -4,8 +4,8 @@ #include "../types.h" #include "../util/OptionalRef.h" - -#include "EventManager.h" +#include "../manager/Mediator.h" +#include "../manager/EventManager.h" namespace crepe { @@ -106,6 +106,12 @@ protected: template <typename EventType> void subscribe(const EventHandler<EventType> & callback); + /** + * \brief Set the next scene using SceneManager + * \see SceneManager::set_next_scene + */ + void set_next_scene(const std::string & name); + //! \} private: @@ -160,10 +166,8 @@ private: game_object_id_t game_object_id; //! Reference to parent component OptionalRef<bool> active; - //! Reference to component manager instance - OptionalRef<ComponentManager> component_manager; - //! Reference to event manager instance - OptionalRef<EventManager> event_manager; + //! Mediator reference + OptionalRef<Mediator> mediator; //! \} private: diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp index a2463bf..45f1ff1 100644 --- a/src/crepe/api/Script.hpp +++ b/src/crepe/api/Script.hpp @@ -1,6 +1,6 @@ #pragma once -#include "../ComponentManager.h" +#include "../manager/ComponentManager.h" #include "BehaviorScript.h" #include "Script.h" @@ -20,7 +20,8 @@ T & Script::get_component() const { template <typename T> RefVector<T> Script::get_components() const { - ComponentManager & mgr = this->component_manager; + Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; return mgr.get_components_by_id<T>(this->game_object_id); } @@ -33,7 +34,8 @@ void Script::logf(Args &&... args) { template <typename EventType> void Script::subscribe_internal(const EventHandler<EventType> & callback, event_channel_t channel) { - EventManager & mgr = this->event_manager; + Mediator & mediator = this->mediator; + EventManager & mgr = mediator.event_manager; subscription_t listener = mgr.subscribe<EventType>( [this, callback](const EventType & data) -> bool { bool & active = this->active; diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp index 4d3abf5..0df1f48 100644 --- a/src/crepe/facade/Sound.cpp +++ b/src/crepe/facade/Sound.cpp @@ -1,3 +1,4 @@ +#include "../api/Asset.h" #include "../util/Log.h" #include "Sound.h" @@ -6,54 +7,42 @@ 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() { 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); +// } -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); -} diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h index 4c68f32..35bccdb 100644 --- a/src/crepe/facade/Sound.h +++ b/src/crepe/facade/Sound.h @@ -1,84 +1,33 @@ #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. */ -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); + Sound(const Asset & src); + ~Sound(); // dbg_trace -private: - void load(std::unique_ptr<Asset> res); + struct Handle { + SoLoud::handle handle; + }; private: SoLoud::Wav sample; - SoLoud::handle handle; - float volume = 1.0f; - bool looping = false; + friend class SoundContext; }; } // namespace crepe diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp index deb2b62..3ae5956 100644 --- a/src/crepe/facade/SoundContext.cpp +++ b/src/crepe/facade/SoundContext.cpp @@ -4,17 +4,33 @@ 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(); +} + +Sound::Handle SoundContext::play(Sound & resource) { + return { + .handle = this->engine.play(resource.sample, this->default_volume), + }; +} + +void SoundContext::stop(Sound::Handle & handle) { + this->engine.stop(handle.handle); } + +void SoundContext::set_volume(Sound & resource, 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) { + this->engine.setLooping(handle.handle, loop); +} + diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h index d703c16..c651cd5 100644 --- a/src/crepe/facade/SoundContext.h +++ b/src/crepe/facade/SoundContext.h @@ -2,6 +2,8 @@ #include <soloud/soloud.h> +#include "../api/Config.h" + #include "Sound.h" namespace crepe { @@ -13,19 +15,25 @@ namespace crepe { * 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; + 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); + private: - static SoundContext & get_instance(); SoLoud::Soloud engine; - friend class Sound; + float default_volume = 1.0f; + + Config & config = Config::get_instance(); }; } // namespace crepe diff --git a/src/crepe/manager/CMakeLists.txt b/src/crepe/manager/CMakeLists.txt new file mode 100644 index 0000000..480c8ee --- /dev/null +++ b/src/crepe/manager/CMakeLists.txt @@ -0,0 +1,23 @@ +target_sources(crepe PUBLIC + ComponentManager.cpp + EventManager.cpp + Manager.cpp + SaveManager.cpp + SceneManager.cpp + ResourceManager.cpp +) + +target_sources(crepe PUBLIC FILE_SET HEADERS FILES + ComponentManager.h + ComponentManager.hpp + EventManager.h + EventManager.hpp + Manager.h + Mediator.h + SaveManager.h + SceneManager.h + SceneManager.hpp + ResourceManager.h + ResourceManager.hpp +) + diff --git a/src/crepe/ComponentManager.cpp b/src/crepe/manager/ComponentManager.cpp index 5b73009..5a96158 100644 --- a/src/crepe/ComponentManager.cpp +++ b/src/crepe/manager/ComponentManager.cpp @@ -1,13 +1,16 @@ -#include "api/GameObject.h" -#include "util/Log.h" +#include "../api/GameObject.h" +#include "../util/Log.h" +#include "../types.h" #include "ComponentManager.h" -#include "types.h" using namespace crepe; using namespace std; -ComponentManager::ComponentManager() { dbg_trace(); } +ComponentManager::ComponentManager(Mediator & mediator) : Manager(mediator) { + mediator.component_manager = *this; + dbg_trace(); +} ComponentManager::~ComponentManager() { dbg_trace(); } void ComponentManager::delete_all_components_of_id(game_object_id_t id) { diff --git a/src/crepe/ComponentManager.h b/src/crepe/manager/ComponentManager.h index 480124f..ad37586 100644 --- a/src/crepe/ComponentManager.h +++ b/src/crepe/manager/ComponentManager.h @@ -5,8 +5,10 @@ #include <unordered_map> #include <vector> -#include "Component.h" -#include "types.h" +#include "../Component.h" +#include "../types.h" + +#include "Manager.h" namespace crepe { @@ -17,7 +19,7 @@ class GameObject; * * This class manages all components. It provides methods to add, delete and get components. */ -class ComponentManager { +class ComponentManager : public Manager { // TODO: This relation should be removed! I (loek) believe that the scene manager should // create/destroy components because the GameObject's are stored in concrete Scene classes, // which will in turn call GameObject's destructor, which will in turn call @@ -26,7 +28,7 @@ class ComponentManager { friend class SceneManager; public: - ComponentManager(); // dbg_trace + ComponentManager(Mediator & mediator); ~ComponentManager(); // dbg_trace /** diff --git a/src/crepe/ComponentManager.hpp b/src/crepe/manager/ComponentManager.hpp index ffb38ec..ffb38ec 100644 --- a/src/crepe/ComponentManager.hpp +++ b/src/crepe/manager/ComponentManager.hpp diff --git a/src/crepe/api/EventManager.cpp b/src/crepe/manager/EventManager.cpp index 20f0dd3..20f0dd3 100644 --- a/src/crepe/api/EventManager.cpp +++ b/src/crepe/manager/EventManager.cpp diff --git a/src/crepe/api/EventManager.h b/src/crepe/manager/EventManager.h index 1a33023..d634f54 100644 --- a/src/crepe/api/EventManager.h +++ b/src/crepe/manager/EventManager.h @@ -5,8 +5,8 @@ #include <unordered_map> #include <vector> -#include "Event.h" -#include "EventHandler.h" +#include "../api/Event.h" +#include "../api/EventHandler.h" namespace crepe { diff --git a/src/crepe/api/EventManager.hpp b/src/crepe/manager/EventManager.hpp index a5f4556..a5f4556 100644 --- a/src/crepe/api/EventManager.hpp +++ b/src/crepe/manager/EventManager.hpp diff --git a/src/crepe/manager/Manager.cpp b/src/crepe/manager/Manager.cpp new file mode 100644 index 0000000..fe7c936 --- /dev/null +++ b/src/crepe/manager/Manager.cpp @@ -0,0 +1,6 @@ +#include "Manager.h" + +using namespace crepe; + +Manager::Manager(Mediator & mediator) : mediator(mediator) { } + diff --git a/src/crepe/manager/Manager.h b/src/crepe/manager/Manager.h new file mode 100644 index 0000000..9adfd0b --- /dev/null +++ b/src/crepe/manager/Manager.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Mediator.h" + +namespace crepe { + +class Manager { +public: + Manager(Mediator & mediator); + virtual ~Manager() = default; + +protected: + Mediator & mediator; +}; + +} + diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h new file mode 100644 index 0000000..475aed9 --- /dev/null +++ b/src/crepe/manager/Mediator.h @@ -0,0 +1,35 @@ +#pragma once + +#include "../util/OptionalRef.h" + +// TODO: remove these singletons: +#include "SaveManager.h" +#include "EventManager.h" + +namespace crepe { + +class ComponentManager; +class SceneManager; +class ResourceManager; + +/** + * Struct to pass references to classes that would otherwise need to be singletons down to + * other classes within the engine hierarchy. Made to prevent constant changes to subclasses to + * pass specific references through dependency injection. All references on this struct + * *should* be explicitly checked for availability as this struct does not guarantee anything. + * + * \note Dereferencing members of this struct should be deferred. If you are a user of this + * class, keep a reference to this mediator instead of just picking references from it when you + * receive an instance. + * + * \warning This class should never be directly accessible from the API + */ +struct Mediator { + OptionalRef<ComponentManager> component_manager; + OptionalRef<SceneManager> scene_manager; + OptionalRef<SaveManager> save_manager = SaveManager::get_instance(); + OptionalRef<EventManager> event_manager = EventManager::get_instance(); + OptionalRef<ResourceManager> resource_manager; +}; + +} diff --git a/src/crepe/manager/ResourceManager.cpp b/src/crepe/manager/ResourceManager.cpp new file mode 100644 index 0000000..87585ad --- /dev/null +++ b/src/crepe/manager/ResourceManager.cpp @@ -0,0 +1,34 @@ +#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..e7e6abc --- /dev/null +++ b/src/crepe/manager/ResourceManager.h @@ -0,0 +1,48 @@ +#pragma once + +#include <memory> +#include <unordered_map> + +#include "../Resource.h" +#include "../api/Asset.h" + +#include "Manager.h" + +namespace crepe { + +/** + * \brief The ResourceManager is responsible for storing and managing assets over + * multiple scenes. + * + * 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. + */ +class ResourceManager : public Manager { +public: + ResourceManager(Mediator & mediator); + virtual ~ResourceManager(); // dbg_trace + +private: + struct CacheEntry { + std::unique_ptr<Resource> resource = nullptr; + bool persistent = false; + }; + //! A cache that holds all the assets, accessible by their file path, over multiple scenes. + std::unordered_map<const Asset, CacheEntry> resources; + CacheEntry & get_entry(const Asset & asset); + +public: + void set_persistent(const Asset & asset, bool persistent); + + template <typename Resource> + Resource & get(const Asset & asset); + + void clear(); + 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..8270bc5 --- /dev/null +++ b/src/crepe/manager/ResourceManager.hpp @@ -0,0 +1,26 @@ +#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; +} + +} + diff --git a/src/crepe/api/SaveManager.cpp b/src/crepe/manager/SaveManager.cpp index c5f43ea..121d017 100644 --- a/src/crepe/api/SaveManager.cpp +++ b/src/crepe/manager/SaveManager.cpp @@ -1,9 +1,9 @@ #include "../facade/DB.h" #include "../util/Log.h" +#include "../api/Config.h" +#include "../ValueBroker.h" -#include "Config.h" #include "SaveManager.h" -#include "ValueBroker.h" using namespace std; using namespace crepe; diff --git a/src/crepe/api/SaveManager.h b/src/crepe/manager/SaveManager.h index 3d8c852..3d8c852 100644 --- a/src/crepe/api/SaveManager.h +++ b/src/crepe/manager/SaveManager.h diff --git a/src/crepe/api/SceneManager.cpp b/src/crepe/manager/SceneManager.cpp index 1f783ad..50a9fbb 100644 --- a/src/crepe/api/SceneManager.cpp +++ b/src/crepe/manager/SceneManager.cpp @@ -1,14 +1,15 @@ #include <algorithm> #include <memory> -#include "../ComponentManager.h" - +#include "ComponentManager.h" #include "SceneManager.h" using namespace crepe; using namespace std; -SceneManager::SceneManager(ComponentManager & mgr) : component_manager(mgr) {} +SceneManager::SceneManager(Mediator & mediator) : Manager(mediator) { + mediator.scene_manager = *this; +} void SceneManager::set_next_scene(const string & name) { next_scene = name; } @@ -26,7 +27,7 @@ void SceneManager::load_next_scene() { unique_ptr<Scene> & scene = *it; // Delete all components of the current scene - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; mgr.delete_all_components(); // Load the new scene diff --git a/src/crepe/api/SceneManager.h b/src/crepe/manager/SceneManager.h index f6f62cd..e0955c2 100644 --- a/src/crepe/api/SceneManager.h +++ b/src/crepe/manager/SceneManager.h @@ -3,7 +3,9 @@ #include <memory> #include <vector> -#include "Scene.h" +#include "../api/Scene.h" + +#include "Manager.h" namespace crepe { @@ -15,10 +17,9 @@ class ComponentManager; * This class manages scenes. It can add new scenes and load them. It also manages the current scene * and the next scene. */ -class SceneManager { +class SceneManager : public Manager { public: - //! \param mgr Reference to the ComponentManager - SceneManager(ComponentManager & mgr); + SceneManager(Mediator & mediator); public: /** @@ -44,8 +45,6 @@ private: std::vector<std::unique_ptr<Scene>> scenes; //! Next scene to load std::string next_scene; - //! Reference to the ComponentManager - ComponentManager & component_manager; }; } // namespace crepe diff --git a/src/crepe/api/SceneManager.hpp b/src/crepe/manager/SceneManager.hpp index 5c8e417..dff4e51 100644 --- a/src/crepe/api/SceneManager.hpp +++ b/src/crepe/manager/SceneManager.hpp @@ -12,7 +12,7 @@ void SceneManager::add_scene(Args &&... args) { Scene * scene = new T(std::forward<Args>(args)...); unique_ptr<Scene> unique_scene(scene); - unique_scene->component_manager = this->component_manager; + unique_scene->mediator = this->mediator; this->scenes.emplace_back(std::move(unique_scene)); diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp index 4c40940..8bb6465 100644 --- a/src/crepe/system/AnimatorSystem.cpp +++ b/src/crepe/system/AnimatorSystem.cpp @@ -1,15 +1,15 @@ #include <cstdint> -#include "api/Animator.h" -#include "facade/SDLContext.h" +#include "../api/Animator.h" +#include "../facade/SDLContext.h" +#include "../manager/ComponentManager.h" #include "AnimatorSystem.h" -#include "ComponentManager.h" using namespace crepe; void AnimatorSystem::update() { - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; RefVector<Animator> animations = mgr.get_components_by_type<Animator>(); diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp new file mode 100644 index 0000000..84a101a --- /dev/null +++ b/src/crepe/system/AudioSystem.cpp @@ -0,0 +1,71 @@ +#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); + + if (component.private_data.empty()) { + auto & data = component.private_data.set<ComponentPrivate>(); + this->update_last(component, data); + data.last_active = false; + } + auto & data = component.private_data.get<ComponentPrivate>(); + + this->diff_update(component, data, resource); + + this->update_last(component, data); + } +} + +void AudioSystem::diff_update(AudioSource & component, ComponentPrivate & data, Sound & resource) { + SoundContext & context = this->get_context(); + + if (component.active != data.last_active) { + if (component.active) { + component.oneshot_play = component.play_on_awake; + } else { + context.stop(data.handle); + return; + } + } + if (!component.active) return; + + if (component.oneshot_play) { + data.handle = context.play(resource); + component.oneshot_play = false; + } + if (component.oneshot_stop) { + context.stop(data.handle); + component.oneshot_stop = false; + } + if (component.volume != data.last_volume) { + context.set_volume(resource, data.handle, component.volume); + } + if (component.loop != data.last_loop) { + context.set_loop(resource, data.handle, component.loop); + } +} + +void AudioSystem::update_last(const AudioSource & component, ComponentPrivate & data) { + data.last_active = component.active; + data.last_loop = component.loop; + data.last_volume = component.volume; +} + +SoundContext & AudioSystem::get_context() { + if (this->context.empty()) + this->context.set<SoundContext>(); + return this->context.get<SoundContext>(); +} + diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h new file mode 100644 index 0000000..a004c60 --- /dev/null +++ b/src/crepe/system/AudioSystem.h @@ -0,0 +1,45 @@ +#pragma once + +#include "../facade/SoundContext.h" +#include "../facade/Sound.h" +#include "../api/AudioSource.h" + +#include "System.h" + +namespace crepe { + +class AudioSystem : public System { +public: + using System::System; + void update() override; + +private: + /** + * \brief Private data stored by AudioSystem on AudioSource component + */ + struct ComponentPrivate { + //! This sample's voice handle + Sound::Handle handle; + + /** + * \name State diffing variables + * \{ + */ + typeof(AudioSource::active) last_active; + typeof(AudioSource::volume) last_volume; + typeof(AudioSource::loop) last_loop; + //! \} + }; + + void update_last(const AudioSource & component, ComponentPrivate & data); + + void diff_update(AudioSource & component, ComponentPrivate & data, Sound & resource); + +protected: + virtual SoundContext & get_context(); +private: + Private context; +}; + +} // namespace crepe + diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt index d658b25..f507b90 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 ) @@ -14,5 +15,6 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES PhysicsSystem.h CollisionSystem.h RenderSystem.h + AudioSystem.h AnimatorSystem.h ) diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index 0e62a57..b14c52f 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -2,17 +2,17 @@ #include <cstdlib> #include <ctime> -#include "api/ParticleEmitter.h" -#include "api/Transform.h" +#include "../api/ParticleEmitter.h" +#include "../api/Transform.h" +#include "../manager/ComponentManager.h" -#include "ComponentManager.h" #include "ParticleSystem.h" using namespace crepe; void ParticleSystem::update() { // Get all emitters - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; RefVector<ParticleEmitter> emitters = mgr.get_components_by_type<ParticleEmitter>(); for (ParticleEmitter & emitter : emitters) { diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp index 514a4b3..eba9dfa 100644 --- a/src/crepe/system/PhysicsSystem.cpp +++ b/src/crepe/system/PhysicsSystem.cpp @@ -1,6 +1,6 @@ #include <cmath> -#include "../ComponentManager.h" +#include "../manager/ComponentManager.h" #include "../api/Config.h" #include "../api/Rigidbody.h" #include "../api/Transform.h" @@ -11,7 +11,7 @@ using namespace crepe; void PhysicsSystem::update() { - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; RefVector<Rigidbody> rigidbodies = mgr.get_components_by_type<Rigidbody>(); RefVector<Transform> transforms = mgr.get_components_by_type<Transform>(); diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp index c196bb1..4e97b3e 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -5,7 +5,7 @@ #include <stdexcept> #include <vector> -#include "../ComponentManager.h" +#include "../manager/ComponentManager.h" #include "../api/Camera.h" #include "../api/ParticleEmitter.h" #include "../api/Sprite.h" @@ -22,7 +22,7 @@ void RenderSystem::clear_screen() { this->context.clear_screen(); } void RenderSystem::present_screen() { this->context.present_screen(); } const Camera & RenderSystem::update_camera() { - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; RefVector<Camera> cameras = mgr.get_components_by_type<Camera>(); @@ -62,7 +62,7 @@ void RenderSystem::update() { bool RenderSystem::render_particle(const Sprite & sprite, const Camera & cam, const double & scale) { - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; vector<reference_wrapper<ParticleEmitter>> emitters = mgr.get_components_by_id<ParticleEmitter>(sprite.game_object_id); @@ -102,7 +102,7 @@ void RenderSystem::render_normal(const Sprite & sprite, const Camera & cam, } void RenderSystem::render() { - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; const Camera & cam = this->update_camera(); RefVector<Sprite> sprites = mgr.get_components_by_type<Sprite>(); diff --git a/src/crepe/system/ScriptSystem.cpp b/src/crepe/system/ScriptSystem.cpp index 20a83f7..2e16eb0 100644 --- a/src/crepe/system/ScriptSystem.cpp +++ b/src/crepe/system/ScriptSystem.cpp @@ -1,4 +1,4 @@ -#include "../ComponentManager.h" +#include "../manager/ComponentManager.h" #include "../api/BehaviorScript.h" #include "../api/Script.h" @@ -10,7 +10,7 @@ using namespace crepe; void ScriptSystem::update() { dbg_trace(); - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; RefVector<BehaviorScript> behavior_scripts = mgr.get_components_by_type<BehaviorScript>(); for (BehaviorScript & behavior_script : behavior_scripts) { diff --git a/src/crepe/system/System.cpp b/src/crepe/system/System.cpp index 937a423..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(ComponentManager & mgr) : component_manager(mgr) { dbg_trace(); } +System::System(const Mediator & mediator) : mediator(mediator) {} diff --git a/src/crepe/system/System.h b/src/crepe/system/System.h index 28ea20e..4e7fc6d 100644 --- a/src/crepe/system/System.h +++ b/src/crepe/system/System.h @@ -1,5 +1,7 @@ #pragma once +#include "../manager/Mediator.h" + namespace crepe { class ComponentManager; @@ -19,11 +21,11 @@ public: virtual void update() = 0; public: - System(ComponentManager &); + System(const Mediator & m); virtual ~System() = default; protected: - ComponentManager & component_manager; + const Mediator & mediator; }; } // namespace crepe diff --git a/src/crepe/util/CMakeLists.txt b/src/crepe/util/CMakeLists.txt index 94ed906..f49d851 100644 --- a/src/crepe/util/CMakeLists.txt +++ b/src/crepe/util/CMakeLists.txt @@ -1,6 +1,7 @@ target_sources(crepe PUBLIC LogColor.cpp Log.cpp + Private.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -11,5 +12,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Proxy.hpp OptionalRef.h OptionalRef.hpp + Private.h + Private.hpp ) diff --git a/src/crepe/util/Private.cpp b/src/crepe/util/Private.cpp new file mode 100644 index 0000000..cb4cb5b --- /dev/null +++ b/src/crepe/util/Private.cpp @@ -0,0 +1,34 @@ +#include "Private.h" + +using namespace crepe; + +bool Private::empty() const noexcept { + return this->instance == nullptr; +} + +Private::~Private() { + if (this->instance == nullptr) return; + this->destructor(this->instance); +} + +Private::Private(Private && other) { + *this = std::move(other); +} + +Private & Private::operator=(Private && other) { + // TODO: ideally this function checks for self-assignment + this->instance = other.instance; + this->destructor = other.destructor; + this->type = other.type; + + other.instance = nullptr; + other.destructor = [](void*){}; + + return *this; +} + +Private::Private(const Private & other) { } +Private & Private::operator=(const Private & other) { + return *this; +} + diff --git a/src/crepe/util/Private.h b/src/crepe/util/Private.h new file mode 100644 index 0000000..6dd28bb --- /dev/null +++ b/src/crepe/util/Private.h @@ -0,0 +1,34 @@ +#pragma once + +#include <typeindex> +#include <functional> + +namespace crepe { + +class Private { +public: + Private() = default; + ~Private(); + Private(const Private &); + Private(Private &&); + Private & operator=(const Private &); + Private & operator=(Private &&); + + template <typename T> + T & get(); + + template <typename T, typename... Args> + T & set(Args &&... args); + + bool empty() const noexcept; + +private: + std::function<void(void *)> destructor; + std::type_index type = typeid(void); + void * instance = nullptr; +}; + +} + +#include "Private.hpp" + diff --git a/src/crepe/util/Private.hpp b/src/crepe/util/Private.hpp new file mode 100644 index 0000000..d6ab23f --- /dev/null +++ b/src/crepe/util/Private.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include <stdexcept> +#include <format> + +#include "Private.h" + +namespace crepe { + +template <typename T, typename... Args> +T & Private::set(Args &&... args) { + if (!this->empty()) this->destructor(this->instance); + T * instance = new T(std::forward<Args>(args)...); + this->instance = static_cast<void*>(instance); + this->destructor = [](void * instance) { + delete static_cast<T*>(instance); + }; + this->type = typeid(T); + return *instance; +} + +template <typename T> +T & Private::get() { + using namespace std; + if (this->empty()) + throw out_of_range("Private: get() called on empty object"); + type_index requested_type = typeid(T); + if (this->type != requested_type) + throw logic_error(format("Private: get() called with [T = {}] (actual is [T = {}])", requested_type.name(), this->type.name())); + return *static_cast<T*>(this->instance); +} + +} diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index 560e2bc..9c3c550 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(gameloop) 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..9c3cb9c --- /dev/null +++ b/src/test/AudioTest.cpp @@ -0,0 +1,153 @@ +#include <gtest/gtest.h> +#include <gmock/gmock.h> + +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/ResourceManager.h> +#include <crepe/api/AudioSource.h> +#include <crepe/api/GameObject.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(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)); + }; + + 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 d310f6a..8c4b855 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -3,7 +3,9 @@ target_sources(test_main PUBLIC PhysicsTest.cpp ScriptTest.cpp ParticleTest.cpp + AudioTest.cpp AssetTest.cpp + ResourceManagerTest.cpp OptionalRefTest.cpp RenderSystemTest.cpp EventTest.cpp @@ -12,4 +14,7 @@ target_sources(test_main PUBLIC ValueBrokerTest.cpp DBTest.cpp Vector2Test.cpp + ScriptEventTest.cpp + ScriptSceneTest.cpp + PrivateTest.cpp ) diff --git a/src/test/DBTest.cpp b/src/test/DBTest.cpp index e80814c..99dedff 100644 --- a/src/test/DBTest.cpp +++ b/src/test/DBTest.cpp @@ -1,6 +1,7 @@ -#include <crepe/facade/DB.h> #include <gtest/gtest.h> +#include <crepe/facade/DB.h> + using namespace std; using namespace crepe; using namespace testing; diff --git a/src/test/ECSTest.cpp b/src/test/ECSTest.cpp index af9d6f2..22c4fe7 100644 --- a/src/test/ECSTest.cpp +++ b/src/test/ECSTest.cpp @@ -2,7 +2,7 @@ #define protected public -#include <crepe/ComponentManager.h> +#include <crepe/manager/ComponentManager.h> #include <crepe/api/GameObject.h> #include <crepe/api/Metadata.h> #include <crepe/api/Transform.h> @@ -12,8 +12,9 @@ using namespace std; using namespace crepe; class ECSTest : public ::testing::Test { + Mediator m; public: - ComponentManager mgr{}; + ComponentManager mgr{m}; }; TEST_F(ECSTest, createGameObject) { diff --git a/src/test/EventTest.cpp b/src/test/EventTest.cpp index b0e6c9c..350dd07 100644 --- a/src/test/EventTest.cpp +++ b/src/test/EventTest.cpp @@ -1,10 +1,11 @@ - -#include "api/Event.h" -#include "api/EventManager.h" -#include "api/IKeyListener.h" -#include "api/IMouseListener.h" #include <gmock/gmock.h> #include <gtest/gtest.h> + +#include <crepe/api/Event.h> +#include <crepe/manager/EventManager.h> +#include <crepe/api/IKeyListener.h> +#include <crepe/api/IMouseListener.h> + using namespace std; using namespace std::chrono_literals; using namespace crepe; @@ -37,7 +38,6 @@ public: TEST_F(EventManagerTest, EventSubscription) { EventHandler<KeyPressEvent> key_handler = [](const KeyPressEvent & e) { - std::cout << "Key Event Triggered" << std::endl; return true; }; diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp index 976f9a1..4e9fa4e 100644 --- a/src/test/ParticleTest.cpp +++ b/src/test/ParticleTest.cpp @@ -1,11 +1,11 @@ -#include "api/Texture.h" -#include <crepe/ComponentManager.h> +#include <crepe/manager/ComponentManager.h> #include <crepe/Particle.h> #include <crepe/api/Config.h> #include <crepe/api/GameObject.h> #include <crepe/api/ParticleEmitter.h> #include <crepe/api/Rigidbody.h> #include <crepe/api/Sprite.h> +#include <crepe/api/Texture.h> #include <crepe/api/Transform.h> #include <crepe/system/ParticleSystem.h> #include <gtest/gtest.h> @@ -16,9 +16,10 @@ using namespace std::chrono_literals; using namespace crepe; class ParticlesTest : public ::testing::Test { + Mediator m; public: - ComponentManager component_manager; - ParticleSystem particle_system{component_manager}; + ComponentManager component_manager{m}; + ParticleSystem particle_system{m}; void SetUp() override { ComponentManager & mgr = this->component_manager; diff --git a/src/test/PhysicsTest.cpp b/src/test/PhysicsTest.cpp index 33b6020..01b7c51 100644 --- a/src/test/PhysicsTest.cpp +++ b/src/test/PhysicsTest.cpp @@ -1,4 +1,4 @@ -#include <crepe/ComponentManager.h> +#include <crepe/manager/ComponentManager.h> #include <crepe/api/Config.h> #include <crepe/api/GameObject.h> #include <crepe/api/Rigidbody.h> @@ -11,9 +11,10 @@ using namespace std::chrono_literals; using namespace crepe; class PhysicsTest : public ::testing::Test { + Mediator m; public: - ComponentManager component_manager; - PhysicsSystem system{component_manager}; + ComponentManager component_manager{m}; + PhysicsSystem system{m}; void SetUp() override { ComponentManager & mgr = this->component_manager; diff --git a/src/test/PrivateTest.cpp b/src/test/PrivateTest.cpp new file mode 100644 index 0000000..0ea67d6 --- /dev/null +++ b/src/test/PrivateTest.cpp @@ -0,0 +1,158 @@ +#include <gtest/gtest.h> + +#include <crepe/util/Private.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class PrivateTest : public Test { +public: + static unsigned constructors; + static unsigned destructors; + + void SetUp() override { + PrivateTest::constructors = 0; + PrivateTest::destructors = 0; + } + + class TestClass { + public: + TestClass() { PrivateTest::constructors++; } + ~TestClass() { PrivateTest::destructors++; } + }; + class Unrelated {}; +}; +unsigned PrivateTest::constructors; +unsigned PrivateTest::destructors; + +TEST_F(PrivateTest, Empty) { + { + Private foo; + } + + EXPECT_EQ(PrivateTest::constructors, 0); + EXPECT_EQ(PrivateTest::destructors, 0); +} + +TEST_F(PrivateTest, WithObject) { + { + Private foo; + foo.set<TestClass>(); + + EXPECT_EQ(PrivateTest::constructors, 1); + EXPECT_EQ(PrivateTest::destructors, 0); + } + + EXPECT_EQ(PrivateTest::constructors, 1); + EXPECT_EQ(PrivateTest::destructors, 1); +} + +TEST_F(PrivateTest, EmptyException) { + Private foo; + EXPECT_THROW(foo.get<TestClass>(), std::out_of_range); + + foo.set<TestClass>(); + EXPECT_NO_THROW(foo.get<TestClass>()); +} + +TEST_F(PrivateTest, IncorrectTypeException) { + Private foo; + foo.set<TestClass>(); + + EXPECT_THROW(foo.get<Unrelated>(), std::logic_error); + EXPECT_NO_THROW(foo.get<TestClass>()); +} + +TEST_F(PrivateTest, MoveConstructor) { + { + Private foo; + foo.set<TestClass>(); + + EXPECT_EQ(PrivateTest::constructors, 1); + EXPECT_EQ(PrivateTest::destructors, 0); + + Private bar(std::move(foo)); + + EXPECT_EQ(PrivateTest::constructors, 1); + EXPECT_EQ(PrivateTest::destructors, 0); + } + + EXPECT_EQ(PrivateTest::constructors, 1); + EXPECT_EQ(PrivateTest::destructors, 1); +} + +TEST_F(PrivateTest, MoveOperator) { + { + Private foo; + foo.set<TestClass>(); + + EXPECT_EQ(PrivateTest::constructors, 1); + EXPECT_EQ(PrivateTest::destructors, 0); + + Private bar = std::move(foo); + + EXPECT_EQ(PrivateTest::constructors, 1); + EXPECT_EQ(PrivateTest::destructors, 0); + } + + EXPECT_EQ(PrivateTest::constructors, 1); + EXPECT_EQ(PrivateTest::destructors, 1); +} + +TEST_F(PrivateTest, CopyConstructor) { + { + Private foo; + foo.set<TestClass>(); + + EXPECT_EQ(PrivateTest::constructors, 1); + EXPECT_EQ(PrivateTest::destructors, 0); + + Private bar(foo); + + EXPECT_TRUE(bar.empty()); + EXPECT_EQ(PrivateTest::constructors, 1); + EXPECT_EQ(PrivateTest::destructors, 0); + } + + EXPECT_EQ(PrivateTest::constructors, 1); + EXPECT_EQ(PrivateTest::destructors, 1); +} + +TEST_F(PrivateTest, CopyOperator) { + { + Private foo; + foo.set<TestClass>(); + + EXPECT_EQ(PrivateTest::constructors, 1); + EXPECT_EQ(PrivateTest::destructors, 0); + + Private bar = foo; + + EXPECT_TRUE(bar.empty()); + EXPECT_EQ(PrivateTest::constructors, 1); + EXPECT_EQ(PrivateTest::destructors, 0); + } + + EXPECT_EQ(PrivateTest::constructors, 1); + EXPECT_EQ(PrivateTest::destructors, 1); +} + +TEST_F(PrivateTest, DoubleAssignment) { + { + Private foo; + foo.set<TestClass>(); + + EXPECT_EQ(PrivateTest::constructors, 1); + EXPECT_EQ(PrivateTest::destructors, 0); + + foo.set<TestClass>(); + + EXPECT_EQ(PrivateTest::constructors, 2); + EXPECT_EQ(PrivateTest::destructors, 1); + } + + EXPECT_EQ(PrivateTest::constructors, 2); + EXPECT_EQ(PrivateTest::destructors, 2); +} + diff --git a/src/test/RenderSystemTest.cpp b/src/test/RenderSystemTest.cpp index bb5b81a..3528e46 100644 --- a/src/test/RenderSystemTest.cpp +++ b/src/test/RenderSystemTest.cpp @@ -1,4 +1,3 @@ -#include "types.h" #include <functional> #include <gtest/gtest.h> #include <memory> @@ -7,8 +6,8 @@ #define private public #define protected public -#include "crepe/api/Camera.h" -#include <crepe/ComponentManager.h> +#include <crepe/api/Camera.h> +#include <crepe/manager/ComponentManager.h> #include <crepe/api/Color.h> #include <crepe/api/GameObject.h> #include <crepe/api/Sprite.h> @@ -21,9 +20,10 @@ using namespace crepe; using namespace testing; class RenderSystemTest : public Test { + Mediator m; public: - ComponentManager mgr{}; - RenderSystem sys{mgr}; + ComponentManager mgr{m}; + RenderSystem sys{m}; GameObject entity1 = this->mgr.new_object("name"); GameObject entity2 = this->mgr.new_object("name"); GameObject entity3 = this->mgr.new_object("name"); diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp new file mode 100644 index 0000000..1f56e23 --- /dev/null +++ b/src/test/ResourceManagerTest.cpp @@ -0,0 +1,75 @@ +#include <gtest/gtest.h> + +#define private public +#define protected public + +#include <crepe/util/Log.h> +#include <crepe/manager/ResourceManager.h> +#include <crepe/api/GameObject.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/SceneManagerTest.cpp b/src/test/SceneManagerTest.cpp index 62b7d33..d027d89 100644 --- a/src/test/SceneManagerTest.cpp +++ b/src/test/SceneManagerTest.cpp @@ -1,12 +1,13 @@ -#include "types.h" -#include <crepe/ComponentManager.h> +#include <gtest/gtest.h> + +#include <crepe/types.h> +#include <crepe/manager/SceneManager.h> +#include <crepe/manager/ComponentManager.h> #include <crepe/api/GameObject.h> #include <crepe/api/Metadata.h> #include <crepe/api/Scene.h> -#include <crepe/api/SceneManager.h> #include <crepe/api/Transform.h> #include <crepe/api/Vector2.h> -#include <gtest/gtest.h> using namespace std; using namespace crepe; @@ -14,7 +15,8 @@ using namespace crepe; class ConcreteScene1 : public Scene { public: void load_scene() { - ComponentManager & mgr = this->component_manager; + Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; GameObject object1 = mgr.new_object("scene_1", "tag_scene_1", vec2{0, 0}, 0, 1); GameObject object2 = mgr.new_object("scene_1", "tag_scene_1", vec2{1, 0}, 0, 1); GameObject object3 = mgr.new_object("scene_1", "tag_scene_1", vec2{2, 0}, 0, 1); @@ -26,7 +28,8 @@ public: class ConcreteScene2 : public Scene { public: void load_scene() { - ComponentManager & mgr = this->component_manager; + Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; GameObject object1 = mgr.new_object("scene_2", "tag_scene_2", vec2{0, 0}, 0, 1); GameObject object2 = mgr.new_object("scene_2", "tag_scene_2", vec2{0, 1}, 0, 1); GameObject object3 = mgr.new_object("scene_2", "tag_scene_2", vec2{0, 2}, 0, 1); @@ -41,7 +44,8 @@ public: ConcreteScene3(const string & name) : name(name) {} void load_scene() { - ComponentManager & mgr = this->component_manager; + Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; GameObject object1 = mgr.new_object("scene_3", "tag_scene_3", vec2{0, 0}, 0, 1); } @@ -52,9 +56,10 @@ private: }; class SceneManagerTest : public ::testing::Test { + Mediator m; public: - ComponentManager component_mgr{}; - SceneManager scene_mgr{component_mgr}; + ComponentManager component_mgr{m}; + SceneManager scene_mgr{m}; }; TEST_F(SceneManagerTest, loadScene) { diff --git a/src/test/ScriptEventTest.cpp b/src/test/ScriptEventTest.cpp new file mode 100644 index 0000000..7a9abbb --- /dev/null +++ b/src/test/ScriptEventTest.cpp @@ -0,0 +1,51 @@ +#include <gtest/gtest.h> + +// stupid hack to allow access to private/protected members under test +#define private public +#define protected public + +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/EventManager.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Event.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Script.h> +#include <crepe/api/Vector2.h> +#include <crepe/system/ScriptSystem.h> + +#include "ScriptTest.h" + +using namespace std; +using namespace crepe; +using namespace testing; + +class ScriptEventTest : public ScriptTest { +public: + EventManager & event_manager = mediator.event_manager; + + class MyEvent : public Event {}; +}; + +TEST_F(ScriptEventTest, Inactive) { + BehaviorScript & behaviorscript = this->behaviorscript; + MyScript & script = this->script; + EventManager & evmgr = this->event_manager; + + unsigned event_count = 0; + script.subscribe<MyEvent>([&](const MyEvent &){ + event_count++; + return true; + }); + + system.update(); + behaviorscript.active = false; + EXPECT_EQ(0, event_count); + + evmgr.trigger_event<MyEvent>(); + EXPECT_EQ(0, event_count); + + behaviorscript.active = true; + evmgr.trigger_event<MyEvent>(); + EXPECT_EQ(1, event_count); +} + diff --git a/src/test/ScriptSceneTest.cpp b/src/test/ScriptSceneTest.cpp new file mode 100644 index 0000000..f96ae8b --- /dev/null +++ b/src/test/ScriptSceneTest.cpp @@ -0,0 +1,31 @@ +#include <gtest/gtest.h> + +// stupid hack to allow access to private/protected members under test +#define private public +#define protected public + +#include <crepe/manager/SceneManager.h> +#include "ScriptTest.h" + +using namespace std; +using namespace crepe; +using namespace testing; + +class ScriptSceneTest : public ScriptTest { +public: + SceneManager scene_manager{mediator}; + + class MyScene : public Scene {}; +}; + +TEST_F(ScriptSceneTest, Inactive) { + BehaviorScript & behaviorscript = this->behaviorscript; + MyScript & script = this->script; + + const char * non_default_value = "foo"; + ASSERT_NE(non_default_value, scene_manager.next_scene); + + script.set_next_scene(non_default_value); + EXPECT_EQ(non_default_value, scene_manager.next_scene); +} + diff --git a/src/test/ScriptTest.cpp b/src/test/ScriptTest.cpp index 78d5061..6d0d5fb 100644 --- a/src/test/ScriptTest.cpp +++ b/src/test/ScriptTest.cpp @@ -1,129 +1,78 @@ #include <gtest/gtest.h> +#include <gmock/gmock.h> // stupid hack to allow access to private/protected members under test #define private public #define protected public -#include <crepe/ComponentManager.h> -#include <crepe/api/BehaviorScript.h> -#include <crepe/api/Event.h> -#include <crepe/api/EventManager.h> +#include "ScriptTest.h" #include <crepe/api/GameObject.h> -#include <crepe/api/Script.h> -#include <crepe/api/Vector2.h> -#include <crepe/system/ScriptSystem.h> using namespace std; using namespace crepe; using namespace testing; -class MyEvent : public Event {}; - -class ScriptTest : public Test { -public: - ComponentManager component_manager{}; - ScriptSystem system{component_manager}; - EventManager & event_manager = EventManager::get_instance(); - - class MyScript : public Script { - // NOTE: default (private) visibility of init and update shouldn't cause - // issues! - void init() { - this->init_count++; - - subscribe<MyEvent>([this](const MyEvent &) { - this->event_count++; - return true; - }); - - // init should never be called more than once - EXPECT_LE(this->init_count, 1); - } - void update() { this->update_count++; } - - public: - unsigned init_count = 0; - unsigned update_count = 0; - unsigned event_count = 0; - }; - - OptionalRef<BehaviorScript> behaviorscript; - OptionalRef<MyScript> script; - - void SetUp() override { - auto & mgr = this->component_manager; - GameObject entity = mgr.new_object("name"); - BehaviorScript & component = entity.add_component<BehaviorScript>(); - - this->behaviorscript = component; - ASSERT_TRUE(this->behaviorscript); - EXPECT_EQ(component.script.get(), nullptr); - component.set_script<MyScript>(); - ASSERT_NE(component.script.get(), nullptr); - - this->script = *(MyScript *) component.script.get(); - ASSERT_TRUE(this->script); - - // sanity - MyScript & script = this->script; - ASSERT_EQ(script.init_count, 0); - ASSERT_EQ(script.update_count, 0); - ASSERT_EQ(script.event_count, 0); - } -}; +void ScriptTest::SetUp() { + auto & mgr = this->component_manager; + GameObject entity = mgr.new_object("name"); + BehaviorScript & component = entity.add_component<BehaviorScript>(); + + this->behaviorscript = component; + ASSERT_TRUE(this->behaviorscript); + EXPECT_EQ(component.script.get(), nullptr); + component.set_script<NiceMock<MyScript>>(); + ASSERT_NE(component.script.get(), nullptr); + + this->script = *(MyScript *) component.script.get(); + ASSERT_TRUE(this->script); +} TEST_F(ScriptTest, Default) { MyScript & script = this->script; - EXPECT_EQ(0, script.init_count); - EXPECT_EQ(0, script.update_count); - EXPECT_EQ(0, script.event_count); + EXPECT_CALL(script, init()).Times(0); + EXPECT_CALL(script, update()).Times(0); } TEST_F(ScriptTest, UpdateOnce) { MyScript & script = this->script; - system.update(); - EXPECT_EQ(1, script.init_count); - EXPECT_EQ(1, script.update_count); - EXPECT_EQ(0, script.event_count); + { + InSequence seq; + + EXPECT_CALL(script, init()).Times(1); + EXPECT_CALL(script, update()).Times(1); + system.update(); + } + + { + InSequence seq; + + EXPECT_CALL(script, init()).Times(0); + EXPECT_CALL(script, update()).Times(1); + system.update(); + } } TEST_F(ScriptTest, UpdateInactive) { BehaviorScript & behaviorscript = this->behaviorscript; MyScript & script = this->script; - behaviorscript.active = false; - system.update(); - EXPECT_EQ(0, script.init_count); - EXPECT_EQ(0, script.update_count); - EXPECT_EQ(0, script.event_count); - - behaviorscript.active = true; - system.update(); - EXPECT_EQ(1, script.init_count); - EXPECT_EQ(1, script.update_count); - EXPECT_EQ(0, script.event_count); -} + { + InSequence seq; -TEST_F(ScriptTest, EventInactive) { - BehaviorScript & behaviorscript = this->behaviorscript; - MyScript & script = this->script; - EventManager & evmgr = this->event_manager; - - system.update(); - behaviorscript.active = false; - EXPECT_EQ(1, script.init_count); - EXPECT_EQ(1, script.update_count); - EXPECT_EQ(0, script.event_count); - - evmgr.trigger_event<MyEvent>(); - EXPECT_EQ(1, script.init_count); - EXPECT_EQ(1, script.update_count); - EXPECT_EQ(0, script.event_count); - - behaviorscript.active = true; - evmgr.trigger_event<MyEvent>(); - EXPECT_EQ(1, script.init_count); - EXPECT_EQ(1, script.update_count); - EXPECT_EQ(1, script.event_count); + EXPECT_CALL(script, init()).Times(0); + EXPECT_CALL(script, update()).Times(0); + behaviorscript.active = false; + system.update(); + } + + { + InSequence seq; + + EXPECT_CALL(script, init()).Times(1); + EXPECT_CALL(script, update()).Times(1); + behaviorscript.active = true; + system.update(); + } } + diff --git a/src/test/ScriptTest.h b/src/test/ScriptTest.h new file mode 100644 index 0000000..9a71ba7 --- /dev/null +++ b/src/test/ScriptTest.h @@ -0,0 +1,29 @@ +#pragma once + +#include <gtest/gtest.h> +#include <gmock/gmock.h> + +#include <crepe/manager/ComponentManager.h> +#include <crepe/system/ScriptSystem.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Script.h> + +class ScriptTest : public testing::Test { +protected: + crepe::Mediator mediator; +public: + crepe::ComponentManager component_manager{mediator}; + crepe::ScriptSystem system{mediator}; + + class MyScript : public crepe::Script { + // NOTE: explicitly stating `public:` is not required on actual scripts + public: + MOCK_METHOD(void, init, (), (override)); + MOCK_METHOD(void, update, (), (override)); + }; + + crepe::OptionalRef<crepe::BehaviorScript> behaviorscript; + crepe::OptionalRef<MyScript> script; + + virtual void SetUp(); +}; diff --git a/src/test/main.cpp b/src/test/main.cpp index aece72d..54f74fd 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -1,8 +1,4 @@ #include <gtest/gtest.h> - -#define protected public -#define private public - #include <crepe/api/Config.h> using namespace crepe; @@ -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, + }, + }; } }; @@ -28,3 +26,4 @@ int main(int argc, char ** argv) { return RUN_ALL_TESTS(); } + |