// Copyright (c) 2015 - 2023 Doozy Entertainment. All Rights Reserved.
// This code can only be used under the standard Unity Asset Store End User License Agreement
// A Copy of the EULA APPENDIX 1 is available at http://unity3d.com/company/legal/as_terms
using System;
using System.Collections.Generic;
using Doozy.Runtime.Pooler;
using UnityEngine;
using UnityEngine.Events;
using Object = UnityEngine.Object;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable MemberCanBeProtected.Global
// ReSharper disable ClassWithVirtualMembersNeverInherited.Global
namespace Doozy.Runtime.Signals
{
public partial class SignalStream
{
public const string k_None = "None";
public const string k_DefaultCategory = k_None;
public const string k_DefaultName = k_None;
/// Stream key (unique) Guid)
public Guid key { get; }
/// Stream category
public string category { get; private set; } = k_DefaultCategory;
/// Stream name
public string name { get; private set; } = k_DefaultName;
/// Number of signals sent through this stream since its creation
public int signalsCounter { get; private set; }
/// Stream receivers
public List receivers { get; } = new List();
/// Number of registered receivers listening to this stream
public int receiversCount => receivers.Count;
/// Action invoked every time a receiver connects to this stream
public UnityAction OnReceiverConnected;
/// Action invoked every time a receiver disconnects from this stream
public UnityAction OnReceiverDisconnected;
/// Reference to the previously sent signal (can be null)
public Signal previousSignal { get; protected set; }
/// Reference to the current signal (cen be null)
public Signal currentSignal { get; protected set; }
/// Action invoked every time a Signal is sent through this stream
public UnityAction OnSignal;
/// Reference to the SignalProvider that created this stream (can be null)
public SignalProvider signalProvider { get; protected set; }
/// Returns TRUE if this stream has a signal provider reference
public bool hasProvider => signalProvider != null;
/// Text message used to display custom info about this stream
public string infoMessage { get; protected set; }
private HashSet sendTempList { get; } = new HashSet();
private HashSet disconnectTempList { get; } = new HashSet();
internal SignalStream(Guid streamKey)
{
key = streamKey;
}
internal SignalStream SetCategory(string streamCategory)
{
category = streamCategory;
return this;
}
internal SignalStream SetName(string streamName)
{
name = streamName;
return this;
}
internal SignalStream SetSignalProvider(SignalProvider provider)
{
signalProvider = provider;
return this;
}
internal SignalStream SetInfoMessage(string message)
{
infoMessage = message;
return this;
}
/// Connect a receiver to this stream
/// Target receiver
/// This stream (for method chaining)
public SignalStream ConnectReceiver(ISignalReceiver receiver)
{
if (receiver == null) return this;
if (receivers.Contains(receiver)) return this;
receivers.Add(receiver);
OnReceiverConnected?.Invoke(receiver);
return this;
}
/// Disconnect a receiver from this stream
/// Target receiver
/// This stream (for method chaining)
public SignalStream DisconnectReceiver(ISignalReceiver receiver)
{
if (receiver == null) return this;
if (!receivers.Contains(receiver)) return this;
receivers.Remove(receiver);
if (receiver.stream != this) return this;
receiver.Disconnect();
OnReceiverDisconnected?.Invoke(receiver);
return this;
}
/// Disconnect all receivers from this stream
/// This stream (for method chaining)
public SignalStream DisconnectAllReceivers()
{
receivers.Remove(null);
foreach (ISignalReceiver receiver in receivers.ToArray())
{
if (receiver == null)
continue;
DisconnectReceiver(receiver);
}
receivers.Clear();
return this;
}
/// Add a callback that will be invoked every time a Signal is sent through this stream
/// Callback to add
/// This stream (for method chaining)
public SignalStream AddOnSignalCallback(UnityAction callback)
{
RemoveOnSignalCallback(callback); //make sure we do not add the same callback twice
OnSignal += callback; //add the callback
return this;
}
/// Remove a callback from the OnSignal unity action
/// Callback to remove
/// This stream (for method chaining)
public SignalStream RemoveOnSignalCallback(UnityAction callback)
{
if (callback == null) return this; //do not remove null callbacks
OnSignal -= callback; //remove the callback
return this;
}
/// Remove all null callbacks from the OnSignal unity action
/// This stream (for method chaining)
public SignalStream RemoveNullCallbackFromOnSignal()
{
if (OnSignal == null) return this;
foreach (Delegate d in OnSignal.GetInvocationList())
if (d == null)
OnSignal -= null;
return this;
}
/// Remove all callbacks from the OnSignal unity action
/// This stream (for method chaining)
public SignalStream ClearCallbacks()
{
OnSignal = null;
return this;
}
/// Close this stream by disconnecting all receivers and nullifying all callbacks
public void Close()
{
DisconnectAllReceivers();
ClearCallbacks();
}
#region Signal
/// Send a Signal on this stream
public virtual bool SendSignal(string message = "") =>
SendSignal(null, null, null, message);
/// Send a Signal on this stream, with a reference to the GameObject from where it was sent
/// Reference to the GameObject from where this Signal is sent from
/// Text message used to pass info about this Signal
public virtual bool SendSignal(GameObject signalSource, string message = "") =>
SendSignal(signalSource, null, null, message);
/// Send a Signal on this stream, with a reference to the SignalProvider that sent it
/// Reference to the SignalProvider that sends this Signal
/// Text message used to pass info about this Signal
public virtual bool SendSignal(SignalProvider provider, string message = "") =>
SendSignal(null, provider, null, message);
/// Send a Signal on this stream, with a reference to the Object that sent it
/// Reference to the Object that sends this Signal
/// Text message used to pass info about this Signal
public virtual bool SendSignal(Object signalSender, string message = "") =>
SendSignal(null, null, signalSender, message);
/// Send a Signal with everything and the kitchen sink
/// Reference to the GameObject from where this Signal is sent from
/// Reference to the SignalProvider that sends this Signal
/// Reference to the Object that sends this Signal
/// Text message used to pass info about this Signal
public virtual bool SendSignal(GameObject signalSource, SignalProvider provider, Object signalSender, string message = "") =>
InternalSendSignal(signalSource, provider, signalSender, message);
private bool InternalSendSignal(GameObject signalSource, SignalProvider provider, Object signalSender, string message = "")
{
Signal signal = SignalPool.Get().Reset();
if (provider != null)
{
signal.SetSignalSource(provider.gameObject);
signal.SetSignalProvider(provider);
signal.SetSignalSender(provider);
}
if (signalSource != null) signal.SetSignalSource(signalSource);
if (signalSender != null) signal.SetSignalSender(signalSender);
signal.SetMessage(message);
return Send(signal);
}
#endregion
#region MetaSignal
/// Send a MetaSignal on this stream
/// Signal value
/// Text message used to pass info about this Signal
/// Signal value type
public virtual bool SendSignal(T signalValue, string message = "") =>
SendSignal(signalValue, null, null, null, message);
/// Send a MetaSignal on this stream, with a reference to the GameObject from where it was sent
/// Signal value
/// Reference to the GameObject from where this Signal is sent from
/// Text message used to pass info about this Signal
/// Signal value type
public virtual bool SendSignal(T signalValue, GameObject signalSource, string message = "") =>
SendSignal(signalValue, signalSource, null, null, message);
/// Send a MetaSignal on this stream, with a reference to the SignalProvider that sent it
/// Signal value
/// Reference to the SignalProvider that sends this Signal
/// Text message used to pass info about this Signal
/// Signal value type
public virtual bool SendSignal(T signalValue, SignalProvider provider, string message = "") =>
SendSignal(signalValue, null, provider, null, message);
/// Send a MetaSignal on this stream, with a reference to the Object that sent it
/// Signal value
/// Reference to the Object that sends this Signal
/// Text message used to pass info about this Signal
/// Signal value type
public virtual bool SendSignal(T signalValue, Object signalSender, string message = "") =>
SendSignal(signalValue, null, null, signalSender, message);
/// Send a MetaSignal with everything and the kitchen sink
/// Signal value
/// Reference to the GameObject from where this Signal is sent from
/// Reference to the SignalProvider that sends this Signal
/// Reference to the Object that sends this Signal
/// Text message used to pass info about this Signal
/// Signal value type
public virtual bool SendSignal(T signalValue, GameObject signalSource, SignalProvider provider, Object signalSender, string message = "") =>
InternalSendSignal(signalValue, signalSource, provider, signalSender, message);
private bool InternalSendSignal(T signalValue, GameObject signalSource, SignalProvider provider, Object signalSender, string message = "")
{
MetaSignal metaSignal = SignalPool.Get>().Reset();
metaSignal.SetSignalValue(signalValue);
if (provider != null)
{
metaSignal.SetSignalSource(provider.gameObject);
metaSignal.SetSignalProvider(provider);
metaSignal.SetSignalSender(provider);
}
if (signalSource != null) metaSignal.SetSignalSource(signalSource);
if (signalSender != null) metaSignal.SetSignalSender(signalSender);
metaSignal.SetMessage(message);
return Send(metaSignal);
}
#endregion
private bool Send(Signal signal)
{
// Debug.Log($"{nameof(SignalStream)}.{nameof(Send)}({nameof(Signal)}): {signal}");
signal.SetStream(this);
signalsCounter++;
if (previousSignal != null)
{
//if the value is poolable -> recycle it (save memory allocations)
if (previousSignal.hasValue && previousSignal.valueAsObject is IPoolable poolable)
poolable.Recycle();
previousSignal.Recycle();
}
previousSignal = currentSignal;
currentSignal = signal;
OnSignal?.Invoke(currentSignal);
SignalsService.OnSignal?.Invoke(currentSignal);
receivers.Remove(null);
foreach (ISignalReceiver receiver in receivers.ToArray())
receiver?.OnSignal(currentSignal);
return true;
}
#region Static Methods
/// Create a new stream and get a reference to it
public static SignalStream Get() =>
SignalsService.GetStream();
///
/// Get the stream with the given stream category and name or,
/// if not found, create a new stream and return a reference to it
///
/// Stream category
/// Stream name
public static SignalStream Get(string streamCategory, string streamName) =>
SignalsService.GetStream(streamCategory, streamName);
/// Get the stream with the given stream key. If not found, this method, returns null
/// Stream key to search for
public static SignalStream Get(Guid streamKey) =>
SignalsService.FindStream(streamKey);
#endregion
}
}