From fedd039e00591bac3e1db8ce5c221f4b405268b9 Mon Sep 17 00:00:00 2001 From: Wincent Holm Date: Mon, 18 Nov 2024 01:46:08 +0100 Subject: [PATCH] Proposal for observers and deferred implementations (#1599) --- dCommon/Implementation.h | 92 ++++++++++++++++++++++ dCommon/Observable.h | 73 +++++++++++++++++ dGame/Entity.cpp | 4 + dGame/Entity.h | 6 ++ dGame/dComponents/DestroyableComponent.cpp | 5 ++ dGame/dComponents/DestroyableComponent.h | 4 + 6 files changed, 184 insertions(+) create mode 100644 dCommon/Implementation.h create mode 100644 dCommon/Observable.h diff --git a/dCommon/Implementation.h b/dCommon/Implementation.h new file mode 100644 index 00000000..3d714a7c --- /dev/null +++ b/dCommon/Implementation.h @@ -0,0 +1,92 @@ +#ifndef __IMPLEMENTATION_H__ +#define __IMPLEMENTATION_H__ + +#include +#include + +/** + * @brief A way to defer the implementation of an action. + * + * @tparam R The result of the action. + * @tparam T The types of the arguments that the implementation requires. + */ +template +class Implementation { +public: + typedef std::function(T...)> ImplementationFunction; + + /** + * @brief Sets the implementation of the action. + * + * @param implementation The implementation of the action. + */ + void SetImplementation(const ImplementationFunction& implementation) { + this->implementation = implementation; + } + + /** + * @brief Clears the implementation of the action. + */ + void ClearImplementation() { + implementation.reset(); + } + + /** + * @brief Checks if the implementation is set. + * + * @return true If the implementation is set. + * @return false If the implementation is not set. + */ + bool IsSet() const { + return implementation.has_value(); + } + + /** + * @brief Executes the implementation if it is set. + * + * @param args The arguments to pass to the implementation. + * @return std::optional The optional result of the implementation. If the result is not set, it indicates that the default action should be taken. + */ + std::optional Execute(T... args) const { + return IsSet() ? implementation.value()(args...) : std::nullopt; + } + + /** + * @brief Exectues the implementation if it is set, otherwise returns a default value. + * + * @param args The arguments to pass to the implementation. + * @param defaultValue The default value to return if the implementation is not set or should not be deferred. + */ + R ExecuteWithDefault(T... args, const R& defaultValue) const { + return Execute(args...).value_or(defaultValue); + } + + /** + * = operator overload. + */ + Implementation& operator=(const Implementation& other) { + implementation = other.implementation; + return *this; + } + + /** + * = operator overload. + */ + Implementation& operator=(const ImplementationFunction& implementation) { + this->implementation = implementation; + return *this; + } + + /** + * () operator overload. + */ + std::optional operator()(T... args) { + return !IsSet() ? std::nullopt : implementation(args...); + } + +private: + std::optional implementation; + +}; + +#endif //!__IMPLEMENTATION_H__ \ No newline at end of file diff --git a/dCommon/Observable.h b/dCommon/Observable.h new file mode 100644 index 00000000..3acee05e --- /dev/null +++ b/dCommon/Observable.h @@ -0,0 +1,73 @@ +#ifndef __OBSERVABLE_H__ +#define __OBSERVABLE_H__ + +#include +#include + +/** + * @brief An event which can be observed by multiple observers. + * + * @tparam T The types of the arguments to be passed to the observers. + */ +template +class Observable { +public: + typedef std::function Observer; + + /** + * @brief Adds an observer to the event. + * + * @param observer The observer to add. + */ + void AddObserver(const Observer& observer) { + observers.push_back(observer); + } + + /** + * @brief Removes an observer from the event. + * + * @param observer The observer to remove. + */ + void RemoveObserver(const Observer& observer) { + observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end()); + } + + /** + * @brief Notifies all observers of the event. + * + * @param args The arguments to pass to the observers. + */ + void Notify(T... args) { + for (const auto& observer : observers) { + observer(args...); + } + } + + /** + * += operator overload. + */ + Observable& operator+=(const Observer& observer) { + AddObserver(observer); + return *this; + } + + /** + * -= operator overload. + */ + Observable& operator-=(const Observer& observer) { + RemoveObserver(observer); + return *this; + } + + /** + * () operator overload. + */ + void operator()(T... args) { + Notify(args...); + } + +private: + std::vector observers; +}; + +#endif //!__OBSERVABLE_H__ \ No newline at end of file diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index d3882a39..8066ce61 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -96,6 +96,8 @@ #include "CDSkillBehaviorTable.h" #include "CDZoneTableTable.h" +Observable Entity::OnPlayerPositionUpdate; + Entity::Entity(const LWOOBJID& objectID, EntityInfo info, User* parentUser, Entity* parentEntity) { m_ObjectID = objectID; m_TemplateID = info.lot; @@ -2133,6 +2135,8 @@ void Entity::ProcessPositionUpdate(PositionUpdate& update) { Game::entityManager->QueueGhostUpdate(GetObjectID()); if (updateChar) Game::entityManager->SerializeEntity(this); + + OnPlayerPositionUpdate.Notify(this, update); } const SystemAddress& Entity::GetSystemAddress() const { diff --git a/dGame/Entity.h b/dGame/Entity.h index ffdcb713..5d2b9527 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -11,6 +11,7 @@ #include "NiQuaternion.h" #include "LDFFormat.h" #include "eKillType.h" +#include "Observable.h" namespace Loot { class Info; @@ -299,6 +300,11 @@ class Entity { // Scale will only be communicated to the client when the construction packet is sent void SetScale(const float scale) { m_Scale = scale; }; + /** + * @brief The observable for player entity position updates. + */ + static Observable OnPlayerPositionUpdate; + protected: LWOOBJID m_ObjectID; diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 8ec1e4c9..cb8afd5a 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -38,6 +38,9 @@ #include "CDComponentsRegistryTable.h" +Implementation DestroyableComponent::IsEnemyImplentation; +Implementation DestroyableComponent::IsFriendImplentation; + DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) { m_iArmor = 0; m_fMaxArmor = 0.0f; @@ -418,6 +421,7 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore } bool DestroyableComponent::IsEnemy(const Entity* other) const { + if (IsEnemyImplentation.ExecuteWithDefault(other, false)) return true; if (m_Parent->IsPlayer() && other->IsPlayer()) { auto* thisCharacterComponent = m_Parent->GetComponent(); if (!thisCharacterComponent) return false; @@ -440,6 +444,7 @@ bool DestroyableComponent::IsEnemy(const Entity* other) const { } bool DestroyableComponent::IsFriend(const Entity* other) const { + if (IsFriendImplentation.ExecuteWithDefault(other, false)) return true; const auto* otherDestroyableComponent = other->GetComponent(); if (otherDestroyableComponent != nullptr) { for (const auto enemyFaction : m_EnemyFactionIDs) { diff --git a/dGame/dComponents/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index 56f30103..8b3cd14c 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -7,6 +7,7 @@ #include "Entity.h" #include "Component.h" #include "eReplicaComponentType.h" +#include "Implementation.h" namespace CppScripts { class Script; @@ -463,6 +464,9 @@ class DestroyableComponent final : public Component { // handle hardcode mode drops void DoHardcoreModeDrops(const LWOOBJID source); + static Implementation IsEnemyImplentation; + static Implementation IsFriendImplentation; + private: /** * Whether or not the health should be serialized