diff --git a/rd-net/RdFramework.Reflection/RdOuterLifetime.cs b/rd-net/RdFramework.Reflection/RdOuterLifetime.cs
new file mode 100644
index 000000000..1fc45e5fe
--- /dev/null
+++ b/rd-net/RdFramework.Reflection/RdOuterLifetime.cs
@@ -0,0 +1,72 @@
+using System;
+using JetBrains.Annotations;
+using JetBrains.Lifetimes;
+using JetBrains.Rd.Base;
+using JetBrains.Rd.Impl;
+using JetBrains.Serialization;
+
+namespace JetBrains.Rd.Reflection
+{
+ ///
+ /// Abstraction to send over the protocol
+ ///
+ public sealed class RdOuterLifetime: RdReactiveBase
+ {
+ [NonSerialized] private LifetimeDefinition myLifetimeDefinition;
+ [NonSerialized] private bool myIsClientSide = false;
+
+ public static implicit operator OuterLifetime (RdOuterLifetime rdOuterLifetime)
+ {
+ return rdOuterLifetime.myLifetimeDefinition;
+ }
+
+ [UsedImplicitly]
+ public RdOuterLifetime()
+ {
+ }
+
+ protected override void Init(Lifetime lifetime)
+ {
+ base.Init(lifetime);
+ myLifetimeDefinition = Lifetime.Define(lifetime);
+ myLifetimeDefinition.AllowTerminationUnderExecution = true;
+ Proto.Wire.Advise(lifetime, this);
+
+ if (!myIsClientSide)
+ myLifetimeDefinition.Lifetime.OnTermination(() => Proto.Wire.Send(RdId, writer => writer.Write(1)));
+ }
+
+ public override void OnWireReceived(UnsafeReader reader)
+ {
+ myLifetimeDefinition.Terminate();
+ }
+
+ ///
+ /// Used on a sender side to
+ ///
+ public void AttachToLifetime(Lifetime lifetime)
+ {
+ if (!lifetime.TryOnTermination(myLifetimeDefinition))
+ myLifetimeDefinition.Terminate();
+ }
+
+ #region Intrinsic
+
+ public static RdOuterLifetime Read(SerializationCtx ctx, UnsafeReader reader)
+ {
+ var id = reader.ReadRdId();
+ var rdOuterLifetime = new RdOuterLifetime()
+ {
+ myIsClientSide = true
+ }.WithId(id);
+ return rdOuterLifetime;
+ }
+
+ public static void Write(SerializationCtx ctx, UnsafeWriter writer, RdOuterLifetime value)
+ {
+ writer.Write(value.RdId);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/rd-net/RdFramework.Reflection/ReflectionRdActivator.cs b/rd-net/RdFramework.Reflection/ReflectionRdActivator.cs
index 9dbc01a97..7a2999aea 100644
--- a/rd-net/RdFramework.Reflection/ReflectionRdActivator.cs
+++ b/rd-net/RdFramework.Reflection/ReflectionRdActivator.cs
@@ -143,7 +143,9 @@ private object ActivateRd(Type type)
#endif
var typeInfo = type.GetTypeInfo();
- ReflectionSerializerVerifier.AssertEitherExtModelAttribute(typeInfo);
+ if (!ReflectionSerializerVerifier.IsSealedClassAssignableFromIRdBindable(typeInfo))
+ ReflectionSerializerVerifier.AssertEitherExtModelAttribute(typeInfo);
+
var implementingType = ReflectionSerializerVerifier.GetImplementingType(typeInfo);
Assertion.Assert(typeof(RdBindableBase).GetTypeInfo().IsAssignableFrom(implementingType),
$"Unable to activate {type.FullName}: type should be {nameof(RdBindableBase)}");
diff --git a/rd-net/RdFramework.Reflection/ReflectionSerializerVerifier.cs b/rd-net/RdFramework.Reflection/ReflectionSerializerVerifier.cs
index 250443190..b55e71af1 100644
--- a/rd-net/RdFramework.Reflection/ReflectionSerializerVerifier.cs
+++ b/rd-net/RdFramework.Reflection/ReflectionSerializerVerifier.cs
@@ -187,7 +187,7 @@ private static bool IsMemberType(TypeInfo typeInfo)
genericDefinition == typeof(RdMap<,>) || // TResponse can be LiveModel
(genericDefinition == typeof(RdCall<,>) && IsScalar(arguments[0]) /*&& IsScalar(arguments[1])*/) ||
// Custom classes support
- (typeInfo.IsClass && typeInfo.IsSealed && typeof(IRdBindable).IsAssignableFrom(typeInfo));
+ (IsSealedClassAssignableFromIRdBindable(typeInfo));
}
if (IsScalar(typeInfo))
@@ -201,9 +201,18 @@ private static bool IsMemberType(TypeInfo typeInfo)
if (hasRdModel)
return true;
+ // Custom classes support
+ if (IsSealedClassAssignableFromIRdBindable(typeInfo))
+ return true;
+
return false;
}
+ public static bool IsSealedClassAssignableFromIRdBindable(TypeInfo typeInfo)
+ {
+ return typeInfo.IsClass && typeInfo.IsSealed && typeof(IRdBindable).IsAssignableFrom(typeInfo);
+ }
+
public static bool IsScalar(Type type)
{
return !typeof(IRdBindable).IsAssignableFrom(type);
@@ -212,7 +221,7 @@ public static bool IsScalar(Type type)
public static void AssertEitherExtModelAttribute(TypeInfo type)
{
/*Assertion.Assert((HasRdExtAttribute(type) || HasRdModelAttribute(type)), $"Invalid RdModel: expected to have either {nameof(RdModelAttribute)} or {nameof(RdExtAttribute)} ({type.ToString(true)}).");*/
- Assertion.Assert(HasRdExtAttribute(type) ^ HasRdModelAttribute(type), $"Invalid RdModel {type.ToString(true)}: expected to have only one of {nameof(RdModelAttribute)} or {nameof(RdExtAttribute)}.");
+ Assertion.Assert(HasRdExtAttribute(type) ^ HasRdModelAttribute(type), $"Invalid RdModel {type.ToString(true)}: expected to have only one of {nameof(RdModelAttribute)} or {nameof(RdExtAttribute)} or be a sealed class assignable from {nameof(IRdBindable)}.");
}
public static void AssertRoot(TypeInfo type)
diff --git a/rd-net/Test.RdFramework/RdOuterLifetimeTest.cs b/rd-net/Test.RdFramework/RdOuterLifetimeTest.cs
new file mode 100644
index 000000000..5c8993634
--- /dev/null
+++ b/rd-net/Test.RdFramework/RdOuterLifetimeTest.cs
@@ -0,0 +1,69 @@
+using JetBrains.Collections.Viewable;
+using JetBrains.Lifetimes;
+using JetBrains.Rd.Reflection;
+using NUnit.Framework;
+using Test.RdFramework.Reflection;
+
+namespace Test.RdFramework
+{
+ [TestFixture]
+ public class RdOuterLifetimeTest: RdReflectionTestBase
+ {
+ private Root myS;
+ private Root myC;
+
+ [RdExt]
+ public sealed class Root : RdExtReflectionBindableBase
+ {
+ public IViewableProperty PolyProperty { get; }
+ }
+
+ [RdModel]
+ public sealed class Model : RdReflectionBindableBase
+ {
+ public RdOuterLifetime OuterLifetime { get; }
+ }
+
+ public override void SetUp()
+ {
+ base.SetUp();
+ myS = SFacade.InitBind(new Root(), TestLifetime, ClientProtocol);
+ myC = CFacade.InitBind(new Root(), TestLifetime, ServerProtocol);
+ }
+
+ [Test]
+ public void Test01()
+ {
+ Lifetime.Using(lifetime =>
+ {
+ bool sTerminated = false;
+ var m = CFacade.Activator.Activate();
+ myC.PolyProperty.Value = m;
+ var ld2 = OuterLifetime.DefineIntersection(lifetime, myS.PolyProperty.Value.OuterLifetime);
+ ld2.Lifetime.OnTermination(() => sTerminated = true);
+
+ Lifetime.Using(outerLifetime => m.OuterLifetime.AttachToLifetime(outerLifetime));
+
+ Assert.IsTrue(sTerminated, "sTerminated");
+ });
+ }
+
+ [Test]
+ public void TestTerminatedLifetime01()
+ {
+ Lifetime.Using(lifetime =>
+ {
+ bool sTerminated = false;
+ var m = CFacade.Activator.Activate();
+ myC.PolyProperty.Value = m;
+ var ld2 = OuterLifetime.DefineIntersection(lifetime, myS.PolyProperty.Value.OuterLifetime);
+ ld2.Lifetime.OnTermination(() => sTerminated = true);
+
+ var lifetimeDefinition = Lifetime.Define(lifetime);
+ lifetimeDefinition.Terminate();
+ m.OuterLifetime.AttachToLifetime(lifetimeDefinition.Lifetime);
+ Assert.IsTrue(sTerminated, "sTerminated");
+ });
+ }
+ }
+}
\ No newline at end of file