diff options
| author | max-001 <maxsmits21@kpnmail.nl> | 2024-12-11 13:21:19 +0100 | 
|---|---|---|
| committer | max-001 <maxsmits21@kpnmail.nl> | 2024-12-11 13:21:19 +0100 | 
| commit | 357ebfc3eac1d39b02875e1bd78ae6f58b10f824 (patch) | |
| tree | 7d79435fa764c0fedcc912ddaf876ff03ef8be5e /src | |
| parent | 6eef90e9ffb1d8fc25161e912bc5d8aa8e4024da (diff) | |
| parent | c45b60941b82dec2097d958b396a117b1634eada (diff) | |
Merge remote-tracking branch 'origin/master' into max/AI
Diffstat (limited to 'src')
52 files changed, 1201 insertions, 422 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..eff5a58 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; 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..eceb15b --- /dev/null +++ b/src/crepe/Resource.h @@ -0,0 +1,29 @@ +#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; + +	Resource(const Resource &) = delete; +	Resource(Resource &&) = delete; +	Resource & operator=(const Resource &) = delete; +	Resource & operator=(Resource &&) = delete; +}; + +} // 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 3b1cc4b..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..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..b20e490 --- /dev/null +++ b/src/crepe/api/AudioSource.h @@ -0,0 +1,74 @@ +#pragma once + +#include "../Component.h" +#include "../facade/SoundHandle.h" +#include "../types.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 f869d88..118c7ce 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 @@ -28,7 +27,7 @@ target_sources(crepe PUBLIC  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES -	# AudioSource.h +	AudioSource.h  	BehaviorScript.h  	Config.h  	Script.h @@ -41,8 +40,6 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	Vector2.hpp  	Color.h  	Texture.h -	AssetManager.h -	AssetManager.hpp  	Scene.h  	Metadata.h  	Camera.h diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h index a9745c3..6472270 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 {  		/** @@ -86,6 +76,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/LoopManager.cpp b/src/crepe/api/LoopManager.cpp index 7617600..1f0ba72 100644 --- a/src/crepe/api/LoopManager.cpp +++ b/src/crepe/api/LoopManager.cpp @@ -1,5 +1,6 @@  #include "../system/AISystem.h"  #include "../system/AnimatorSystem.h" +#include "../system/AudioSystem.h"  #include "../system/CollisionSystem.h"  #include "../system/InputSystem.h"  #include "../system/ParticleSystem.h" @@ -21,6 +22,7 @@ LoopManager::LoopManager() {  	this->load_system<RenderSystem>();  	this->load_system<ScriptSystem>();  	this->load_system<InputSystem>(); +	this->load_system<AudioSystem>();  	this->load_system<AISystem>();  } @@ -40,6 +42,7 @@ void LoopManager::fixed_update() {  	this->get_system<AISystem>().update();  	this->get_system<PhysicsSystem>().update();  	this->get_system<CollisionSystem>().update(); +	this->get_system<AudioSystem>().update();  }  void LoopManager::loop() { diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h index d8910a0..33a61a3 100644 --- a/src/crepe/api/LoopManager.h +++ b/src/crepe/api/LoopManager.h @@ -4,7 +4,9 @@  #include "../facade/SDLContext.h"  #include "../manager/ComponentManager.h" +#include "../manager/ResourceManager.h"  #include "../manager/SceneManager.h" +#include "../manager/SaveManager.h"  #include "../system/System.h"  #include "LoopTimer.h" @@ -95,6 +97,10 @@ private:  	ComponentManager component_manager{mediator};  	//! Scene manager instance  	SceneManager scene_manager{mediator}; +	//! Resource manager instance +	ResourceManager resource_manager{mediator}; +	//! Save manager instance +	SaveManager save_manager{mediator};  	//! SDL context \todo no more singletons!  	SDLContext & sdl_context = SDLContext::get_instance(); diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp index 4091fd4..753a9e3 100644 --- a/src/crepe/api/Script.cpp +++ b/src/crepe/api/Script.cpp @@ -8,8 +8,7 @@ using namespace crepe;  using namespace std;  Script::~Script() { -	Mediator & mediator = this->mediator; -	EventManager & mgr = mediator.event_manager; +	EventManager & mgr = this->mediator->event_manager;  	for (auto id : this->listeners) {  		mgr.unsubscribe(id);  	} @@ -21,7 +20,8 @@ void Script::subscribe(const EventHandler<CollisionEvent> & callback) {  }  void Script::set_next_scene(const string & name) { -	Mediator & mediator = this->mediator; -	SceneManager & mgr = mediator.scene_manager; +	SceneManager & mgr = this->mediator->scene_manager;  	mgr.set_next_scene(name);  } + +SaveManager & Script::get_save_manager() const { return this->mediator->save_manager; } diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h index d99ab0e..668e5d1 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -86,6 +86,25 @@ protected:  	RefVector<T> get_components() const;  	/** +	 * \copydoc ComponentManager::get_components_by_id +	 * \see ComponentManager::get_components_by_id +	 */ +	template <typename T> +	RefVector<T> get_components_by_id(game_object_id_t id) const; +	/** +	 * \copydoc ComponentManager::get_components_by_name +	 * \see ComponentManager::get_components_by_name +	 */ +	template <typename T> +	RefVector<T> get_components_by_name(const std::string & name) const; +	/** +	 * \copydoc ComponentManager::get_components_by_tag +	 * \see ComponentManager::get_components_by_tag +	 */ +	template <typename T> +	RefVector<T> get_components_by_tag(const std::string & tag) const; + +	/**  	 * \brief Log a message using Log::logf  	 *  	 * \tparam Args Log::logf parameters @@ -113,6 +132,9 @@ protected:  	 */  	void set_next_scene(const std::string & name); +	//! Retrieve SaveManager reference +	SaveManager & get_save_manager() const; +  	//! \}  private: diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp index 45f1ff1..225a51c 100644 --- a/src/crepe/api/Script.hpp +++ b/src/crepe/api/Script.hpp @@ -20,10 +20,7 @@ T & Script::get_component() const {  template <typename T>  RefVector<T> Script::get_components() const { -	Mediator & mediator = this->mediator; -	ComponentManager & mgr = mediator.component_manager; - -	return mgr.get_components_by_id<T>(this->game_object_id); +	return this->get_components_by_id<T>(this->game_object_id);  }  template <typename... Args> @@ -34,8 +31,7 @@ void Script::logf(Args &&... args) {  template <typename EventType>  void Script::subscribe_internal(const EventHandler<EventType> & callback,  								event_channel_t channel) { -	Mediator & mediator = this->mediator; -	EventManager & mgr = mediator.event_manager; +	EventManager & mgr = this->mediator->event_manager;  	subscription_t listener = mgr.subscribe<EventType>(  		[this, callback](const EventType & data) -> bool {  			bool & active = this->active; @@ -56,4 +52,26 @@ void Script::subscribe(const EventHandler<EventType> & callback) {  	this->subscribe_internal(callback, EventManager::CHANNEL_ALL);  } +template <typename T> +RefVector<T> Script::get_components_by_id(game_object_id_t id) const { +	Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; + +	return mgr.get_components_by_id<T>(id); +} +template <typename T> +RefVector<T> Script::get_components_by_name(const std::string & name) const { +	Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; + +	return mgr.get_components_by_name<T>(name); +} +template <typename T> +RefVector<T> Script::get_components_by_tag(const std::string & tag) const { +	Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; + +	return mgr.get_components_by_tag<T>(tag); +} +  } // namespace crepe 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..b1f8cb3 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(); +} + +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..d986c59 100644 --- a/src/crepe/facade/SoundContext.h +++ b/src/crepe/facade/SoundContext.h @@ -2,30 +2,80 @@  #include <soloud/soloud.h> +#include "../api/Config.h" +  #include "Sound.h" +#include "SoundHandle.h"  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..b7925fc --- /dev/null +++ b/src/crepe/facade/SoundHandle.h @@ -0,0 +1,12 @@ +#pragma once + +#include <cstddef> + +namespace crepe { + +/** + * \brief Voice handle returned by + */ +typedef size_t SoundHandle; + +} // 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/ComponentManager.cpp b/src/crepe/manager/ComponentManager.cpp index 80cf8b4..df30d27 100644 --- a/src/crepe/manager/ComponentManager.cpp +++ b/src/crepe/manager/ComponentManager.cpp @@ -1,4 +1,5 @@  #include "../api/GameObject.h" +#include "../api/Metadata.h"  #include "../types.h"  #include "../util/Log.h" @@ -61,3 +62,13 @@ GameObject ComponentManager::new_object(const string & name, const string & tag,  void ComponentManager::set_persistent(game_object_id_t id, bool persistent) {  	this->persistent[id] = persistent;  } + +set<game_object_id_t> ComponentManager::get_objects_by_name(const string & name) const { +	return this->get_objects_by_predicate<Metadata>( +		[name](const Metadata & data) { return data.name == name; }); +} + +set<game_object_id_t> ComponentManager::get_objects_by_tag(const string & tag) const { +	return this->get_objects_by_predicate<Metadata>( +		[tag](const Metadata & data) { return data.tag == tag; }); +} diff --git a/src/crepe/manager/ComponentManager.h b/src/crepe/manager/ComponentManager.h index 44429d9..19a8e81 100644 --- a/src/crepe/manager/ComponentManager.h +++ b/src/crepe/manager/ComponentManager.h @@ -1,6 +1,7 @@  #pragma once  #include <memory> +#include <set>  #include <typeindex>  #include <unordered_map>  #include <vector> @@ -134,8 +135,77 @@ public:  	 */  	template <typename T>  	RefVector<T> get_components_by_type() const; +	/** +	 * \brief Get all components of a specific type on a GameObject with name \c name +	 *  +	 * \tparam T The type of the component +	 * \param name Metadata::name for the same game_object_id as the returned components +	 * \return Components matching criteria +	 */ +	template <typename T> +	RefVector<T> get_components_by_name(const std::string & name) const; +	/** +	 * \brief Get all components of a specific type on a GameObject with tag \c tag +	 *  +	 * \tparam T The type of the component +	 * \param name Metadata::tag for the same game_object_id as the returned components +	 * \return Components matching criteria +	 */ +	template <typename T> +	RefVector<T> get_components_by_tag(const std::string & tag) const; + +private: +	/** +	 * \brief Get object IDs by predicate function +	 * +	 * This function calls the predicate function \c pred for all components matching type \c T, +	 * and adds their parent game_object_id to a \c std::set if the predicate returns true. +	 * +	 * \tparam T The type of the component to check the predicate against +	 * \param pred Predicate function +	 * +	 * \note The predicate function may be called for multiple components with the same \c +	 * game_object_id. In this case, the ID is added if *any* call returns \c true. +	 * +	 * \returns game_object_id for all components where the predicate returned true +	 */ +	template <typename T> +	std::set<game_object_id_t> +	get_objects_by_predicate(const std::function<bool(const T &)> & pred) const; + +	/** +	 * \brief Get components of type \c T for multiple game object IDs +	 * +	 * \tparam T The type of the components to return +	 * \param ids The object IDs +	 * +	 * \return All components matching type \c T and one of the IDs in \c ids +	 */ +	template <typename T> +	RefVector<T> get_components_by_ids(const std::set<game_object_id_t> & ids) const; + +	/** +	 * \brief Get object IDs for objects with name \c name +	 * +	 * \param name Object name to match +	 * \returns Object IDs where Metadata::name is equal to \c name +	 */ +	std::set<game_object_id_t> get_objects_by_name(const std::string & name) const; +	/** +	 * \brief Get object IDs for objects with tag \c tag +	 * +	 * \param tag Object tag to match +	 * \returns Object IDs where Metadata::tag is equal to \c tag +	 */ +	std::set<game_object_id_t> get_objects_by_tag(const std::string & tag) const;  private: +	//! By Component \c std::type_index (readability helper type) +	template <typename T> +	using by_type = std::unordered_map<std::type_index, T>; +	//! By \c game_object_id index (readability helper type) +	template <typename T> +	using by_id_index = std::vector<T>;  	/**  	 * \brief The components  	 * @@ -146,8 +216,7 @@ private:  	 * The first vector is for the ids of the GameObjects and the second vector is for the  	 * components (because a GameObject might have multiple components).  	 */ -	std::unordered_map<std::type_index, std::vector<std::vector<std::unique_ptr<Component>>>> -		components; +	by_type<by_id_index<std::vector<std::unique_ptr<Component>>>> components;  	//! Persistent flag for each GameObject  	std::unordered_map<game_object_id_t, bool> persistent; diff --git a/src/crepe/manager/ComponentManager.hpp b/src/crepe/manager/ComponentManager.hpp index ffb38ec..9e70865 100644 --- a/src/crepe/manager/ComponentManager.hpp +++ b/src/crepe/manager/ComponentManager.hpp @@ -95,32 +95,25 @@ template <typename T>  RefVector<T> ComponentManager::get_components_by_id(game_object_id_t id) const {  	using namespace std; -	// Determine the type of T (this is used as the key of the unordered_map<>) -	type_index type = typeid(T); - -	// Create an empty vector<> -	RefVector<T> component_vector; - -	if (this->components.find(type) == this->components.end()) return component_vector; - -	// Get the correct vector<> -	const vector<vector<unique_ptr<Component>>> & component_array = this->components.at(type); - -	// Make sure that the id (that we are looking for) is within the boundaries of the vector<> -	if (id >= component_array.size()) return component_vector; - -	// Loop trough the whole vector<> -	for (const unique_ptr<Component> & component_ptr : component_array[id]) { -		// Cast the unique_ptr to a raw pointer -		T * casted_component = static_cast<T *>(component_ptr.get()); - -		if (casted_component == nullptr) continue; +	static_assert(is_base_of<Component, T>::value, +				  "get_components_by_id must recieve a derivative class of Component"); -		// Add the dereferenced raw pointer to the vector<> -		component_vector.push_back(*casted_component); +	type_index type = typeid(T); +	if (!this->components.contains(type)) return {}; + +	const by_id_index<vector<unique_ptr<Component>>> & components_by_id +		= this->components.at(type); +	if (id >= components_by_id.size()) return {}; + +	RefVector<T> out = {}; +	const vector<unique_ptr<Component>> & components = components_by_id.at(id); +	for (auto & component_ptr : components) { +		if (component_ptr == nullptr) continue; +		Component & component = *component_ptr.get(); +		out.push_back(static_cast<T &>(component));  	} -	return component_vector; +	return out;  }  template <typename T> @@ -158,4 +151,46 @@ RefVector<T> ComponentManager::get_components_by_type() const {  	return component_vector;  } +template <typename T> +std::set<game_object_id_t> +ComponentManager::get_objects_by_predicate(const std::function<bool(const T &)> & pred) const { +	using namespace std; + +	set<game_object_id_t> objects = {}; +	RefVector<T> components = this->get_components_by_type<T>(); + +	for (const T & component : components) { +		game_object_id_t id = dynamic_cast<const Component &>(component).game_object_id; +		if (objects.contains(id)) continue; +		if (!pred(component)) continue; +		objects.insert(id); +	} + +	return objects; +} + +template <typename T> +RefVector<T> +ComponentManager::get_components_by_ids(const std::set<game_object_id_t> & ids) const { +	using namespace std; + +	RefVector<T> out = {}; +	for (game_object_id_t id : ids) { +		RefVector<T> components = get_components_by_id<T>(id); +		out.insert(out.end(), components.begin(), components.end()); +	} + +	return out; +} + +template <typename T> +RefVector<T> ComponentManager::get_components_by_name(const std::string & name) const { +	return this->get_components_by_ids<T>(this->get_objects_by_name(name)); +} + +template <typename T> +RefVector<T> ComponentManager::get_components_by_tag(const std::string & tag) const { +	return this->get_components_by_ids<T>(this->get_objects_by_tag(tag)); +} +  } // namespace crepe diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h index 8094d80..35ac181 100644 --- a/src/crepe/manager/Mediator.h +++ b/src/crepe/manager/Mediator.h @@ -5,13 +5,14 @@  // TODO: remove these singletons:  #include "../facade/SDLContext.h"  #include "EventManager.h" -#include "SaveManager.h"  #include "api/LoopTimer.h"  namespace crepe {  class ComponentManager;  class SceneManager; +class SaveManager; +class ResourceManager;  /**   * Struct to pass references to classes that would otherwise need to be singletons down to @@ -28,8 +29,9 @@ class SceneManager;  struct Mediator {  	OptionalRef<ComponentManager> component_manager;  	OptionalRef<SceneManager> scene_manager; -	OptionalRef<SaveManager> save_manager = SaveManager::get_instance(); +	OptionalRef<SaveManager> save_manager;  	OptionalRef<EventManager> event_manager = EventManager::get_instance(); +	OptionalRef<ResourceManager> resource_manager;  	OptionalRef<SDLContext> sdl_context = SDLContext::get_instance();  	OptionalRef<LoopTimer> timer = LoopTimer::get_instance();  }; 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/manager/SaveManager.cpp b/src/crepe/manager/SaveManager.cpp index d4ed1c1..39b92d4 100644 --- a/src/crepe/manager/SaveManager.cpp +++ b/src/crepe/manager/SaveManager.cpp @@ -1,13 +1,27 @@  #include "../ValueBroker.h"  #include "../api/Config.h"  #include "../facade/DB.h" -#include "../util/Log.h"  #include "SaveManager.h"  using namespace std;  using namespace crepe; +SaveManager::SaveManager(Mediator & mediator) : Manager(mediator) { +	mediator.save_manager = *this; +} + +DB & SaveManager::get_db() { +	if (this->db == nullptr) { +		Config & cfg = Config::get_instance(); +		this->db = { +			new DB(cfg.savemgr.location), +			[](void * db){ delete static_cast<DB *>(db); } +		}; +	} +	return *static_cast<DB *>(this->db.get()); +} +  template <>  string SaveManager::serialize(const string & value) const noexcept {  	return value; @@ -90,22 +104,6 @@ int32_t SaveManager::deserialize(const string & value) const noexcept {  	return deserialize<int64_t>(value);  } -SaveManager::SaveManager() { dbg_trace(); } - -SaveManager & SaveManager::get_instance() { -	dbg_trace(); -	static SaveManager instance; -	return instance; -} - -DB & SaveManager::get_db() { -	Config & cfg = Config::get_instance(); -	// TODO: make this path relative to XDG_DATA_HOME on Linux and whatever the -	// default equivalent is on Windows using some third party library -	static DB db(cfg.savemgr.location); -	return db; -} -  bool SaveManager::has(const string & key) {  	DB & db = this->get_db();  	return db.has(key); @@ -155,7 +153,8 @@ ValueBroker<T> SaveManager::get(const string & key) {  	return {  		[this, key](const T & target) { this->set<T>(key, target); },  		[this, key, value]() mutable -> const T & { -			value = this->deserialize<T>(this->get_db().get(key)); +			DB & db = this->get_db(); +			value = this->deserialize<T>(db.get(key));  			return value;  		},  	}; diff --git a/src/crepe/manager/SaveManager.h b/src/crepe/manager/SaveManager.h index 3d8c852..27e625c 100644 --- a/src/crepe/manager/SaveManager.h +++ b/src/crepe/manager/SaveManager.h @@ -1,9 +1,12 @@  #pragma once  #include <memory> +#include <functional>  #include "../ValueBroker.h" +#include "Manager.h" +  namespace crepe {  class DB; @@ -18,7 +21,7 @@ class DB;   *   * The underlying database is a key-value store.   */ -class SaveManager { +class SaveManager : public Manager {  public:  	/**  	 * \brief Get a read/write reference to a value and initialize it if it does not yet exist @@ -63,8 +66,8 @@ public:  	 */  	bool has(const std::string & key); -private: -	SaveManager(); +public: +	SaveManager(Mediator & mediator);  	virtual ~SaveManager() = default;  private: @@ -89,26 +92,12 @@ private:  	template <typename T>  	T deserialize(const std::string & value) const noexcept; -public: -	// singleton -	static SaveManager & get_instance(); -	SaveManager(const SaveManager &) = delete; -	SaveManager(SaveManager &&) = delete; -	SaveManager & operator=(const SaveManager &) = delete; -	SaveManager & operator=(SaveManager &&) = delete; - +protected: +	//! Create or return DB +	virtual DB & get_db();  private: -	/** -	 * \brief Create an instance of DB and return its reference -	 * -	 * \returns DB instance -	 * -	 * This function exists because DB is a facade class, which can't directly be used in the API -	 * without workarounds -	 * -	 * TODO: better solution -	 */ -	static DB & get_db(); +	//! Database +	std::unique_ptr<void, std::function<void(void*)>> db = nullptr;  };  } // namespace crepe diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp new file mode 100644 index 0000000..ddba268 --- /dev/null +++ b/src/crepe/system/AudioSystem.cpp @@ -0,0 +1,63 @@ +#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) { +			context.stop(component.voice); +			return; +		} +		if (component.play_on_awake) +			component.oneshot_play = true; +	} +	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 7de5198..0e2db76 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  	AISystem.cpp @@ -16,6 +17,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	PhysicsSystem.h  	CollisionSystem.h  	RenderSystem.h +	AudioSystem.h  	AnimatorSystem.h  	InputSystem.h  	AISystem.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/crepe/util/OptionalRef.h b/src/crepe/util/OptionalRef.h index 3201667..1b2cb3f 100644 --- a/src/crepe/util/OptionalRef.h +++ b/src/crepe/util/OptionalRef.h @@ -25,7 +25,7 @@ public:  	 */  	OptionalRef<T> & operator=(T & ref);  	/** -	 * \brief Retrieve this reference +	 * \brief Retrieve this reference (cast)  	 *  	 * \returns Internal reference if it is set  	 * @@ -33,6 +33,14 @@ public:  	 */  	operator T &() const;  	/** +	 * \brief Retrieve this reference (member access) +	 * +	 * \returns Internal reference if it is set +	 * +	 * \throws std::runtime_error if this function is called while the reference it not set +	 */ +	T * operator->() const; +	/**  	 * \brief Check if this reference is not empty  	 *  	 * \returns `true` if reference is set, or `false` if it is not diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp index 4608c9e..5e36b3a 100644 --- a/src/crepe/util/OptionalRef.hpp +++ b/src/crepe/util/OptionalRef.hpp @@ -19,6 +19,13 @@ OptionalRef<T>::operator T &() const {  }  template <typename T> +T * OptionalRef<T>::operator->() const { +	if (this->ref == nullptr) +		throw std::runtime_error("OptionalRef: attempt to dereference nullptr"); +	return this->ref; +} + +template <typename T>  OptionalRef<T> & OptionalRef<T>::operator=(T & ref) {  	this->ref = &ref;  	return *this; diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index 149c412..187ed46 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -16,8 +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)  add_example(button) 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/example/savemgr.cpp b/src/example/savemgr.cpp deleted file mode 100644 index 65c4a34..0000000 --- a/src/example/savemgr.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/** \file - *  - * Standalone example for usage of the save manager - */ - -#include <cassert> -#include <crepe/api/Config.h> -#include <crepe/api/SaveManager.h> -#include <crepe/util/Log.h> -#include <crepe/util/Proxy.h> - -using namespace crepe; - -// unrelated setup code -int _ = []() { -	// make sure all log messages get printed -	auto & cfg = Config::get_instance(); -	cfg.log.level = Log::Level::TRACE; - -	return 0; // satisfy compiler -}(); - -int main() { -	const char * key = "mygame.test"; - -	SaveManager & mgr = SaveManager::get_instance(); - -	dbg_logf("has key = {}", mgr.has(key)); -	ValueBroker<int> prop = mgr.get<int>(key, 0); -	Proxy<int> val = mgr.get<int>(key, 0); - -	dbg_logf("val = {}", mgr.get<int>(key).get()); -	prop.set(1); -	dbg_logf("val = {}", mgr.get<int>(key).get()); -	val = 2; -	dbg_logf("val = {}", mgr.get<int>(key).get()); -	mgr.set<int>(key, 3); -	dbg_logf("val = {}", mgr.get<int>(key).get()); - -	dbg_logf("has key = {}", mgr.has(key)); -	assert(true == mgr.has(key)); - -	return 0; -} diff --git a/src/test/AudioTest.cpp b/src/test/AudioTest.cpp new file mode 100644 index 0000000..48bba1b --- /dev/null +++ b/src/test/AudioTest.cpp @@ -0,0 +1,161 @@ +#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(); +	} +} + +TEST_F(AudioTest, PlayImmediately) { +	component.play_on_awake = false; +	component.play(); + +	EXPECT_CALL(context, play(_)).Times(1); + +	system.update(); +} diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index c9cbac5..7196404 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 @@ -17,4 +19,7 @@ target_sources(test_main PUBLIC  	ScriptEventTest.cpp  	ScriptSceneTest.cpp  	Profiling.cpp +	SaveManagerTest.cpp +	ScriptSaveManagerTest.cpp +	ScriptECSTest.cpp  ) diff --git a/src/test/ECSTest.cpp b/src/test/ECSTest.cpp index 3e6c61c..af2b7b0 100644 --- a/src/test/ECSTest.cpp +++ b/src/test/ECSTest.cpp @@ -1,6 +1,7 @@  #include <gtest/gtest.h>  #define protected public +#define private public  #include <crepe/api/GameObject.h>  #include <crepe/api/Metadata.h> @@ -16,6 +17,10 @@ class ECSTest : public ::testing::Test {  public:  	ComponentManager mgr{m}; + +	class TestComponent : public Component { +		using Component::Component; +	};  };  TEST_F(ECSTest, createGameObject) { @@ -387,3 +392,77 @@ TEST_F(ECSTest, resetPersistent) {  	EXPECT_EQ(metadata.size(), 0);  	EXPECT_EQ(transform.size(), 0);  } + +TEST_F(ECSTest, IDByName) { +	GameObject foo = mgr.new_object("foo"); +	GameObject bar = mgr.new_object("bar"); + +	{ +		auto objects = mgr.get_objects_by_name(""); +		EXPECT_EQ(objects.size(), 0); +	} + +	{ +		auto objects = mgr.get_objects_by_name("foo"); +		EXPECT_EQ(objects.size(), 1); +		EXPECT_TRUE(objects.contains(foo.id)); +	} +} + +TEST_F(ECSTest, IDByTag) { +	GameObject foo = mgr.new_object("foo", "common tag"); +	GameObject bar = mgr.new_object("bar", "common tag"); + +	{ +		auto objects = mgr.get_objects_by_tag(""); +		EXPECT_EQ(objects.size(), 0); +	} + +	{ +		auto objects = mgr.get_objects_by_tag("common tag"); +		EXPECT_EQ(objects.size(), 2); +		EXPECT_TRUE(objects.contains(foo.id)); +		EXPECT_TRUE(objects.contains(bar.id)); +	} +} + +TEST_F(ECSTest, ComponentsByName) { +	GameObject foo = mgr.new_object("foo"); +	foo.add_component<TestComponent>(); +	GameObject bar = mgr.new_object("bar"); +	bar.add_component<TestComponent>(); +	bar.add_component<TestComponent>(); + +	{ +		auto objects = mgr.get_components_by_name<TestComponent>(""); +		EXPECT_EQ(objects.size(), 0); +	} + +	{ +		auto objects = mgr.get_components_by_name<TestComponent>("foo"); +		EXPECT_EQ(objects.size(), 1); +	} + +	{ +		auto objects = mgr.get_components_by_name<TestComponent>("bar"); +		EXPECT_EQ(objects.size(), 2); +	} +} + +TEST_F(ECSTest, ComponentsByTag) { +	GameObject foo = mgr.new_object("foo", "common tag"); +	foo.add_component<TestComponent>(); +	GameObject bar = mgr.new_object("bar", "common tag"); +	bar.add_component<TestComponent>(); +	bar.add_component<TestComponent>(); + +	{ +		auto objects = mgr.get_components_by_tag<TestComponent>(""); +		EXPECT_EQ(objects.size(), 0); +	} + +	{ +		auto objects = mgr.get_components_by_tag<TestComponent>("common tag"); +		EXPECT_EQ(objects.size(), 3); +	} +} diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp new file mode 100644 index 0000000..44a5921 --- /dev/null +++ b/src/test/ResourceManagerTest.cpp @@ -0,0 +1,81 @@ +#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}; + +	class Unrelated : public Resource { +		using Resource::Resource; +	}; + +	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); +} + +TEST_F(ResourceManagerTest, UnmatchedType) { +	EXPECT_NO_THROW({ resource_manager.get<TestResource>(asset_a); }); + +	EXPECT_THROW({ resource_manager.get<Unrelated>(asset_a); }, runtime_error); +} diff --git a/src/test/SaveManagerTest.cpp b/src/test/SaveManagerTest.cpp new file mode 100644 index 0000000..e9b0c29 --- /dev/null +++ b/src/test/SaveManagerTest.cpp @@ -0,0 +1,40 @@ +#include <gtest/gtest.h> + +#include <crepe/ValueBroker.h> +#include <crepe/facade/DB.h> +#include <crepe/manager/SaveManager.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class SaveManagerTest : public Test { +	Mediator m; +	class TestSaveManager : public SaveManager { +		using SaveManager::SaveManager; + +		// in-memory database for testing +		DB db{}; +		virtual DB & get_db() override { return this->db; } +	}; + +public: +	TestSaveManager mgr{m}; +}; + +TEST_F(SaveManagerTest, ReadWrite) { +	ASSERT_FALSE(mgr.has("foo")); +	mgr.set<string>("foo", "bar"); +	ASSERT_TRUE(mgr.has("foo")); + +	ValueBroker value = mgr.get<string>("foo"); +	EXPECT_EQ(value.get(), "bar"); +} + +TEST_F(SaveManagerTest, DefaultValue) { +	ValueBroker value = mgr.get<int>("foo", 3); + +	ASSERT_EQ(value.get(), 3); +	value.set(5); +	ASSERT_EQ(value.get(), 5); +} diff --git a/src/test/ScriptECSTest.cpp b/src/test/ScriptECSTest.cpp new file mode 100644 index 0000000..1ec33ba --- /dev/null +++ b/src/test/ScriptECSTest.cpp @@ -0,0 +1,41 @@ +#include <gtest/gtest.h> + +#define protected public + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Metadata.h> +#include <crepe/api/Script.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/system/ScriptSystem.h> + +#include "ScriptTest.h" + +using namespace std; +using namespace crepe; +using namespace testing; + +class ScriptECSTest : public ScriptTest { +public: +	class TestComponent : public Component { +		using Component::Component; +	}; +}; + +TEST_F(ScriptECSTest, GetOwnComponent) { +	MyScript & script = this->script; +	Metadata & metadata = script.get_component<Metadata>(); + +	EXPECT_EQ(metadata.name, OBJ_NAME); +} + +TEST_F(ScriptECSTest, GetOwnComponents) { +	const unsigned COUNT = 4; + +	for (unsigned i = 0; i < COUNT; i++) entity.add_component<TestComponent>(); + +	MyScript & script = this->script; +	RefVector<TestComponent> components = script.get_components<TestComponent>(); + +	EXPECT_EQ(components.size(), COUNT); +} diff --git a/src/test/ScriptEventTest.cpp b/src/test/ScriptEventTest.cpp index 5da31e7..c1b4028 100644 --- a/src/test/ScriptEventTest.cpp +++ b/src/test/ScriptEventTest.cpp @@ -26,7 +26,7 @@ public:  	class MyEvent : public Event {};  }; -TEST_F(ScriptEventTest, Inactive) { +TEST_F(ScriptEventTest, Default) {  	BehaviorScript & behaviorscript = this->behaviorscript;  	MyScript & script = this->script;  	EventManager & evmgr = this->event_manager; diff --git a/src/test/ScriptSaveManagerTest.cpp b/src/test/ScriptSaveManagerTest.cpp new file mode 100644 index 0000000..64403c4 --- /dev/null +++ b/src/test/ScriptSaveManagerTest.cpp @@ -0,0 +1,35 @@ +#include <gtest/gtest.h> + +// stupid hack to allow access to private/protected members under test +#define private public +#define protected public + +#include <crepe/facade/DB.h> +#include <crepe/manager/SaveManager.h> + +#include "ScriptTest.h" + +using namespace std; +using namespace crepe; +using namespace testing; + +class ScriptSaveManagerTest : public ScriptTest { +public: +	class TestSaveManager : public SaveManager { +		using SaveManager::SaveManager; + +		// in-memory database for testing +		DB db{}; +		virtual DB & get_db() override { return this->db; } +	}; + +	TestSaveManager save_mgr{mediator}; +}; + +TEST_F(ScriptSaveManagerTest, GetSaveManager) { +	MyScript & script = this->script; + +	SaveManager & mgr = script.get_save_manager(); + +	EXPECT_EQ(&mgr, &save_mgr); +} diff --git a/src/test/ScriptSceneTest.cpp b/src/test/ScriptSceneTest.cpp index 9ee1e52..2568049 100644 --- a/src/test/ScriptSceneTest.cpp +++ b/src/test/ScriptSceneTest.cpp @@ -18,7 +18,7 @@ public:  	class MyScene : public Scene {};  }; -TEST_F(ScriptSceneTest, Inactive) { +TEST_F(ScriptSceneTest, Default) {  	BehaviorScript & behaviorscript = this->behaviorscript;  	MyScript & script = this->script; diff --git a/src/test/ScriptTest.cpp b/src/test/ScriptTest.cpp index 1d2d6dd..acdae70 100644 --- a/src/test/ScriptTest.cpp +++ b/src/test/ScriptTest.cpp @@ -6,7 +6,6 @@  #define protected public  #include "ScriptTest.h" -#include <crepe/api/GameObject.h>  using namespace std;  using namespace crepe; @@ -14,7 +13,6 @@ using namespace testing;  void ScriptTest::SetUp() {  	auto & mgr = this->component_manager; -	GameObject entity = mgr.new_object("name");  	BehaviorScript & component = entity.add_component<BehaviorScript>();  	this->behaviorscript = component; diff --git a/src/test/ScriptTest.h b/src/test/ScriptTest.h index 1bbfdd3..309e016 100644 --- a/src/test/ScriptTest.h +++ b/src/test/ScriptTest.h @@ -11,10 +11,12 @@  class ScriptTest : public testing::Test {  protected:  	crepe::Mediator mediator; +	static constexpr const char * OBJ_NAME = "foo";  public:  	crepe::ComponentManager component_manager{mediator};  	crepe::ScriptSystem system{mediator}; +	crepe::GameObject entity = component_manager.new_object(OBJ_NAME);  	class MyScript : public crepe::Script {  		// NOTE: explicitly stating `public:` is not required on actual scripts 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, +			}, +		};  	}  };  |