diff options
Diffstat (limited to 'src/crepe')
27 files changed, 590 insertions, 74 deletions
diff --git a/src/crepe/Asset.cpp b/src/crepe/Asset.cpp deleted file mode 100644 index 9c41ecb..0000000 --- a/src/crepe/Asset.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include <filesystem> - -#include "Asset.h" - -using namespace crepe; -using namespace std; - -// FIXME: restore this -// src(std::filesystem::canonical(src)) -Asset::Asset(const std::string & src) : src(src) { - this->file = std::ifstream(this->src, std::ios::in | std::ios::binary); -} - -istream & Asset::get_stream() { return this->file; } - -const string & Asset::get_canonical() const { return this->src; } diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt index 3b05742..df15b8f 100644 --- a/src/crepe/CMakeLists.txt +++ b/src/crepe/CMakeLists.txt @@ -1,19 +1,19 @@ target_sources(crepe PUBLIC - Asset.cpp Particle.cpp ComponentManager.cpp Component.cpp Collider.cpp + Resource.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES - Asset.h ComponentManager.h ComponentManager.hpp Component.h Collider.h ValueBroker.h ValueBroker.hpp + Resource.h ) add_subdirectory(api) diff --git a/src/crepe/Component.h b/src/crepe/Component.h index 5279fb3..2e4ef7d 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; public: @@ -40,6 +45,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/Asset.cpp b/src/crepe/api/Asset.cpp new file mode 100644 index 0000000..5271cf7 --- /dev/null +++ b/src/crepe/api/Asset.cpp @@ -0,0 +1,58 @@ +#include <filesystem> +#include <stdexcept> +#include <whereami.h> + +#include "Asset.h" +#include "api/Config.h" + +using namespace crepe; +using namespace std; + +Asset::Asset(const string & src) : src(find_asset(src)) { } +Asset::Asset(const char * src) : src(find_asset(src)) { } + +const string & Asset::get_path() const noexcept { return this->src; } + +string Asset::find_asset(const string & src) const { + auto & cfg = Config::get_instance(); + auto & root_pattern = cfg.asset.root_pattern; + + // if root_pattern is empty, find_asset must return all paths as-is + if (root_pattern.empty()) return src; + + // absolute paths do not need to be resolved, only canonicalized + filesystem::path path = src; + if (path.is_absolute()) + return filesystem::canonical(path); + + // find directory matching root_pattern + filesystem::path root = this->whereami(); + while (1) { + if (filesystem::exists(root / root_pattern)) + break; + if (!root.has_parent_path()) + throw runtime_error(format("Asset: Cannot find root pattern ({})", root_pattern)); + root = root.parent_path(); + } + + // join path to root (base directory) and canonicalize + return filesystem::canonical(root / path); +} + +string Asset::whereami() const noexcept { + string path; + size_t path_length = wai_getExecutablePath(NULL, 0, NULL); + path.resize(path_length + 1); // wai writes null byte + wai_getExecutablePath(path.data(), path_length, NULL); + path.resize(path_length); + return path; +} + +bool Asset::operator==(const Asset & other) const noexcept { + return this->src == other.src; +} + +size_t std::hash<const Asset>::operator()(const Asset & asset) const noexcept { + return std::hash<string>{}(asset.get_path()); +}; + diff --git a/src/crepe/api/Asset.h b/src/crepe/api/Asset.h new file mode 100644 index 0000000..05dccba --- /dev/null +++ b/src/crepe/api/Asset.h @@ -0,0 +1,60 @@ +#pragma once + +#include <string> +#include <unordered_map> + +namespace crepe { + +/** + * \brief Asset location helper + * + * This class is used to locate game asset files, and should *always* be used + * instead of reading file paths directly. + */ +class Asset { +public: + /** + * \param src Unique identifier to asset + */ + Asset(const std::string & src); + /** + * \param src Unique identifier to asset + */ + Asset(const char * src); + +public: + /** + * \brief Get the path to this asset + * \return path to this asset + */ + const std::string & get_path() const noexcept; + + /** + * \brief Comparison operator + * \param other Possibly different instance of \c Asset to test equality against + * \return True if \c this and \c other are equal + */ + bool operator == (const Asset & other) const noexcept; + +private: + //! path to asset + const std::string src; + +private: + std::string find_asset(const std::string & src) const; + /** + * \returns The path to the current executable + */ + std::string whereami() const noexcept; +}; + +} // namespace crepe + +namespace std { + +template<> struct hash<const crepe::Asset> { + size_t operator()(const crepe::Asset & asset) const noexcept; +}; + +} + 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/AudioSource.cpp b/src/crepe/api/AudioSource.cpp new file mode 100644 index 0000000..4baac9a --- /dev/null +++ b/src/crepe/api/AudioSource.cpp @@ -0,0 +1,20 @@ +#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->playing = true; +} + +void AudioSource::stop() { + this->playing = false; + this->rewind = true; +} + diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h new file mode 100644 index 0000000..1264790 --- /dev/null +++ b/src/crepe/api/AudioSource.h @@ -0,0 +1,60 @@ +#pragma once + +#include "../Component.h" +#include "../types.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; + + //! If this source is playing audio + bool playing = false; + //! Rewind the sample location + bool rewind = false; + +private: + //! Value of \c active after last system update + bool last_active = false; + //! Value of \c playing after last system update + bool last_playing = false; + //! Value of \c volume after last system update + float last_volume = 1.0; + //! Value of \c loop after last system update + bool last_loop = false; +}; + +} // namespace crepe + diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index f9b370f..cca0e8b 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,7 @@ target_sources(crepe PUBLIC Transform.cpp Color.cpp Texture.cpp - AssetManager.cpp + ResourceManager.cpp Sprite.cpp SaveManager.cpp Config.cpp @@ -19,10 +19,11 @@ target_sources(crepe PUBLIC Animator.cpp LoopManager.cpp LoopTimer.cpp + Asset.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES - # AudioSource.h + AudioSource.h BehaviorScript.h Config.h Script.h @@ -34,8 +35,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Vector2.h Color.h Texture.h - AssetManager.h - AssetManager.hpp + ResourceManager.h SaveManager.h Scene.h Metadata.h @@ -45,4 +45,5 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Animator.h LoopManager.h LoopTimer.h + Asset.h ) diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h index 3ab877a..13eabd1 100644 --- a/src/crepe/api/Config.h +++ b/src/crepe/api/Config.h @@ -62,6 +62,20 @@ public: */ double gravity = 1; } physics; + + //! Asset loading options + struct { + /** + * \brief Pattern to match for Asset base directory + * + * All non-absolute paths resolved using \c Asset will be made relative to + * the first parent directory relative to the calling executable where + * appending this pattern results in a path that exists. If this string is + * empty, path resolution is disabled, and Asset will return all paths + * as-is. + */ + std::string root_pattern = ".crepe-root"; + } asset; }; } // namespace crepe diff --git a/src/crepe/api/ResourceManager.cpp b/src/crepe/api/ResourceManager.cpp new file mode 100644 index 0000000..7877ed9 --- /dev/null +++ b/src/crepe/api/ResourceManager.cpp @@ -0,0 +1,41 @@ +#include <stdexcept> + +#include "util/Log.h" + +#include "ResourceManager.h" + +using namespace crepe; +using namespace std; + +ResourceManager & ResourceManager::get_instance() { + static ResourceManager instance; + return instance; +} + +ResourceManager::~ResourceManager() { dbg_trace(); } +ResourceManager::ResourceManager() { dbg_trace(); } + +void ResourceManager::clear() { + this->resources.clear(); +} + +template <typename T> +T & ResourceManager::cache(const Asset & asset) { + dbg_trace(); + static_assert(is_base_of<Resource, T>::value, "cache must recieve a derivative class of Resource"); + + if (!this->resources.contains(asset)) + this->resources[asset] = make_unique<T>(asset); + + Resource * resource = this->resources.at(asset).get(); + T * concrete_resource = dynamic_cast<T *>(resource); + + if (concrete_resource == nullptr) + throw runtime_error(format("ResourceManager: mismatch between requested type and actual type of resource ({})", asset.get_path())); + + return *concrete_resource; +} + +#include "../facade/Sound.h" +template Sound & ResourceManager::cache(const Asset &); + diff --git a/src/crepe/api/ResourceManager.h b/src/crepe/api/ResourceManager.h new file mode 100644 index 0000000..efdd5c5 --- /dev/null +++ b/src/crepe/api/ResourceManager.h @@ -0,0 +1,69 @@ +#pragma once + +#include <memory> +#include <unordered_map> + +#include "Asset.h" +#include "Resource.h" + +namespace crepe { + +class Sound; + +/** + * \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 { + +private: + //! A cache that holds all the assets, accessible by their file path, over multiple scenes. + std::unordered_map<const Asset, std::unique_ptr<Resource>> resources; + +private: + ResourceManager(); // dbg_trace + virtual ~ResourceManager(); // dbg_trace + + ResourceManager(const ResourceManager &) = delete; + ResourceManager(ResourceManager &&) = delete; + ResourceManager & operator=(const ResourceManager &) = delete; + ResourceManager & operator=(ResourceManager &&) = delete; + +public: + /** + * \brief Retrieves the singleton instance of the ResourceManager. + * + * \return A reference to the single instance of the ResourceManager. + */ + static ResourceManager & 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 reference to the resource + * + * This template function caches the asset at the given file path. If the + * asset is already cached, the existing instance will be returned. + * Otherwise, the concrete resource will be instantiated and added to the + * cache. + */ + template <typename T> + T & cache(const Asset & asset); + + //! Clear the resource cache + void clear(); +}; + +} // namespace crepe + diff --git a/src/crepe/api/Texture.cpp b/src/crepe/api/Texture.cpp index de0d0ea..6a1e4d8 100644 --- a/src/crepe/api/Texture.cpp +++ b/src/crepe/api/Texture.cpp @@ -26,7 +26,7 @@ Texture::~Texture() { void Texture::load(unique_ptr<Asset> res) { SDLContext & ctx = SDLContext::get_instance(); - this->texture = std::move(ctx.texture_from_path(res->get_canonical())); + this->texture = std::move(ctx.texture_from_path(res->get_path())); } int Texture::get_width() const { diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index 83e91f8..f2daada 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -149,7 +149,7 @@ SDLContext::texture_from_path(const std::string & path) { SDL_Surface * tmp = IMG_Load(path.c_str()); if (tmp == nullptr) { - tmp = IMG_Load("../asset/texture/ERROR.png"); + tmp = IMG_Load("asset/texture/ERROR.png"); } std::unique_ptr<SDL_Surface, std::function<void(SDL_Surface *)>> img_surface; diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp index 7aa89a9..b589759 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,20 +7,14 @@ 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_canonical().c_str()); } +Sound::~Sound() { dbg_trace(); } void Sound::play() { - SoundContext & ctx = SoundContext::get_instance(); + SoundContext & ctx = this->context.get(); if (ctx.engine.getPause(this->handle)) { // resume if paused ctx.engine.setPause(this->handle, false); @@ -31,13 +26,13 @@ void Sound::play() { } void Sound::pause() { - SoundContext & ctx = SoundContext::get_instance(); + SoundContext & ctx = this->context.get(); if (ctx.engine.getPause(this->handle)) return; ctx.engine.setPause(this->handle, true); } void Sound::rewind() { - SoundContext & ctx = SoundContext::get_instance(); + SoundContext & ctx = this->context.get(); if (!ctx.engine.isValidVoiceHandle(this->handle)) return; ctx.engine.seek(this->handle, 0); } @@ -45,7 +40,7 @@ void Sound::rewind() { void Sound::set_volume(float volume) { this->volume = volume; - SoundContext & ctx = SoundContext::get_instance(); + SoundContext & ctx = this->context.get(); if (!ctx.engine.isValidVoiceHandle(this->handle)) return; ctx.engine.setVolume(this->handle, this->volume); } @@ -53,7 +48,12 @@ void Sound::set_volume(float volume) { void Sound::set_looping(bool looping) { this->looping = looping; - SoundContext & ctx = SoundContext::get_instance(); + SoundContext & ctx = this->context.get(); if (!ctx.engine.isValidVoiceHandle(this->handle)) return; ctx.engine.setLooping(this->handle, this->looping); } + +void Sound::set_context(SoundContext & ctx) { + this->context = ctx; +} + diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h index 32b6478..a84aa8c 100644 --- a/src/crepe/facade/Sound.h +++ b/src/crepe/facade/Sound.h @@ -1,21 +1,25 @@ #pragma once -#include <memory> #include <soloud/soloud.h> #include <soloud/soloud_wav.h> -#include "../Asset.h" +#include "../util/OptionalRef.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 { +class Sound : public Resource { public: + Sound(const Asset & src); + ~Sound(); // dbg_trace /** * \brief Pause this sample * @@ -67,15 +71,12 @@ public: bool get_looping() const { return this->looping; } public: - Sound(const char * src); - Sound(std::unique_ptr<Asset> res); - -private: - void load(std::unique_ptr<Asset> res); + void set_context(SoundContext & ctx); private: SoLoud::Wav sample; SoLoud::handle handle; + OptionalRef<SoundContext> context; float volume = 1.0f; bool looping = false; diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp index deb2b62..b65dfb2 100644 --- a/src/crepe/facade/SoundContext.cpp +++ b/src/crepe/facade/SoundContext.cpp @@ -4,11 +4,6 @@ using namespace crepe; -SoundContext & SoundContext::get_instance() { - static SoundContext instance; - return instance; -} - SoundContext::SoundContext() { dbg_trace(); engine.init(); diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h index d703c16..d22ff7a 100644 --- a/src/crepe/facade/SoundContext.h +++ b/src/crepe/facade/SoundContext.h @@ -13,18 +13,18 @@ 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; private: - static SoundContext & get_instance(); SoLoud::Soloud engine; + //! Sound directly calls methods on \c engine friend class Sound; }; diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp new file mode 100644 index 0000000..c8dae9d --- /dev/null +++ b/src/crepe/system/AudioSystem.cpp @@ -0,0 +1,56 @@ +#include "AudioSystem.h" +#include "ComponentManager.h" + +#include "../api/AudioSource.h" + +using namespace crepe; +using namespace std; + +void AudioSystem::update() { + ComponentManager & mgr = this->component_manager; + vector<reference_wrapper<AudioSource>> components = mgr.get_components_by_type<AudioSource>(); + + for (auto component_ref : components) { + AudioSource & component = component_ref.get(); + if (!component.active) continue; + + /** + * How this is supposed to work: + * - Get an instance of Sound for this resource/component combo (Sound + * instance is supposed to be unique per component, even if they use the + * same underlying asset). + * OR + * - Use the same instance of Sound if this is what the cache returns + * (= what the game programmer's wishes to do). + * + * NOT supposed to happen but still the case: + * - Below function call causes assets to be cached unintentionally + * - Cached assets are deleted at the end of a scene (i think?) + * - I'm not sure if the ResourceManager is even supposed to have a public + * `.clear()` method since the control over resource lifetime is + * explicitly handed over to the game programmer by using ResourceManager + * to cache/uncache. I believe the proper methods are supposed to be: + * + * - get() get a reference to resource (used here) + * - clear() clears NON-cached assets + * - cache() marks asset as "do not delete at end of scene" + * - uncache() undoes the above + * + * I think somewhere in the above function calls a unique identifier for + * the Asset/GameObject should be given to make sure the unique instance + * shit works as intended. The resource manager is also used for things + * other than sounds. + * + * Also need to check: + * - Is it an issue if there are multiple AudioSource components playing + * the same sample (= identical Asset), while they are all triggered + * using the same underlying instance of Sound (esp. w/ + * play/pause/retrigger behavior). + */ + Sound & sound = this->resman.cache<Sound>(component.source); + sound.set_context(this->context); + + // TODO: lots of state diffing + } +} + diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h new file mode 100644 index 0000000..d0b4f9a --- /dev/null +++ b/src/crepe/system/AudioSystem.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../facade/SoundContext.h" +#include "../api/ResourceManager.h" + +#include "System.h" + +namespace crepe { + +class AudioSystem : public System { +public: + using System::System; + void update() override; + +private: + SoundContext context {}; + ResourceManager & resman = ResourceManager::get_instance(); +}; + +} // 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/System.h b/src/crepe/system/System.h index 28ea20e..36f7edc 100644 --- a/src/crepe/system/System.h +++ b/src/crepe/system/System.h @@ -1,5 +1,7 @@ #pragma once +#include "../ComponentManager.h" + namespace crepe { class ComponentManager; diff --git a/src/crepe/util/CMakeLists.txt b/src/crepe/util/CMakeLists.txt index 4be738a..94ed906 100644 --- a/src/crepe/util/CMakeLists.txt +++ b/src/crepe/util/CMakeLists.txt @@ -9,5 +9,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Log.hpp Proxy.h Proxy.hpp + OptionalRef.h + OptionalRef.hpp ) diff --git a/src/crepe/util/OptionalRef.h b/src/crepe/util/OptionalRef.h new file mode 100644 index 0000000..1ad3a6d --- /dev/null +++ b/src/crepe/util/OptionalRef.h @@ -0,0 +1,41 @@ +#pragma once + +namespace crepe { + +/** + * \brief Optional reference utility + * + * This class doesn't need to know the full definition of \c T to be used. + * + * \tparam T Value type + */ +template <typename T> +class OptionalRef { +public: + OptionalRef() = default; + OptionalRef(T &); + OptionalRef<T> & operator=(T &); + explicit operator bool() const noexcept; + + void set(T &) noexcept; + T & get() const; + void clear() noexcept; + + OptionalRef(const OptionalRef<T> &); + OptionalRef(OptionalRef<T> &&); + OptionalRef<T> & operator=(const OptionalRef<T> &); + OptionalRef<T> & operator=(OptionalRef<T> &&); + +private: + /** + * \brief Reference to the value of type \c T + * + * \note This raw pointer is *not* managed, and only used as a reference! + */ + T * ref = nullptr; +}; + +} + +#include "OptionalRef.hpp" + diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp new file mode 100644 index 0000000..7b201b0 --- /dev/null +++ b/src/crepe/util/OptionalRef.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include <stdexcept> + +#include "OptionalRef.h" + +namespace crepe { + +template <typename T> +OptionalRef<T>::OptionalRef(T & ref) { + this->set(ref); +} + +template <typename T> +OptionalRef<T>::OptionalRef(const OptionalRef<T> & other) { + this->ref = other.ref; +} + +template <typename T> +OptionalRef<T>::OptionalRef(OptionalRef<T> && other) { + this->ref = other.ref; + other.clear(); +} + +template <typename T> +OptionalRef<T> & OptionalRef<T>::operator=(const OptionalRef<T> & other) { + this->ref = other.ref; + return *this; +} + +template <typename T> +OptionalRef<T> & OptionalRef<T>::operator=(OptionalRef<T> && other) { + this->ref = other.ref; + other.clear(); + return *this; +} + +template <typename T> +T & OptionalRef<T>::get() const { + if (this->ref == nullptr) + throw std::runtime_error("OptionalRef: attempt to dereference nullptr"); + return *this->ref; +} + +template <typename T> +void OptionalRef<T>::set(T & ref) noexcept { + this->ref = &ref; +} + +template <typename T> +void OptionalRef<T>::clear() noexcept { + this->ref = nullptr; +} + +template <typename T> +OptionalRef<T> & OptionalRef<T>::operator=(T & ref) { + this->set(ref); + return *this; +} + +template <typename T> +OptionalRef<T>::operator bool() const noexcept { + return this->ref != nullptr; +} + +} + |