From 652f87dd9d728966837b0d551f736649bb7e307a Mon Sep 17 00:00:00 2001 From: Sinan Date: Mon, 24 Sep 2018 12:48:07 +0200 Subject: [PATCH 4/4] Add rsb rpc pattern Asynchronous calls are not implemented! --- rsb-cil-test/Program.cs | 36 ++++++++++++++++ rsb-cil/Rsb/Patterns/DataCallback.cs | 25 +++++++++++ rsb-cil/Rsb/Patterns/ICallback.cs | 8 ++++ rsb-cil/Rsb/Patterns/LocalServer.cs | 47 +++++++++++++++++++++ rsb-cil/Rsb/Patterns/Method.cs | 53 +++++++++++++++++++++++ rsb-cil/Rsb/Patterns/RemoteServer.cs | 81 ++++++++++++++++++++++++++++++++++++ rsb-cil/rsb-cil.csproj | 5 +++ 7 files changed, 255 insertions(+) create mode 100644 rsb-cil/Rsb/Patterns/DataCallback.cs create mode 100644 rsb-cil/Rsb/Patterns/ICallback.cs create mode 100644 rsb-cil/Rsb/Patterns/LocalServer.cs create mode 100644 rsb-cil/Rsb/Patterns/Method.cs create mode 100644 rsb-cil/Rsb/Patterns/RemoteServer.cs diff --git a/rsb-cil-test/Program.cs b/rsb-cil-test/Program.cs index 3b8fcc8..8ba624d 100644 --- a/rsb-cil-test/Program.cs +++ b/rsb-cil-test/Program.cs @@ -14,6 +14,7 @@ using log4net.Config; using Google.Protobuf; using Google.Protobuf.Reflection; +using Rsb.Patterns; namespace rsbciltest { @@ -119,7 +120,42 @@ namespace rsbciltest repoInformer.Deactivate(); Console.ReadKey(); repoListener.Deactivate(); + + Console.WriteLine("\nRPC pattern test"); + + RemoteServer remote = new RemoteServer(new Scope("/test/methods")); + LocalServer server = new LocalServer(new Scope("/test/methods")); + server.AddMethod("echo", new Caller()); + + remote.timeout = 1000; + //should have a timeout, since the LocalServer is not activated + try + { + remote.Call("echo", "hallo"); + } + catch (RSBException e) + { + Console.WriteLine(e.Message); + } + + server.Activate(); + // should work even though it is added after activation + server.AddMethod("echo2", new Caller()); + + Console.WriteLine("Echo : {0}", remote.Call("echo", "hallo")); + Console.WriteLine("Echo2: {0}", remote.Call("echo2", "hallo")); + + Console.ReadKey(); + server.Deactivate(); } } + + public class Caller : DataCallback + { + public override string Invoke(string data) + { + return data + data; + } + } } diff --git a/rsb-cil/Rsb/Patterns/DataCallback.cs b/rsb-cil/Rsb/Patterns/DataCallback.cs new file mode 100644 index 0000000..31cc3be --- /dev/null +++ b/rsb-cil/Rsb/Patterns/DataCallback.cs @@ -0,0 +1,25 @@ + +namespace Rsb.Patterns +{ + public abstract class DataCallback : ICallback + { + public Event InternalInvoke(Event e) { + + if (e.Method != "REQUEST") { + throw new RSBException("Request expected!"); + } + ReplyType result = this.Invoke((RequestType)e.Data); + + var reply = new Rsb.Event(); + reply.Method = "REPLY"; + reply.Scope = e.Scope; + reply.AddCause(e.EventId); + reply.Data = result; + + return reply; + } + + public abstract ReplyType Invoke(RequestType param); + } + +} diff --git a/rsb-cil/Rsb/Patterns/ICallback.cs b/rsb-cil/Rsb/Patterns/ICallback.cs new file mode 100644 index 0000000..1d10228 --- /dev/null +++ b/rsb-cil/Rsb/Patterns/ICallback.cs @@ -0,0 +1,8 @@ + +namespace Rsb.Patterns +{ + public interface ICallback + { + Event InternalInvoke(Event e); + } +} diff --git a/rsb-cil/Rsb/Patterns/LocalServer.cs b/rsb-cil/Rsb/Patterns/LocalServer.cs new file mode 100644 index 0000000..c042fda --- /dev/null +++ b/rsb-cil/Rsb/Patterns/LocalServer.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rsb.Patterns +{ + public class LocalServer + { + private Scope scope; + private Dictionary registeredMethods = new Dictionary(); + + private bool activated = false; + + // forced to be created with the Factory + public LocalServer(Scope scope) { + this.scope = scope; + } + + + public void AddMethod(string name, ICallback callback) { + if (registeredMethods.ContainsKey(name)) { + throw new ArgumentException("method already registered"); + } + Method method = new Method(scope, name, callback); + registeredMethods.Add(name, method); + if (activated) { + method.Activate(); + } + } + + public void Activate() { + foreach (Method method in registeredMethods.Values) { + method.Activate(); + } + activated = true; + } + + public void Deactivate() { + foreach (Method method in registeredMethods.Values) { + method.Deactivate(); + } + activated = false; + } + } +} diff --git a/rsb-cil/Rsb/Patterns/Method.cs b/rsb-cil/Rsb/Patterns/Method.cs new file mode 100644 index 0000000..befcedf --- /dev/null +++ b/rsb-cil/Rsb/Patterns/Method.cs @@ -0,0 +1,53 @@ + +using System; + +namespace Rsb.Patterns +{ + public class Method + { + private Informer informer; + private Listener listener; + private string name; + public string Name { get => name; } + + private ICallback callback; + + public Method(Scope scope, string name, ICallback callback) { + this.name = name; + this.callback = callback; + Scope work = scope.Concat(new Scope("/" + name)); + + informer = Factory.Instance.createInformer(work); + listener = Factory.Instance.createListener(work); + + listener.EventReceived += proceedCall; + } + + private void proceedCall(Event theEvent) + { + // this is with the highest probability our own reply... + if (theEvent.Method == "REPLY") { + return; + } + // actually this would be handled in the ICallback too, but to make it more consistent... + if (theEvent.Method != "REQUEST") + { + throw new RSBException("Request expected!"); + } + + informer.Send(callback.InternalInvoke(theEvent)); + } + + public void Activate() { + informer.Activate(); + listener.Activate(); + } + + public void Deactivate() + { + informer.Deactivate(); + listener.Deactivate(); + } + + } +} diff --git a/rsb-cil/Rsb/Patterns/RemoteServer.cs b/rsb-cil/Rsb/Patterns/RemoteServer.cs new file mode 100644 index 0000000..92bf08b --- /dev/null +++ b/rsb-cil/Rsb/Patterns/RemoteServer.cs @@ -0,0 +1,81 @@ +using System; +using System.Threading; + +namespace Rsb.Patterns +{ + public class RemoteServer + { + public int timeout = 3000; // milliseconds + private Scope scope; + + public RemoteServer(Scope scope) { + this.scope = scope; + } + + public Event Call(string name, Event request) { + Scope methodScope = scope.Concat(new Scope("/" + name)); + + // checks if the request event is well-formed instead of well-forming the event + if (!request.Method.Equals("REQUEST")) { + throw new RSBException(String.Format("Request event expected! (Received: {0})", request.Method)); + } + + if (!request.Scope.Equals(methodScope)) { + throw new RSBException(String.Format("Event is not the expected scope. (Expected: {0} | Got: {1})", methodScope, request.Scope)); + } + + // creating the callback for the reply + Event result = new Event(); + object syncPrimitive = new object(); + NewEventHandler handler = (e) => { + if (e.Method == "REPLY" && e.IsCause(request.EventId)) + { + result = e; + // unlocking the SYNCHRONOUS call + lock (syncPrimitive) + { + Monitor.Pulse(syncPrimitive); + } + } + }; + + Informer requestInformer = Factory.Instance.createInformer(methodScope); + requestInformer.Activate(); + + Listener replyListener = Factory.Instance.createListener(methodScope); + replyListener.EventReceived += handler; + replyListener.Activate(); + + + bool timeout; + // Locking until reply arrives (see handler above) + lock (syncPrimitive) + { + requestInformer.Send(request); + requestInformer.Deactivate(); + timeout = !Monitor.Wait(syncPrimitive, this.timeout); + } + replyListener.Deactivate(); + if (timeout) { + throw new RSBException(String.Format("Timeout after {0} milliseconds", this.timeout)); + } + return result; + } + + public ReplyType Call(string name, RequestType requestData) { + Event request = new Event(); + request.Data = requestData; + request.Scope = scope.Concat(new Scope("/" + name)); + request.Method = "REQUEST"; + + Event reply = this.Call(name, request); + if (reply.Data.GetType().Equals(typeof(ReplyType))) + { + return (ReplyType)reply.Data; + } + else { + throw new RSBException(String.Format("Unexpected Datatype! (Expected: {0} | Got: {1})", typeof(ReplyType).ToString(), reply.Data.GetType().ToString())); + } + } + } +} diff --git a/rsb-cil/rsb-cil.csproj b/rsb-cil/rsb-cil.csproj index 1e2ed2f..d4bdf10 100644 --- a/rsb-cil/rsb-cil.csproj +++ b/rsb-cil/rsb-cil.csproj @@ -51,6 +51,11 @@ + + + + + -- 2.14.2.windows.1