Program Listing for File Reactivity.hpp

↰ Return to documentation for file (engine/include/Cacao/Reactivity.hpp)

#pragma once

#include "DllHelper.hpp"

#include "astra/json.hpp"
#include "astra/traits.hpp"

#include <functional>
#include <mutex>
#include <optional>
#include <type_traits>

namespace Cacao::Reactivity {
    class CACAO_API Dependency {
      protected:
        void EvalReadOk();
        void EvalWriteOk();
    };

    class CACAO_API EffectOutput {};

    template<astra::Serializable T>
    class CACAO_API State : public Dependency, public EffectOutput {
      public:
        State()
            requires std::is_default_constructible_v<T>
        {}

        template<typename... Args>
        State(Args&&... args)
            requires std::is_constructible_v<T, Args&&...>
          : storage(std::forward<Args>(args)...) {}

        operator const T&() const {
            EvalReadOk();
            std::lock_guard lk(storageGuard);
            return storage;
        }

        const T* operator->() const {
            EvalReadOk();
            std::lock_guard lk(storageGuard);
            return &storage;
        }

        State& operator=(const T& value)
            requires std::is_copy_assignable_v<T>
        {
            EvalWriteOk();
            std::lock_guard lk(storageGuard);
            storage = value;
            ++writeCounter;
            //TODO: set global dirty flag
        }

        State& operator=(T&& value)
            requires std::is_move_assignable_v<T>
        {
            EvalWriteOk();
            std::lock_guard lk(storageGuard);
            storage = value;
            ++writeCounter;
            //TODO: set global dirty flag
        }

        class MutationProxy {
          public:
            operator T&() {
                return *ref;
            }

            T* operator->() {
                return ref;
            }

            ~MutationProxy() {
                lock.unlock();
                ++(*writeCounter);
                //TODO: set global dirty flag
            }

          private:
            MutationProxy() {}
            T* ref;
            unsigned int* writeCounter;
            std::unique_lock<std::mutex> lock;
            friend class State;
        };

        MutationProxy Modify() {
            EvalWriteOk();
            MutationProxy proxy;
            proxy.lock = std::unique_lock<std::mutex>(storageGuard);
            proxy.ref = &storage;
            proxy.writeCounter = &writeCounter;
            return proxy;
        }

        State(const State& other)
            requires std::is_copy_constructible_v<T>
          : storage((std::lock_guard {other.storageGuard}, other.storage)) {}
        State& operator=(const State& other)
            requires std::is_copy_assignable_v<T>
        {
            other.EvalReadOk();
            EvalWriteOk();
            if(this != &other) {
                std::lock_guard lk(storageGuard);
                std::lock_guard lk2(other.storageGuard);
                storage = other;
                ++writeCounter;
                //TODO: set global dirty flag
            }
            return *this;
        }
        State(State&& other)
            requires std::is_move_constructible_v<T>
          : storage((std::lock_guard {other.storageGuard}, std::move(other.storage))) {}
        State& operator=(State&& other)
            requires std::is_move_assignable_v<T>
        {
            other.EvalWriteOk();
            EvalWriteOk();
            if(this != &other) {
                std::lock_guard lk(storageGuard);
                std::lock_guard lk2(other.storageGuard);
                storage = std::move(other);
                ++writeCounter;
                //TODO: set global dirty flag
            }
            return *this;
        }

      private:
        T storage;
        std::mutex storageGuard;
        unsigned int writeCounter = 0;
        //TODO: friend class WhateverChecksTheWriteCounter;
    };

    template<astra::Serializable T>
    class CACAO_API Computed : public Dependency {
      public:
        Computed(std::initializer_list<Dependency*> inputs, std::function<T(void)> evaluator)
          : evaluator(evaluator) {
            //TODO: register self as a dependency and do cycle checks
        }

        operator const T&() const {
            EvalReadOk();
            return result.value();
        }

        const T* operator->() const {
            EvalReadOk();
            return &result.value();
        }

      private:
        std::function<T(void)> evaluator;
        std::optional<T> result;
        unsigned int updateCounter;
        //TODO: friend class WhateverTriggersEvaluate;

        void Evaluate() {
            ++updateCounter;
            result = evaluator();
        }
    };

    class CACAO_API SideEffect {
      public:
        SideEffect(std::initializer_list<Dependency*> inputs, std::initializer_list<EffectOutput*> outputs, std::function<void(void)> callback)
          : callback(callback) {
            //TODO: register self as a dependency and do cycle checks
        }

      private:
        std::function<void(void)> callback;
        //TODO: friend class WhateverCallsTheCallback;
    };

    template<astra::Serializable T>
        requires std::is_move_constructible_v<T>
    class CACAO_API ExternalBinding : public Dependency {
      public:
        ExternalBinding(std::function<T(void)> fetcher, bool fluid = false)
          : fetcher(fetcher), fluid(fluid) {}

        operator const T&() const {
            EvalReadOk();
            return *cache;
        }

        const T* operator->() const {
            EvalReadOk();
            return cache.get();
        }

      private:
        std::function<T()> fetcher;
        std::unique_ptr<T> cache;
        std::size_t cacheHash = 0;
        bool fluid;
        //TODO: friend class WhateverCallsCheckRefresh

        void CheckRefresh() {
            T temp = fetcher();
            if(!fluid) {
                astra::SerializedSubstitute<T> sub(temp);
                std::size_t hash = std::hash<std::string> {}(astra::json::toString(&sub));
                if(hash == cacheHash) return;
                cacheHash = hash;
            }
            cache = std::make_unique<T>(std::move(temp));
            //TODO: set global dirty flag
        }
    };
}