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 }