Thread-safe console for Torque 3D
by Fyodor "bank" Osokin · 09/13/2011 (5:01 am) · 6 comments
This resources makes the console sub-system to be thread-safe in Torque 3D.
Same can be applied to other engines (TGEA/T2D).
Engine/source/core/strings/stringFunctions.cpp:
Inside:
Next, StringTable!
Engine/source/core/strings/stringTable.h:
Inside
Engine/source/core/strings/stringTable.cpp:
Change the head of insert() so it look like this (adding lock of mutex):
Open
Engine/source/core/strings/unicode.cpp:
And disable define:
The console itself:
Engine/source/console/console.cpp:
At the top of file, after includes, add:
Fixing _printf:
Add include at top of same file:
Change _printf() from
change from:
After the printf() function, add:
Now, the fixed functions:
Now it's even safe to call the following in a thread:
Thanks to Rene for helping on parts of this!
Same can be applied to other engines (TGEA/T2D).
Engine/source/core/strings/stringFunctions.cpp:
Inside:
S32 dSscanf(const char *buffer, const char *format, ...)change
static void* sVarArgs[20];to:
void* sVarArgs[20];(here we are removing "static", as statics are bad idea to use in threads!).
Next, StringTable!
Engine/source/core/strings/stringTable.h:
Inside
class _StringTable:Find
//..skipped StringTableEntry _EmptyString; protected: static const U32 csm_stInitSize;and add Mutex declaration:
//..skipped StringTableEntry _EmptyString; Mutex mMutex; protected: static const U32 csm_stInitSize;
Engine/source/core/strings/stringTable.cpp:
Change the head of insert() so it look like this (adding lock of mutex):
StringTableEntry _StringTable::insert(const char* _val, const bool caseSens)
{
MutexHandle mutex;
mutex.lock( &mMutex, true );
// Added 3/29/2007 -- If this is undesirable behavior, let me know -patw
const char *val = _val;
if( val == NULL )
val = "";Same for lookup():StringTableEntry _StringTable::lookup(const char* val, const bool caseSens)
{
MutexHandle mutex;
mutex.lock( &mMutex, true );
Node **walk, *temp;
U32 key = hashString(val);and lookupn():StringTableEntry _StringTable::lookupn(const char* val, S32 len, const bool caseSens)
{
MutexHandle mutex;
mutex.lock( &mMutex, true );
Node **walk, *temp;
U32 key = hashStringn(val, len);Open
Engine/source/core/strings/unicode.cpp:
And disable define:
//#define TORQUE_ENABLE_UTF16_CACHEas it doesn't play nice in multi-threaded environment.
The console itself:
Engine/source/console/console.cpp:
At the top of file, after includes, add:
static Mutex* sLogMutex;Add mutex at the init():
void init()
{
AssertFatal(active == false, "Con::init should only be called once.");
// Set up general init values.
active = true;
logFileName = NULL;
newLogFile = true;
gWarnUndefinedScriptVariables = false;
sLogMutex = new Mutex;Don't forget to clean-up:void shutdown()
{
AssertFatal(active == true, "Con::shutdown should only be called once.");
active = false;
smConsoleInput.remove(postConsoleInput);
consoleLogFile.close();
Namespace::shutdown();
AbstractClassRep::shutdown();
Compiler::freeConsoleParserList();
SAFE_DELETE( sLogMutex );
}The actual lock:static void log(const char *string)
{
// Lock.
MutexHandle mutex;
if( sLogMutex )
mutex.lock( sLogMutex, true );
// Bail if we ain't logging.
if (!consoleLogMode)
{
return;
}Fixing _printf:
Add include at top of same file:
#include "console/simEvents.h"
Change _printf() from
static void _printf(ConsoleLogEntry::Level level, ConsoleLogEntry::Type type, const char* fmt, va_list argptr)to:
static void _printf(ConsoleLogEntry::Level level, ConsoleLogEntry::Type type, const char* fmt)Down inside function:
change from:
dVsprintf(buffer + offset, sizeof(buffer) - offset, fmt, argptr);to:
dSprintf(buffer + offset, sizeof(buffer) - offset, "%s", fmt);
After the printf() function, add:
class ConPrinfThreadedEvent : public SimEvent
{
ConsoleLogEntry::Level mLevel;
ConsoleLogEntry::Type mType;
char *mBuf;
public:
ConPrinfThreadedEvent(ConsoleLogEntry::Level level = ConsoleLogEntry::Normal, ConsoleLogEntry::Type type = ConsoleLogEntry::General, const char *buf = NULL)
{
mLevel = level;
mType = type;
if(buf)
{
mBuf = (char*)dMalloc(dStrlen(buf)+1);
dMemcpy((void*)mBuf, (void*)buf, dStrlen(buf));
mBuf[dStrlen(buf)] = 0;
}
else
mBuf = NULL;
}
~ConPrinfThreadedEvent()
{
SAFE_FREE(mBuf);
}
virtual void process(SimObject *object)
{
if(mBuf)
{
switch(mLevel)
{
case ConsoleLogEntry::Normal :
Con::printf(mBuf);
break;
case ConsoleLogEntry::Warning :
Con::warnf(mType, mBuf);
break;
case ConsoleLogEntry::Error :
Con::errorf(mType, mBuf);
break;
case ConsoleLogEntry::Hack :
Con::hackf(mType, mBuf);
break;
}
}
}
};Now, the fixed functions:
void printf(ConsoleLogEntry::Type type, const char* fmt,...)
{
va_list argptr;
va_start(argptr, fmt);
char buf[8192];
dVsprintf(buf, sizeof(buf), fmt, argptr);
if(!ThreadManager::isMainThread())
Sim::postEvent(Sim::getRootGroup(), new ConPrinfThreadedEvent(ConsoleLogEntry::Normal, type, buf), Sim::getTargetTime());
else
_printf(ConsoleLogEntry::Normal, type, buf);
va_end(argptr);
}
void warnf(ConsoleLogEntry::Type type, const char* fmt,...)
{
va_list argptr;
va_start(argptr, fmt);
char buf[8192];
dVsprintf(buf, sizeof(buf), fmt, argptr);
if(!ThreadManager::isMainThread())
Sim::postEvent(Sim::getRootGroup(), new ConPrinfThreadedEvent(ConsoleLogEntry::Warning, type, buf), Sim::getTargetTime());
else
_printf(ConsoleLogEntry::Warning, type, buf);
va_end(argptr);
}
void errorf(ConsoleLogEntry::Type type, const char* fmt,...)
{
va_list argptr;
va_start(argptr, fmt);
char buf[8192];
dVsprintf(buf, sizeof(buf), fmt, argptr);
if(!ThreadManager::isMainThread())
Sim::postEvent(Sim::getRootGroup(), new ConPrinfThreadedEvent(ConsoleLogEntry::Error, type, buf), Sim::getTargetTime());
else
_printf(ConsoleLogEntry::Error, type, buf);
va_end(argptr);
}
void printf(const char* fmt,...)
{
va_list argptr;
va_start(argptr, fmt);
char buf[8192];
dVsprintf(buf, sizeof(buf), fmt, argptr);
if(!ThreadManager::isMainThread())
Sim::postEvent(Sim::getRootGroup(), new ConPrinfThreadedEvent(ConsoleLogEntry::Normal, ConsoleLogEntry::General, buf), Sim::getTargetTime());
else
_printf(ConsoleLogEntry::Normal, ConsoleLogEntry::General, buf);
va_end(argptr);
}
void warnf(const char* fmt,...)
{
va_list argptr;
va_start(argptr, fmt);
char buf[8192];
dVsprintf(buf, sizeof(buf), fmt, argptr);
if(!ThreadManager::isMainThread())
Sim::postEvent(Sim::getRootGroup(), new ConPrinfThreadedEvent(ConsoleLogEntry::Warning, ConsoleLogEntry::General, buf), Sim::getTargetTime());
else
_printf(ConsoleLogEntry::Warning, ConsoleLogEntry::General, buf);
va_end(argptr);
}
void errorf(const char* fmt,...)
{
va_list argptr;
va_start(argptr, fmt);
char buf[8192];
dVsprintf(buf, sizeof(buf), fmt, argptr);
if(!ThreadManager::isMainThread())
Sim::postEvent(Sim::getRootGroup(), new ConPrinfThreadedEvent(ConsoleLogEntry::Error, ConsoleLogEntry::General, buf), Sim::getTargetTime());
else
_printf(ConsoleLogEntry::Error, ConsoleLogEntry::General, buf);
va_end(argptr);
}Now it's even safe to call the following in a thread:
Con::printf("This one is called from a separate thread! Woo-hoo!! It's easier to debug stuff now!");Thanks to Rene for helping on parts of this!
About the author
Game developer.
#2
09/27/2011 (11:17 am)
This is very useful. Thanks, bank, for another great resource.
#3
how can do in TGEA+AFX+MMOKIT?
and I very hope this all can release in T3D 1.2...
09/27/2011 (11:43 pm)
S32 dSscanf(const char *buffer, const char *format, ...)how can do in TGEA+AFX+MMOKIT?
and I very hope this all can release in T3D 1.2...
#4
Two minor corrections (I applied this to a fresh "FPS Example" for Torque 3D 1.1):
stringTable.h/cpp is in Engine/source/core/ not Engine/source/core/strings/
And I needed to add the following to the includes of stringTable.h:
Thanks!
10/02/2011 (6:31 am)
Nice resource!Two minor corrections (I applied this to a fresh "FPS Example" for Torque 3D 1.1):
stringTable.h/cpp is in Engine/source/core/ not Engine/source/core/strings/
And I needed to add the following to the includes of stringTable.h:
#ifndef _PLATFORM_THREADS_MUTEX_H_ #include "platform/threads/mutex.h" #endif
Thanks!
#5
in Torque 1.2
Is this entry still needed somewhere?
11/25/2011 (5:33 pm)
I couldn't get it to compile without commenting outcase ConsoleLogEntry::Hack :
Con::hackf(mType, mBuf);
break;in Torque 1.2
Is this entry still needed somewhere?
#6
12/07/2011 (5:01 pm)
@Dion: I ran into the same problem; I can only assume that's something custom in his engine build...
Associate Konrad Kiss
Bitgap Games