// 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 class StateOwnerEventLegacy : public IStateOwnerEventLegacy { public: IStateOwner *owner; Event *event; std::function 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 eventsSubscribed; std::vector 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 useEffect( const std::function &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 useEffect( const std::function &fn, std::vector 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 useEffectWithTeardown( const std::function()> &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()> &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 std::function useEvent(F fn, StateEvent &event) { // Create a listener structure struct StateEventListener listener; listener.listener = fn; listener.owner = this; listener.event = &event; listener.unsubWithParams = [&](struct StateEventListener 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 std::function useEventLegacy( F fn, Event &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(); bridge->owner = this; bridge->event = &event; bridge->fn = fn; event.addListener(bridge, &StateOwnerEventLegacy::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; } } }; }