Dawn/src/dawn/state/State.hpp

245 lines
8.7 KiB
C++

// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "StateEvent.hpp"
#include "StateProperty.hpp"
namespace Dawn {
template<typename ...A>
class StateOwnerEventLegacy : public IStateOwnerEventLegacy {
public:
IStateOwner *owner;
Event<A...> *event;
std::function<void(A...)> fn;
/**
* Function used to simply remove the event listener from the legacy
* event, does not deal with the state owner.
*/
void removeListener() override {
event->removeListener(this, &StateOwnerEventLegacy::callback);
}
/**
* Function that can be used to tear down this legacy event.
*/
void teardown() override {
this->removeListener();
owner->_stateLegacyEventDisposed(this);
}
/**
* Callbaack method that is invoked by the legacy event.
* @param args Arguments received by legacy event.
*/
void callback(A... args) {
this->fn(args...);
}
};
class StateOwner : public IStateOwner {
private:
std::vector<IStateEvent*> eventsSubscribed;
std::vector<IStateOwnerEventLegacy*> eventLegacyBridge;
public:
/**
* Called by the state event when it is disposing (before the StateOwner
* itself is).
*
* @param evt Event that is being disposed.
*/
void _stateEventDisposed(IStateEvent *evt) override {
auto it = eventsSubscribed.begin();
while(it != eventsSubscribed.end()) {
if(*it == evt) {
it = eventsSubscribed.erase(it);
} else {
++it;
}
}
}
/**
* Called by legacy events when they are being disposed in a way that was
* not called by this state owner disposing.
*
* @param evt Event that is being disposed.
*/
void _stateLegacyEventDisposed(IStateOwnerEventLegacy *evt) override {
auto it = this->eventLegacyBridge.begin();
while(it != this->eventLegacyBridge.end()) {
if(*it == evt) {
this->eventLegacyBridge.erase(it);
break;
} else {
++it;
}
}
}
/**
* Listen for changes to a state property and invoke the provided func
* when the value is changed.
*
* @param fn The callback to be invoked when the state value changes.
* @param property Property to listen for affect changees to.
* @return Returns callback that invokes the provided FN immediately.
*/
std::function<void()> useEffect(
const std::function<void()> &fn,
IStateProperty &property
) {
if(property.owner == nullptr) {
property.owner = this;
} else {
// TODO: This actually isn't needed, but because I need to teardown
// all the listeners on StateProperty<> that belong to this StateOwner
// I need to keep track of what events I've subbed to, consuming more
// memory, and I don't know if I actually need to do this or not.
assertTrue(property.owner == this);
}
property._effectListners.push_back(fn);
return fn;
}
/**
* Listen for changes to a set of state properties and invoke the provided
* func when any of their values are changed.
*
* @param fn The callback to be invoked when the state value changes.
* @param property Vector list of properties to listen for changes to.
* @return Returns callback that invokes the provided FN immediately.
*/
std::function<void()> useEffect(
const std::function<void()> &fn,
std::vector<IStateProperty*> props
) {
auto itProp = props.begin();
while(itProp != props.end()) {
auto property = *itProp;
if(property->owner == nullptr) { property->owner = this; } else { assertTrue(property->owner == this); }
property->_effectListners.push_back(fn);
++itProp;
}
return fn;
}
/**
* Listen for changes to a state property and invoke the provided callback
* also, when state is changed this will run the returned teardown
* callback.
*
* @param fn The callback to be invoked when the state value changes.
* @param property Vector list of properties to listen for changes to.
* @return Returns callback that invokes the provided FN immediately.
*/
std::function<void()> useEffectWithTeardown(
const std::function<std::function<void()>()> &fn,
IStateProperty &property
) {
if(property.owner == nullptr) { property.owner = this; } else { assertTrue(property.owner == this); }
property._effectListnersWithTeardown.push_back(fn);
return std::bind([&](
std::function<std::function<void()>()> &callback,
IStateProperty *prop
) {
auto teardown = callback();
prop->_effectTeardowns.push_back(teardown);
}, fn, &property);
}
/**
* Listen for when an event is invoked by a state event. This is intended
* to allow for cross-state-owner communication in a simple and effective
* way.
*
* @tparam F The type of the callback function.
* @tparam A The arguments from the state event that are calledback.
* @param fn The function to be inokved on event trigger.
* @param event The event that is being subscribed to.
* @return A method that, when invoked, will unsubscribe from the event.
*/
template<typename F, typename... A>
std::function<void()> useEvent(F fn, StateEvent<A...> &event) {
// Create a listener structure
struct StateEventListener<A...> listener;
listener.listener = fn;
listener.owner = this;
listener.event = &event;
listener.unsubWithParams = [&](struct StateEventListener<A...> listener) {
auto itFound = listener.event->_eventListeners.begin();
while(itFound != listener.event->_eventListeners.end()) {
if(itFound->id == listener.id) {
listener.event->_eventListeners.erase(itFound);
break;
}
++itFound++;
}
};
listener.id = event.stateEventId++;
listener.unsub = std::bind(listener.unsubWithParams, listener);
// Put that listener structure on to the event stack
event._eventListeners.push_back(listener);
this->eventsSubscribed.push_back(&event);
return listener.unsub;
}
/**
* Listen for callback of a legacy styled event. This will be removed in
* the future in favour of everything using State Events. This uses a lot
* more memory than the new state event listener.
*
* @deprecated In favour of StateEvent<>
* @tparam F The type of the callback function.
* @tparam A Argument types for the event.
* @param fn Callback function to be invoked when the event is triggered.
* @param event Event that will be listened to.
* @return A method that, when invoked, will unsubscribe from the event.
*/
template<typename F, typename... A>
std::function<void()> useEventLegacy(
F fn,
Event<A...> &event
) {
// This is a legacy feature to make upgrading to the new useEffect a bit
// easier for me. For the time being I am just bodging this together to
// do what I need here.
auto bridge = new StateOwnerEventLegacy<A...>();
bridge->owner = this;
bridge->event = &event;
bridge->fn = fn;
event.addListener(bridge, &StateOwnerEventLegacy<A...>::callback);
eventLegacyBridge.push_back(bridge);
return std::bind([&](IStateOwnerEventLegacy *evt){
evt->teardown();
}, bridge);
}
/**
* State Owner teardown function. Mostly just used to remove any lingering
* useEffects or useEvents.
*/
virtual ~StateOwner() {
auto it = this->eventsSubscribed.begin();
while(it != this->eventsSubscribed.end()) {
(*it)->_stateOwnerDestroyed(this);
++it;
}
auto itBridge = this->eventLegacyBridge.begin();
while(itBridge != this->eventLegacyBridge.end()) {
(*itBridge)->removeListener();
delete *itBridge;
++itBridge;
}
}
};
}