OpenTNL         Very Simple Interface for OpenTNL

by Brent Rossen Using .net 2003

This tutorial is based on the SimpleEventConnection tutorial at OpenTNL.org, much of the code is identical. A few corrections
and improvements have been made. No Ogre specific calls are made in this file to keep the code as general as possible.

Please, use this forum thread (http://www.ogre3d.org/phpBB2/viewtopic.php?p=156411) to discuss problems, suggestions, etc.

INSTALLATION & USAGE:

Download OpenTNL from: http://www.opentnl.org/download.php (TNL code snapshots), unzip tnl.zip into your Project folder
(should unzip into a folder called "tnl"). Open the tnl solution (tnl/tnl.sln) and compile in both debug and then release mode,
so you have both.

Visual Studio Sections for Linking and Setup

Linker - Input - Additional Dependencies: libtomcryptd.lib tnld.lib wsock32.lib (libtomcrypt.lib and tnl.lib for release mode)

Linker - Input - Ignore Specific Libraries: LIBCD.lib (LIBC.lib for release), this may not be necessary for all applications,
will give compiler error if it is

Linker - General - Additional Library Directories: tnl/lib

C++ - Language - Enable Runtime Type Info - Yes (/GR)

A Few Notes

1) Make sure the interface for SimpleEventConnection is identical on both the server and client
Both must have all methods implemented, but only the client needs to actually perform client
functions and only the server needs server functions, they just both need to call the implement
macro for all methods

2) Enter the correct cmdAddress and port in the startXConnection() method (127.0.0.1:28001 is for running locally,
any other unused port works too)

3) Call startServerConnection() or startClientConnection() to get things started, the server should be started first

4) Call checkMessages() regularly to keep messages going back and forth smoothly

5) All "TODO:" notes are marked as things you should do and depend on your implementation

6) Please note that the server must call the broadcast functions, or find the appropriate
SimpleEventConnection as is done in the broadcast functions to call the client rpc methods

Put this in server.bat file to run in console. Close console window when finished.

SimpleNetTest.exe -server


Put this in client.bat file to run in console. It will close when done running. Navigate to project folder in a console window (Run>"cmd") and type the same if you want the window to stay open.

SimpleNetTest.exe -client


You should receive "Hello Clients (Server)!" back and forth as well as SetAnimation, PlayAudio, and SetObjectPosition calls

A Brief Explanation of the Original Use:

I am a Graduate Researcher at the University of Florida. We are working on an Interpersonal Simulator using ogre and have
a separation between the "Brain" of the on screen avatar and the "Frontend" (OGRE, FMOD, etc). So, the cleanest
way to maintain this separation was to have them communicate through network protocol. That way, if we want to
run the brains and renderer on different machines, or possibly have multiple rendering systems, we have that option.
OpenTNL was my choice for this. This setup is not intended to scale into an MMO! You need to thoroughly review
the OpenTNL documentation for a project of that magnitude. I wish you luck with OGRE and OpenTNL!

SimpleNet.h

#include "../tnl/tnl.h" 
#include "../tnl/tnlEventConnection.h" 
#include "../tnl/tnlNetInterface.h" 
#include "../tnl/tnlRPC.h" 
#include <stdio.h>
#include <string>

using namespace TNL; // make sure we can simply use the TNL classes. 

bool gQuit = false; // a flag used when the client wants to quit. 

class SimpleEventConnection : public EventConnection
{
    typedef EventConnection Parent;
private:
    RefPtr<NetInterface> theNetInterface;
    int messageLimit; //built in limit so the client will quit after 14 messages

public:
    SimpleEventConnection();
    // Let the network system know this is a valid network connection.
    TNL_DECLARE_NETCONNECTION(SimpleEventConnection);

    // declare the client to server message
    TNL_DECLARE_RPC(rpcMessageClientToServer, (StringPtr messageString));

    // declare the server to client message
    TNL_DECLARE_RPC(rpcMessageServerToClient, (StringPtr theMessageString));

    // declare the backend to frontend methods
    TNL_DECLARE_RPC(rpcSetAnimation, (StringPtr animation));
    TNL_DECLARE_RPC(rpcPlayAudio, (StringPtr audioFileName));
    TNL_DECLARE_RPC(rpcSetObjectPosition, (StringPtr name, float x, float y, float z));//items or cameras
    
    void confirmMessageReceipt(const char * message);
    void checkMessages();
    void startClientConnection();
    void startServerConnection();
    
    //server calls
    void rpcBroadcastMessageClients(StringPtr message);
    void rpcBroadcastSetAnimation(StringPtr animation);
    void rpcBroadcastPlayAudio(StringPtr audioFileName);
    void rpcBroadcastSetObjectPosition(StringPtr name, float x, float y, float z);
};

SimpleEventConnection::SimpleEventConnection(){
    messageLimit = 18;
}

//SimpleEventConnection implementations

TNL_IMPLEMENT_NETCONNECTION(SimpleEventConnection, NetClassGroupGame, true);

/**
Client to server message
Note, a ServerToClient method can be called within this ClientToServer method.
The rpcMessageServerToClient will be executed in the server application, and will
automatically be routed to the client connection who called it.
To reiterate, the client who called rpcMessageClientToServer will have rpcMessageServerToClient
called on it, that method will execute on the client side.
This can be a bit confusing at first, but you will get used to it.
**/
TNL_IMPLEMENT_RPC(SimpleEventConnection, rpcMessageClientToServer, 
                  (StringPtr messageString), (messageString),
                  NetClassGroupGameMask, RPCGuaranteedOrdered, RPCDirClientToServer, 0)
{
    // display the message the client sent
    printf("Got message from client: ");
    printf(messageString);

}

/**
The server to client messages
confirmMessageReceipt is used so that you only need to turn it off in one place
All of these server to client rpc functions have a corresponding broadcast function
it is important that you understand why the broadcast is used. If you tried calling these
directly from the newConnection object you would get an error because you need to know
which client to send the message to!
**/
TNL_IMPLEMENT_RPC(SimpleEventConnection, rpcMessageServerToClient, 
                  (StringPtr messageString), (messageString),
                  NetClassGroupGameMask, RPCGuaranteedOrdered, RPCDirServerToClient, 0)
{
    //confirm receipt of server call
    std::string str = "Messsage To Client Received: ";
    str += messageString.getString();
    str += "\n";

    confirmMessageReceipt(str.c_str());

    messageLimit--;
    if(messageLimit <= 0){
        rpcMessageClientToServer("Disconnecting. Good bye!\n");
        gQuit = true;
    }
}

TNL_IMPLEMENT_RPC(SimpleEventConnection, rpcSetAnimation, 
                  (StringPtr animation), (animation),
                  NetClassGroupGameMask, RPCGuaranteedOrdered, RPCDirServerToClient, 0)
{
    //confirm receipt
    std::string str = "Animation Request Received: ";
    str += animation.getString();
    str += "\n";

    confirmMessageReceipt(str.c_str());

    //TODO: play animation
}

TNL_IMPLEMENT_RPC(SimpleEventConnection, rpcPlayAudio, 
                  (StringPtr audioFileName), (audioFileName),
                  NetClassGroupGameMask, RPCGuaranteedOrdered, RPCDirServerToClient, 0)
{
    //confirm receipt
    std::string str = "Audio Request Received: ";
    str += audioFileName.getString();
    str += "\n";

    confirmMessageReceipt(str.c_str());

    //TODO: play audio
}

TNL_IMPLEMENT_RPC(SimpleEventConnection, rpcSetObjectPosition, 
                  (StringPtr objectName, float x, float y, float z), (objectName, x, y, z),
                  NetClassGroupGameMask, RPCGuaranteedOrdered, RPCDirServerToClient, 0)
{
    //confirm receipt
    std::string str = "Position Set Received For: ";
    str += objectName.getString();
    str += "\n";

    confirmMessageReceipt(str.c_str());
    
    //TODO: move objects
}

void SimpleEventConnection::confirmMessageReceipt(const char * message){
    // display the message the server sent
    printf(message);

    //TODO: comment this out when you no longer want message confirmations
    rpcMessageClientToServer(message);
}

void SimpleEventConnection::checkMessages(){
    theNetInterface->checkIncomingPackets();
    theNetInterface->processConnections();
}

void SimpleEventConnection::startClientConnection(){    

    Address cmdAddress("127.0.0.1:28001");

    Address bindAddress(IPProtocol, Address::Any, 0);

    // create a new NetInterface bound to any interface and any port
    theNetInterface = new NetInterface(bindAddress);

    // create a new SimpleEventConnection and tell it to connect to the
    // server at cmdAddress.
    connect(theNetInterface, cmdAddress);
    
    // post an RPC, to be executed when the connection is established
    rpcMessageClientToServer("Client Connection Requested");
}

void SimpleEventConnection::startServerConnection(){
    Address cmdAddress("127.0.0.1:28001");

    // create a server net interface, bound to the cmdAddress
    theNetInterface = new NetInterface(cmdAddress);

    // notify the NetInterface that it can allow connections
    theNetInterface->setAllowsConnections(true);
}

//broadcast functions send the message to all n registered clients
void SimpleEventConnection::rpcBroadcastMessageClients(StringPtr message){
    TNL::Vector<TNL::NetConnection* > con_list = theNetInterface->getConnectionList();
    for(int i = 0; i < con_list.size(); i++){
        SimpleEventConnection *con = (SimpleEventConnection *)con_list[i];
        con->rpcMessageServerToClient(message);
    }
}

void SimpleEventConnection::rpcBroadcastSetAnimation(StringPtr animation){
    TNL::Vector<TNL::NetConnection* > con_list = theNetInterface->getConnectionList();
    for(int i = 0; i < con_list.size(); i++){
        SimpleEventConnection *con = (SimpleEventConnection *)con_list[i];
        con->rpcSetAnimation(animation);
    }
}

void SimpleEventConnection::rpcBroadcastPlayAudio(StringPtr audioFileName){
    TNL::Vector<TNL::NetConnection* > con_list = theNetInterface->getConnectionList();
    for(int i = 0; i < con_list.size(); i++){
        SimpleEventConnection *con = (SimpleEventConnection *)con_list[i];
        con->rpcPlayAudio(audioFileName);
    }
}

void SimpleEventConnection::rpcBroadcastSetObjectPosition(StringPtr name, float x, float y, float z){
    TNL::Vector<TNL::NetConnection* > con_list = theNetInterface->getConnectionList();
    for(int i = 0; i < con_list.size(); i++){
        SimpleEventConnection *con = (SimpleEventConnection *)con_list[i];
        con->rpcSetObjectPosition(name, x, y, z);
    }
}

//runs either the server or client code depending on your call to argv
int main(int argc, const char **argv)
{
    if(argc != 3)
    {
        printf("usage: simpletnltest <-server|-client>");
        return 1;
    }
    bool isClient = !strcmp(argv[1], "-client");//check to see if we want to start a client or server

    SimpleEventConnection *newConnection = new SimpleEventConnection;
    if(isClient)
    {
        //This is your client protocol and should be run in your client application
        newConnection->startClientConnection();

        // post an RPC, to be executed when the connection is established
        newConnection->rpcMessageClientToServer("Client Connection Requested");
    }
    else
    {
        //This is your server protocol and should be run in your server application 
        newConnection->startServerConnection();
    }

    // now just loop, processing incoming packets and sending outgoing packets
    // until the global quit flag is set.
    while(!gQuit)
    {
        if(isClient){
            newConnection->rpcMessageClientToServer("Hello Server!\n");
        }
        else{
            newConnection->rpcBroadcastMessageClients("Hello Clients!");
            newConnection->rpcBroadcastPlayAudio("audio.ogg");
            newConnection->rpcBroadcastSetAnimation("Dance");
            newConnection->rpcBroadcastSetObjectPosition("ogreHead", 0, 200, -300);
        }
        //if you use SimpleEventConnection
        newConnection->checkMessages();
        Platform::sleep(10); //you may not need to sleep if you have a place to call check messages regularly, 
                                       //such as each frame
    }

    return 0;
}