Learn from Examples

https://github.com/samchon/tgrid.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.

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.

Something to be aware of

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.

TGrid says

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.

Diagram of Composite Calculator Diagram of Hierarchical Calculator
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.

Have you ever found anything 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.

Recommend a Library

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

Diagram of Remote Critical Section

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 WorkerServers 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

results matching ""

    No results matching ""