rd-net/RdFramework/Impl/Protocol.cs (146 lines of code) (raw):
using System;
using System.Collections.Generic;
using System.Threading;
using JetBrains.Annotations;
using JetBrains.Collections.Synchronized;
using JetBrains.Collections.Viewable;
using JetBrains.Diagnostics;
using JetBrains.Lifetimes;
using JetBrains.Rd.Base;
namespace JetBrains.Rd.Impl
{
public class Protocol : IProtocol
{
public static readonly ILog Logger = Log.GetLog("protocol");
public static readonly ILog InitLogger = Logger.GetSublogger("INIT");
public static LogWithLevel? InitTrace = InitLogger.WhenTrace();
/// <summary>
/// Should match textual RdId of protocol intern root in Kotlin/js/cpp counterpart
/// </summary>
const string ProtocolInternRootRdId = "ProtocolInternRoot";
const string ContextHandlerRdId = "ProtocolContextHandler";
internal const string ProtocolExtCreatedRdId = "ProtocolExtCreated";
/// <summary>
/// Should match whatever is in rd-gen for ProtocolInternScope
/// </summary>
const string ProtocolInternScopeStringId = "Protocol";
public Lifetime Lifetime { get; }
public RdEntitiesRegistrar RdEntitiesRegistrar { get; }
private readonly Protocol? myParentProtocol;
private readonly Dictionary<string, object> myExtensions = new();
public Protocol(string name, ISerializers serializers, IIdentities identities, IScheduler scheduler,
IWire wire, Lifetime lifetime, params RdContextBase[] initialContexts)
: this(name, serializers, identities, scheduler, wire, lifetime, null, null, initialContexts)
{ }
internal Protocol(string name, ISerializers serializers, IIdentities identities, IScheduler scheduler,
IWire wire, Lifetime lifetime, Protocol? parentProtocol, RdSignal<ExtCreationInfo>? parentExtConfirmation = null, params RdContextBase[] initialContexts)
{
Lifetime = lifetime;
Name = name ?? throw new ArgumentNullException(nameof(name));
Location = new RName(name);
Serializers = serializers ?? throw new ArgumentNullException(nameof(serializers));
Identities = identities ?? throw new ArgumentNullException(nameof(identities));
Scheduler = scheduler ?? throw new ArgumentNullException(nameof(scheduler));
Wire = wire ?? throw new ArgumentNullException(nameof(wire));
myParentProtocol = parentProtocol;
RdEntitiesRegistrar = parentProtocol?.RdEntitiesRegistrar ?? new RdEntitiesRegistrar();
SerializationContext = parentProtocol?.SerializationContext ?? new SerializationCtx(this, new Dictionary<string, IInternRoot<object>>() {{ProtocolInternScopeStringId, CreateProtocolInternRoot(lifetime)}});
Contexts = parentProtocol?.Contexts ?? new ProtocolContexts(SerializationContext);
wire.Contexts = Contexts;
if (parentProtocol?.SerializationContext == null)
SerializationContext.InternRoots[ProtocolInternScopeStringId].BindTopLevel(lifetime, this, ProtocolInternRootRdId);
foreach (var rdContextBase in initialContexts) rdContextBase.RegisterOn(Contexts);
if (parentProtocol?.Contexts == null)
BindContexts(lifetime);
OutOfSyncModels = new ViewableSet<RdExtBase>();
ExtCreated = parentProtocol?.ExtCreated ?? new Signal<ExtCreationInfoEx>();
ExtConfirmation = parentExtConfirmation ?? this.CreateExtSignal();
ExtIsLocal = new ThreadLocal<bool>(() => false);
ExtConfirmation.Advise(lifetime, message =>
{
ExtCreated.Fire(new ExtCreationInfoEx(message, ExtIsLocal.Value));
});
using (AllowBindCookie.Create())
ExtConfirmation.BindTopLevel(lifetime, this, ProtocolExtCreatedRdId);
if (wire is IWireWithDelayedDelivery wireWithMessageBroker)
wireWithMessageBroker.StartDeliveringMessages();
}
public bool TryGetSerializationContext(out SerializationCtx ctx)
{
ctx = SerializationContext;
return true;
}
private InternRoot<object> CreateProtocolInternRoot(Lifetime lifetime)
{
var root = new InternRoot<object>();
root.RdId = RdId.Nil.Mix(ProtocolInternRootRdId);
return root;
}
private void BindContexts(Lifetime lifetime)
{
Contexts.RdId = RdId.Nil.Mix(ContextHandlerRdId);
using (AllowBindCookie.Create())
{
Contexts.PreBind(lifetime, this, ContextHandlerRdId);
Contexts.Bind();
}
}
internal void SubmitExtCreated(ExtCreationInfo info)
{
Assertion.Assert(!ExtIsLocal.Value, "!ExtIsLocal");
ExtIsLocal.Value = true;
try
{
ExtConfirmation.Fire(info);
}
finally
{
ExtIsLocal.Value = false;
}
}
public string Name { get; }
public IWire Wire { get; }
public ISerializers Serializers { get; }
public IIdentities Identities { get; }
public IScheduler Scheduler { get; }
public SerializationCtx SerializationContext { get; }
public ViewableSet<RdExtBase> OutOfSyncModels { get; }
public ProtocolContexts Contexts { get; }
public ISignal<ExtCreationInfoEx> ExtCreated { get; }
private RdSignal<ExtCreationInfo> ExtConfirmation { get; }
private ThreadLocal<bool> ExtIsLocal { get; }
[PublicAPI] public bool ThrowErrorOnOutOfSyncModels = true;
public RName Location { get; }
IProtocol IRdDynamic.TryGetProto() => this;
public virtual T? GetExtension<T>() where T : RdExtBase
{
var parentProtocol = myParentProtocol;
if (parentProtocol != null)
return parentProtocol.GetExtension<T>();
lock (myExtensions)
{
return myExtensions.TryGetValue(typeof(T).Name, out var extension) ? (T)extension : default;
}
}
public virtual T GetOrCreateExtension<T>(Func<T> create) where T : RdExtBase
{
if (create == null) throw new ArgumentNullException(nameof(create));
var parentProtocol = myParentProtocol;
if (parentProtocol != null)
return parentProtocol.GetOrCreateExtension(create);
lock (myExtensions)
{
var name = typeof(T).Name;
if (myExtensions.TryGetValue(name, out var existing))
{
var val = existing.NotNull("Found null value for key: '{0}'", name) as T;
Assertion.Require(val != null, $"Found bad value for key '{name}'. Expected type: '{typeof(T).FullName}', actual:'{existing.GetType().FullName}");
return val;
}
var res = create().NotNull("'Create' result must not be null");
myExtensions[name] = res;
if (res is IRdBindable rdBindable)
{
rdBindable.Identify(Identities, RdId.Root.Mix(name));
rdBindable.PreBind(Lifetime, this, name);
rdBindable.Bind();
}
return res;
}
}
}
}