1 /**
2 Copyright: Copyright (c) 2021, Joakim Brännström. All rights reserved.
3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 */
6 module my.actor.typed;
7 
8 import std.datetime : SysTime, Clock;
9 import std.meta : AliasSeq, staticMap;
10 import std.traits : Unqual, isFunction, isDelegate, Parameters, ReturnType, isFunctionPointer;
11 import std.typecons : Tuple, tuple;
12 
13 import my.actor.actor : Actor, ErrorHandler, DownHandler, ExitHandler,
14     ExceptionHandler, DefaultHandler;
15 import my.actor.common : makeSignature;
16 import my.actor.mailbox : StrongAddress, WeakAddress, Address, Msg, MsgType, makeAddress2;
17 import my.actor.msg;
18 import my.actor.system : System;
19 
20 enum isTypedActor(T) = is(T : TypedActor!U, U);
21 enum isTypedActorImpl(T) = is(T : TypedActorImpl!U, U);
22 enum isTypedAddress(T) = is(T : TypedAddress!U, U);
23 
24 /// Signature for a typed actor.
25 struct TypedActor(AllowedMsg...) {
26     alias AllowedMessages = AliasSeq!AllowedMsg;
27     alias Address = TypedAddress!AllowedMessages;
28     alias Impl = TypedActorImpl!AllowedMessages;
29 }
30 
31 private template toTypedMsgs(AllowedMsg...) {
32     static if (AllowedMsg.length == 1)
33         alias toTypedMsgs = ToTypedMsg!(AllowedMsg[0], false);
34     else
35         alias toTypedMsgs = AliasSeq!(ToTypedMsg!(AllowedMsg[0], false),
36                 toTypedMsgs!(AllowedMsg[1 .. $]));
37 }
38 
39 /// Construct the type representing a typed actor.
40 template typedActor(AllowedMsg...) {
41     alias typedActor = TypedActor!(toTypedMsgs!AllowedMsg);
42 }
43 
44 /// Actor implementing the type actors requirements.
45 struct TypedActorImpl(AllowedMsg...) {
46     alias AllowedMessages = AliasSeq!AllowedMsg;
47     alias Address = TypedAddress!AllowedMessages;
48 
49     package Actor* actor;
50 
51     void shutdown() @safe nothrow {
52         actor.shutdown;
53     }
54 
55     void forceShutdown() @safe nothrow {
56         actor.forceShutdown;
57     }
58 
59     /// Set name name of the actor.
60     void name(string n) @safe pure nothrow @nogc {
61         actor.name = n;
62     }
63 
64     void errorHandler(ErrorHandler v) @safe pure nothrow @nogc {
65         actor.errorHandler = v;
66     }
67 
68     void downHandler(DownHandler v) @safe pure nothrow @nogc {
69         actor.downHandler = v;
70     }
71 
72     void exitHandler(ExitHandler v) @safe pure nothrow @nogc {
73         actor.exitHandler = v;
74     }
75 
76     void exceptionHandler(ExceptionHandler v) @safe pure nothrow @nogc {
77         actor.exceptionHandler = v;
78     }
79 
80     void defaultHandler(DefaultHandler v) @safe pure nothrow @nogc {
81         actor.defaultHandler = v;
82     }
83 
84     TypedAddress!AllowedMessages address() @safe {
85         return TypedAddress!AllowedMessages(actor.address);
86     }
87 
88     ref System homeSystem() @safe pure nothrow @nogc {
89         return actor.homeSystem;
90     }
91 }
92 
93 /// Type safe address used to verify messages before they are sent.
94 struct TypedAddress(AllowedMsg...) {
95     alias AllowedMessages = AliasSeq!AllowedMsg;
96     package {
97         WeakAddress address;
98 
99         this(StrongAddress a) {
100             address = a.weakRef;
101         }
102 
103         this(WeakAddress a) {
104             address = a;
105         }
106 
107         package ref inout(WeakAddress) opCall() inout {
108             return address;
109         }
110     }
111 }
112 
113 auto extend(TActor, AllowedMsg...)() if (isTypedActor!TActor) {
114     alias dummy = typedActor!AllowedMsg;
115     return TypedActor!(AliasSeq!(TActor.AllowedMessages, dummy.AllowedMessages));
116 }
117 
118 package template ParamsToTuple(T...)
119         if (T.length > 1 || T.length == 1 && !is(T[0] == void)) {
120     static if (T.length == 1)
121         alias ParamsToTuple = Tuple!(T[0]);
122     else
123         alias ParamsToTuple = Tuple!(staticMap!(Unqual, T));
124 }
125 
126 package template ReturnToTupleOrVoid(T) {
127     static if (is(T == void))
128         alias ReturnToTupleOrVoid = void;
129     else {
130         static if (is(T == Tuple!U, U))
131             alias ReturnToTupleOrVoid = T;
132         else
133             alias ReturnToTupleOrVoid = Tuple!T;
134     }
135 }
136 
137 package template ToTypedMsg(T, bool HasContext)
138         if ((isFunction!T || isDelegate!T || isFunctionPointer!T) && Parameters!T.length != 0) {
139     import my.actor.actor : Promise, RequestResult;
140 
141     static if (HasContext)
142         alias RawParams = Parameters!T[1 .. $];
143     else
144         alias RawParams = Parameters!T;
145     static if (is(ReturnType!T == Promise!PT, PT))
146         alias RawReply = PT;
147     else static if (is(ReturnType!T == RequestResult!RT, RT)) {
148         alias RawReply = RT;
149     } else
150         alias RawReply = ReturnType!T;
151 
152     alias Params = ParamsToTuple!RawParams;
153     alias Reply = ReturnToTupleOrVoid!RawReply;
154 
155     alias ToTypedMsg = TypedMsg!(Params, Reply);
156 }
157 
158 package struct TypedMsg(P, R) {
159     alias Params = P;
160     alias Reply = R;
161 }
162 
163 package bool IsEqual(TypedMsg1, TypedMsg2)() {
164     return is(TypedMsg1.Params == TypedMsg2.Params) && is(TypedMsg1.Reply == TypedMsg2.Reply);
165 }
166 
167 /** Check that `Behavior` implement the actor interface `TActor` at compile
168  * time. If successfull an actor is built that implement `TActor`.
169  */
170 auto impl(TActor, Behavior...)(TActor actor, Behavior behaviors)
171         if (isTypedActorImpl!TActor && typeCheckImpl!(TActor, Behavior)) {
172     import my.actor.actor : build;
173     import my.actor.msg : isCapture, Capture;
174 
175     auto bactor = build(actor.actor);
176     static foreach (const i; 0 .. Behavior.length) {
177         {
178             alias b = Behavior[i];
179 
180             static if (!isCapture!b) {
181                 static if (!(isFunction!(b) || isFunctionPointer!(b)))
182                     static assert(0, "behavior may only be functions, not delgates: " ~ b.stringof);
183 
184                 static if (i + 1 < Behavior.length && isCapture!(Behavior[i + 1])) {
185                     bactor.set(behaviors[i], behaviors[i + 1]);
186                 } else
187                     bactor.set(behaviors[i]);
188             }
189         }
190     }
191     return TypedActorImpl!(TActor.AllowedMessages)(bactor.finalize);
192 }
193 
194 private string prettify(T)() if (is(T : TypedMsg!(U, V), U, V)) {
195     import std.traits : fullyQualifiedName;
196 
197     string s;
198     s ~= "(";
199     static foreach (P; T.Params.expand)
200         s ~= fullyQualifiedName!(typeof(P)) ~ ",";
201     s ~= ") -> (";
202     static if (is(T.Reply == void)) {
203         s ~= "void";
204     } else {
205         static foreach (P; T.Reply.expand)
206             s ~= fullyQualifiedName!(typeof(P)) ~ ",";
207     }
208     s ~= ")";
209     return s;
210 }
211 
212 private bool typeCheckImpl(TActor, Behavior...)() {
213     // check that the specification is implemented and no duplications.
214     // will allow an actor have more behaviors than the specification allow.
215     foreach (T; TActor.AllowedMessages) {
216         bool reqRepOk;
217         // only one parameter match is allowed or else req/rep is overwritten
218         // when constructing the actor.
219         bool paramsMatch;
220         static foreach (const i; 0 .. Behavior.length) {
221             {
222                 alias bh = Behavior[i];
223                 // look ahead one step to determine if it is a capture. If so then the parameters are reduced
224                 static if (i + 1 < Behavior.length && isCapture!(Behavior[i + 1]))
225                     enum HasContext = true;
226                 else
227                     enum HasContext = false;
228 
229                 static if (!isCapture!bh) {
230                     alias Msg = ToTypedMsg!(bh, HasContext);
231 
232                     static if (is(T.Params == Msg.Params)) {
233                         if (paramsMatch) {
234                             assert(false, "duplicate implementation parameters of " ~ prettify!T);
235                         }
236                         paramsMatch = true;
237                     }
238 
239                     static if (IsEqual!(T, Msg)) {
240                         if (reqRepOk) {
241                             assert(false, "duplicate implementation of " ~ prettify!T);
242                         }
243                         reqRepOk = true;
244                     }
245                 }
246             }
247         }
248 
249         if (!reqRepOk) {
250             assert(false, "missing implementation of " ~ prettify!T);
251         }
252     }
253     return true;
254 }
255 
256 /// Check if `TAddress` can receive the message.
257 package bool typeCheckMsg(TAllowed, R, Params...)() {
258     alias AllowedTypes = TAllowed.AllowedMessages;
259     alias MsgT = TypedMsg!(ParamsToTuple!Params, ReturnToTupleOrVoid!R);
260 
261     //pragma(msg, TAllowed);
262     //pragma(msg, R);
263     //pragma(msg, Params);
264     //pragma(msg, MsgT);
265 
266     bool rval;
267     foreach (T; AllowedTypes) {
268         static if (IsEqual!(T, MsgT)) {
269             rval = true;
270             break;
271         }
272     }
273     assert(rval, "actor cannot receive message " ~ prettify!MsgT);
274 
275     return rval;
276 }
277 
278 @(
279         "shall construct a typed actor with a behavior for msg->reply and process two messages with response")
280 unittest {
281     alias MyActor = typedActor!(int delegate(int), Tuple!(string, int) delegate(int, double));
282 
283     int called;
284     static int fn1(ref Capture!(int*, "called") c, int x) {
285         return (*c.called)++;
286     }
287 
288     auto aa1 = Actor(makeAddress2);
289     auto actor = impl(MyActor.Impl(&aa1), &fn1, capture(&called),
290             (ref Capture!(int*, "called") ctx, int, double) {
291         (*ctx.called)++;
292         return tuple("hej", 42);
293     }, capture(&called));
294 
295     actor.request(actor.address, infTimeout).send(42).capture(&called)
296         .then((ref Capture!(int*, "called") ctx, int x) {
297             return (*ctx.called)++;
298         });
299     actor.request(actor.address, infTimeout).send(42, 43.0).capture(&called)
300         .then((ref Capture!(int*, "called") ctx, string a, int b) {
301             if (a == "hej" && b == 42)
302                 (*ctx.called)++;
303         });
304 
305     // check that the code in __traits is correct
306     static assert(__traits(compiles, {
307             actor.request(actor.address, infTimeout).send(42).then((int x) {});
308         }));
309     // check that the type check works, rejecting the message because the actor
310     // do not accept it or the continuation (.then) has the wrong parameters.
311     //static assert(!__traits(compiles, {
312     //        actor.request(actor.address, infTimeout).send(43.0).then((int x) {});
313     //    }));
314     //static assert(!__traits(compiles, {
315     //        actor.request(actor.address, infTimeout).send(42).then((string x) {});
316     //    }));
317 
318     foreach (_; 0 .. 3)
319         actor.actor.process(Clock.currTime);
320 
321     assert(called == 4);
322 }
323 
324 @("shall construct a typed actor and process two messages")
325 unittest {
326     alias MyActor = typedActor!(void delegate(int), void delegate(int, double));
327 
328     int called;
329     static void fn1(ref Capture!(int*, "called") c, int x) {
330         (*c.called)++;
331     }
332 
333     auto aa1 = Actor(makeAddress2);
334     auto actor = impl(MyActor.Impl(&aa1), &fn1, capture(&called),
335             (ref Capture!(int*, "called") c, int, double) { (*c.called)++; }, capture(&called));
336 
337     send(actor.address, 42);
338     send(actor, 42, 43.0);
339 
340     // check that the code in __traits is correct
341     static assert(__traits(compiles, { send(actor.address, 42); }));
342     // check that the type check works, rejecting the message because the actor do not accept it.
343     static assert(!__traits(compiles, { send(actor.address, 43.0); }));
344 
345     actor.actor.process(Clock.currTime);
346     actor.actor.process(Clock.currTime);
347 
348     assert(called == 2);
349 }
350 
351 @("shall type check msg->reply")
352 unittest {
353     {
354         alias Msg = ToTypedMsg!(string delegate(int), false);
355         static assert(is(Msg == TypedMsg!(Tuple!int, Tuple!string)));
356     }
357     {
358         alias Msg = ToTypedMsg!(string delegate(int, int), false);
359         static assert(is(Msg == TypedMsg!(Tuple!(int, int), Tuple!string)));
360     }
361     {
362         alias Msg = ToTypedMsg!(Tuple!(int, string) delegate(int, int), false);
363         static assert(is(Msg == TypedMsg!(Tuple!(int, int), Tuple!(int, string))));
364     }
365     {
366         alias Msg = ToTypedMsg!(void delegate(int, int), false);
367         static assert(is(Msg == TypedMsg!(Tuple!(int, int), void)));
368     }
369 
370     static assert(IsEqual!(ToTypedMsg!(string delegate(int), false),
371             ToTypedMsg!(string delegate(int), false)));
372 }
373 
374 package StrongAddress underlyingAddress(T)(T address)
375         if (is(T == Actor*) || is(T == StrongAddress) || is(T == WeakAddress)
376             || isTypedAddress!T || isTypedActorImpl!T) {
377     static StrongAddress toStrong(WeakAddress wa) {
378         if (auto a = wa.lock)
379             return a;
380         return StrongAddress.init;
381     }
382 
383     static if (isTypedAddress!T) {
384         return toStrong(address.address);
385     } else static if (isTypedActorImpl!T)
386         return toStrong(address.address.address);
387     else static if (is(T == Actor*))
388         return address.addressRef;
389     else static if (is(T == WeakAddress)) {
390         return toStrong(address);
391     } else
392         return address;
393 }
394 
395 package WeakAddress underlyingWeakAddress(T)(T x)
396         if (is(T == Actor*) || is(T == StrongAddress) || is(T == WeakAddress)
397             || isTypedAddress!T || isTypedActorImpl!T) {
398     static if (isTypedAddress!T) {
399         return x.address;
400     } else static if (isTypedActorImpl!T)
401         return x.address.address;
402     else static if (is(T == Actor*))
403         return x.address;
404     else static if (is(T == StrongAddress)) {
405         return x.weakRef;
406     } else
407         return x;
408 }
409 
410 package auto underlyingTypedAddress(T)(T address)
411         if (isTypedAddress!T || isTypedActorImpl!T) {
412     static if (isTypedAddress!T)
413         return address;
414     else
415         return address.address;
416 }
417 
418 package Actor* underlyingActor(T)(T actor) if (is(T == Actor*) || isTypedActorImpl!T) {
419     static if (isTypedActorImpl!T)
420         return actor.actor;
421     else
422         return actor;
423 }