// 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 } }