245 lines
8.7 KiB
C++
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;
|
|
}
|
|
}
|
|
};
|
|
} |