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 }