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
}
};
}