namespace Zinnia.Action
{
using System;
using UnityEngine;
using UnityEngine.Events;
using Zinnia.Action.Collection;
using Zinnia.Data.Collection.List;
using Zinnia.Extension;
///
/// Allows actions to dynamically register listeners to other actions.
///
public class ActionRegistrar : MonoBehaviour
{
///
/// A source action to register a listener against.
///
[Serializable]
public struct ActionSource
{
[Tooltip("Determines if the source can be used.")]
[SerializeField]
private bool enabled;
///
/// Determines if the source can be used.
///
public bool Enabled
{
get
{
return enabled;
}
set
{
enabled = value;
}
}
[Tooltip("The main container of the action.")]
[SerializeField]
private GameObject container;
///
/// The main container of the action.
///
public GameObject Container
{
get
{
return container;
}
set
{
container = value;
}
}
[Tooltip("The action to subscribe to.")]
[SerializeField]
private Action action;
///
/// The action to subscribe to.
///
public Action Action
{
get
{
return action;
}
set
{
action = value;
}
}
}
///
/// Defines the event with the .
///
[Serializable]
public class ActionUnityEvent : UnityEvent { }
///
/// Defines the event with the .
///
[Serializable]
public class GameObjectUnityEvent : UnityEvent { }
#region Resgistrar Settings
[Header("Resgistrar Settings")]
[Tooltip("The action that will have the sources populated by the given Sources.")]
[SerializeField]
private Action target;
///
/// The action that will have the sources populated by the given .
///
public Action Target
{
get
{
return target;
}
set
{
if (this.IsMemberChangeAllowed())
{
OnBeforeTargetChange();
}
target = value;
if (this.IsMemberChangeAllowed())
{
OnAfterTargetChange();
}
}
}
[Tooltip("A list of ActionSources to populate the target sources list with.")]
[SerializeField]
private ActionRegistrarSourceObservableList sources;
///
/// A list of s to populate the target sources list with.
///
public ActionRegistrarSourceObservableList Sources
{
get
{
return sources;
}
set
{
if (this.IsMemberChangeAllowed())
{
OnBeforeSourcesChange();
}
sources = value;
if (this.IsMemberChangeAllowed())
{
OnAfterSourcesChange();
}
}
}
[Tooltip("A list of GameObjects that are the limits of Sources by matching against ActionSource.Container.")]
[SerializeField]
private GameObjectObservableList sourceLimits;
///
/// A list of s that are the limits of by matching against .
///
public GameObjectObservableList SourceLimits
{
get
{
return sourceLimits;
}
set
{
if (this.IsMemberChangeAllowed())
{
OnBeforeSourceLimitsChange();
}
sourceLimits = value;
if (this.IsMemberChangeAllowed())
{
OnAfterSourceLimitsChange();
}
}
}
#endregion
#region Action Events
///
/// Emitted when the Action is added to the target.
///
[Header("Action Events")]
public ActionUnityEvent ActionAdded = new ActionUnityEvent();
///
/// Emitted when the Action is removed from the target.
///
public ActionUnityEvent ActionRemoved = new ActionUnityEvent();
#endregion
#region Limit Events
///
/// Emitted when the Action is registered against the given limit.
///
[Header("Limit Events")]
public GameObjectUnityEvent LimitRegistered = new GameObjectUnityEvent();
///
/// Emitted when the Action is unregistered from the given limit.
///
public GameObjectUnityEvent LimitUnregistered = new GameObjectUnityEvent();
#endregion
protected virtual void OnEnable()
{
AddSourcesListeners();
AddSourceLimitsListeners();
TryAddTargetSources();
}
protected virtual void OnDisable()
{
RemoveSourcesListeners();
RemoveSourcesLimitsListeners();
ClearTargetSources();
}
///
/// Subscribes to events of .
///
protected virtual void AddSourcesListeners()
{
if (Sources == null)
{
return;
}
Sources.Added.AddListener(OnSourceAdded);
Sources.Removed.AddListener(OnSourceRemoved);
}
///
/// Unsubscribes from events of .
///
protected virtual void RemoveSourcesListeners()
{
if (Sources == null)
{
return;
}
Sources.Added.RemoveListener(OnSourceAdded);
Sources.Removed.RemoveListener(OnSourceRemoved);
}
///
/// Subscribes to events of .
///
protected virtual void AddSourceLimitsListeners()
{
if (SourceLimits == null)
{
return;
}
SourceLimits.Added.AddListener(OnSourceLimitAdded);
SourceLimits.Removed.AddListener(OnSourceLimitRemoved);
}
///
/// Unsubscribes from events of .
///
protected virtual void RemoveSourcesLimitsListeners()
{
if (SourceLimits == null)
{
return;
}
SourceLimits.Added.RemoveListener(OnSourceLimitAdded);
SourceLimits.Removed.RemoveListener(OnSourceLimitRemoved);
}
///
/// Adds all action sources from if their matches any .
///
protected virtual void TryAddTargetSources()
{
if (Sources == null || SourceLimits == null)
{
return;
}
// It is expected that we have less SourceLimits than we have Sources, so this order of nesting the loops is the preferred one.
foreach (GameObject limit in SourceLimits.SubscribableElements)
{
foreach (ActionSource source in Sources.SubscribableElements)
{
TryAddTargetSource(source, limit);
}
}
}
///
/// Clears all sources on .
///
protected virtual void ClearTargetSources()
{
if (Target != null)
{
Target.ClearSources();
}
}
///
/// Adds the given source if its matches the given limit.
///
/// The source to try to add.
/// The limit to try to match against any .
protected virtual void TryAddTargetSource(ActionSource source, GameObject limit)
{
if (source.Enabled && (limit == null || limit == source.Container))
{
Target.AddSource(source.Action);
ActionAdded?.Invoke(source.Action);
LimitRegistered?.Invoke(limit);
}
}
///
/// Removes the given source if its matches the given limit.
///
/// The source to try to remove.
/// The limit to try to match against any .
protected virtual void TryRemoveTargetSource(ActionSource source, GameObject limit)
{
if (limit == source.Container)
{
if (Target.RemoveSource(source.Action))
{
ActionRemoved?.Invoke(source.Action);
LimitUnregistered?.Invoke(limit);
}
}
}
///
/// Called after an element is added to .
///
/// The element added to the collection.
protected virtual void OnSourceAdded(ActionSource source)
{
if (!this.IsValidState())
{
return;
}
foreach (GameObject limit in SourceLimits.SubscribableElements)
{
TryAddTargetSource(source, limit);
}
}
///
/// Called after an element is removed from .
///
/// The element removed from the collection.
protected virtual void OnSourceRemoved(ActionSource source)
{
if (!this.IsValidState())
{
return;
}
foreach (GameObject limit in SourceLimits.SubscribableElements)
{
TryRemoveTargetSource(source, limit);
}
}
///
/// Called after an element is added to .
///
/// The element added to the collection.
protected virtual void OnSourceLimitAdded(GameObject limit)
{
if (!this.IsValidState())
{
return;
}
foreach (ActionSource source in Sources.SubscribableElements)
{
TryAddTargetSource(source, limit);
}
}
///
/// Called after an element is removed from .
///
/// The element removed from the collection.
protected virtual void OnSourceLimitRemoved(GameObject limit)
{
if (!this.IsValidState())
{
return;
}
foreach (ActionSource source in Sources.SubscribableElements)
{
TryRemoveTargetSource(source, limit);
}
}
///
/// Called before has been changed.
///
protected virtual void OnBeforeTargetChange()
{
ClearTargetSources();
}
///
/// Called after has been changed.
///
protected virtual void OnAfterTargetChange()
{
if (Target != null)
{
TryAddTargetSources();
}
}
///
/// Called before has been changed.
///
protected virtual void OnBeforeSourcesChange()
{
if (Sources == null)
{
return;
}
RemoveSourcesListeners();
foreach (ActionSource source in Sources.SubscribableElements)
{
OnSourceRemoved(source);
}
}
///
/// Called after has been changed.
///
protected virtual void OnAfterSourcesChange()
{
AddSourcesListeners();
TryAddTargetSources();
}
///
/// Called before has been changed.
///
protected virtual void OnBeforeSourceLimitsChange()
{
if (SourceLimits == null)
{
return;
}
RemoveSourcesLimitsListeners();
foreach (GameObject limit in SourceLimits.SubscribableElements)
{
OnSourceLimitRemoved(limit);
}
}
///
/// Called after has been changed.
///
protected virtual void OnAfterSourceLimitsChange()
{
AddSourceLimitsListeners();
TryAddTargetSources();
}
}
}