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();
}
}
}