Skip to content

Commit

Permalink
Merge pull request #8 from ismailhabib/separate-send-and-ask
Browse files Browse the repository at this point in the history
Separate send and ask
  • Loading branch information
ismailhabib authored Oct 15, 2018
2 parents 507cfef + ceb961f commit 4895476
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 139 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,14 @@ Create a new actor inside the actorSystem. The options parameter are as follow:
#### From an Actor
```TypeScript
this.at(actorRef).yourMethodName(payload?);
this.sendTo(actorRef).yourMethodName(payload?);
```
**actorRef**: _(Required)_ the target actorRef where we send the message to<br/>
**payload**: _(As defined by the target actor)_ payload of the message as defined by the target actor
```TypeScript
this.at<TargetActorAPI>(address).yourMethodName(payload?);
this.sendTo<TargetActorAPI>(address).yourMethodName(payload?);
```
**address**: _(Required)_ the target address where we send the message to, if `TargetActorAPI` is not specified then there will be no compile-time check<br/>
Expand All @@ -98,7 +98,7 @@ this.at<TargetActorAPI>(address).yourMethodName(payload?);
#### From Everywhere Else
```TypeScript
actorRef.invoke(sender?).yourMethodName(payload?);
actorRef.send(sender?).yourMethodName(payload?);
```
This is the typical way to send a message to an actor from outside of actors. Sender parameter is optional, but if you need to use it, better to just use the previous API.
Expand All @@ -110,7 +110,7 @@ This is the typical way to send a message to an actor from outside of actors. Se
```TypeScript
const senderRef = this.context.senderRef;
this.at(senderRef).yourMethodName(payload?);
this.sendTo(senderRef).yourMethodName(payload?);
```
### Getting Address of Actors
Expand Down
139 changes: 90 additions & 49 deletions src/Actor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,34 @@ export type MailBoxMessage<T> = {
callback: (error?: any, result?: any) => void;
};

export type ValidActorMethodProps<T> = Pick<T, ValidActorMethodPropNames<T>>;
export type ActorSendAPI<T> = {
[K in ValidActorMethodPropNames<T>]: T[K] extends () => any
? () => void
: T[K] extends (arg1: infer A1) => any
? (arg: A1) => void
: T[K] extends (arg1: infer A1, arg2: infer A2) => any
? (arg1: A1, arg2: A2) => void
: T[K] extends (arg1: infer A1, arg2: infer A2, arg3: infer A3) => any
? (arg1: A1, arg2: A2, arg3: A3) => void
: T[K] extends (
arg1: infer A1,
arg2: infer A2,
arg3: infer A3,
arg4: infer A4
) => any
? (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => void
: T[K] extends (
arg1: infer A1,
arg2: infer A2,
arg3: infer A3,
arg4: infer A4,
arg5: infer A5
) => any
? (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => void
: never
};

export type ActorAskAPI<T> = Pick<T, ValidActorMethodPropNames<T>>;
export type ValidActorMethodPropNames<T> = {
[K in Exclude<keyof T, keyof Actor>]: T[K] extends (...args: any[]) => infer R
? R extends Promise<any> ? K : never
Expand All @@ -28,24 +55,45 @@ export type ActorCons<T extends Actor<K>, K = undefined> = new (
strategies?: Strategy[]
) => T;

function createProxy<T>(
actorSystem: ActorSystem,
targetAddressorActorRef: Address | ActorRef<T>,
sender?: Address,
ask = false
) {
return new Proxy(
{},
{
get: (target, prop, receiver) => {
return (...payload: any[]) => {
return ask
? actorSystem.sendMessageAndWait(
targetAddressorActorRef,
prop as any,
sender || null,
...payload
)
: actorSystem.sendMessage(
targetAddressorActorRef,
prop as any,
sender || null,
...payload
);
};
}
}
);
}

export class ActorRef<T> {
constructor(public address: Address, private actorSystem: ActorSystem) {}

invoke(sender?: Address) {
return new Proxy(
{},
{
get: (target, prop, receiver) => {
return (...payload: any[]) =>
this.actorSystem.sendMessage(
this.address,
prop as any,
sender || null,
...payload
);
}
}
) as ValidActorMethodProps<T>;
send(sender?: Address) {
return createProxy(this.actorSystem, this.address, sender) as ActorSendAPI<T>;
}

ask(sender?: Address) {
return createProxy(this.actorSystem, this.address, sender, true) as ActorAskAPI<T>;
}
}

Expand Down Expand Up @@ -78,37 +126,6 @@ export abstract class Actor<InitParam = undefined> {
});
}

at<A>(targetRef: ActorRef<A> | Address) {
return new Proxy(
{},
{
get: (target, prop, receiver) => {
return (...payload: any[]) =>
this.actorSystem.sendMessage(
targetRef,
prop as any,
this.address,
...payload
);
}
}
) as Handler<A>;
}

// For some reason the typings is not working properly
atSelf() {
// return this.at<ValidActorMethodProps<this>>(this.address);
return this.at<any>(this.address); // TODO: introduce generic for actor
}

onNewMessage = <K extends ValidActorMethodPropNames<this>, L extends PayloadPropNames<this>>(
type: K,
senderAddress: Address | null,
...payload: L[]
) => {
// should be overridden by implementator (when necessary)
};

pushToMailbox = <K extends ValidActorMethodPropNames<this>, L extends PayloadPropNames<this>>(
type: K,
senderAddress: Address | null,
Expand Down Expand Up @@ -150,11 +167,34 @@ export abstract class Actor<InitParam = undefined> {
return promise;
};

// TODO: 'ref' vs 'at' will confuse people
ref = <T>(address: Address) => {
protected ref = <T>(address: Address) => {
return this.actorSystem.ref<T>(address);
};

protected sendTo<A>(targetRef: ActorRef<A> | Address) {
return createProxy(this.actorSystem, targetRef, this.address) as ActorSendAPI<A>;
}

protected askTo<A>(targetRef: ActorRef<A> | Address) {
return createProxy(this.actorSystem, targetRef, this.address, true) as ActorAskAPI<A>;
}

// For some reason the typings is not working properly
protected sendToSelf() {
// return this.at<ValidActorMethodProps<this>>(this.address);
return this.sendTo<any>(this.address); // TODO: introduce generic for actor
}

protected onNewMessage = <
K extends ValidActorMethodPropNames<this>,
L extends PayloadPropNames<this>
>(
type: K,
senderAddress: Address | null,
...payload: L[]
) => {
// should be overridden by implementator (when necessary)
};
protected init(options?: InitParam) {
// can be implemented by the concrete actor
}
Expand Down Expand Up @@ -217,6 +257,7 @@ export abstract class Actor<InitParam = undefined> {
this.currentPromise = this.handleMessage(type, ...payload);
try {
result = await this.currentPromise;
this.log("Output of the handled message", result);
success = true;
} catch (error) {
this.log("Caught an exception when handling message", error);
Expand Down
34 changes: 30 additions & 4 deletions src/ActorSystem.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Actor, ActorCons, ActorRef, ValidActorMethodProps } from "./Actor";
import { Actor, ActorCons, ActorRef, ActorAskAPI } from "./Actor";
import { EventEmitter } from "events";
import {
Message,
Expand Down Expand Up @@ -68,13 +68,13 @@ export class ActorSystem {
`Sending the message to the appropriate actor. Type: ${type}, sender: ${senderAddress}, and payload:`,
payload
);
this.sendMessage(actorRef, type, senderAddress, ...payload);
this.sendMessageAndWait(actorRef, type, senderAddress, ...payload);
} else {
this.log(
`Sending the question to the appropriate actor. Type: ${type}, sender: ${senderAddress}, and payload:`,
payload
);
this.sendMessage(actorRef, type, senderAddress, ...payload).then(
this.sendMessageAndWait(actorRef, type, senderAddress, ...payload).then(
message => {
this.log(
`Received an answer, sending the answer "${message}" for the question with type: ${type}, sender: ${senderAddress}, and payload:`,
Expand Down Expand Up @@ -107,7 +107,7 @@ export class ActorSystem {
(options as any).paramOptions,
(options as any).strategies
);
return this.ref<ValidActorMethodProps<T>>(fullAddress);
return this.ref<ActorAskAPI<T>>(fullAddress);
};

removeActor = (refOrAddress: ActorRef<any> | Address) => {
Expand Down Expand Up @@ -154,6 +154,32 @@ export class ActorSystem {
type: string,
senderAddress: Address | null,
...payload: any[]
): void => {
this.sendMessageAndWait(target, type, senderAddress, ...payload).then(
() => {
/* do nothing */
},
error => {
this.log(
`Catch an error when executing message with type ${type}`,
"Target",
target,
"Sender",
senderAddress,
"Payload",
payload,
"Error",
error
);
}
);
};

sendMessageAndWait = (
target: ActorRef<any> | Address,
type: string,
senderAddress: Address | null,
...payload: any[]
): Promise<any> => {
this.log(
`Received a request to send a message with type: ${type}`,
Expand Down
Loading

0 comments on commit 4895476

Please sign in to comment.