Using OgreSocks with Ogre         OgreSocks is an OO wrapper around the winsock dll, enabling you to integrate a networking layer into your Ogre applications with very little code

Introduction

This tutorial will show you how to integrate OgreSocks with Ogre. The current version of OgreSocks is 2.0.1. I am going to assume that you already have Ogre installed on your machine, and compiling and running properly. This tutorial extends Ogre's basic tutorial 5, so if you have not done this then you can do so here. In the basic tutorial 5, you learn how to use buffered input with OIS to move the camera around the screen. After working through this tutorial, you will be able to move the camera around by pressing keys on a remote PC. Please make sure that basic tutorial 5 is building properly, and that you can move the camera around the screen using the keys before you continue.

Before we get started, I should tell you that the current version of OgreSocks is built against version 1.4.8 of Ogre. OgreSocks does depend on Ogre, but it only uses Ogre's String and Exception classes, so if you have a different version of Ogre then chances are it will build against this too. But I have only tested with Ogre 1.4.8. You should also know that OgreSocks is built using Visual Studio.NET 2008. So if you want to use OgreSocks straight 'out of the box', then you will need to be building Ogre in Visual Studio.NET 2008 too. If you are using an earlier version of Visual Studio to build Ogre, then you will have to do a bit of project converting for OgreSocks, but I'm not going to talk about that here. Also, Visual Studio.NET express edition does not come with ATL, which you will need to run OgreSocks. And sorry Linux users, at the moment OgreSocks only runs on windows.

Getting Started

Before we start hammering away at the code, let's get Visual Studio set up correctly. I will also assume that you have already downloaded OgreSocks. If not, what are you waiting for ?! Get it from here. When you download the zip file, you will find a direcrory called OgreSocks_v2.0.1_vs2008 (assuming you have downloaded the latest version). The main project is contained in this folder. Also inside this folder is another folder called 'documentation'. This contains loads of html files, as generated by doxygen. The html files basically contain descriptions of all the public function in OgreSocks, and you can use these as reference should you feel the need. There are quite a few html files in the documentation folder, but all you really need is index.html.

Getting OgreSocks to build is mega easy. Open it up and hit build. Chances are you get a compile error saying:

Error 1 fatal error C1083: Cannot open include file: 'Ogre.h': No such file or directory

To fix this, you need to go to the property pages for the project, and in the C/C++ tab, under General, you need to ammend the Additional Include Directories so that it includes the path to your Ogre files (specifically Ogre.h (!)

Ok, hit build again. This time you've got a linker error:

Error 1 fatal error LNK1104: cannot open file 'OgreMain_d.lib' OgreSocks OgreSocks

To fix this, you need to go back to the property pages for the project, and in the Linker tab, under General, you need to ammend the Additional Library Directories so that it includes the path to the OgreMain_d.lib file (or OgreMain.lib file if you are in release mode).

Hit build again...hurrah ! You should be able to go to to your Debug (or Release) folder in your OgreSocks directory and use the .lib and .dll files you have just built !

Adding OgreSocks to your project

Now that you have built the relevant files, you need to add them to the basic tutorial 5 project. Go to the project's property pages and do the following:

1) Add the path to OgreSocks.h in your Additional Include Directories.
2) Add the path to OgreSocks_d.lib in your Additional Library Directories.
3) Add OgreSocks_d.lib in your Additional Dependencies.
4) Put the OgreSocks_d.dll in the same directory as the .exe for the basic tutorial 5.
5) Finally, add the following line of code at the top of the source file.

#Include "OgreSocks.h"


Make sure this still builds before you continue. OK, let's start adding some code to basic tutorial 5...

Initialising OgreSocks

Before you can use any OgreSocks function, you need to initialise OgreSocks. If you try and call any function without doing this, an exception will be raised. Similarly, you need to un-initialise OgreSocks when your application ends. You initialise OgreSocks via the SocketManager. You never need to create the SocketManager because it is a singleton. All you need to do is include OgreSocks.h in your application and you are good to go (which we have already done).

Let's add a constructor and a destructor to the TutorialApplication class, and we will do the initialising/unitialising of OgreSocks there. Add the following code to the TutorialApplication class:

TutorialApplication()
{
    OgreSocks::OgreSocksManager::GetSingleton()->Initialise();
}

~TutorialApplication()
{
    OgreSocks::OgreSocksManager::GetSingleton()->CleanUp();
}

Creating OgreSockets

Now we have initialised OgreSocks, we can create OgreSockets. OgreSocks allows you to create client and server TCP sockets via the SocketManager. You will never create or delete OgreSockets directly (ie by using 'new' and 'delete') - again you will use the SocketManager to do this.

We will create a server socket. This will allow a client to connect to us and control our camera remotely. Add the following code to the TutorialApplication class:

private:
    OgreSocks::OgreSocksTCPServer* m_ServerSock;


Then ammend the TutorialApplication constructor so that it NULLs the pointer:

TutorialApplication()
{
    OgreSocks::OgreSocksManager::GetSingleton()->Initialise();
    m_ServerSock = NULL;
}


Now we can create the socket. Add the following code to the top of CreateScene:

void createScene(void)
{
    try
    {
        OgreSocks::OgreSocksManager::GetSingleton()->CreateSocket(m_ServerSock, 6789);
    }
    catch(OgreSocks::OgreSocksException& e)
    {
        MessageBox(NULL, "Caught OgreSocks Exeption!", e.getFullDescription().c_str(), MB_OK);
    }


Note that NULL-ing the pointer (as we did in the constructor) is very important because if you do not pass a NULL pointer to the CreateSocket functions an exception will be raised.

The above code creates a server socket listening on port 6789. Notice that we have put a try/catch around the CreateSocket function. Functions in OgreSocks follow the error handling structure of Ogre in that they never return a value. Instead, it uses exceptions (inherited from the Ogre::Exception class) to signify errors. I won't go into to much detail with this just yet, but if you want to know which functions throw which exceptions, take a look at the documentation.

Listening for Events

OgreSocks uses callbacks to signify when a certain event has taken place. For example, you will probably want to know when the socket has just connected, or when it receives data. The callback facility in OgreSocks is quite powerful because usually if you want to use class member functions as callbacks, you either need to store a global pointer to your class (not very OO), and/or create template classes and on top of that write 5 or 6 lines or code per callback. With OgreSocks, you don't need to worry about any of this.

Firstly, we want to know when a client has connected. We will also want to know when the socket has received data. We will probably also want to know when the client has disconnected. Add the following lines of code just below the CreateSocket call:

try
{
    OgreSocks::OgreSocksManager::GetSingleton()->CreateSocket(m_ServerSock, 6789);

    m_ServerSock->SetConnectListener(OgreSocks::MakeConnectCallback(TutorialApplication, ConnectListener));
    m_ServerSock->SetReceiveListener(OgreSocks::MakeReceiveCallback(TutorialApplication, ReceiveListener));
    m_ServerSock->SetDisconnectListener(OgreSocks::MakeDisconnectCallback(TutorialApplication, DisconnectListener));


The SetXXXXXListener functions take the name of the class, and the name of the function within that class, that is to be fired when the relevant event happens. You must wrap the class name and the function name in the MakeXXXXXXCallback functions. Actually, the MakeXXXXXXCallback 'functions' are actually macros that hide alot of messiness.

So, when the OgreSocksTCPServer socket accepts a connection, the ConnectListener function of the TutorialApplication class will be fired. When the OgreSocksTCPServer socket receives data from the client, the ReceiveListener function of the TutorialApplication class will be fired. And, yes you've guessed it, when the client socket disconnects from the OgreSocksTCPServer socket, the DisconnectListener function of the TutorialApplication class will be fired.

But where are these functions? Let's create them ! Add the following code to the TutorialApplication class:

void ConnectListener(Ogre::String addr, unsigned long port, int i)
{
}

void ReceiveListener(std::list<char> data, Ogre::String addr, unsigned long port)
{
}

void DisconnectListener(Ogre::String addr, unsigned long port)
{
}

Make sure this builds at this point. Congratulations, your application is now a server !

Responding to Events

Now that our application is configured to listen for events, we can respond to them. Let's modify the ConnectListener and DisconnectListener functions so that when a client connects and disconnects we know about it. We will create a box that turns a different colour when we are connected. Add the following line of code to the TutorialApplication class to declare our box:

private:
    .
        .
        .
    Entity *m_boxent;


Now, add the following lines of code to the CreateScene function, just above the code that creates the ninja:

//add a box !
m_boxent = mSceneMgr->createEntity("Box", "cube.mesh");
SceneNode *boxnode = mSceneMgr->getRootSceneNode()->createChildSceneNode("BoxNode");
boxnode->attachObject(m_boxent);
boxnode->setScale(Ogre::Vector3(1, 0.2, 1));

// add the ninja
.
.
.


If you run the application now, the box should be white by default, because it has no material assigned to it. So, when a client connects, let's turn the box a different colour. I've chosen yellow, but feel free to change this should you wish. Also, when the client disconnects, we will turn the box back to white. Add the follwing code in bold to the ConnectListener and DisconnectListener functions:

void ConnectListener(Ogre::String addr, unsigned long port, int i)
{
    m_boxent->setMaterialName("Examples/Hilite/Yellow");
}

void ReceiveListener(std::list<char> data, Ogre::String addr, unsigned long port)
{
}

void DisconnectListener(Ogre::String addr, unsigned long port)
{
    m_boxent->setMaterialName("");
}

Sending and Receiving Data

Now were are connected, we can start sending and receiving data. Let's start by receiving data. We have already got our ReceiveListener hooked up, so now we can wait for the client to send us some data, and move our camera. The problem at the moment, though, is that the camera is currently controlled by the mDirection variable in the TutorialFrameListener, and our OgreSocket is in the TutorialApplication class. So we will need to add one or two lines of code so that our TutorialApplication class can access the mDirection in the TutorialFrameListener class. Add the following public functions to the TutorialFrameListener class:

void SetDirection(Vector3 dir)
{
    mDirection = dir;
}

Vector3 GetDirection()
{
    return mDirection;
}


Now we can access the direction of the camera, we will move it in response to whatever the client sends us. To make things easy, we will just use the current controls. so if the client sends a 'W', then we will move the camera forwards, if we receive an 'S' from the client, we will move our camera backwards etc etc. If we receive a string of text, we will process each character in turn. So, add the following code to the ReceiveListener function:

void ReceiveListener(std::list<char> data, Ogre::String addr, unsigned long port)
{
    for(std::list<char>::iterator iter=data.begin(); iter!=data.end(); iter++)
    {
        Vector3 direction = ((TutorialFrameListener*)mFrameListener)->GetDirection();
               
                char c = (*iter);
        switch(c)
        {
        case 'W':
            direction.z = -50;
            break;
        case 'S':
            direction.z = 50;
            break;
        case 'A':
            direction.x = -50;
            break;
        case 'D':
            direction.x = 50;
            break;
        }

        ((TutorialFrameListener*)mFrameListener)->SetDirection(direction);
    }
}


So as you can see, when our OgreSocket receives data from the client, we get the current direction from the TutorialFrameListener, process the characters to determine the new direction, and then re-set the direction on the TutorialFrameListener. We are only moving slowly compared to processing the keys on the local PC, and there is a reason for this that I will explain in a minute.

So we can now receive and process data from the client. How about sending data back ? We will send a simple string of text back to the client to inform it that we have received the data. Modify the ReceiveListener function so that it looks like the following:

void ReceiveListener(std::list<char> data, Ogre::String addr, unsigned long port)
{
    Ogre::String strReply = "";

    for(std::list<char>::iterator iter=data.begin(); iter!=data.end(); iter++)
    {
        Vector3 direction = ((TutorialFrameListener*)mFrameListener)->GetDirection();

        char c = (*iter);
                switch(c)
        {
        case 'W':
            direction.z = -50;
            strReply = "camera moved down";
            break;
        case 'S':
            direction.z = 50;
            strReply = "camera moved up";
            break;
        case 'A':
            direction.x = -50;
            strReply = "camera moved left";
            break;
        case 'D':
            direction.x = 50;
            strReply = "camera moved right";
            break;
        }

        ((TutorialFrameListener*)mFrameListener)->SetDirection(direction);
    }

        std::list<char> replyData;
        for(int i=0;i<strReply.Length(); i++)
        {
           replyData.push_back(strReply[i]);
        } 
    m_ServerSock->Send(replyData);
}


Almost there ! There is just one more thing we need to do and that is stop the camera when the client disconnects. Add the following code in bold to the DisconnectListener:

void DisconnectListener(Ogre::String addr, unsigned long port)
{
    m_boxent->setMaterialName("");

    ((TutorialFrameListener*)mFrameListener)->SetDirection(Vector3(0, 0, 0));
}


And that's it ! You will now be able to get a client and connect to the application. Of course, you can write a client application using OgreSocks, but I will not cover that in this tutorial. For the time being, use something like hyperterminal on a remote PC (which comes with Windows and can be found at Start/All Programs/Accessories/Communications). Connect to the IP address:port of the machine that is running your new application and watch the box turn yellow. Then press a few WSAD keys (note they need to be in CAPS) and watch the camera move around.

Done !

Future Work

Incidently, the reason why I slowed down the camera so much when controlling remotely is because we do not have the 'key up' event to stop the camera, like we do when we are controlling the camera using the local keyboard. Hyperterminal will only send the characters that you type, so you could try implementing a simple client yourself that mimics firing the key up and key down characters.

You could also modify this tutorial so that it is a server AND a client. To do this you will need to create an OgreSockTCPClient. I haven't covered client OgreSockets yet but they are no different in terms of how you create them, set their callbacks, send/receive data etc.

any problems with this tutorial, or the OgreSocks documentation, or if you encounter any bugs, feel free to send me a PM (unclepauly).


Alias: Using_OgreSocks_with_Ogre