Skip to content

Commit ff34b03

Browse files
committed
Close #90: add Communicator.on() message with event types.
1 parent 755cef8 commit ff34b03

File tree

5 files changed

+372
-34
lines changed

5 files changed

+372
-34
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "tgrid",
3-
"version": "1.0.3",
3+
"version": "1.1.0",
44
"main": "lib/index.js",
55
"typings": "lib/index.d.ts",
66
"exports": {

src/components/Communicator.ts

+163-29
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { ConditionVariable, HashMap, Pair } from "tstl";
1+
import { ConditionVariable, HashMap, HashSet } from "tstl";
22

33
import { Driver } from "../typings/Driver";
44
import { serializeError } from "../utils/internal/serializeError";
55
import { Invoke } from "./Invoke";
6+
import { InvokeEvent } from "./InvokeEvent";
67

78
/**
89
* The basic communicator.
@@ -44,7 +45,15 @@ export abstract class Communicator<
4445
/**
4546
* @hidden
4647
*/
47-
private promises_: HashMap<number, Pair<FunctionLike, FunctionLike>>;
48+
private promises_: HashMap<number, IFunctionReservation>;
49+
50+
/**
51+
* @hidden
52+
*/
53+
private event_listeners_: HashMap<
54+
InvokeEvent.Type,
55+
HashSet<(event: InvokeEvent) => void>
56+
>;
4857

4958
/**
5059
* @hidden
@@ -72,6 +81,50 @@ export abstract class Communicator<
7281
// OTHER MEMBERS
7382
this.promises_ = new HashMap();
7483
this.join_cv_ = new ConditionVariable();
84+
this.event_listeners_ = new HashMap();
85+
}
86+
87+
/**
88+
* Add invoke event listener.
89+
*
90+
* Add an event listener for the invoke event. The event listener would be called
91+
* when some invoke event has been occured; sending, receiving, completing, or returning.
92+
*
93+
* If you change the requesting parameters or returning value in the event listener,
94+
* it would affect to the RPC (Remote Procedure Call) communication. Therefore, you have
95+
* to be careful when modifying the remote function calling.
96+
*
97+
* Of course, you can utilize the event listener just for monitoring the RPC events.
98+
*
99+
* @param type Type of the event
100+
* @param listener The listener function to enroll
101+
*/
102+
public on<Type extends InvokeEvent.Type>(
103+
type: Type,
104+
listener: (event: InvokeEvent.EventMapper[Type]) => void,
105+
): void {
106+
this.event_listeners_
107+
.take(type, () => new HashSet())
108+
.insert(listener as (event: InvokeEvent) => void);
109+
}
110+
111+
/**
112+
* Erase invoke event listener.
113+
*
114+
* Erase an event listener from the invoke event. The event listener would not be
115+
* called anymore when the specific invoke event has been occured.
116+
*
117+
* @param type Type of the event
118+
* @param listener The listener function to erase
119+
*/
120+
public off<Type extends InvokeEvent.Type>(
121+
type: Type,
122+
listener: (event: InvokeEvent.EventMapper[Type]) => void,
123+
): void {
124+
const it = this.event_listeners_.find(type);
125+
if (it.equals(this.event_listeners_.end()) === false)
126+
it.second.erase(listener as (event: InvokeEvent) => void);
127+
if (it.second.empty()) this.event_listeners_.erase(it);
75128
}
76129

77130
/**
@@ -93,7 +146,7 @@ export abstract class Communicator<
93146
: new Error("Connection has been closed.");
94147

95148
for (const entry of this.promises_) {
96-
const reject: FunctionLike = entry.second.second;
149+
const reject: FunctionLike = entry.second.reject;
97150
reject(rejectError);
98151
}
99152

@@ -116,7 +169,6 @@ export abstract class Communicator<
116169
*/
117170
private _Proxy_func(name: string): FunctionLike {
118171
const func = (...params: any[]) => this._Call_function(name, ...params);
119-
120172
return new Proxy(func, {
121173
get: ({}, newName: string) => {
122174
if (newName === "bind")
@@ -125,7 +177,6 @@ export abstract class Communicator<
125177
return (thisArg: any, ...args: any[]) => func.call(thisArg, ...args);
126178
else if (newName === "apply")
127179
return (thisArg: any, args: any[]) => func.apply(thisArg, args);
128-
129180
return this._Proxy_func(`${name}.${newName}`);
130181
},
131182
});
@@ -155,8 +206,27 @@ export abstract class Communicator<
155206
})),
156207
};
157208

209+
// CALL EVENT LISTENERS
210+
const eventSetIterator = this.event_listeners_.find("send");
211+
if (eventSetIterator.equals(this.event_listeners_.end()) === false) {
212+
const event: InvokeEvent.ISend = {
213+
type: "send",
214+
time: new Date(),
215+
function: invoke,
216+
};
217+
for (const listener of eventSetIterator.second)
218+
try {
219+
listener(event);
220+
} catch {}
221+
}
222+
158223
// DO SEND WITH PROMISE
159-
this.promises_.emplace(invoke.uid, new Pair(resolve, reject));
224+
this.promises_.emplace(invoke.uid, {
225+
function: invoke,
226+
time: new Date(),
227+
resolve,
228+
reject,
229+
});
160230
await this.sendData(invoke);
161231
});
162232
}
@@ -271,14 +341,15 @@ export abstract class Communicator<
271341
protected replyData(invoke: Invoke): void {
272342
if ((invoke as Invoke.IFunction).listener)
273343
this._Handle_function(invoke as Invoke.IFunction).catch(() => {});
274-
else this._Handle_return(invoke as Invoke.IReturn);
344+
else this._Handle_complete(invoke as Invoke.IReturn);
275345
}
276346

277347
/**
278348
* @hidden
279349
*/
280350
private async _Handle_function(invoke: Invoke.IFunction): Promise<void> {
281351
const uid: number = invoke.uid;
352+
const time: Date = new Date();
282353

283354
try {
284355
//----
@@ -323,33 +394,80 @@ export abstract class Communicator<
323394
}
324395
func = func.bind(thisArg);
325396

397+
// CALL EVENT LISTENERS
398+
const eventSetIterator: HashMap.Iterator<
399+
InvokeEvent.Type,
400+
HashSet<(event: InvokeEvent) => void>
401+
> = this.event_listeners_.find("receive");
402+
if (eventSetIterator.equals(this.event_listeners_.end()) === false) {
403+
const event: InvokeEvent.IReceive = {
404+
type: "receive",
405+
time,
406+
function: invoke,
407+
};
408+
for (const closure of eventSetIterator.second)
409+
try {
410+
closure(event);
411+
} catch {}
412+
}
413+
326414
//----
327415
// RETURN VALUE
328416
//----
329417
// CALL FUNCTION
330418
const parameters: any[] = invoke.parameters.map((p) => p.value);
331-
const ret: any = await func(...parameters);
332-
333-
await this._Send_return(uid, true, ret);
419+
const result: any = await func(...parameters);
420+
await this._Send_return({
421+
invoke,
422+
time,
423+
return: {
424+
uid,
425+
success: true,
426+
value: result,
427+
},
428+
});
334429
} catch (exp) {
335-
await this._Send_return(uid, false, exp);
430+
await this._Send_return({
431+
invoke,
432+
time,
433+
return: {
434+
uid,
435+
success: false,
436+
value: exp,
437+
},
438+
});
336439
}
337440
}
338441

339442
/**
340443
* @hidden
341444
*/
342-
private _Handle_return(invoke: Invoke.IReturn): void {
343-
// GET THE PROMISE OBJECT
445+
private _Handle_complete(invoke: Invoke.IReturn): void {
446+
// FIND TARGET FUNCTION CALL
344447
const it = this.promises_.find(invoke.uid);
345448
if (it.equals(this.promises_.end())) return;
346449

450+
// CALL EVENT LISTENERS
451+
const eventSetIterator = this.event_listeners_.find("complete");
452+
if (eventSetIterator.equals(this.event_listeners_.end()) === false) {
453+
const event: InvokeEvent.IComplete = {
454+
type: "complete",
455+
function: it.second.function,
456+
return: invoke,
457+
requested_at: it.second.time,
458+
completed_at: new Date(),
459+
};
460+
for (const closure of eventSetIterator.second)
461+
try {
462+
closure(event);
463+
} catch {}
464+
}
465+
347466
// RETURNS
348467
const func: FunctionLike = invoke.success
349-
? it.second.first
350-
: it.second.second;
468+
? it.second.resolve
469+
: it.second.reject;
351470
this.promises_.erase(it);
352-
353471
func(invoke.value);
354472
}
355473

@@ -366,23 +484,39 @@ export abstract class Communicator<
366484
/**
367485
* @hidden
368486
*/
369-
private async _Send_return(
370-
uid: number,
371-
success: boolean,
372-
value: any,
373-
): Promise<void> {
487+
private async _Send_return(props: {
488+
invoke: Invoke.IFunction;
489+
return: Invoke.IReturn;
490+
time: Date;
491+
}): Promise<void> {
492+
const eventSet = this.event_listeners_.find("return");
493+
if (eventSet.equals(this.event_listeners_.end()) === false) {
494+
const event: InvokeEvent.IReturn = {
495+
type: "return",
496+
function: props.invoke,
497+
return: props.return,
498+
requested_at: props.time,
499+
completed_at: new Date(),
500+
};
501+
for (const closure of eventSet.second)
502+
try {
503+
closure(event);
504+
} catch {}
505+
}
506+
374507
// SPECIAL LOGIC FOR ERROR -> FOR CLEAR JSON ENCODING
375-
if (success === false && value instanceof Error)
376-
value = serializeError(value);
508+
if (props.return.success === false && props.return.value instanceof Error)
509+
props.return.value = serializeError(props.return.value);
377510

378511
// RETURNS
379-
const ret: Invoke.IReturn = {
380-
uid,
381-
success,
382-
value,
383-
};
384-
await this.sendData(ret);
512+
await this.sendData(props.return);
385513
}
386514
}
387515

388516
type FunctionLike = (...args: any[]) => any;
517+
interface IFunctionReservation {
518+
function: Invoke.IFunction;
519+
time: Date;
520+
resolve: FunctionLike;
521+
reject: FunctionLike;
522+
}

src/components/Invoke.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ export namespace Invoke {
1313
/**
1414
* Unique identifier.
1515
*/
16-
uid: number;
16+
readonly uid: number;
1717

1818
/**
1919
* Target function (sometimes calsuled in objects) to call.
2020
*/
21-
listener: string;
21+
readonly listener: string;
2222

2323
/**
2424
* Parameters for the function call.
@@ -49,12 +49,12 @@ export namespace Invoke {
4949
/**
5050
* Unique identifier.
5151
*/
52-
uid: number;
52+
readonly uid: number;
5353

5454
/**
5555
* `true` -> return, `false` -> exception.
5656
*/
57-
success: boolean;
57+
readonly success: boolean;
5858

5959
/**
6060
* Returned value or thrown exception.

0 commit comments

Comments
 (0)