Game Development Community

Asynchronous MySQL++ for TGEA 1.8.x

by Tony Richards · 04/05/2009 (3:10 pm) · 28 comments

This resource is a sequel to my Event Driven Database resource, except written specifically for TGEA 1.8.x. I've continued using mysql++ in this example, but it should not be terribly difficult to modify for use with other databases, or even with other service oriented architectures.
This resource ended up being a lot simpler than what I had originally anticipated, primarily because TGEA now supports script callbacks from threads other than the main render thread.

I started by copying the ProjectsTemplate folder into ProjectsMySQLDemo.

Then I renamed Template.sln to MySQLDemo.sln, and Template.vcproj to MySQLDemo.vcproj. When you open MySQLDemo.sln, you'll have to remove the Template project and add the MySQLDemo project (and rename it as necessary).

Next, download mysql++. I downloaded the source and placed it in ProjectsMySQLDemo/mysql++, and then I added the mysqlpp project to my solution.

In the mysqlpp project properites, under C/C++, Code Generation, make sure you change Runtime Library to match Torque (which is normally Multi-threaded and Multi-threaded Debug).

Next, make sure you add the MySQL header location to C/C++, General, Additional Include Directories. You'll need to do this to both mysqlpp and MySQLDemo projects. Also, add the location of the mysql++ headers to the MySQLDemo project.

Mine looked like this:
../../../mysql++/lib
C:/Program Files/MySQL/MySQL Server 5.0/include

It's best if you go ahead and make these adjustments to Debug, Release and Optimized configurations while it's fresh on your mind.

Also, right-click on MySQLDemo and click on Project Dependencies... and add mysqlpp project as a dependency (actually, add all of the projects as a dependency to the MySQLDemo project).

Next, in main.cpp include "Database.hpp" and add Database::init();.
...

#include "Database.hpp"

...

S32 TorqueMain(S32 argc, const char **argv)
...

   // Initialize the subsystems.
   StandardMainLoop::init();

   // Handle any command line args.
   if(!StandardMainLoop::handleCommandLine(argc, argv))
   {
      Platform::AlertOK("Error", "Failed to initialize game, shutting down.");

      return 1;
   }

   Database::init();

   // Main loop
   while(StandardMainLoop::doMainLoop());

   // Clean everything up.
   StandardMainLoop::shutdown();
...

Now, add these four files:

Database.hpp:
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
// Zen Worlds
//
// Copyright (C) 2001 - 2009 Tony Richards
//
//  This software is provided 'as-is', without any express or implied
//  warranty.  In no event will the authors be held liable for any damages
//  arising from the use of this software.
//
//  Permission is granted to anyone to use this software for any purpose,
//  including commercial applications, and to alter it and redistribute it
//  freely, subject to the following restrictions:
//
//  1. The origin of this software must not be misrepresented; you must not
//     claim that you wrote the original software. If you use this software
//     in a product, an acknowledgment in the product documentation would be
//     appreciated but is not required.
//  2. Altered source versions must be plainly marked as such, and must not be
//     misrepresented as being the original software.
//  3. This notice may not be removed or altered from any source distribution.
//
//  Tony Richards trichards@indiezen.com
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

#ifndef ZEN_WORLDS_ZGARAGE_DATABASE_HPP_INCLUDED
#define ZEN_WORLDS_ZGARAGE_DATABASE_HPP_INCLUDED

#include "core/util/tVector.h"

#include "platform/event.h"

#include "platform/threads/thread.h"

#include <mysql++.h>

#include "RecordSet.hpp"

struct DatabaseRequest
{
   const char*    pSQLString;
   const char*    pQueryCallback;
   RecordSet*     pRecordSet;
};

struct DatabaseReply
{
   const char*    pQueryCallback;
   RecordSet*     pRecordSet;
};

class Database
{
    /// @name 'Structors
    /// @{
public:
            Database();
   virtual ~Database();
    /// @}

    /// @name Static methods
    /// @{
public:
    static void init();
    /// @}

    /// @name Implementation
    /// @{
public:

    /// Place an event in Game's event queue.
    virtual void postEvent(DatabaseRequest* _pEvent);

    /// Static thread startup method.
    /// @note Don't call this; the thread lifetime
    ///          is automatically handled.
    static void processThread(void *udata);
    /// @}

    /// @name Additional implementation
    /// @{
protected:
    /// Main process loop
    void processLoop();

    /// Process a single event
    void processEvent(DatabaseRequest* event);

    /// Process a generic query
   void processRequest(DatabaseRequest* _pRequest);
    /// @}

    /// @name Member Variables
    /// @{
private:

    /// Events are stored here by any thread, for processing by the main thread.
    Vector<DatabaseRequest*> m_eventQueue1, m_eventQueue2, *m_pEventQueue;

    volatile bool   m_shuttingDown;
    Mutex*          m_pEventQueueMutex;
    Semaphore*      m_pNotEmptyEvent;
    Thread*         m_processThread;

    mysqlpp::Connection m_con;
    /// @}

};  // class Database

extern Database* gDatabase;

#endif // ZEN_WORLDS_ZGARAGE_DATABASE_HPP_INCLUDED

Database.cpp
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
// Zen Worlds
//
// Copyright (C) 2001 - 2009 Tony Richards
//
//  This software is provided 'as-is', without any express or implied
//  warranty.  In no event will the authors be held liable for any damages
//  arising from the use of this software.
//
//  Permission is granted to anyone to use this software for any purpose,
//  including commercial applications, and to alter it and redistribute it
//  freely, subject to the following restrictions:
//
//  1. The origin of this software must not be misrepresented; you must not
//     claim that you wrote the original software. If you use this software
//     in a product, an acknowledgment in the product documentation would be
//     appreciated but is not required.
//  2. Altered source versions must be plainly marked as such, and must not be
//     misrepresented as being the original software.
//  3. This notice may not be removed or altered from any source distribution.
//
//  Tony Richards trichards@indiezen.com
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

#include "Database.hpp"

#include "platform/platform.h"
#include "platform/threads/mutex.h"
#include "platform/threads/semaphore.h"

#include "util/messaging/eventManager.h"

#include "console/console.h"

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
Database *gDatabase = NULL;

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
Database::Database()
:  m_pEventQueue(&m_eventQueue1)
,  m_pEventQueueMutex(new Mutex)
,  m_pNotEmptyEvent(new Semaphore)
,  m_shuttingDown(false)
,  m_con(false)
{
   m_processThread = new Thread((ThreadRunFunction)processThread, this, 1);
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
Database::~Database()
{
   // TODO: Shutdown gracefully.  For now everything just quieces correctly
   // anyway, but it'd be nice to do this correctly.
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
Database::init()
{
    gDatabase = new Database();
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
Database::postEvent(DatabaseRequest* _pEvent)
{
   // Only one thread can post at a time.
   Mutex::lockMutex(m_pEventQueueMutex);

   m_pEventQueue->push_back(_pEvent);

   // Assert the condition variable
   m_pNotEmptyEvent->release();

   Mutex::unlockMutex(m_pEventQueueMutex);
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
Database::processEvent(DatabaseRequest* _pEvent)
{
    try
    {
        // First, always ping to attempt to reconnect if we've dropped
        if (!m_con.connected())
        {
            Con::printf("Lost database connection... reconnecting");

            if (!m_con.connect(
                Con::getVariable("Database::database"), 
                Con::getVariable("Database::host"), 
                Con::getVariable("Database::user"), 
                Con::getVariable("Database::password")))
            {
                Con::printf("Unable to reconnect with connect()");
            }
        }

        processRequest(_pEvent);
    }
    catch(...)
    {
        Con::printf("Database thread threw an unhandled exception.");
    }
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void Database::processRequest(DatabaseRequest* _pRequest)
{
    // Create the query
    mysqlpp::Query query = m_con.query();
    query << _pRequest->pSQLString;

    // Free up the memory for this query
    delete _pRequest->pSQLString;

    // Execute the query
    mysqlpp::UseQueryResult res = query.use();

    if (res)
    {
        try
        {
            mysqlpp::Row row;

            // Set the field count
            _pRequest->pRecordSet->setFieldCount(res.num_fields());

            // Loop through all of the columns and set them in the recordset
            for(int x = 0; x < res.num_fields(); x++)
            {
                _pRequest->pRecordSet->setFieldName(x, res.field_name(x).c_str());
            }

            // Loop through all of the rows and set the record field values
            while (row = res.fetch_row()) 
            {
                _pRequest->pRecordSet->addRecord();
                for(int x = 0; x < res.num_fields(); x++)
                {
                    _pRequest->pRecordSet->setFieldValue(x, row.at(x).c_str());
                }
            }
        }
        catch(...)
        {
            // Hard error
            Con::errorf("DatabaseInterface::processEvent exception!");
        }
    }
    else
    {
        // Soft error
        Con::errorf("DatabaseInterface::processEvent error: %s", query.error());
    }

    // Since TGEA supports executef() from threads other than the main thread, this
    // is safe.
    Con::executef(_pRequest->pQueryCallback, _pRequest->pRecordSet->getIdString());
    delete _pRequest->pQueryCallback;

    // This will be done in processLoop, so don't do it here.
    //delete _pRequest;
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
Database::processLoop()
{
    if (!m_con.connect(
        Con::getVariable("Database::database"), 
        Con::getVariable("Database::host"), 
        Con::getVariable("Database::user"), 
        Con::getVariable("Database::password")))
    {
        Con::printf("Error connecting to database!");

        // TODO: Loop a few times before giving up?
        return;
    }

    while(!m_shuttingDown)
    {
        // Wait for the event to be signalled
        m_pNotEmptyEvent->acquire(true);

        m_pEventQueueMutex->lock(true);

        // swap event queue pointers
        Vector<DatabaseRequest*> &fullEventQueue = *m_pEventQueue;
        if(m_pEventQueue == &m_eventQueue1)
            m_pEventQueue = &m_eventQueue2;
        else
            m_pEventQueue = &m_eventQueue1;

        m_pEventQueueMutex->unlock();

        // Keep track of the original size
        const int size = fullEventQueue.size();

        // Walk the event queue in fifo order, processing the events, then clear the queue.
        for(int i=0; i < size; i++)
        {
            processEvent(fullEventQueue[i]);
            dFree(fullEventQueue[i]);
        }
        fullEventQueue.clear();
    }

}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
Database::processThread( void *udata )
{
   Database* pThis = (Database *)udata;
   pThis->processLoop();
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

RecordSet.hpp
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
// Zen Worlds
//
// Copyright (C) 2001 - 2009 Tony Richards
//
//  This software is provided 'as-is', without any express or implied
//  warranty.  In no event will the authors be held liable for any damages
//  arising from the use of this software.
//
//  Permission is granted to anyone to use this software for any purpose,
//  including commercial applications, and to alter it and redistribute it
//  freely, subject to the following restrictions:
//
//  1. The origin of this software must not be misrepresented; you must not
//     claim that you wrote the original software. If you use this software
//     in a product, an acknowledgment in the product documentation would be
//     appreciated but is not required.
//  2. Altered source versions must be plainly marked as such, and must not be
//     misrepresented as being the original software.
//  3. This notice may not be removed or altered from any source distribution.
//
//  Tony Richards trichards@indiezen.com
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

#ifndef ZEN_WORLDS_ZGARAGE_RECORD_SET_HPP_INCLUDED
#define ZEN_WORLDS_ZGARAGE_RECORD_SET_HPP_INCLUDED

#include "console/simBase.h"

#include <vector>
#include <map>
#include <string>
#include <list>

class RecordSet 
:   public SimObject
{
    /// @name Types
    /// @{
private:
    typedef SimObject Parent;
    /// @}

    /// @name 'Structors
    /// @{
public:
             RecordSet();
    virtual ~RecordSet();
    /// @}

    /// @name SimObject implementation
    /// @{
public:
    virtual bool onAdd();
    virtual void onRemove();

    static void initPersistFields();
    /// @}

    /// @name Getter / Setter methods
    /// @{
public:
    void setName(const char* _pName);
    const char* getName();

    void setFieldCount(S32 fieldCount);
    S32 getFieldCount();

    void setFieldName(S32 index, const char* pFieldName);
    const char* getFieldName(S32 index);

    void setFieldValue(S32 index, const char* pValue);
    const char* getFieldValue(S32 index);
    const char* getFieldValue(const char* pFieldName);
    /// @}

    /// @name Record traversal
    /// @{
public:
    void addRecord();
    bool nextRecord();
    /// @}

    /// @name Member variables
    /// @{
private:
    typedef std::vector<std::string>    record_type;
    typedef std::list<record_type>      record_set_type;

    S32 m_queryId;
    S32 m_fieldCount;

    /// Field names kept in order
    std::vector<std::string>            m_fieldNames;

    /// Maps the name of the field to the index
    /// This is slightly inefficient to store the
    /// full copy of the string instead of a reference
    /// since it's already being stored in m_fieldNames,
    /// but I'm lazy and it's really not an important
    /// optimization unless you have a ton of recordsets
    /// hanging around.
    std::map<std::string, S32>          m_fieldNameMap;

    /// Record set
    record_set_type                     m_recordSet;

    bool                                m_firstRecord;
    record_set_type::iterator           m_currentRecord;

    std::string                         m_name;
    /// @}

    /// @name Console Object Stuff
    /// @{
public:
    DECLARE_CONOBJECT(RecordSet);
    /// @}

};  // class RecordSet


#endif // ZEN_WORLDS_ZGARAGE_RECORD_SET_HPP_INCLUDED

And RecordSet.cpp
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
// Zen Worlds
//
// Copyright (C) 2001 - 2009 Tony Richards
//
//  This software is provided 'as-is', without any express or implied
//  warranty.  In no event will the authors be held liable for any damages
//  arising from the use of this software.
//
//  Permission is granted to anyone to use this software for any purpose,
//  including commercial applications, and to alter it and redistribute it
//  freely, subject to the following restrictions:
//
//  1. The origin of this software must not be misrepresented; you must not
//     claim that you wrote the original software. If you use this software
//     in a product, an acknowledgment in the product documentation would be
//     appreciated but is not required.
//  2. Altered source versions must be plainly marked as such, and must not be
//     misrepresented as being the original software.
//  3. This notice may not be removed or altered from any source distribution.
//
//  Tony Richards trichards@indiezen.com
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
#include "RecordSet.hpp"

#include "console/simBase.h"
#include "console/consoleInternal.h"

#include "Database.hpp"

#include <algorithm>
#include <cctype>

IMPLEMENT_CONOBJECT(RecordSet);

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
RecordSet::RecordSet()
:   m_queryId(0)
,   m_fieldCount(0)
,   m_firstRecord(true)
{
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
RecordSet::~RecordSet()
{
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
RecordSet::setName(const char* _pName)
{
    m_name = _pName;
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
const char*
RecordSet::getName()
{
    return m_name.c_str();
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
bool
RecordSet::onAdd()
{
   if (!Parent::onAdd())
      return false;

   const char *name = getName();
   if(name && name[0] && getClassRep())
   {
      Namespace *parent = getClassRep()->getNameSpace();
      Con::linkNamespaces(parent->mName, name);
      mNameSpace = Con::lookupNamespace(name);
   }

   return true;
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
RecordSet::onRemove()
{
   Parent::onRemove();
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
RecordSet::initPersistFields()
{
   Parent::initPersistFields();
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
RecordSet::setFieldCount(S32 fieldCount)
{
   // We don't really need to keep track of this, but we do use it
   // as a hint for the vector size.
   m_fieldCount = fieldCount;
   m_fieldNames.resize(fieldCount);
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
S32
RecordSet::getFieldCount()
{
   return m_fieldCount;
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
RecordSet::setFieldName(S32 index, const char* pFieldName)
{
   std::string tmpFieldName(pFieldName);
   m_fieldNames[index] = tmpFieldName;
   m_fieldNameMap[tmpFieldName] = index;
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
const char*
RecordSet::getFieldName(S32 index)
{
   return m_fieldNames[index].c_str();
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
RecordSet::setFieldValue(S32 index, const char *pValue)
{
   m_recordSet.back()[index] = pValue;
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
const char*
RecordSet::getFieldValue(S32 index)
{
   if (m_firstRecord)
   {
      Con::errorf("Call RecordSet::nextRecord() before calling getFieldValue");
      return "";
   }

   return (*m_currentRecord)[index].c_str();
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
const char*
RecordSet::getFieldValue(const char* pFieldName)
{
   std::string tmpFieldName(pFieldName);
   std::transform(tmpFieldName.begin(), tmpFieldName.end(), tmpFieldName.begin(), 
      (int(*)(int)) std::tolower);

   // TODO: Use find() to see if the field name actually 
   // exists and display an error if it doesn't exist.
   // For now, this will return the value of the first field
   // if the specified field name doesn't exist.
   return getFieldValue(m_fieldNameMap[tmpFieldName]);
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
RecordSet::addRecord()
{
   record_type tmpRecord;
   m_recordSet.push_back(tmpRecord);
   m_recordSet.back().resize(m_fieldCount);
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
bool
RecordSet::nextRecord()
{
   if (m_firstRecord)
   {
      m_firstRecord = false;
      m_currentRecord = m_recordSet.begin();
   }
   else
   {
      ++m_currentRecord;
   }

   return m_currentRecord != m_recordSet.end();
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
ConsoleMethod(RecordSet, getName, const char*, 2, 2, "getName()")
{
   return object->getName();
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
ConsoleMethod(RecordSet, nextRecord, bool, 2, 2, "nextRecord()")
{
   return object->nextRecord();
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
ConsoleMethod(RecordSet, getFieldCount, S32, 2, 2, "getFieldCount()")
{
   return object->getFieldCount();
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
ConsoleMethod(RecordSet, getFieldName, const char*, 3, 3, "getFieldName(%index)")
{
   return object->getFieldName(dAtoi(argv[2]));
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
ConsoleMethod(RecordSet, getFieldValue, const char*, 3, 3, "getFieldValue(%index or %fieldName)")
{
   if (dIsalpha(argv[2][0]))
   {
      return object->getFieldValue(argv[2]);
   }
   else
   {
      return object->getFieldValue(dAtoi(argv[2]));
   }
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
ConsoleFunction(query, void, 3, 3, "query(%sqlStatement, %callback)")
{
   DatabaseRequest* pRequest = new DatabaseRequest;
   pRequest->pRecordSet = static_cast<RecordSet*>(ConsoleObject::create("RecordSet"));
   pRequest->pRecordSet->registerObject();

   pRequest->pSQLString = dStrdup(argv[1]);
   pRequest->pQueryCallback = dStrdup(argv[2]);

   gDatabase->postEvent(pRequest);
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

You might want to do conditional compiles so that this only gets compiled when you're compiling your dedicated server.
#ifdef DEDICATED
.. (mysql code here)
#endif


And finally, in scriptAndAssets/server/init.cs, either in initServer() or initDedicated() (depending on if you want this only for dedicated servers or if you want this for all servers)
function initServer()
...

    exec("./database.cs")
}

And then in that same directory, add database.cs file:
$Database::database  = "test";
$Database::host      = "node1";
$Database::user      = "sgtflame";
$Database::password  = "password";

function testDatabase()
{
   query("Select * from User", testDatabaseCallback);
}

function testDatabaseCallback(%recordSet)
{
   while(%recordSet.nextRecord())
   {
      // Dump all of the fields
      for(%x = 0; %x < %recordSet.getFieldCount(); %x++)
      {
         echo(%recordSet.getFieldName(%x) SPC "=" SPC %recordSet.getFieldValue(%x));
      }

      echo(%recordSet.getFieldValue("firstName") SPC %recordSet.getFieldValue("lastName"));
   }

   // Make sure to clean this up.
   %recordSet.delete();
}

So there you go. Torque + mysql++, including multi-threading asynchronous database requests and a scriptable record set.

Enjoy!

About the author

I am the founder of IndieZen.org, a website dedicated to the Indie 2.0 Revolution where a number of Indie game development studios and individuals collaborate and share a suite of custom built open source game development tools and middleware.

Page«First 1 2 Next»
#21
05/10/2009 (10:09 pm)
I could know exactly which version of mysqlpp you used? With svn can not I ever connect to db, and I get an exception.

The problem is obviously mysql + +. Indeed, the exception is thrown in debug mode when executing the function:

mysql_real_connect (& mysql_, host, user, password, db, port, socket_name, mysql_.client_flag);

First-chance exception at 0x10057072 (libmysql.dll) in eYe_Server.exe: 0xC0000005: Access violation writing location 0x00000000.

Environment:
VS2008 Team System SP1
MySQL 5.0.1
MySQL++ 3.0.9 or svn
#22
05/30/2009 (3:50 pm)
Tony did you test this resource with T3D already?
#23
05/30/2009 (4:03 pm)
Not yet, but it's on my list of things to do :P

I don't expect any problems with it. This version of the resource is less intrusive than the TGE version was, and so probably will work fine.
#24
06/28/2009 (7:32 pm)
I've used mysql as part of web projects for years but have never compiled it as part of a C++ solution before. I downloaded the source but not sure which directories to include as part of the mysqlpp project? Can anyone point me in the right direction here? Can anyone show me a screenshot of their Solution Explorer so I can see which files to include? Thanks in advance...

Peter

Edit: Nevermind - I think I figured it out - looks like I downloaded the wrong source for mysql++ . But if anyone can verify with a link to the latest source files to download - would be appreciated...thanks
#25
06/29/2009 (12:50 pm)
I have solved quite simply using this resource:

www.garagegames.com/community/resource/view/9656
#26
09/28/2009 (12:58 am)
Hello there,

I've just tried following this tutorial on T3D and looks like I've even managed to compile everything. The only difference is that I've added the database related files (database.cpp, database.h etc.) to the Project's DLL, not the main project itself.

But upon running an SQL query (e.g." query("Select * from test1", testDatabaseCallback); ") T3D crashes with the following error:

R6016 - Not enough space for thread data.

The crash occurs on the following line (in recordset.cpp):

pRequest->pSQLString = dStrdup(argv[1]);

What can cause such a behaviour?
Is it because of my implementation via the DLL?

#27
09/28/2009 (2:31 am)
@St.: You want to add all new files to the DLL, not that source folder- I did the same thing with an ODBC resource two weeks ago and wondered why it was mucked up. The DLL is the actual "meat" of T3D.
#28
09/28/2009 (2:54 am)
[EDITED: Please forget what I said, I've found the source of the problem and it was me putting the database initialization function in a comment for a long forgotten purpose]
Page«First 1 2 Next»