Comments (16)
I'm sorry, but I wasn't able to reproduce the issue. Here's a test project I used (copy-pasted your code and added build/run infrasturcture). Could you please help me with that?
from rd.
@al-darmonski, hello! We're glad you found the project interesting.
We have a community Slack channel for .NET IDE plugin writers. Please drop me a email to [REDACTED]@jetbrains.com, and I'll send you an invite.
Also, we're working on an improved documentation; thanks for raising this issue.
from rd.
Hi @ForNeVeR ,
Thanks for your reply. I sent you an email.
Do you have expectations about when the improved documentation will be available?
I have some technical questions, that hopefully you or someone else from the RD team can answer:
- Can we use RD in one server and multiple clients setup? If yes, can you provide an example?
- Does the RD modelling DSL (Kotlin based) supports references? We want to implement hierarchy (tree) where some tree nodes will refer to another node in the same or another tree. Can we set the RdId’s ourselves or is it meant to be internally set and used?
- How does the conflict resolution work? What is the algorithm behind it?
- Does RD allows for security measures (authentication and authorization) in case client and server run on different machines over the network?
from rd.
Hello!
First, I have sent the invites to the addresses you've sent me by email. Now, about the questions.
Do you have expectations about when the improved documentation will be available?
I think maybe a week or two. Though I'm not sure the documentation will be very useful from the beginning: right now, I'm listing the main concepts and adding some simple examples for the main Rd features.
Can we use RD in one server and multiple clients setup? If yes, can you provide an example?
Not that I'm aware of. Usually, a separate Rd session is established for every client-server pair.
Does the RD modelling DSL (Kotlin based) supports references? We want to implement hierarchy (tree) where some tree nodes will refer to another node in the same or another tree. Can we set the RdId’s ourselves or is it meant to be internally set and used?
You better not use RdId
for your purposes. The usual scheme is to add a map
or a list
, and then use the element indices from these collections as item identifiers in further structures, where you would usually like to use a reference.
How does the conflict resolution work? What is the algorithm behind it?
For certain structures (say, RdProperty
) there's IsMaster
property, and the "master" version is supposed to "win" in concurrent modification scenarios.
Though I should mention that the usual way to design a protocol for most purposes is to not rely on concurrent modifications at all. If there's only one side that has rights to modify a property at any moment of time (and there's a proper synchronization between the sides of the protocol), it will be much easier to reason about the whole interaction. In some cases, this is impossible, and more complex entities like RdTextBuffer
are introduced.
Does RD allows for security measures (authentication and authorization) in case client and server run on different machines over the network?
Currently, Rd offers no embedded security measures. There're internal cases when this is necessary. For such cases, we could recommend implementing your own Wire
.
P.S. As a side note (not directly related to any of the above), I'v found this excellent talk from @maartenba, which includes a more or less detailed explanation of some protocol entities and conventions: https://youtu.be/ujcp2LiPds8.
from rd.
Hi @ForNeVeR ,
Your answers are quite useful. Thanks!
I was wondering of you can provide some explanation about the RD primitives (or concepts is the better term) like classdef
, structdef
, agregatedef
but also property
vs field
, signal
vs call
/callback
. It's also useful to have the full list of primitives.
I've got some intuitive understanding and some insight from the generated codes but it will be useful to have the intended meaning and maybe some examples demonstrating use cases. For instance field
it is not synchronized (bound), what's it useful for?
from rd.
-
classdef
vsstructdef
should be more or less intuitive for a .NET developer: aclassdef
may own properties and other reactive values (and be bound to the procotol), while astructdef
is just a collection of values without any behavior. -
property
vsfield
is exactly what you've written. Fields are useful for structs, rarely for classes I'd say (probably for readonly constructor props though?) -
signal
is a "fire and forget"-sort of thing, while acall
allows to return a result to the caller. -
For signals and calls, there's a difference between symmetric and asymmetric models. When modeling a signal or a call, there's often a difference between the called and callee side. For such cases, you may choose to generate two different sets of source files: on one side,
signal
may only be fired, and on the other one, it may only be subscribed to. To discriminate these cases, usesignal
(only fire) /sink
(only subscribe), orcall
(only call) /callback
(only subscribe / be called)."Asymmetric" generation is an opt-in feature; by default, I think the symmetric model will be generated (where both sides are free to both call and subscribe to a signal or a call, and you control the correctness yourself).
Hope I answered your questions. Feel free to ask more!
from rd.
Hi @ForNeVeR ,
Yes, you have answered my questions. Thank you for your support.
I have another question: I want to specify an interface with a method that has a nullable return type.
var IMyInterface = interfacedef("IMyInterface") {
method("get", PredefinedType.string.nullable)
}
But it generates to:
interface IMyInterface
{
fun get() : StringNullable
}
I expected fun get() : String?
Is what I expect possible? How can I do that?
from rd.
Another question: what's the intended use case of interfacedef
?
I've seen what it generates to in Kotlin and C#. If the class C1 implementing the interface I1 is not abstract (classdef
or openclass
), the interface methods are overridden and throw UnsupportedOperationException
. That gives the opportunity to create a non generated derived class DC1 that can override the methods in a meaningful way.
Consider this scenario:
- DC1 object is created on server side
- DC1 object is handed over to RD model, it will be cast to C1 and will be transferred to client as C1 object
- At the client side we can get notification that C1 is added. But we're not able to call the DC1 methods.
If the class C1 is abstract (baseclass
) than the I1 methods are abstract. We still have opportunity to create derived class DC1 overriding the I1 methods. But RD model doesn't know about them and also can't transfer DC1 objects since such can't be created (DC1 is abstract).
So it seems I'm missing the point of having interfacedef
. Please enlighten me.
from rd.
I want to specify an interface with a method that has a nullable return type.
var IMyInterface = interfacedef("IMyInterface") { method("get", PredefinedType.string.nullable) }But it generates to:
interface IMyInterface { fun get() : StringNullable }I expected
fun get() : String?
Is what I expect possible? How can I do that?
I believe this is a bug. Extracted to #232.
Another question: what's the intended use case of interfacedef?
In our code, we only use it for marker interfaces with no methods at all, so it's hard to answer this question properly now. I'll ask my colleagues.
from rd.
Thanks @ForNeVeR! I'm looking forward to the follow-up on interfacedef
use cases (other than marker interfaces).
from rd.
I have asked around and got confirmation: you're supposed to register custom serializers for your custom interface implementations.
You're passing a ISerializers
object when creating a protocol, and may call register
(or registerSerializersOwnerOnce
, which is usually called from generated models) on it to add your custom serializers.
Maybe you even could register a serializer for your generated model, so that it always creates your own model inheritor instead of the generated one.
from rd.
That seems quite useful for my use case. Can you point me to (public) examples that I can look at?
from rd.
Unfortunately, there're no known public examples of this technique.
from rd.
Hello @ForNeVeR,
I've tried custom serializers. They work fine when used with baseclass
. But not when used with openclass
.
Here is what I tried for both baseclass
and openclass
:
- Create inheritors of generated classes (
baseclass
andopenclass
). - The inheritors' companion object implements
IMarshaller
and thus itsread()
andwrite()
methods. - Register my inheritors with
ISerializers
object before passing it toProtocol
constructor. - At the server side create inheritors objects and adds them to a list in the top-level RD model.
write()
ofbaseclass
inheritor is engagedwrite()
ofopenclass
inheritor is NOT engaged but thewrite()
of theopenclass
itself
- At the client side corresponding objects are created/synced.
read()
ofbaseclass
inheritor is engagedread()
ofopenclass
inheritor is NOT engaged but theread()
of theopenclass
itself
It seems like an inconsistent/undesirable/unintended behavior to me. Or do I miss something?
Thank you for providing information and support!
I was wondering what is the development cycle of RD? What is a time line that we can expect a recognized issue to be addressed? I'm asking so that we can set our expectation right and potentially look for (temporary) workaround(s).
from rd.
@al-darmonski, could you please share the sources? It's very hard to understand what's going on without them.
I was wondering what is the development cycle of RD?
We develop new features and fixes on the basis of necessity for our main use cases (we accept community pull requests, though!).
There are no immediate plans for fixing that issue. I'd say that the models should be very simple, and there shouldn't be any need for custom serializers. If you write these, then you're mostly in the uncharted territory, since there's little use of this feature in Rider and other products we develop.
from rd.
I work with @al-darmonski at Sioux.
We found that it even is even happens with non-custom serializers. I tried to create a simple example with openclass in the RD model, but I got an Fatal error. Internal CLR error.
I used the following RD model
@file:Suppress("unused")
package model
import com.jetbrains.rd.generator.nova.*
const val folder = "demo"
object DemoModel : Root() {
val a = openclass("a") {
property("prop1", PredefinedType.string)
}
init {
property("a", a)
}
}
And used the following C# code with that. (this is as well the server as client, based on a command line parameter)
using System;
using System.Net;
using JetBrains.Collections.Viewable;
using JetBrains.Lifetimes;
using JetBrains.Rd;
using JetBrains.Rd.Impl;
using rdTest2Implementation.model;
namespace rdTest2Implementation
{
class Program
{
private static ApplicationType _applicationType = ApplicationType.Client;
private static int port = 10001;
private static LifetimeDefinition ModelLifetimeDef { get; } = Lifetime.Eternal.CreateNested();
private static LifetimeDefinition SocketLifetimeDef { get; } = Lifetime.Eternal.CreateNested();
private static IProtocol Protocol { get; set; }
private static IScheduler Scheduler { get; set; }
private static DemoModel Model { get; set; } = null;
private static Lifetime ModelLifetime { get; set; }
private static Lifetime SocketLifetime { get; set; }
static void Main(string[] args)
{
if (args.Length != 1) throw new ArgumentException("needs one parameter");
if (args[0].ToLowerInvariant() == "server")
_applicationType = ApplicationType.Server;
ModelLifetime = ModelLifetimeDef.Lifetime;
SocketLifetime = SocketLifetimeDef.Lifetime;
Scheduler = SingleThreadScheduler.RunOnSeparateThread(SocketLifetime, "Worker", scheduler =>
{
IWire client;
IdKind idKind;
switch (_applicationType)
{
case ApplicationType.Server:
client = new SocketWire.Server(ModelLifetime, scheduler, new IPEndPoint(IPAddress.Loopback, port),
"server");
idKind = IdKind.Server;
break;
case ApplicationType.Client:
client = new SocketWire.Client(ModelLifetime, scheduler, new IPEndPoint(IPAddress.Loopback, port),
$"client");
idKind = IdKind.Client;
break;
default:
throw new ArgumentOutOfRangeException();
}
var serializers = new Serializers(ModelLifetime, scheduler, null);
Protocol = new Protocol(_applicationType == ApplicationType.Server?"server":"client", serializers,
new Identities(idKind), scheduler, client, SocketLifetime);
});
Scheduler.Queue(() =>
{
Model = new DemoModel(ModelLifetime, Protocol);
});
while (true)
{
Console.Out.WriteLine($"Press any to check type and close");
switch (Console.ReadKey().Key)
{
case ConsoleKey.A:
Scheduler.Queue(() =>
{
Model.A.Value = new A();
});
break;
case ConsoleKey.B:
Scheduler.Queue(() =>
{
Console.Out.WriteLine($"result: {(Model.A.Maybe.HasValue?Model.A.Maybe.Value.GetType():"Nothing")}");
});
break;
}
}
SocketLifetimeDef.Terminate();
ModelLifetimeDef.Terminate();
}
}
enum ApplicationType
{
Server,
Client
}
}
And the log is:
14:08:45.988 |V| JetBrains.Threading.ByteBufferAsyncProcessor.ClientSocket-client-Sender | :1 | PAUSE ('Disconnected') :: state=Initialized
14:08:46.126 |V| JetBrains.Rd.Impl.SocketWire+Client | ClientSocket-client-Receiver:8 | ClientSocket-client : connecting
14:08:46.129 |V| JetBrains.Rd.Impl.SocketWire+Client | ClientSocket-client-Receiver:8 | ClientSocket-client : connected
14:08:46.164 |V| JetBrains.Threading.ByteBufferAsyncProcessor.ClientSocket-client-Sender | ClientSocket-client-Receiver:8 | RESUME :: state=AsyncProcessing
Press any to check type and close
Fatal error. Internal CLR error. (0x80131506)
at System.Buffer._Memmove(Byte ByRef, Byte ByRef, UIntPtr)
at System.Buffer.Memmove(Byte ByRef, Byte ByRef, UIntPtr)
at System.String.Ctor(Char*, Int32, Int32)
at JetBrains.Serialization.UnsafeReader.ReadString()
at JetBrains.Rd.Impl.Serializers+<>c.<.cctor>b__82_9(JetBrains.Rd.SerializationCtx, JetBrains.Serialization.UnsafeReader)
at JetBrains.Rd.Impl.RdProperty`1[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Read(JetBrains.Rd.SerializationCtx, JetBrains.Serialization.UnsafeReader, Je
tBrains.Rd.CtxReadDelegate`1<System.__Canon>, JetBrains.Rd.CtxWriteDelegate`1<System.__Canon>)
at rdTest2Implementation.model.A+<>c.<.cctor>b__9_0(JetBrains.Rd.SerializationCtx, JetBrains.Serialization.UnsafeReader)
at JetBrains.Rd.Impl.RdProperty`1[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnWireReceived(JetBrains.Serialization.UnsafeReader)
at JetBrains.Rd.Impl.MessageBroker.Execute(JetBrains.Rd.Base.IRdWireable, Byte[])
at JetBrains.Rd.Impl.MessageBroker+<>c__DisplayClass8_0.<Invoke>b__0()
at JetBrains.Collections.Viewable.SingleThreadScheduler.ExecuteOneAction(Boolean)
at JetBrains.Collections.Viewable.SingleThreadScheduler.Run()
at JetBrains.Collections.Viewable.SingleThreadScheduler+<>c__DisplayClass15_0.<RunOnSeparateThread>b__0()
at System.Threading.ThreadHelper.ThreadStart_Context(System.Object)
at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
at System.Threading.ThreadHelper.ThreadStart()
For completion the versions I have used:
- RD-gen: 0.213.389
- RD Framework: 2021.1.1
- dotnet: 5.0.206
from rd.
Related Issues (20)
- Square complexity in Signal when using AdviseOnce pattern
- Help !! Support types that present contiguous region of memory HOT 1
- Add deterministic build.
- interfacedef's method() generates strange signature for a nullable return type
- Weird code in rd-cpp SocketWire.cpp read_package()
- Improving rd-cpp HOT 2
- QueueProcessorTest.test1 is flaky HOT 1
- Need a way to define different termination timeout for nested lifetimes HOT 3
- ViewableProperty<T>.Value equality HOT 3
- Kotlin: Mutable collections have methods to change them and not notify the protocol
- IReadonlyProperty<R> Select<T, R> (.net)
- Inlined FrameworkMarshallers.create spawns more classes in Metaspace than you could imagine HOT 2
- Support net3.5 in RdFramework.Tests HOT 4
- global.json in rd-net is not found with .NET 7.0.202 SDK
- Do not mark basestruct as abstract in generated models
- rd-cpp: Lifetime for RD endpoint callback not bounded to result lifetime on client
- rd-cpp: Fails to serialize uninitialized property (should just mark this property as unitialized for the client)
- rd-cpp: Unlike other Rd implementationsCallback doesn't auto-bind return result.
- Serializers.register() throws exception when models have same names
- Lifetimes are awesome!!! HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from rd.