Files
dungeons/Assets/Plugins/Zenject/OptionalExtras/Signals/Internal/SignalDeclaration.cs

183 lines
6.3 KiB
C#

using System;
using System.Collections.Generic;
using ModestTree;
#if ZEN_SIGNALS_ADD_UNIRX
using UniRx;
#endif
namespace Zenject
{
public class SignalDeclaration : ITickable, IDisposable
{
readonly List<SignalSubscription> _subscriptions = new List<SignalSubscription>();
readonly List<object> _asyncQueue = new List<object>();
readonly BindingId _bindingId;
readonly SignalMissingHandlerResponses _missingHandlerResponses;
readonly bool _isAsync;
readonly ZenjectSettings.SignalSettings _settings;
#if ZEN_SIGNALS_ADD_UNIRX
readonly Subject<object> _stream = new Subject<object>();
#endif
public SignalDeclaration(
SignalDeclarationBindInfo bindInfo,
[InjectOptional]
ZenjectSettings zenjectSettings)
{
zenjectSettings = zenjectSettings ?? ZenjectSettings.Default;
_settings = zenjectSettings.Signals ?? ZenjectSettings.SignalSettings.Default;
_bindingId = new BindingId(bindInfo.SignalType, bindInfo.Identifier);
_missingHandlerResponses = bindInfo.MissingHandlerResponse;
_isAsync = bindInfo.RunAsync;
TickPriority = bindInfo.TickPriority;
}
#if ZEN_SIGNALS_ADD_UNIRX
public IObservable<object> Stream
{
get { return _stream; }
}
#endif
public List<SignalSubscription> Subscriptions => _subscriptions;
public int TickPriority
{
get; private set;
}
public bool IsAsync
{
get { return _isAsync; }
}
public BindingId BindingId
{
get { return _bindingId; }
}
public void Dispose()
{
if (_settings.RequireStrictUnsubscribe)
{
Assert.That(_subscriptions.IsEmpty(),
"Found {0} signal handlers still added to declaration {1}", _subscriptions.Count, _bindingId);
}
else
{
// We can't rely entirely on the destruction order in Unity because of
// the fact that OnDestroy is completely unpredictable.
// So if you have a GameObjectContext at the root level in your scene, then it
// might be destroyed AFTER the SceneContext. So if you have some signal declarations
// in the scene context, they might get disposed before some of the subscriptions
// so in this case you need to disconnect from the subscription so that it doesn't
// try to remove itself after the declaration has been destroyed
for (int i = 0; i < _subscriptions.Count; i++)
{
_subscriptions[i].OnDeclarationDespawned();
}
}
}
public void Fire(object signal)
{
Assert.That(signal.GetType().DerivesFromOrEqual(_bindingId.Type));
if (_isAsync)
{
_asyncQueue.Add(signal);
}
else
{
// Cache the callback list to allow handlers to be added from within callbacks
using (var block = DisposeBlock.Spawn())
{
var subscriptions = block.SpawnList<SignalSubscription>();
subscriptions.AddRange(_subscriptions);
FireInternal(subscriptions, signal);
}
}
}
void FireInternal(List<SignalSubscription> subscriptions, object signal)
{
if (subscriptions.IsEmpty()
#if ZEN_SIGNALS_ADD_UNIRX
&& !_stream.HasObservers
#endif
)
{
if (_missingHandlerResponses == SignalMissingHandlerResponses.Warn)
{
Log.Warn("Fired signal '{0}' but no subscriptions found! If this is intentional then either add OptionalSubscriber() to the binding or change the default in ZenjectSettings", signal.GetType());
}
else if (_missingHandlerResponses == SignalMissingHandlerResponses.Throw)
{
throw Assert.CreateException(
"Fired signal '{0}' but no subscriptions found! If this is intentional then either add OptionalSubscriber() to the binding or change the default in ZenjectSettings", signal.GetType());
}
}
for (int i = 0; i < subscriptions.Count; i++)
{
var subscription = subscriptions[i];
// This is a weird check for the very rare case where an Unsubscribe is called
// from within the same callback (see TestSignalsAdvanced.TestSubscribeUnsubscribeInsideHandler)
if (_subscriptions.Contains(subscription))
{
subscription.Invoke(signal);
}
}
#if ZEN_SIGNALS_ADD_UNIRX
_stream.OnNext(signal);
#endif
}
public void Tick()
{
Assert.That(_isAsync);
if (!_asyncQueue.IsEmpty())
{
// Cache the callback list to allow handlers to be added from within callbacks
using (var block = DisposeBlock.Spawn())
{
var subscriptions = block.SpawnList<SignalSubscription>();
subscriptions.AddRange(_subscriptions);
// Cache the signals so that if the signal is fired again inside the handler that it
// is not executed until next frame
var signals = block.SpawnList<object>();
signals.AddRange(_asyncQueue);
_asyncQueue.Clear();
for (int i = 0; i < signals.Count; i++)
{
FireInternal(subscriptions, signals[i]);
}
}
}
}
public void Add(SignalSubscription subscription)
{
Assert.That(!_subscriptions.Contains(subscription));
_subscriptions.Add(subscription);
}
public void Remove(SignalSubscription subscription)
{
_subscriptions.RemoveWithConfirm(subscription);
}
public class Factory : PlaceholderFactory<SignalDeclarationBindInfo, SignalDeclaration>
{
}
}
}