Learn from Examples
In this lesson, we'll learn about TGrid in depth with simple examples. Also, let's take a look at the code of Remote Function Call and demonstrate how the Remote Function Call come true the word: computers to be a computer.
- Demonstration of TGrid, who makes computers to be a computer
1. Remote Function Call
Let's see what Remote Function Call and how to use it with the below example code. What we're going to implement in this chapter is a remote calculator. The server provides a simple arithmetic calculator and client uses it ti perform calculations through the Remote Function Call.
- Server: Provides arithmetic operations
- Client: Use it
1.1. Features
ISimpleCalculator
is an interface that defines the functions provided to client from the server. Some of you may have noticed already. Ues, ISimpleCalculator
is the Controller. Then, what is the role of SimpleCalculator
? Right, it's the Provider class provided from server to client.
export interface ISimpleCalculator
{
plus(x: number, y: number): number;
minus(x: number, y: number): number;
multiplies(x: number, y: number): number;
divides(x: number, y: number): number;
}
Controller
export interface ISimpleCalculator
{
plus(x: number, y: number): number;
minus(x: number, y: number): number;
multiplies(x: number, y: number): number;
divides(x: number, y: number): number;
}
Provider
import { ISimpleCalculator } from "../controllers/ICalculator";
export class SimpleCalculator implements ISimpleCalculator
{
public plus(x: number, y: number): number
{
return x + y;
}
public minus(x: number, y: number): number
{
return x - y;
}
public multiplies(x: number, y: number): number
{
return x * y;
}
public divides(x: number, y: number): number
{
if (y === 0)
throw new Error("Divided by zero.");
return x / y;
}
}
1.2. Server
As I mentioned, the role of server in this example is to providing arithmetic calculator for the client. Therefore, server program code is very simple. Just opens a server and provides arithmetic calculator SimpleCalculator
(be Provider) to clients who access to server.
simple-calculator/server.ts
import { WebServer, WebAcceptor } from "tgrid/protocols/web";
import { SimpleCalculator } from "../../providers/Calculator";
async function main(): Promise<void>
{
let server: WebServer = new WebServer();
await server.open(10101, async (acceptor: WebAcceptor) =>
{
await acceptor.accept(new SimpleCalculator());
});
}
main();
1.3. Client
Finally, the time to make the long-awaited client program has come. If you compare two example codes, you may find a fact: Two codes are similar. You can see that (a program code connecting to server and using remote calculator provided from server as a client) and (another program code using its own calculator as a single program), both of them have similar program code.
In technical terms, it's considered as "Bsuiness Logic code is perfectly similar". Of course, two program codes are not 100% matched, because of domain logics like client should connect to server or an extra symbol await
is required for the Remote Function Call. However, the Business Logic portion of the two codes is completely homogeneous in fundamental level. Therefore, it helps us to separating Business Logic from Network System perfectly. do you agree?
The ability to call a networked remote system freely of its functions, just like it was my memory object from the beginning. This is the Remote Function Call.
On the 17 th line, you can see that after the client has finished connecting to the server, it constructs a Driver<Controller> object, by calling the connector.getDriver<ISimpleCalculator>()
method from Communicator, for preparing the remote function calls.
As you can see, all of the remote function calls to remote system's Provider are always done through this Driver<Controller> object. It would be repeated in all tutorial lessons. Thus, please be aware of it, the Driver<Controller>.
simple-calculator/client.ts
import { WebConnector } from "tgrid/protocols/web";
import { Driver } from "tgrid/components";
import { ISimpleCalculator } from "../../controllers/ICalculator";
async function main(): Promise<void>
{
//----
// CONNECTION
//----
let connector: WebConnector = new WebConnector();
await connector.connect("ws://127.0.0.1:10101");
//----
// CALL FUNCTIONS
//----
// GET DRIVER
let calc: Driver<ISimpleCalculator> = connector.getDriver<ISimpleCalculator>();
// CALL FUNCTIONS WITH AWAIT SYMBOL
console.log("1 + 3 =", await calc.plus(1, 3));
console.log("7 - 4 =", await calc.minus(7, 4));
console.log("8 x 9 =", await calc.multiplies(8, 9));
// TO CATCH EXCEPTION IS ALSO POSSIBLE
try
{
await calc.divides(4, 0);
}
catch (err)
{
console.log("4 / 0 -> Error:", err.message);
}
//----
// TERMINATE
//----
await connector.close();
}
main();
Remote-Function-Call
import { WebConnector } from "tgrid/protocols/web";
import { Driver } from "tgrid/components";
import { ISimpleCalculator } from "../../controllers/ICalculator";
async function main(): Promise<void>
{
//----
// CONNECTION
//----
let connector: WebConnector = new WebConnector();
await connector.connect("ws://127.0.0.1:10101");
//----
// CALL FUNCTIONS
//----
// GET DRIVER
let calc: Driver<ISimpleCalculator> = connector.getDriver<ISimpleCalculator>();
// CALL FUNCTIONS WITH AWAIT SYMBOL
console.log("1 + 3 =", await calc.plus(1, 3));
console.log("7 - 4 =", await calc.minus(7, 4));
console.log("8 x 9 =", await calc.multiplies(8, 9));
// TO CATCH EXCEPTION IS ALSO POSSIBLE
try
{
await calc.divides(4, 0);
}
catch (err)
{
console.log("4 / 0 -> Error:", err.message);
}
//----
// TERMINATE
//----
await connector.close();
}
main();
Single-Program
import { SimpleCalculator } from "../../providers/Calculator";
function main(): void
{
//----
// CALL FUNCTIONS
//----
// CONSTRUCT CALCULATOR
let calc: SimpleCalculator = new SimpleCalculator();
// CALL FUNCTIONS DIRECTLY
console.log("1 + 3 =", calc.plus(1, 3));
console.log("7 - 4 =", calc.minus(7, 4));
console.log("8 x 9 =", calc.multiplies(8, 9));
// TO CATCH EXCEPTION
try
{
calc.divides(4, 0);
}
catch (err)
{
console.log("4 / 0 -> Error:", err.message);
}
}
main();
1 + 3 = 4 7 - 4 = 3 8 x 9 = 72 4 / 0 -> Error: Divided by zero.
2. Remote Object Call
In the previous chapter, we've learned how to call functions of remote system. However, the previous chapter handled only singular structured object which defined all functions in its root scope. Therefore, we've never handled the composite structure.
What if the remote object you want to use is a composite structure? What if the final remote function you want to call is placed into deep depth hierarchy with stack of objects? In this case, TGrid's answer is simple and clear.
Just use it~!
2.1. Features
In this chapter, we'll demonstrate the Remote Object Call using CompositeCalculator
. The CompositeCalculator
class extends the SimpleCalculator to keep supporting four arithmetic operations continuosly. Also, supports scientific
and statistics
calculators with internal objects. Therefore, CompositeCalculator
is a typical of composite structured class.
I think you may already know that, the CompositeCalculator
would be Provider in the server. Also, ICompositeCalculator
would be a Controller in the client.
export interface ICompositeCalculator
extends ISimpleCalculator
{
scientific: IScientific;
statistics: IStatistics;
}
export interface ISimpleCalculator
{
plus(x: number, y: number): number;
minus(x: number, y: number): number;
multiplies(x: number, y: number): number;
divides(x: number, y: number): number;
}
export interface IScientific
{
pow(x: number, y: number): number;
sqrt(x: number): number;
log(x: number, y: number): number;
}
export interface IStatistics
{
mean(...elems: number[]): number;
stdev(...elems: number[]): number;
}
Controller
export interface ICompositeCalculator
extends ISimpleCalculator
{
scientific: IScientific;
statistics: IStatistics;
}
export interface ISimpleCalculator
{
plus(x: number, y: number): number;
minus(x: number, y: number): number;
multiplies(x: number, y: number): number;
divides(x: number, y: number): number;
}
export interface IScientific
{
pow(x: number, y: number): number;
sqrt(x: number): number;
log(x: number, y: number): number;
}
export interface IStatistics
{
mean(...elems: number[]): number;
stdev(...elems: number[]): number;
}
Provider
import {
ICompositeCalculator,
ISimpleCalculator, IScientific, IStatistics
} from "../controllers/ICalculator";
export class SimpleCalculator
implements ISimpleCalculator
{
public plus(x: number, y: number): number
{
return x + y;
}
public minus(x: number, y: number): number
{
return x - y;
}
public multiplies(x: number, y: number): number
{
return x * y;
}
public divides(x: number, y: number): number
{
if (y === 0)
throw new Error("Divided by zero.");
return x / y;
}
}
export class CompositeCalculator
extends SimpleCalculator
implements ICompositeCalculator
{
public scientific = new Scientific();
public statistics = new Statistics();
}
export class Scientific implements IScientific
{
public pow(x: number, y: number): number
{
return Math.pow(x, y);
}
public log(x: number, y: number): number
{
if (x < 0 || y < 0)
throw new Error("Negative value on log.");
return Math.log(y) / Math.log(x);
}
public sqrt(x: number): number
{
if (x < 0)
throw new Error("Negative value on sqaure.");
return Math.sqrt(x);
}
}
export class Statistics implements IStatistics
{
public mean(...elems: number[]): number
{
let ret: number = 0;
for (let val of elems)
ret += val;
return ret / elems.length;
}
public stdev(...elems: number[]): number
{
let mean: number = this.mean(...elems);
let ret: number = 0;
for (let val of elems)
ret += Math.pow(val - mean, 2);
return Math.sqrt(ret / elems.length);
}
}
2.2. Server
Need any explanation about this server code? Only difference between 1. Remote Function Call is Provider has been changed from SimpleCalculator
to CompositeCalculator
. Oh, and ports number also has been changed.
composite-calculator/server.ts
import { WebServer, WebAcceptor } from "tgrid/protocols/web";
import { CompositeCalculator } from "../../providers/Calculator";
async function main(): Promise<void>
{
let server: WebServer = new WebServer();
await server.open(10102, async (acceptor: WebAcceptor) =>
{
await acceptor.accept(new CompositeCalculator());
});
}
main();
Remote-Object-Call
import { WebServer, WebAcceptor } from "tgrid/protocols/web";
import { CompositeCalculator } from "../../providers/Calculator";
async function main(): Promise<void>
{
let server: WebServer = new WebServer();
await server.open(10102, async (acceptor: WebAcceptor) =>
{
await acceptor.accept(new CompositeCalculator());
});
}
main();
Remote-Function-Call
import { WebServer, WebAcceptor } from "tgrid/protocols/web";
import { SimpleCalculator } from "../../providers/Calculator";
async function main(): Promise<void>
{
let server: WebServer = new WebServer();
await server.open(10101, async (acceptor: WebAcceptor) =>
{
await acceptor.accept(new SimpleCalculator());
});
}
main();
2.3. Client
In the client program, Controller has been changed from ISimpleCalculator
to ICompositeCalculator
. Also, scopes of target functions have been changed from root scope to composite scope. But as you can see, no problem on the remote function calls.
I named remote function calls to composite scoped objects as "Remote Object Call" and distinguished it from Remote Function Call. However, they're same in the fundamental level. Despite Provider and Controller have been changed from singular object to composite object, Business Logic codes between network system and single program are still the same.
composite-calculator/client.ts
import { WebConnector } from "tgrid/protocols/web";
import { Driver } from "tgrid/components";
import { ICompositeCalculator } from "../../controllers/ICalculator";
async function main(): Promise<void>
{
//----
// CONNECTION
//----
let connector: WebConnector = new WebConnector();
await connector.connect("ws://127.0.0.1:10102");
//----
// CALL FUNCTIONS
//----
// GET DRIVER
let calc: Driver<ICompositeCalculator> = connector.getDriver<ICompositeCalculator>();
// FUNCTIONS IN THE ROOT SCOPE
console.log("1 + 6 =", await calc.plus(1, 6));
console.log("7 * 2 =", await calc.multiplies(7, 2));
// FUNCTIONS IN AN OBJECT (SCIENTIFIC)
console.log("3 ^ 4 =", await calc.scientific.pow(3, 4));
console.log("log (2, 32) =", await calc.scientific.log(2, 32));
try
{
// TO CATCH EXCEPTION IS STILL POSSIBLE
await calc.scientific.sqrt(-4);
}
catch (err)
{
console.log("SQRT (-4) -> Error:", err.message);
}
// FUNCTIONS IN AN OBJECT (STATISTICS)
console.log("Mean (1, 2, 3, 4) =", await calc.statistics.mean(1, 2, 3, 4));
console.log("Stdev. (1, 2, 3, 4) =", await calc.statistics.stdev(1, 2, 3, 4));
//----
// TERMINATE
//----
await connector.close();
}
main();
Remote-Object-Call
import { WebConnector } from "tgrid/protocols/web";
import { Driver } from "tgrid/components";
import { ICompositeCalculator } from "../../controllers/ICalculator";
async function main(): Promise<void>
{
//----
// CONNECTION
//----
let connector: WebConnector = new WebConnector();
await connector.connect("ws://127.0.0.1:10102");
//----
// CALL FUNCTIONS
//----
// GET DRIVER
let calc: Driver<ICompositeCalculator> = connector.getDriver<ICompositeCalculator>();
// FUNCTIONS IN THE ROOT SCOPE
console.log("1 + 6 =", await calc.plus(1, 6));
console.log("7 * 2 =", await calc.multiplies(7, 2));
// FUNCTIONS IN AN OBJECT (SCIENTIFIC)
console.log("3 ^ 4 =", await calc.scientific.pow(3, 4));
console.log("log (2, 32) =", await calc.scientific.log(2, 32));
try
{
// TO CATCH EXCEPTION IS STILL POSSIBLE
await calc.scientific.sqrt(-4);
}
catch (err)
{
console.log("SQRT (-4) -> Error:", err.message);
}
// FUNCTIONS IN AN OBJECT (STATISTICS)
console.log("Mean (1, 2, 3, 4) =", await calc.statistics.mean(1, 2, 3, 4));
console.log("Stdev. (1, 2, 3, 4) =", await calc.statistics.stdev(1, 2, 3, 4));
//----
// TERMINATE
//----
await connector.close();
}
main();
Single-Program
import { CompositeCalculator } from "../../providers/Calculator";
function main(): void
{
//----
// CALL FUNCTIONS
//----
// CONSTRUCT CALCULATOR
let calc: CompositeCalculator = new CompositeCalculator();
// FUNCTIONS IN THE ROOT SCOPE
console.log("1 + 6 =", calc.plus(1, 6));
console.log("7 * 2 =", calc.multiplies(7, 2));
// FUNCTIONS IN AN OBJECT (SCIENTIFIC)
console.log("3 ^ 4 =", calc.scientific.pow(3, 4));
console.log("log (2, 32) =", calc.scientific.log(2, 32));
try
{
// TO CATCH EXCEPTION
calc.scientific.sqrt(-4);
}
catch (err)
{
console.log("SQRT (-4) -> Error:", err.message);
}
// FUNCTIONS IN AN OBJECT (STATISTICS)
console.log("Mean (1, 2, 3, 4) =", calc.statistics.mean(1, 2, 3, 4));
console.log("Stdev. (1, 2, 3, 4) =", calc.statistics.stdev(1, 2, 3, 4));
}
main();
1 + 6 = 7 7 * 2 = 14 3 ^ 4 = 81 log (2, 32) = 5 SQRT (-4) -> Error: Negative value on sqaure. Mean (1, 2, 3, 4) = 2.5 Stdev. (1, 2, 3, 4) = 1.118033988749895
3. Object Oriented Network
In this chapter, we'll re-make the Composite Calculaotr created in the previous chapter 2. Remote Object Call again but with a little bit different; Hierarchical Calculator. Previous CompositeCalculator
has a scientific and statistics calculators as internal member objects. In the new HierarchicalCalculator, we would create in this chapter, those scientific and statistics calculators would be separated into independent servers.
It means that we need split one calculator server into three different servers. At first, we'll create two new servers responsible for scientific
and statistics
calculations. At last, we'll make a mainframe server responsible for 4 arithmetic operations itself and shifting responsibilities to related servers about scientific and statistics operations; calculator
.
After we've built all of the servers, we should make the client program using the Hierarchical Calculator. Would the Business Logic be kept despite of critical change on the network system's structure? Let's demonstrate it.
![]() |
![]() |
---|---|
Composite Calculator | Hierarchical Calculator |
3.1. Features
In this section, Controllers used by mainframe server are IScientific
and ISatistics
. Also, Controller used by the client program is ICompositeCalculator
. Wait a minute! Controller of the client program is exactly same with the previous chapter 2. Remote Object Call. Have you noticed or felt something?
Congratulations if you've understood how to implement the Hierarchical Calculator, just by looking at the Controllers. You've already understood everything about the TGrid.
export interface ICompositeCalculator
extends ISimpleCalculator
{
scientific: IScientific;
statistics: IStatistics;
}
export interface ISimpleCalculator
{
plus(x: number, y: number): number;
minus(x: number, y: number): number;
multiplies(x: number, y: number): number;
divides(x: number, y: number): number;
}
export interface IScientific
{
pow(x: number, y: number): number;
sqrt(x: number): number;
log(x: number, y: number): number;
}
export interface IStatistics
{
mean(...elems: number[]): number;
stdev(...elems: number[]): number;
}
Controller
export interface ICompositeCalculator
extends ISimpleCalculator
{
scientific: IScientific;
statistics: IStatistics;
}
export interface ISimpleCalculator
{
plus(x: number, y: number): number;
minus(x: number, y: number): number;
multiplies(x: number, y: number): number;
divides(x: number, y: number): number;
}
export interface IScientific
{
pow(x: number, y: number): number;
sqrt(x: number): number;
log(x: number, y: number): number;
}
export interface IStatistics
{
mean(...elems: number[]): number;
stdev(...elems: number[]): number;
}
Provider
import { Driver } from "tgrid/components";
import { SimpleCalculator } from "../../providers/Calculator";
import { IScientific, IStatistics } from "../../controllers/ICalculator";
export class HierarchicalCalculator
extends SimpleCalculator
{
// REMOTE CALCULATORS
public scientific: Driver<IScientific>;
public statistics: Driver<IStatistics>;
}
3.2. Servers
hierarchical-calculator/scientific.ts
Let's create scientific calculator server. It seems very easy, isn't it?
import { WorkerServer } from "tgrid/protocols/workers";
import { Scientific } from "../../providers/Calculator";
async function main(): Promise<void>
{
let server: WorkerServer<Scientific> = new WorkerServer();
await server.open(new Scientific());
}
main();
hierarchical-calculator/statistics.ts
Implementing statistics calculator server is also very easy, too.
import { WorkerServer } from "tgrid/protocols/workers";
import { Statistics } from "../../providers/Calculator";
async function main(): Promise<void>
{
let server: WorkerServer<Statistics> = new WorkerServer();
await server.open(new Statistics());
}
main();
hierarchical-calculator/calculator.ts
Now, it's time to create the mainframe server.
Mainframe server computes four arithmetic operations by itself. The scientific and statistics operations are shifted to related servers and the mainframe server only intermediates computation results. It means that mainframe server has implementation code about the four arthmetic operations and scientific and statistics operations are done by Driver<Controller> objects interacting with related servers.
As a result, the HierarchicalCalculator
is same with previous chapter's CompositeCalculator in logically. Although detailed implementation codes of two classes are something different, they have same interface and also provide same features. Now, can't you imagine the client's implementation code, even if you haven't seen the example code?
import { WorkerServer, WorkerConnector } from "tgrid/protocols/workers";
import { Driver } from "tgrid/components";
import { SimpleCalculator } from "../../providers/Calculator";
import { IScientific, IStatistics } from "../../controllers/ICalculator";
class HierarchicalCalculator extends SimpleCalculator
{
// REMOTE CALCULATOR
public scientific!: Driver<IScientific>;
public statistics!: Driver<IStatistics>;
}
async function get<Controller extends object>
(path: string): Promise<Driver<Controller>>
{
// DO CONNECT
let connector = new WorkerConnector();
await connector.connect(__dirname + "/" + path);
// RETURN DRIVER
return connector.getDriver<Controller>();
}
async function main(): Promise<void>
{
// PREPARE REMOTE CALCULATOR
let calc = new HierarchicalCalculator();
calc.scientific = await get<IScientific>("scientific.js");
calc.statistics = await get<IStatistics>("statistics.js");
// OPEN SERVER
let server = new WorkerServer();
await server.open(calc);
}
main();
Object-Oriented-Network
import { WorkerServer, WorkerConnector } from "tgrid/protocols/workers";
import { Driver } from "tgrid/components";
import { SimpleCalculator } from "../../providers/Calculator";
import { IScientific, IStatistics } from "../../controllers/ICalculator";
class HierarchicalCalculator extends SimpleCalculator
{
// REMOTE CALCULATOR
public scientific!: Driver<IScientific>;
public statistics!: Driver<IStatistics>;
}
async function get<Controller extends object>
(path: string): Promise<Driver<Controller>>
{
// DO CONNECT
let connector = new WorkerConnector();
await connector.connect(__dirname + "/" + path);
// RETURN DRIVER
return connector.getDriver<Controller>();
}
async function main(): Promise<void>
{
// PREPARE REMOTE CALCULATOR
let calc = new HierarchicalCalculator();
calc.scientific = await get<IScientific>("scientific.js");
calc.statistics = await get<IStatistics>("statistics.js");
// OPEN SERVER
let server = new WorkerServer();
await server.open(calc);
}
main();
Remote-Object-Call
import { WebServer, WebAcceptor } from "tgrid/protocols/web";
import { CompositeCalculator } from "../../providers/Calculator";
async function main(): Promise<void>
{
let server: WebServer = new WebServer();
await server.open(10102, async (acceptor: WebAcceptor) =>
{
await acceptor.accept(new CompositeCalculator());
});
}
main();
3.3. Client
The Provider class HierarchicalCaluclator is same with previous chapter's CompositeCalculator in logically. Also, they have same interface although their detailed implementation codes are slightly different.
It means that the client program of this chapter can re-use the ICompositeCalculator
, which had been used in the previous chapter 2. Remote Object Call. Controller is same with previous chapter and Business Logic is also same. Therefore, these two client programs would have similar implementation code. Le'ts looko at the below codes and find what is different.
The 11th to 12th lines are slightly different. The server address to connect is different.
This is the TGrid. It's not a matter whether the system you want to imlement is a program that runs on a single computer or a distributed processing system using network communications. Even within the same network system category, it's no problem how its distributed architecture is constructed. Just remember this one.
With TGrid, your Business Logic codes are always similar in any situation.
hierarchical-calculator/index.ts
import { WorkerConnector } from "tgrid/protocols/workers";
import { Driver } from "tgrid/components";
import { ICompositeCalculator } from "../../controllers/ICalculator";
async function main(): Promise<void>
{
//----
// CONNECTION
//----
// DO CONNECT
let connector: WorkerConnector = new WorkerConnector();
await connector.connect(__dirname + "/calculator.js");
//----
// CALL REMOTE FUNCTIONS
//----
// GET DRIVER
let calc: Driver<ICompositeCalculator> = connector.getDriver<ICompositeCalculator>();
// FUNCTIONS IN THE ROOT SCOPE
console.log("1 + 6 =", await calc.plus(1, 6));
console.log("7 * 2 =", await calc.multiplies(7, 2));
// FUNCTIONS IN AN OBJECT (SCIENTIFIC)
console.log("3 ^ 4 =", await calc.scientific.pow(3, 4));
console.log("log (2, 32) =", await calc.scientific.log(2, 32));
try
{
// TO CATCH EXCEPTION IS STILL POSSIBLE
await calc.scientific.sqrt(-4);
}
catch (err)
{
console.log("SQRT (-4) -> Error:", err.message);
}
// FUNCTIONS IN AN OBJECT (STATISTICS)
console.log("Mean (1, 2, 3, 4) =", await calc.statistics.mean(1, 2, 3, 4));
console.log("Stdev. (1, 2, 3, 4) =", await calc.statistics.stdev(1, 2, 3, 4));
//----
// TERMINATE
//----
await connector.close();
}
main().catch(exp =>
{
console.log(exp);
});
Object-Oriented-Network
import { WorkerConnector } from "tgrid/protocols/workers";
import { Driver } from "tgrid/components";
import { ICompositeCalculator } from "../../controllers/ICalculator";
async function main(): Promise<void>
{
//----
// CONNECTION
//----
// DO CONNECT
let connector: WorkerConnector = new WorkerConnector();
await connector.connect(__dirname + "/calculator.js");
//----
// CALL REMOTE FUNCTIONS
//----
// GET DRIVER
let calc: Driver<ICompositeCalculator> = connector.getDriver<ICompositeCalculator>();
// FUNCTIONS IN THE ROOT SCOPE
console.log("1 + 6 =", await calc.plus(1, 6));
console.log("7 * 2 =", await calc.multiplies(7, 2));
// FUNCTIONS IN AN OBJECT (SCIENTIFIC)
console.log("3 ^ 4 =", await calc.scientific.pow(3, 4));
console.log("log (2, 32) =", await calc.scientific.log(2, 32));
try
{
// TO CATCH EXCEPTION IS STILL POSSIBLE
await calc.scientific.sqrt(-4);
}
catch (err)
{
console.log("SQRT (-4) -> Error:", err.message);
}
// FUNCTIONS IN AN OBJECT (STATISTICS)
console.log("Mean (1, 2, 3, 4) =", await calc.statistics.mean(1, 2, 3, 4));
console.log("Stdev. (1, 2, 3, 4) =", await calc.statistics.stdev(1, 2, 3, 4));
//----
// TERMINATE
//----
await connector.close();
}
main().catch(exp =>
{
console.log(exp);
});
Remote-Object-Call
import { WebConnector } from "tgrid/protocols/web";
import { Driver } from "tgrid/components";
import { ICompositeCalculator } from "../../controllers/ICalculator";
async function main(): Promise<void>
{
//----
// CONNECTION
//----
let connector: WebConnector = new WebConnector();
await connector.connect("ws://127.0.0.1:10102");
//----
// CALL FUNCTIONS
//----
// GET DRIVER
let calc: Driver<ICompositeCalculator> = connector.getDriver<ICompositeCalculator>();
// FUNCTIONS IN THE ROOT SCOPE
console.log("1 + 6 =", await calc.plus(1, 6));
console.log("7 * 2 =", await calc.multiplies(7, 2));
// FUNCTIONS IN AN OBJECT (SCIENTIFIC)
console.log("3 ^ 4 =", await calc.scientific.pow(3, 4));
console.log("log (2, 32) =", await calc.scientific.log(2, 32));
try
{
// TO CATCH EXCEPTION IS STILL POSSIBLE
await calc.scientific.sqrt(-4);
}
catch (err)
{
console.log("SQRT (-4) -> Error:", err.message);
}
// FUNCTIONS IN AN OBJECT (STATISTICS)
console.log("Mean (1, 2, 3, 4) =", await calc.statistics.mean(1, 2, 3, 4));
console.log("Stdev. (1, 2, 3, 4) =", await calc.statistics.stdev(1, 2, 3, 4));
//----
// TERMINATE
//----
await connector.close();
}
main();
Single-Program
import { CompositeCalculator } from "../../providers/Calculator";
function main(): void
{
//----
// CALL FUNCTIONS
//----
// CONSTRUCT CALCULATOR
let calc: CompositeCalculator = new CompositeCalculator();
// FUNCTIONS IN THE ROOT SCOPE
console.log("1 + 6 =", calc.plus(1, 6));
console.log("7 * 2 =", calc.multiplies(7, 2));
// FUNCTIONS IN AN OBJECT (SCIENTIFIC)
console.log("3 ^ 4 =", calc.scientific.pow(3, 4));
console.log("log (2, 32) =", calc.scientific.log(2, 32));
try
{
// TO CATCH EXCEPTION
calc.scientific.sqrt(-4);
}
catch (err)
{
console.log("SQRT (-4) -> Error:", err.message);
}
// FUNCTIONS IN AN OBJECT (STATISTICS)
console.log("Mean (1, 2, 3, 4) =", calc.statistics.mean(1, 2, 3, 4));
console.log("Stdev. (1, 2, 3, 4) =", calc.statistics.stdev(1, 2, 3, 4));
}
main();
1 + 6 = 7 7 * 2 = 14 3 ^ 4 = 81 log (2, 32) = 5 SQRT (-4) -> Error: Negative value on sqaure. Mean (1, 2, 3, 4) = 2.5 Stdev. (1, 2, 3, 4) = 1.118033988749895
4. Remote Critical Section
With TGrid, multiple computers using entwork communication can be turned into A single program running on a virtual computer. The example codes I've shown you throughout this lesson are also intended to demonstrate such concept.
However, the the single virtual program doens't mean the single-threaded program. Rather, it is more like a multi-threading (or multi-processing) program in logically. In other words, all of the systems made by TGrid are a type of virtual multi-threading programs.
So, what do you imagine when you hear the word multi-thread? Perhaps most of you thought about the critical sections like mutex or semaphore. Yes, the most important thing for multi-threading program is to controll these critical sections carefully.
Although you succeeded to turn multiple computers using network communications into a single virtual computer through TGrid, it doesn't mean anything if cannot controll those critical sections. Thus, in this chapter, we will learn how to control these critical sections in network system. However, do not worry, it's never difficult. Controlling network level's critical sections are also done by the Remote Function Call.
TSTL
TypeScript Standard Template Library
I strongly recommend a library named TSTL, which supports controlling critical sections through <thread>
module. The classes implemented in the <thread>
module can be used not only in the single program level, but also in the network level.
Mutex
used in this chapter is also imported from the TSTL.
import {
ConditionVariable,
Mutex, TimedMutex,
SharedMutex, SharedTimedMutex,
UniqueLock, SharedLock,
Latch, Barrier, FlexBarrier,
Semaphore
} from "tstl/thread";
4.1. Features
Controller and Provider to be used for controlling remote section are as follows. The client program provides the CriticalSection
object as a Provider. Also, the server program utilizes the remote Provider through Driver<Controller> with ICriticalSection
.
As you can see, the features are very simple. IMutex
would be used to control critical section in the server program. Also, server program would utilize the ICriticalSection.print()
method. Let's take a look what the method is for.
export interface ICriticalSection
{
mutex: IMutex;
print(character: string): void;
}
interface IMutex
{
lock(): void;
unlock(): void;
}
Controller
export interface ICriticalSection
{
mutex: IMutex;
print(character: string): void;
}
interface IMutex
{
lock(): void;
unlock(): void;
}
Provider
import { Mutex } from "tstl/thread/Mutex";
export class CriticalSection
{
public mutex: Mutex = new Mutex();
public print(character: string): void
{
process.stdout.write(str);
}
}
4.2. Client
The client program creates 4 Worker instances and the 4 Worker instances construct 4 WorkerServers
objects. Also, provides CriticalSection
classes to each server as a Provider. After the construction, client program waits until 4 WorkerServer
s to be closed.
As you can see, the actions of the client program is simple. It just provides CriticalSection
object to each server. The important is how each server controls the critical section in network level using the Remote Function Call.
thread/index.ts
import { WorkerConnector } from "tgrid/protocols/workers";
import { Vector } from "tstl/container";
import { Mutex } from "tstl/thread";
// FEATURES TO PROVIDE
namespace provider
{
export var mutex = new Mutex();
export function print(str: string): void
{
process.stdout.write(str);
}
}
async function main(): Promise<void>
{
let workers: Vector<WorkerConnector<typeof provider>> = new Vector();
// CREATE WORKERS
for (let i: number = 0; i < 4; ++i)
{
// CONNECT TO WORKER
let w = new WorkerConnector(provider);
await w.connect(__dirname + "/child.js");
// ENROLL IT
workers.push_back(w);
}
// WAIT THEM TO BE CLOSED
for (let w of workers)
await w.join();
}
main();
4.3. Server
Server program controls the critical section in network level through the Mutex which is provided from the client program.
As you can see, the server programs picks a random character up (line #38). After that, monopolizes critical section for 1 to 2 seconds and prints the picked up character repeatedly, (line #19 to #33).
If the Remote Function Call can control the critical sections even in the network level, the console will repeat the same characters on each line. Otherwise the TGrid and TSTL failed to control the critical sections in network level, the console will print dirty characters.
Now, let's see what happend to the console. The TGrid even can implement the remote critical section?
thread/child.ts
import { WorkerServer } from "tgrid/protocols/workers";
import { Driver } from "tgrid/components";
import { Mutex, sleep_for } from "tstl/thread";
import { randint } from "tstl/algorithm";
interface IController
{
mutex: Mutex;
print(str: string): void;
}
async function main(character: string): Promise<void>
{
// PREPARE SERVER
let server: WorkerServer = new WorkerServer();
await server.open();
// REMOTE FUNCTION CALLS
let driver: Driver<IController> = server.getDriver<IController>();
await driver.mutex.lock();
{
// PRINT LINE WITH CRITICAL SECTION
for (let i: number = 0; i < 20; ++i)
{
await driver.print(character); // PRINT A CHARACTER
await sleep_for(randint(50, 100)); // RANDOM SLEEP
}
await driver.print("\n");
}
await driver.mutex.unlock();
// CLOSE THE SERVER (WORKER)
await server.close();
}
main(randint(0, 9) + "");
11111111111111111111 88888888888888888888 44444444444444444444 33333333333333333333