namespace Zinnia.Action { using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using Zinnia.Data.Type; using Zinnia.Extension; /// /// The basis for all action types. /// public abstract class Action : MonoBehaviour { /// /// Defines the event with the . /// [Serializable] public class BooleanUnityEvent : UnityEvent { } [Tooltip("Whether to emit the action events.")] [SerializeField] private bool emitEvents = true; /// /// Whether to emit the action events. /// public bool EmitEvents { get { return emitEvents; } set { emitEvents = value; } } /// /// Emitted when changes. /// public BooleanUnityEvent ActivationStateChanged = new BooleanUnityEvent(); /// /// The backing field for holding the value of . /// private bool isActivated; /// /// Whether the action is currently activated. /// public virtual bool IsActivated { get => isActivated; protected set { if (isActivated == value) { return; } isActivated = value; if (emitEvents) { ActivationStateChanged?.Invoke(value); } } } /// /// Adds a given action to the sources collection. /// /// The action to add. public abstract void AddSource(Action action); /// /// Removes the given action from the sources collection. /// /// The action to remove. /// Whether the remove was successful. public abstract bool RemoveSource(Action action); /// /// Clears all sources. /// public abstract void ClearSources(); /// /// Determines whether the sources collection contains an action. /// /// The action to check. /// Whether it contains the given action. public abstract bool SourcesContains(Action action); /// /// Emits the appropriate event for when the activation state changes from Activated or Deactivated. /// public abstract void EmitActivationState(); /// /// Makes the action receive its own initial value to reset it back to when it was first created. /// public abstract void ReceiveInitialValue(); /// /// Makes the action receive its own default value to set it back to inactive. /// public abstract void ReceiveDefaultValue(); /// /// Whether the event should be emitted. /// /// if the event should be emitted. protected virtual bool CanEmit() { return this.CheckIsActiveAndEnabled(); } } /// /// A generic type that forms as the basis for all action types. /// /// This type itself. /// The variable type the action will be utilizing. /// The type the action will be utilizing. public abstract class Action : Action where TSelf : Action where TEvent : UnityEvent, new() { [Tooltip("The initial value upon creation of the component.")] [SerializeField] private TValue initialValue; /// /// The initial value upon creation of the component. /// public TValue InitialValue { get { return initialValue; } set { initialValue = value; } } [Tooltip("The value that is considered the inactive value.")] [SerializeField] private TValue defaultValue; /// /// The value that is considered the inactive value. /// public TValue DefaultValue { get { return defaultValue; } set { defaultValue = value; if (this.IsMemberChangeAllowed()) { OnAfterDefaultValueChange(); } } } [Tooltip("Actions to subscribe to when this action is Behaviour.enabled. Allows chaining the source actions to this action.")] [SerializeField] private List sources = new List(); /// /// Actions to subscribe to when this action is . Allows chaining the source actions to this action. /// protected List Sources { get { return sources; } set { if (this.IsMemberChangeAllowed()) { OnBeforeSourcesChange(); } sources = value; if (this.IsMemberChangeAllowed()) { OnAfterSourcesChange(); } } } /// /// Emitted when the action becomes active. /// public TEvent Activated = new TEvent(); /// /// Emitted when the of the action changes. /// public TEvent ValueChanged = new TEvent(); /// /// Emitted when the of the action remains unchanged. /// public TEvent ValueUnchanged = new TEvent(); /// /// Emitted when the action becomes deactivated. /// public TEvent Deactivated = new TEvent(); [Tooltip("The value of the action.")] [SerializeField] private TValue value; /// /// The value of the action. /// public TValue Value { get { return value; } set { this.value = value; } } /// /// Actions subscribed to when this action is . Allows chaining the source actions to this action. /// public virtual HeapAllocationFreeReadOnlyList ReadOnlySources => sources; /// public override void AddSource(Action action) { if (!this.IsValidState() || action == null) { return; } Sources.Add((TSelf)action); SubscribeToSource((TSelf)action); } /// public override bool RemoveSource(Action action) { if (!this.IsValidState() || action == null) { return false; } UnsubscribeFromSource((TSelf)action); return Sources.Remove((TSelf)action); } /// public override void ClearSources() { if (!this.IsValidState()) { return; } UnsubscribeFromSources(); Sources.Clear(); } /// public override bool SourcesContains(Action action) { if (!this.IsValidState() || action == null) { return false; } return Sources.Contains((TSelf)action); } /// public override void EmitActivationState() { if (!this.IsValidState()) { return; } if (IsActivated) { if (EmitEvents) { Activated?.Invoke(Value); ValueChanged?.Invoke(Value); } } else { if (EmitEvents) { ValueChanged?.Invoke(Value); Deactivated?.Invoke(Value); } } } /// public override void ReceiveInitialValue() { if (!this.IsValidState()) { return; } Receive(InitialValue); } /// public override void ReceiveDefaultValue() { if (!this.IsValidState()) { return; } Receive(DefaultValue); } /// /// Acts on the value. /// /// The value to act on. public virtual void Receive(TValue value) { if (!this.IsValidState()) { return; } if (IsValueEqual(value)) { if (EmitEvents) { ValueUnchanged?.Invoke(Value); } return; } ProcessValue(value); } /// /// Resets the action to the initial value. /// public virtual void ResetToInitialValue() { ResetToValue(InitialValue); } /// /// Resets the action to the default value. /// public virtual void ResetToDefaultValue() { ResetToValue(DefaultValue); } protected virtual void Awake() { Value = DefaultValue; } protected virtual void OnEnable() { SubscribeToSources(); } protected virtual void Start() { if (!IsValueEqual(InitialValue)) { ProcessValue(InitialValue); } } protected virtual void OnDisable() { ProcessValue(DefaultValue); UnsubscribeFromSources(); } /// /// Resets the action to the given value. /// /// The value to reset to. protected virtual void ResetToValue(TValue value) { Value = value; IsActivated = ShouldActivate(value); } /// /// Subscribes the current action as a listener to the given action. /// /// The source action to subscribe listeners on. protected virtual void SubscribeToSource(TSelf source) { if (source == null) { return; } source.ValueChanged.AddListener(Receive); } /// /// Unsubscribes the current action from listening to the given action. /// /// The source action to unsubscribe listeners on. protected virtual void UnsubscribeFromSource(TSelf source) { if (source == null) { return; } source.ValueChanged.RemoveListener(Receive); } /// /// Attempts to subscribe listeners to each of the source actions. /// protected virtual void SubscribeToSources() { if (Sources == null) { return; } foreach (TSelf source in Sources) { SubscribeToSource(source); } } /// /// Attempts to unsubscribe existing listeners from each of the source actions. /// protected virtual void UnsubscribeFromSources() { if (Sources == null) { return; } foreach (TSelf source in Sources) { UnsubscribeFromSource(source); } } /// /// Processes the given value and emits the appropriate events. /// /// The new value. protected virtual void ProcessValue(TValue value) { Value = value; bool shouldActivate = ShouldActivate(value); if (IsActivated != shouldActivate) { IsActivated = shouldActivate; EmitActivationState(); } else { if (EmitEvents) { ValueChanged?.Invoke(Value); } } } /// /// Whether the given is equal to the action's cached . /// /// The value to check equality for. /// if the given is equal to the action's cached . protected virtual bool IsValueEqual(TValue value) { return EqualityComparer.Default.Equals(Value, value); } /// /// Whether the action should become active. /// /// The current value to check activation state on. /// if the action should activate. protected virtual bool ShouldActivate(TValue value) { return !EqualityComparer.Default.Equals(DefaultValue, value); } /// /// Called after has been changed. /// protected virtual void OnAfterDefaultValueChange() { bool shouldActivate = ShouldActivate(Value); if (IsActivated == shouldActivate) { return; } IsActivated = shouldActivate; EmitActivationState(); } /// /// Called before has been changed. /// protected virtual void OnBeforeSourcesChange() { UnsubscribeFromSources(); } /// /// Called after has been changed. /// protected virtual void OnAfterSourcesChange() { SubscribeToSources(); } } }