using JetBrains.Annotations; using StansAssets.Foundation.Patterns; using System; using System.Collections.Generic; using System.Linq; using UnityEngine.Assertions; namespace StansAssets.SceneManagement { public class ApplicationStateStack : IApplicationStateStack where T : Enum { readonly Dictionary> m_EnumToState = new Dictionary>(); readonly Dictionary, T> m_StateToEnum = new Dictionary, T>(); readonly List m_StatesStack; readonly List> m_Subscriptions; Action, Action> m_PreprocessAction; Action, Action> m_PostprocessAction; public ApplicationStateStack() { m_StatesStack = new List(); m_Subscriptions = new List>(); } public bool IsBusy { get; private set; } public IEnumerable States => m_StatesStack; public void AddDelegate(IApplicationStateDelegate d) { m_Subscriptions.Add(d); } public void RemoveDelegate(IApplicationStateDelegate d) { for (int i = m_Subscriptions.Count - 1; i >= 0; i--) { if (m_Subscriptions[i] != d) continue; m_Subscriptions.RemoveAt(i); break; } } public void RegisterState(T key, IApplicationState value) { m_EnumToState.Add(key, value); m_StateToEnum.Add(value, key); } public IApplicationState GetStateFromEnum(T key) { return m_EnumToState[key]; } public T GetStateEnum(IApplicationState key) { return m_StateToEnum[key]; } public void Set(T applicationState) => Set(applicationState, () => { }); public void Set(T applicationState, [NotNull] Action onComplete) { Assert.IsFalse(IsBusy); Assert.IsNotNull(onComplete); if (m_StatesStack.Count == 1 && m_StatesStack[0].Equals(applicationState)) { onComplete.Invoke(); return; } IsBusy = true; var oldStackState = ListPool.Get(); oldStackState.AddRange(m_StatesStack); var newStackState = ListPool.Get(); newStackState.Add(applicationState); var stackSetOperationEvent = StackOperationEvent.GetPooled(StackOperation.Set, applicationState, oldStackState, newStackState); Preprocess(stackSetOperationEvent, () => { InvokeStateWillChange(stackSetOperationEvent); RunStackAction(StackAction.Removed, oldStackState, () => { //oldStackState will be released so we need to make new one oldStackState = ListPool.Get(); oldStackState.AddRange(m_StatesStack); var addEvent = StackChangeEvent.GetPooled(StackAction.Added, applicationState); var changeStateRequest = new ProgressListenerRequest(); changeStateRequest.OnProgressChange += () => InvokeProgressChange(changeStateRequest.Progress, addEvent); changeStateRequest.OnComplete += () => { var stackSeComplete = StackOperationEvent.GetPooled(StackOperation.Set, applicationState, oldStackState, newStackState); Postprocess(stackSeComplete, () => { IsBusy = false; m_StatesStack.Clear(); m_StatesStack.Add(applicationState); InvokeStateChanged(stackSeComplete); ListPool.Release(oldStackState); ListPool.Release(newStackState); StackChangeEvent.Release(addEvent); onComplete.Invoke(); }); }; GetStateFromEnum(applicationState).ChangeState(addEvent, changeStateRequest); }); }); } public void Push(T applicationState) => Push(applicationState, () => { }); public void Push(T applicationState, [NotNull] Action onComplete) { Assert.IsFalse(IsBusy); Assert.IsNotNull(onComplete); if (m_StatesStack.Count > 0 && m_StatesStack[0].Equals(applicationState)) { onComplete.Invoke(); return; } IsBusy = true; var oldStackState = ListPool.Get(); oldStackState.AddRange(m_StatesStack); var newStackState = ListPool.Get(); newStackState.AddRange(m_StatesStack); newStackState.Add(applicationState); var pausedSate = m_StatesStack.Last(); var stateWillChangeEvent = StackOperationEvent.GetPooled(StackOperation.Push, applicationState, oldStackState, newStackState); Preprocess(stateWillChangeEvent, () => { InvokeStateWillChange(stateWillChangeEvent); var pauseEvent = StackChangeEvent.GetPooled(StackAction.Paused, pausedSate); var addEvent = StackChangeEvent.GetPooled(StackAction.Added, applicationState); var changeStateRequest = new ProgressListenerRequest(); changeStateRequest.OnProgressChange += () => InvokeProgressChange(changeStateRequest.Progress, pauseEvent); changeStateRequest.OnComplete += () => { changeStateRequest = new ProgressListenerRequest(); changeStateRequest.OnProgressChange += () => InvokeProgressChange(changeStateRequest.Progress, addEvent); changeStateRequest.OnComplete += () => { var stackSeComplete = StackOperationEvent.GetPooled(StackOperation.Push, applicationState, oldStackState, newStackState); Postprocess(stackSeComplete, () => { IsBusy = false; m_StatesStack.Add(applicationState); InvokeStateChanged(stackSeComplete); ListPool.Release(oldStackState); ListPool.Release(newStackState); StackChangeEvent.Release(addEvent); StackChangeEvent.Release(pauseEvent); onComplete.Invoke(); }); }; GetStateFromEnum(applicationState).ChangeState(addEvent, changeStateRequest); }; GetStateFromEnum(pausedSate).ChangeState(pauseEvent, changeStateRequest); }); } public void Pop() => Pop(() => { }); public void Pop([NotNull] Action onComplete) { Assert.IsFalse(IsBusy); Assert.IsNotNull(onComplete); if (m_StatesStack.Count == 0) { throw new Exception("States are empty"); } IsBusy = true; var oldStackState = ListPool.Get(); oldStackState.AddRange(m_StatesStack); var removedSate = m_StatesStack.Last(); var newStackState = ListPool.Get(); newStackState.AddRange(m_StatesStack); newStackState.Remove(removedSate); var resumedSate = newStackState.Last(); var stateWillChangeEvent = StackOperationEvent.GetPooled(StackOperation.Pop, removedSate, oldStackState, newStackState); Preprocess(stateWillChangeEvent, () => { InvokeStateWillChange(stateWillChangeEvent); var removedEvent = StackChangeEvent.GetPooled(StackAction.Removed, removedSate); var resumedEvent = StackChangeEvent.GetPooled(StackAction.Resumed, resumedSate); var changeStateRequest = new ProgressListenerRequest(); changeStateRequest.OnProgressChange += () => InvokeProgressChange(changeStateRequest.Progress, removedEvent); changeStateRequest.OnComplete += () => { changeStateRequest = new ProgressListenerRequest(); changeStateRequest.OnProgressChange += () => InvokeProgressChange(changeStateRequest.Progress, resumedEvent); changeStateRequest.OnComplete += () => { var stackPopComplete = StackOperationEvent.GetPooled(StackOperation.Pop, removedSate, oldStackState, newStackState); Postprocess(stackPopComplete, () => { IsBusy = false; m_StatesStack.Remove(removedSate); InvokeStateChanged(stackPopComplete); ListPool.Release(oldStackState); ListPool.Release(newStackState); StackChangeEvent.Release(removedEvent); StackChangeEvent.Release(resumedEvent); onComplete.Invoke(); }); }; GetStateFromEnum(resumedSate).ChangeState(resumedEvent, changeStateRequest); }; GetStateFromEnum(removedSate).ChangeState(removedEvent, changeStateRequest); }); } public bool IsCurrent(T applicationState) { return States.Any() && States.Last().Equals(applicationState); } public void SetPreprocessAction(Action, Action> preprocessAction) { m_PreprocessAction = preprocessAction; } public void SetPostprocessAction(Action, Action> postprocessAction) { m_PostprocessAction = postprocessAction; } void Preprocess(StackOperationEvent stackOperationEvent, Action onComplete) { if (m_PreprocessAction != null) m_PreprocessAction.Invoke(stackOperationEvent, onComplete); else onComplete.Invoke(); } void Postprocess(StackOperationEvent stackOperationEvent, Action onComplete) { if (m_PostprocessAction != null) m_PostprocessAction.Invoke(stackOperationEvent, onComplete); else onComplete.Invoke(); } void RunStackAction(StackAction action, List stack, Action onComplete) { if (stack.Count == 0) { ListPool.Release(stack); onComplete.Invoke(); return; } var applicationState = stack.Last(); stack.RemoveAt(stack.Count - 1); var changeEvent = StackChangeEvent.GetPooled(action, applicationState); var changeStateRequest = new ProgressListenerRequest(); changeStateRequest.OnProgressChange += () => InvokeProgressChange(changeStateRequest.Progress, changeEvent); changeStateRequest.OnComplete += () => { RunStackAction(action, stack, onComplete); StackChangeEvent.Release(changeEvent); }; GetStateFromEnum(applicationState).ChangeState(changeEvent, changeStateRequest); } void InvokeStateWillChange(StackOperationEvent eventArg) { foreach (var subscription in m_Subscriptions) subscription.ApplicationStateWillChange(eventArg); StackOperationEvent.Release(eventArg); } void InvokeProgressChange(float p, StackChangeEvent eventArg) { foreach (var subscription in m_Subscriptions) subscription.ApplicationStateChangeProgressUpdated(p, eventArg); } void InvokeStateChanged(StackOperationEvent eventArg) { foreach (var subscription in m_Subscriptions) subscription.ApplicationStateChanged(eventArg); StackOperationEvent.Release(eventArg); } } }