mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 04:34:48 +00:00
2748 lines
78 KiB
C++
2748 lines
78 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Copyright (c) 2012 GarageGames, LLC
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "platform/platform.h"
|
|
#include "platform/platformTLS.h"
|
|
#include "platform/threads/thread.h"
|
|
#include "console/console.h"
|
|
#include "console/consoleInternal.h"
|
|
#include "console/consoleObject.h"
|
|
#include "console/consoleParser.h"
|
|
#include "core/stream/fileStream.h"
|
|
#include "console/ast.h"
|
|
#include "core/tAlgorithm.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "console/telnetDebugger.h"
|
|
#include "console/simBase.h"
|
|
#include "console/compiler.h"
|
|
#include "console/stringStack.h"
|
|
#include "console/ICallMethod.h"
|
|
#include "console/engineAPI.h"
|
|
#include <stdarg.h>
|
|
#include "platform/threads/mutex.h"
|
|
#include "core/util/journal/journal.h"
|
|
|
|
extern StringStack STR;
|
|
extern ConsoleValueStack CSTK;
|
|
|
|
ConsoleDocFragment* ConsoleDocFragment::smFirst;
|
|
ExprEvalState gEvalState;
|
|
StmtNode *gStatementList;
|
|
StmtNode *gAnonFunctionList;
|
|
U32 gAnonFunctionID = 0;
|
|
ConsoleConstructor *ConsoleConstructor::mFirst = NULL;
|
|
bool gWarnUndefinedScriptVariables;
|
|
|
|
static char scratchBuffer[4096];
|
|
|
|
CON_DECLARE_PARSER(CMD);
|
|
|
|
static const char * prependDollar ( const char * name )
|
|
{
|
|
if(name[0] != '$')
|
|
{
|
|
S32 len = dStrlen(name);
|
|
AssertFatal(len < sizeof(scratchBuffer)-2, "CONSOLE: name too long");
|
|
scratchBuffer[0] = '$';
|
|
dMemcpy(scratchBuffer + 1, name, len + 1);
|
|
name = scratchBuffer;
|
|
}
|
|
return name;
|
|
}
|
|
|
|
static const char * prependPercent ( const char * name )
|
|
{
|
|
if(name[0] != '%')
|
|
{
|
|
S32 len = dStrlen(name);
|
|
AssertFatal(len < sizeof(scratchBuffer)-2, "CONSOLE: name too long");
|
|
scratchBuffer[0] = '%';
|
|
dMemcpy(scratchBuffer + 1, name, len + 1);
|
|
name = scratchBuffer;
|
|
}
|
|
return name;
|
|
}
|
|
|
|
//--------------------------------------
|
|
void ConsoleConstructor::init( const char *cName, const char *fName, const char *usg, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
|
|
{
|
|
mMina = minArgs;
|
|
mMaxa = maxArgs;
|
|
mFuncName = fName;
|
|
mUsage = usg;
|
|
mClassName = cName;
|
|
mSC = 0; mFC = 0; mVC = 0; mBC = 0; mIC = 0;
|
|
mCallback = mGroup = false;
|
|
mNext = mFirst;
|
|
mNS = false;
|
|
mFirst = this;
|
|
mToolOnly = isToolOnly;
|
|
mHeader = header;
|
|
}
|
|
|
|
void ConsoleConstructor::setup()
|
|
{
|
|
for(ConsoleConstructor *walk = mFirst; walk; walk = walk->mNext)
|
|
{
|
|
#ifdef TORQUE_DEBUG
|
|
walk->validate();
|
|
#endif
|
|
|
|
if( walk->mSC )
|
|
Con::addCommand( walk->mClassName, walk->mFuncName, walk->mSC, walk->mUsage, walk->mMina, walk->mMaxa, walk->mToolOnly, walk->mHeader);
|
|
else if( walk->mIC )
|
|
Con::addCommand( walk->mClassName, walk->mFuncName, walk->mIC, walk->mUsage, walk->mMina, walk->mMaxa, walk->mToolOnly, walk->mHeader);
|
|
else if( walk->mFC )
|
|
Con::addCommand( walk->mClassName, walk->mFuncName, walk->mFC, walk->mUsage, walk->mMina, walk->mMaxa, walk->mToolOnly, walk->mHeader);
|
|
else if( walk->mVC )
|
|
Con::addCommand( walk->mClassName, walk->mFuncName, walk->mVC, walk->mUsage, walk->mMina, walk->mMaxa, walk->mToolOnly, walk->mHeader);
|
|
else if( walk->mBC )
|
|
Con::addCommand( walk->mClassName, walk->mFuncName, walk->mBC, walk->mUsage, walk->mMina, walk->mMaxa, walk->mToolOnly, walk->mHeader);
|
|
else if( walk->mGroup )
|
|
Con::markCommandGroup( walk->mClassName, walk->mFuncName, walk->mUsage);
|
|
else if( walk->mClassName)
|
|
Con::noteScriptCallback( walk->mClassName, walk->mFuncName, walk->mUsage, walk->mHeader);
|
|
else if( walk->mNS )
|
|
{
|
|
Namespace* ns = Namespace::find( StringTable->insert( walk->mClassName) );
|
|
if( ns )
|
|
ns->mUsage = walk->mUsage;
|
|
}
|
|
else
|
|
{
|
|
AssertISV( false, "Found a ConsoleConstructor with an indeterminate type!" );
|
|
}
|
|
}
|
|
}
|
|
|
|
ConsoleConstructor::ConsoleConstructor(const char *className, const char *funcName, StringCallback sfunc, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
|
|
{
|
|
init( className, funcName, usage, minArgs, maxArgs, isToolOnly, header );
|
|
mSC = sfunc;
|
|
}
|
|
|
|
ConsoleConstructor::ConsoleConstructor(const char *className, const char *funcName, IntCallback ifunc, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
|
|
{
|
|
init( className, funcName, usage, minArgs, maxArgs, isToolOnly, header );
|
|
mIC = ifunc;
|
|
}
|
|
|
|
ConsoleConstructor::ConsoleConstructor(const char *className, const char *funcName, FloatCallback ffunc, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
|
|
{
|
|
init( className, funcName, usage, minArgs, maxArgs, isToolOnly, header );
|
|
mFC = ffunc;
|
|
}
|
|
|
|
ConsoleConstructor::ConsoleConstructor(const char *className, const char *funcName, VoidCallback vfunc, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
|
|
{
|
|
init( className, funcName, usage, minArgs, maxArgs, isToolOnly, header );
|
|
mVC = vfunc;
|
|
}
|
|
|
|
ConsoleConstructor::ConsoleConstructor(const char *className, const char *funcName, BoolCallback bfunc, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
|
|
{
|
|
init( className, funcName, usage, minArgs, maxArgs, isToolOnly, header );
|
|
mBC = bfunc;
|
|
}
|
|
|
|
ConsoleConstructor::ConsoleConstructor(const char* className, const char* groupName, const char* aUsage)
|
|
{
|
|
init(className, groupName, mUsage, -1, -2);
|
|
|
|
mGroup = true;
|
|
|
|
// Somewhere, the entry list is getting flipped, partially.
|
|
// so we have to do tricks to deal with making sure usage
|
|
// is properly populated.
|
|
|
|
// This is probably redundant.
|
|
static char * lastUsage = NULL;
|
|
if(aUsage)
|
|
lastUsage = (char *)aUsage;
|
|
|
|
mUsage = lastUsage;
|
|
}
|
|
|
|
ConsoleConstructor::ConsoleConstructor(const char *className, const char *callbackName, const char *usage, ConsoleFunctionHeader* header )
|
|
{
|
|
init( className, callbackName, usage, -2, -3, false, header );
|
|
mCallback = true;
|
|
mNS = true;
|
|
}
|
|
|
|
void ConsoleConstructor::validate()
|
|
{
|
|
#ifdef TORQUE_DEBUG
|
|
// Don't do the following check if we're not a method/func.
|
|
if(mGroup)
|
|
return;
|
|
|
|
// In debug, walk the list and make sure this isn't a duplicate.
|
|
for(ConsoleConstructor *walk = mFirst; walk; walk = walk->mNext)
|
|
{
|
|
// Skip mismatching func/method names.
|
|
if(dStricmp(walk->mFuncName, mFuncName))
|
|
continue;
|
|
|
|
// Don't compare functions with methods or vice versa.
|
|
if(bool(mClassName) != bool(walk->mClassName))
|
|
continue;
|
|
|
|
// Skip mismatching classnames, if they're present.
|
|
if(mClassName && walk->mClassName && dStricmp(walk->mClassName, mClassName))
|
|
continue;
|
|
|
|
// If we encounter ourselves, stop searching; this prevents duplicate
|
|
// firing of the assert, instead only firing for the latter encountered
|
|
// entry.
|
|
if(this == walk)
|
|
break;
|
|
|
|
// Match!
|
|
if(mClassName)
|
|
{
|
|
AssertISV(false, avar("ConsoleConstructor::setup - ConsoleMethod '%s::%s' collides with another of the same name.", mClassName, mFuncName));
|
|
}
|
|
else
|
|
{
|
|
AssertISV(false, avar("ConsoleConstructor::setup - ConsoleFunction '%s' collides with another of the same name.", mFuncName));
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// We comment out the implementation of the Con namespace when doxygenizing because
|
|
// otherwise Doxygen decides to ignore our docs in console.h
|
|
#ifndef DOXYGENIZING
|
|
|
|
namespace Con
|
|
{
|
|
|
|
static Vector<ConsumerCallback> gConsumers(__FILE__, __LINE__);
|
|
static Vector< String > sInstantGroupStack( __FILE__, __LINE__ );
|
|
static DataChunker consoleLogChunker;
|
|
static Vector<ConsoleLogEntry> consoleLog(__FILE__, __LINE__);
|
|
static bool consoleLogLocked;
|
|
static bool logBufferEnabled=true;
|
|
static S32 printLevel = 10;
|
|
static FileStream consoleLogFile;
|
|
static const char *defLogFileName = "console.log";
|
|
static S32 consoleLogMode = 0;
|
|
static bool active = false;
|
|
static bool newLogFile;
|
|
static const char *logFileName;
|
|
|
|
static const S32 MaxCompletionBufferSize = 4096;
|
|
static char completionBuffer[MaxCompletionBufferSize];
|
|
static char tabBuffer[MaxCompletionBufferSize] = {0};
|
|
static SimObjectPtr<SimObject> tabObject;
|
|
static U32 completionBaseStart;
|
|
static U32 completionBaseLen;
|
|
|
|
String gInstantGroup;
|
|
Con::ConsoleInputEvent smConsoleInput;
|
|
|
|
/// Current script file name and root, these are registered as
|
|
/// console variables.
|
|
/// @{
|
|
|
|
///
|
|
StringTableEntry gCurrentFile;
|
|
StringTableEntry gCurrentRoot;
|
|
/// @}
|
|
|
|
S32 gObjectCopyFailures = -1;
|
|
|
|
bool alwaysUseDebugOutput = true;
|
|
bool useTimestamp = false;
|
|
|
|
ConsoleFunctionGroupBegin( Clipboard, "Miscellaneous functions to control the clipboard and clear the console.");
|
|
|
|
DefineConsoleFunction( cls, void, (), , "()"
|
|
"@brief Clears the console output.\n\n"
|
|
"@ingroup Console")
|
|
{
|
|
if(consoleLogLocked)
|
|
return;
|
|
consoleLogChunker.freeBlocks();
|
|
consoleLog.setSize(0);
|
|
};
|
|
|
|
DefineConsoleFunction( getClipboard, const char*, (), , "()"
|
|
"@brief Get text from the clipboard.\n\n"
|
|
"@internal")
|
|
{
|
|
return Platform::getClipboard();
|
|
};
|
|
|
|
DefineConsoleFunction( setClipboard, bool, (const char* text), , "(string text)"
|
|
"@brief Set the system clipboard.\n\n"
|
|
"@internal")
|
|
{
|
|
return Platform::setClipboard(text);
|
|
};
|
|
|
|
ConsoleFunctionGroupEnd( Clipboard );
|
|
|
|
|
|
void postConsoleInput( RawData data );
|
|
|
|
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;
|
|
|
|
// Initialize subsystems.
|
|
Namespace::init();
|
|
ConsoleConstructor::setup();
|
|
|
|
// Set up the parser(s)
|
|
CON_ADD_PARSER(CMD, "cs", true); // TorqueScript
|
|
|
|
// Setup the console types.
|
|
ConsoleBaseType::initialize();
|
|
|
|
// And finally, the ACR...
|
|
AbstractClassRep::initialize();
|
|
|
|
// Variables
|
|
setVariable("Con::prompt", "% ");
|
|
addVariable("Con::logBufferEnabled", TypeBool, &logBufferEnabled, "If true, the log buffer will be enabled.\n"
|
|
"@ingroup Console\n");
|
|
addVariable("Con::printLevel", TypeS32, &printLevel,
|
|
"@brief This is deprecated.\n\n"
|
|
"It is no longer in use and does nothing.\n"
|
|
"@ingroup Console\n");
|
|
addVariable("Con::warnUndefinedVariables", TypeBool, &gWarnUndefinedScriptVariables, "If true, a warning will be displayed in the console whenever a undefined variable is used in script.\n"
|
|
"@ingroup Console\n");
|
|
addVariable( "instantGroup", TypeRealString, &gInstantGroup, "The group that objects will be added to when they are created.\n"
|
|
"@ingroup Console\n");
|
|
|
|
addVariable("Con::objectCopyFailures", TypeS32, &gObjectCopyFailures, "If greater than zero then it counts the number of object creation "
|
|
"failures based on a missing copy object and does not report an error..\n"
|
|
"@ingroup Console\n");
|
|
|
|
// Current script file name and root
|
|
addVariable( "Con::File", TypeString, &gCurrentFile, "The currently executing script file.\n"
|
|
"@ingroup FileSystem\n");
|
|
addVariable( "Con::Root", TypeString, &gCurrentRoot, "The mod folder for the currently executing script file.\n"
|
|
"@ingroup FileSystem\n" );
|
|
|
|
// alwaysUseDebugOutput determines whether to send output to the platform's
|
|
// "debug" system. see winConsole for an example.
|
|
// in ship builds we don't expose this variable to script
|
|
// and we set it to false by default (don't want to provide more information
|
|
// to potential hackers). platform code should also ifdef out the code that
|
|
// pays attention to this in ship builds (see winConsole.cpp)
|
|
// note that enabling this can slow down your game
|
|
// if you are running from the debugger and printing a lot of console messages.
|
|
#ifndef TORQUE_SHIPPING
|
|
addVariable("Con::alwaysUseDebugOutput", TypeBool, &alwaysUseDebugOutput,
|
|
"@brief Determines whether to send output to the platform's \"debug\" system.\n\n"
|
|
"@note This is disabled in shipping builds.\n"
|
|
"@ingroup Console");
|
|
#else
|
|
alwaysUseDebugOutput = false;
|
|
#endif
|
|
|
|
// controls whether a timestamp is prepended to every console message
|
|
addVariable("Con::useTimestamp", TypeBool, &useTimestamp, "If true a timestamp is prepended to every console message.\n"
|
|
"@ingroup Console\n");
|
|
|
|
// Plug us into the journaled console input signal.
|
|
smConsoleInput.notify(postConsoleInput);
|
|
}
|
|
|
|
//--------------------------------------
|
|
|
|
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();
|
|
}
|
|
|
|
bool isActive()
|
|
{
|
|
return active;
|
|
}
|
|
|
|
bool isMainThread()
|
|
{
|
|
#ifdef TORQUE_MULTITHREAD
|
|
return ThreadManager::isMainThread();
|
|
#else
|
|
// If we're single threaded we're always in the main thread.
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
//--------------------------------------
|
|
|
|
void getLockLog(ConsoleLogEntry *&log, U32 &size)
|
|
{
|
|
consoleLogLocked = true;
|
|
log = consoleLog.address();
|
|
size = consoleLog.size();
|
|
}
|
|
|
|
void unlockLog()
|
|
{
|
|
consoleLogLocked = false;
|
|
}
|
|
|
|
U32 tabComplete(char* inputBuffer, U32 cursorPos, U32 maxResultLength, bool forwardTab)
|
|
{
|
|
// Check for null input.
|
|
if (!inputBuffer[0])
|
|
{
|
|
return cursorPos;
|
|
}
|
|
|
|
// Cap the max result length.
|
|
if (maxResultLength > MaxCompletionBufferSize)
|
|
{
|
|
maxResultLength = MaxCompletionBufferSize;
|
|
}
|
|
|
|
// See if this is the same partial text as last checked.
|
|
if (dStrcmp(tabBuffer, inputBuffer))
|
|
{
|
|
// If not...
|
|
// Save it for checking next time.
|
|
dStrcpy(tabBuffer, inputBuffer, MaxCompletionBufferSize);
|
|
// Scan backward from the cursor position to find the base to complete from.
|
|
S32 p = cursorPos;
|
|
while ((p > 0) && (inputBuffer[p - 1] != ' ') && (inputBuffer[p - 1] != '.') && (inputBuffer[p - 1] != '('))
|
|
{
|
|
p--;
|
|
}
|
|
completionBaseStart = p;
|
|
completionBaseLen = cursorPos - p;
|
|
// Is this function being invoked on an object?
|
|
if (inputBuffer[p - 1] == '.')
|
|
{
|
|
// If so...
|
|
if (p <= 1)
|
|
{
|
|
// Bail if no object identifier.
|
|
return cursorPos;
|
|
}
|
|
|
|
// Find the object identifier.
|
|
S32 objLast = --p;
|
|
while ((p > 0) && (inputBuffer[p - 1] != ' ') && (inputBuffer[p - 1] != '('))
|
|
{
|
|
p--;
|
|
}
|
|
|
|
if (objLast == p)
|
|
{
|
|
// Bail if no object identifier.
|
|
return cursorPos;
|
|
}
|
|
|
|
// Look up the object identifier.
|
|
dStrncpy(completionBuffer, inputBuffer + p, objLast - p);
|
|
completionBuffer[objLast - p] = 0;
|
|
tabObject = Sim::findObject(completionBuffer);
|
|
if (tabObject == NULL)
|
|
{
|
|
// Bail if not found.
|
|
return cursorPos;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Not invoked on an object; we'll use the global namespace.
|
|
tabObject = 0;
|
|
}
|
|
}
|
|
|
|
// Chop off the input text at the cursor position.
|
|
inputBuffer[cursorPos] = 0;
|
|
|
|
// Try to find a completion in the appropriate namespace.
|
|
const char *newText;
|
|
|
|
if (tabObject != 0)
|
|
{
|
|
newText = tabObject->tabComplete(inputBuffer + completionBaseStart, completionBaseLen, forwardTab);
|
|
}
|
|
else
|
|
{
|
|
// In the global namespace, we can complete on global vars as well as functions.
|
|
if (inputBuffer[completionBaseStart] == '$')
|
|
{
|
|
newText = gEvalState.globalVars.tabComplete(inputBuffer + completionBaseStart, completionBaseLen, forwardTab);
|
|
}
|
|
else
|
|
{
|
|
newText = Namespace::global()->tabComplete(inputBuffer + completionBaseStart, completionBaseLen, forwardTab);
|
|
}
|
|
}
|
|
|
|
if (newText)
|
|
{
|
|
// If we got something, append it to the input text.
|
|
S32 len = dStrlen(newText);
|
|
if (len + completionBaseStart > maxResultLength)
|
|
{
|
|
len = maxResultLength - completionBaseStart;
|
|
}
|
|
dStrncpy(inputBuffer + completionBaseStart, newText, len);
|
|
inputBuffer[completionBaseStart + len] = 0;
|
|
// And set the cursor after it.
|
|
cursorPos = completionBaseStart + len;
|
|
}
|
|
|
|
// Save the modified input buffer for checking next time.
|
|
dStrcpy(tabBuffer, inputBuffer, MaxCompletionBufferSize);
|
|
|
|
// Return the new (maybe) cursor position.
|
|
return cursorPos;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
static void log(const char *string)
|
|
{
|
|
// Bail if we ain't logging.
|
|
if (!consoleLogMode)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// In mode 1, we open, append, close on each log write.
|
|
if ((consoleLogMode & 0x3) == 1)
|
|
{
|
|
consoleLogFile.open(defLogFileName, Torque::FS::File::ReadWrite);
|
|
}
|
|
|
|
// Write to the log if its status is hunky-dory.
|
|
if ((consoleLogFile.getStatus() == Stream::Ok) || (consoleLogFile.getStatus() == Stream::EOS))
|
|
{
|
|
consoleLogFile.setPosition(consoleLogFile.getStreamSize());
|
|
// If this is the first write...
|
|
if (newLogFile)
|
|
{
|
|
// Make a header.
|
|
Platform::LocalTime lt;
|
|
Platform::getLocalTime(lt);
|
|
char buffer[128];
|
|
dSprintf(buffer, sizeof(buffer), "//-------------------------- %d/%d/%d -- %02d:%02d:%02d -----\r\n",
|
|
lt.month + 1,
|
|
lt.monthday,
|
|
lt.year + 1900,
|
|
lt.hour,
|
|
lt.min,
|
|
lt.sec);
|
|
consoleLogFile.write(dStrlen(buffer), buffer);
|
|
newLogFile = false;
|
|
if (consoleLogMode & 0x4)
|
|
{
|
|
// Dump anything that has been printed to the console so far.
|
|
consoleLogMode -= 0x4;
|
|
U32 size, line;
|
|
ConsoleLogEntry *log;
|
|
getLockLog(log, size);
|
|
for (line = 0; line < size; line++)
|
|
{
|
|
consoleLogFile.write(dStrlen(log[line].mString), log[line].mString);
|
|
consoleLogFile.write(2, "\r\n");
|
|
}
|
|
unlockLog();
|
|
}
|
|
}
|
|
// Now write what we came here to write.
|
|
consoleLogFile.write(dStrlen(string), string);
|
|
consoleLogFile.write(2, "\r\n");
|
|
}
|
|
|
|
if ((consoleLogMode & 0x3) == 1)
|
|
{
|
|
consoleLogFile.close();
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static void _printf(ConsoleLogEntry::Level level, ConsoleLogEntry::Type type, const char* fmt, va_list argptr)
|
|
{
|
|
if (!active)
|
|
return;
|
|
Con::active = false;
|
|
|
|
char buffer[8192];
|
|
U32 offset = 0;
|
|
if( gEvalState.traceOn && gEvalState.getStackDepth() > 0 )
|
|
{
|
|
offset = gEvalState.getStackDepth() * 3;
|
|
for(U32 i = 0; i < offset; i++)
|
|
buffer[i] = ' ';
|
|
}
|
|
|
|
if (useTimestamp)
|
|
{
|
|
static U32 startTime = Platform::getRealMilliseconds();
|
|
U32 curTime = Platform::getRealMilliseconds() - startTime;
|
|
offset += dSprintf(buffer + offset, sizeof(buffer) - offset, "[+%4d.%03d]", U32(curTime * 0.001), curTime % 1000);
|
|
}
|
|
dVsprintf(buffer + offset, sizeof(buffer) - offset, fmt, argptr);
|
|
|
|
for(S32 i = 0; i < gConsumers.size(); i++)
|
|
gConsumers[i](level, buffer);
|
|
|
|
if(logBufferEnabled || consoleLogMode)
|
|
{
|
|
char *pos = buffer;
|
|
while(*pos)
|
|
{
|
|
if(*pos == '\t')
|
|
*pos = '^';
|
|
pos++;
|
|
}
|
|
pos = buffer;
|
|
|
|
for(;;)
|
|
{
|
|
char *eofPos = dStrchr(pos, '\n');
|
|
if(eofPos)
|
|
*eofPos = 0;
|
|
|
|
log(pos);
|
|
if(logBufferEnabled && !consoleLogLocked)
|
|
{
|
|
ConsoleLogEntry entry;
|
|
entry.mLevel = level;
|
|
entry.mType = type;
|
|
#ifndef TORQUE_SHIPPING // this is equivalent to a memory leak, turn it off in ship build
|
|
dsize_t logStringLen = dStrlen(pos) + 1;
|
|
entry.mString = (const char *)consoleLogChunker.alloc(logStringLen);
|
|
dStrcpy(const_cast<char*>(entry.mString), pos, logStringLen);
|
|
|
|
// This prevents infinite recursion if the console itself needs to
|
|
// re-allocate memory to accommodate the new console log entry, and
|
|
// LOG_PAGE_ALLOCS is defined. It is kind of a dirty hack, but the
|
|
// uses for LOG_PAGE_ALLOCS are limited, and it is not worth writing
|
|
// a lot of special case code to support this situation. -patw
|
|
const bool save = Con::active;
|
|
Con::active = false;
|
|
consoleLog.push_back(entry);
|
|
Con::active = save;
|
|
#endif
|
|
}
|
|
if(!eofPos)
|
|
break;
|
|
pos = eofPos + 1;
|
|
}
|
|
}
|
|
|
|
Con::active = true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void printf(const char* fmt,...)
|
|
{
|
|
va_list argptr;
|
|
va_start(argptr, fmt);
|
|
_printf(ConsoleLogEntry::Normal, ConsoleLogEntry::General, fmt, argptr);
|
|
va_end(argptr);
|
|
}
|
|
|
|
void warnf(ConsoleLogEntry::Type type, const char* fmt,...)
|
|
{
|
|
va_list argptr;
|
|
va_start(argptr, fmt);
|
|
_printf(ConsoleLogEntry::Warning, type, fmt, argptr);
|
|
va_end(argptr);
|
|
}
|
|
|
|
void errorf(ConsoleLogEntry::Type type, const char* fmt,...)
|
|
{
|
|
va_list argptr;
|
|
va_start(argptr, fmt);
|
|
_printf(ConsoleLogEntry::Error, type, fmt, argptr);
|
|
va_end(argptr);
|
|
}
|
|
|
|
void warnf(const char* fmt,...)
|
|
{
|
|
va_list argptr;
|
|
va_start(argptr, fmt);
|
|
_printf(ConsoleLogEntry::Warning, ConsoleLogEntry::General, fmt, argptr);
|
|
va_end(argptr);
|
|
}
|
|
|
|
void errorf(const char* fmt,...)
|
|
{
|
|
va_list argptr;
|
|
va_start(argptr, fmt);
|
|
_printf(ConsoleLogEntry::Error, ConsoleLogEntry::General, fmt, argptr);
|
|
va_end(argptr);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool getVariableObjectField(const char *name, SimObject **object, const char **field)
|
|
{
|
|
// get the field info from the object..
|
|
const char *dot = dStrchr(name, '.');
|
|
if(name[0] != '$' && dot)
|
|
{
|
|
S32 len = dStrlen(name);
|
|
AssertFatal(len < sizeof(scratchBuffer)-1, "Sim::getVariable - name too long");
|
|
dMemcpy(scratchBuffer, name, len+1);
|
|
|
|
char * token = dStrtok(scratchBuffer, ".");
|
|
SimObject * obj = Sim::findObject(token);
|
|
if(!obj)
|
|
return false;
|
|
|
|
token = dStrtok(0, ".\0");
|
|
if(!token)
|
|
return false;
|
|
|
|
while(token != NULL)
|
|
{
|
|
const char * val = obj->getDataField(StringTable->insert(token), 0);
|
|
if(!val)
|
|
return false;
|
|
|
|
char *fieldToken = token;
|
|
token = dStrtok(0, ".\0");
|
|
if(token)
|
|
{
|
|
obj = Sim::findObject(token);
|
|
if(!obj)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
*object = obj;
|
|
*field = fieldToken;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Dictionary::Entry *getLocalVariableEntry(const char *name)
|
|
{
|
|
name = prependPercent(name);
|
|
return gEvalState.getCurrentFrame().lookup(StringTable->insert(name));
|
|
}
|
|
|
|
Dictionary::Entry *getVariableEntry(const char *name)
|
|
{
|
|
name = prependDollar(name);
|
|
return gEvalState.globalVars.lookup(StringTable->insert(name));
|
|
}
|
|
|
|
Dictionary::Entry *addVariableEntry(const char *name)
|
|
{
|
|
name = prependDollar(name);
|
|
return gEvalState.globalVars.add(StringTable->insert(name));
|
|
}
|
|
|
|
Dictionary::Entry *getAddVariableEntry(const char *name)
|
|
{
|
|
name = prependDollar(name);
|
|
StringTableEntry stName = StringTable->insert(name);
|
|
Dictionary::Entry *entry = gEvalState.globalVars.lookup(stName);
|
|
if (!entry)
|
|
entry = gEvalState.globalVars.add(stName);
|
|
return entry;
|
|
}
|
|
|
|
Dictionary::Entry *getAddLocalVariableEntry(const char *name)
|
|
{
|
|
name = prependPercent(name);
|
|
StringTableEntry stName = StringTable->insert(name);
|
|
Dictionary::Entry *entry = gEvalState.getCurrentFrame().lookup(stName);
|
|
if (!entry)
|
|
entry = gEvalState.getCurrentFrame().add(stName);
|
|
return entry;
|
|
}
|
|
|
|
void setVariable(const char *name, const char *value)
|
|
{
|
|
SimObject *obj = NULL;
|
|
const char *objField = NULL;
|
|
|
|
if (getVariableObjectField(name, &obj, &objField))
|
|
{
|
|
obj->setDataField(StringTable->insert(objField), 0, value);
|
|
}
|
|
else
|
|
{
|
|
name = prependDollar(name);
|
|
gEvalState.globalVars.setVariable(StringTable->insert(name), value);
|
|
}
|
|
}
|
|
|
|
void setLocalVariable(const char *name, const char *value)
|
|
{
|
|
name = prependPercent(name);
|
|
gEvalState.getCurrentFrame().setVariable(StringTable->insert(name), value);
|
|
}
|
|
|
|
void setBoolVariable(const char *varName, bool value)
|
|
{
|
|
SimObject *obj = NULL;
|
|
const char *objField = NULL;
|
|
|
|
if (getVariableObjectField(varName, &obj, &objField))
|
|
{
|
|
obj->setDataField(StringTable->insert(objField), 0, value ? "1" : "0");
|
|
}
|
|
else
|
|
{
|
|
varName = prependDollar(varName);
|
|
Dictionary::Entry *entry = getAddVariableEntry(varName);
|
|
entry->setStringValue(value ? "1" : "0");
|
|
}
|
|
}
|
|
|
|
void setIntVariable(const char *varName, S32 value)
|
|
{
|
|
SimObject *obj = NULL;
|
|
const char *objField = NULL;
|
|
|
|
if (getVariableObjectField(varName, &obj, &objField))
|
|
{
|
|
char varBuffer[32];
|
|
dSprintf(varBuffer, sizeof(varBuffer), "%d", value);
|
|
obj->setDataField(StringTable->insert(objField), 0, varBuffer);
|
|
}
|
|
else
|
|
{
|
|
varName = prependDollar(varName);
|
|
Dictionary::Entry *entry = getAddVariableEntry(varName);
|
|
entry->setIntValue(value);
|
|
}
|
|
}
|
|
|
|
void setFloatVariable(const char *varName, F32 value)
|
|
{
|
|
SimObject *obj = NULL;
|
|
const char *objField = NULL;
|
|
|
|
if (getVariableObjectField(varName, &obj, &objField))
|
|
{
|
|
char varBuffer[32];
|
|
dSprintf(varBuffer, sizeof(varBuffer), "%g", value);
|
|
obj->setDataField(StringTable->insert(objField), 0, varBuffer);
|
|
}
|
|
else
|
|
{
|
|
varName = prependDollar(varName);
|
|
Dictionary::Entry *entry = getAddVariableEntry(varName);
|
|
entry->setFloatValue(value);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
void addConsumer(ConsumerCallback consumer)
|
|
{
|
|
gConsumers.push_back(consumer);
|
|
}
|
|
|
|
// dhc - found this empty -- trying what I think is a reasonable impl.
|
|
void removeConsumer(ConsumerCallback consumer)
|
|
{
|
|
for(S32 i = 0; i < gConsumers.size(); i++)
|
|
{
|
|
if (gConsumers[i] == consumer)
|
|
{
|
|
// remove it from the list.
|
|
gConsumers.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void stripColorChars(char* line)
|
|
{
|
|
char* c = line;
|
|
char cp = *c;
|
|
while (cp)
|
|
{
|
|
if (cp < 18)
|
|
{
|
|
// Could be a color control character; let's take a closer look.
|
|
if ((cp != 8) && (cp != 9) && (cp != 10) && (cp != 13))
|
|
{
|
|
// Yep... copy following chars forward over this.
|
|
char* cprime = c;
|
|
char cpp;
|
|
do
|
|
{
|
|
cpp = *++cprime;
|
|
*(cprime - 1) = cpp;
|
|
}
|
|
while (cpp);
|
|
// Back up 1 so we'll check this position again post-copy.
|
|
c--;
|
|
}
|
|
}
|
|
cp = *++c;
|
|
}
|
|
}
|
|
|
|
//
|
|
const char *getObjectTokenField(const char *name)
|
|
{
|
|
const char *dot = dStrchr(name, '.');
|
|
if(name[0] != '$' && dot)
|
|
{
|
|
S32 len = dStrlen(name);
|
|
AssertFatal(len < sizeof(scratchBuffer)-1, "Sim::getVariable - object name too long");
|
|
dMemcpy(scratchBuffer, name, len+1);
|
|
|
|
char * token = dStrtok(scratchBuffer, ".");
|
|
SimObject * obj = Sim::findObject(token);
|
|
if(!obj)
|
|
return("");
|
|
|
|
token = dStrtok(0, ".\0");
|
|
if(!token)
|
|
return("");
|
|
|
|
while(token != NULL)
|
|
{
|
|
const char * val = obj->getDataField(StringTable->insert(token), 0);
|
|
if(!val)
|
|
return("");
|
|
|
|
token = dStrtok(0, ".\0");
|
|
if(token)
|
|
{
|
|
obj = Sim::findObject(token);
|
|
if(!obj)
|
|
return("");
|
|
}
|
|
else
|
|
return(val);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const char *getVariable(const char *name, const char* def)
|
|
{
|
|
const char *objField = getObjectTokenField(name);
|
|
if (objField)
|
|
{
|
|
return objField;
|
|
}
|
|
else
|
|
{
|
|
Dictionary::Entry *entry = getVariableEntry(name);
|
|
return entry ? entry->getStringValue() : def;
|
|
}
|
|
}
|
|
|
|
const char *getLocalVariable(const char *name)
|
|
{
|
|
name = prependPercent(name);
|
|
|
|
return gEvalState.getCurrentFrame().getVariable(StringTable->insert(name));
|
|
}
|
|
|
|
bool getBoolVariable(const char *varName, bool def)
|
|
{
|
|
const char *objField = getObjectTokenField(varName);
|
|
if (objField)
|
|
{
|
|
return *objField ? dAtob(objField) : def;
|
|
}
|
|
else
|
|
{
|
|
Dictionary::Entry *entry = getVariableEntry(varName);
|
|
objField = entry ? entry->getStringValue() : "";
|
|
return *objField ? dAtob(objField) : def;
|
|
}
|
|
}
|
|
|
|
S32 getIntVariable(const char *varName, S32 def)
|
|
{
|
|
const char *objField = getObjectTokenField(varName);
|
|
if (objField)
|
|
{
|
|
return *objField ? dAtoi(objField) : def;
|
|
}
|
|
else
|
|
{
|
|
Dictionary::Entry *entry = getVariableEntry(varName);
|
|
return entry ? entry->getIntValue() : def;
|
|
}
|
|
}
|
|
|
|
F32 getFloatVariable(const char *varName, F32 def)
|
|
{
|
|
const char *objField = getObjectTokenField(varName);
|
|
if (objField)
|
|
{
|
|
return *objField ? dAtof(objField) : def;
|
|
}
|
|
else
|
|
{
|
|
Dictionary::Entry *entry = getVariableEntry(varName);
|
|
return entry ? entry->getFloatValue() : def;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
void addVariable( const char *name,
|
|
S32 type,
|
|
void *dptr,
|
|
const char* usage )
|
|
{
|
|
gEvalState.globalVars.addVariable( name, type, dptr, usage );
|
|
}
|
|
|
|
void addConstant( const char *name,
|
|
S32 type,
|
|
const void *dptr,
|
|
const char* usage )
|
|
{
|
|
Dictionary::Entry* entry = gEvalState.globalVars.addVariable( name, type, const_cast< void* >( dptr ), usage );
|
|
entry->mIsConstant = true;
|
|
}
|
|
|
|
bool removeVariable(const char *name)
|
|
{
|
|
name = StringTable->lookup(prependDollar(name));
|
|
return name!=0 && gEvalState.globalVars.removeVariable(name);
|
|
}
|
|
|
|
void addVariableNotify( const char *name, const NotifyDelegate &callback )
|
|
{
|
|
gEvalState.globalVars.addVariableNotify( name, callback );
|
|
}
|
|
|
|
void removeVariableNotify( const char *name, const NotifyDelegate &callback )
|
|
{
|
|
gEvalState.globalVars.removeVariableNotify( name, callback );
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
void addCommand( const char *nsName, const char *name,StringCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
|
|
{
|
|
Namespace *ns = lookupNamespace(nsName);
|
|
ns->addCommand( StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly, header );
|
|
}
|
|
|
|
void addCommand( const char *nsName, const char *name,VoidCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
|
|
{
|
|
Namespace *ns = lookupNamespace(nsName);
|
|
ns->addCommand( StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly, header );
|
|
}
|
|
|
|
void addCommand( const char *nsName, const char *name,IntCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
|
|
{
|
|
Namespace *ns = lookupNamespace(nsName);
|
|
ns->addCommand( StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly, header );
|
|
}
|
|
|
|
void addCommand( const char *nsName, const char *name,FloatCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
|
|
{
|
|
Namespace *ns = lookupNamespace(nsName);
|
|
ns->addCommand( StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly, header );
|
|
}
|
|
|
|
void addCommand( const char *nsName, const char *name,BoolCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
|
|
{
|
|
Namespace *ns = lookupNamespace(nsName);
|
|
ns->addCommand( StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly, header );
|
|
}
|
|
|
|
void noteScriptCallback( const char *className, const char *funcName, const char *usage, ConsoleFunctionHeader* header )
|
|
{
|
|
Namespace *ns = lookupNamespace(className);
|
|
ns->addScriptCallback( StringTable->insert(funcName), usage, header );
|
|
}
|
|
|
|
void markCommandGroup(const char * nsName, const char *name, const char* usage)
|
|
{
|
|
Namespace *ns = lookupNamespace(nsName);
|
|
ns->markGroup(name,usage);
|
|
}
|
|
|
|
void beginCommandGroup(const char * nsName, const char *name, const char* usage)
|
|
{
|
|
markCommandGroup(nsName, name, usage);
|
|
}
|
|
|
|
void endCommandGroup(const char * nsName, const char *name)
|
|
{
|
|
markCommandGroup(nsName, name, NULL);
|
|
}
|
|
|
|
void addCommand( const char *name,StringCallback cb,const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
|
|
{
|
|
Namespace::global()->addCommand( StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly, header );
|
|
}
|
|
|
|
void addCommand( const char *name,VoidCallback cb,const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
|
|
{
|
|
Namespace::global()->addCommand( StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly, header );
|
|
}
|
|
|
|
void addCommand( const char *name,IntCallback cb,const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
|
|
{
|
|
Namespace::global()->addCommand( StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly, header );
|
|
}
|
|
|
|
void addCommand( const char *name,FloatCallback cb,const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
|
|
{
|
|
Namespace::global()->addCommand( StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly, header );
|
|
}
|
|
|
|
void addCommand( const char *name,BoolCallback cb,const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
|
|
{
|
|
Namespace::global()->addCommand( StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly, header );
|
|
}
|
|
|
|
bool executeFile(const char* fileName, bool noCalls, bool journalScript)
|
|
{
|
|
bool journal = false;
|
|
|
|
char scriptFilenameBuffer[1024];
|
|
U32 execDepth = 0;
|
|
U32 journalDepth = 1;
|
|
|
|
execDepth++;
|
|
if (journalDepth >= execDepth)
|
|
journalDepth = execDepth + 1;
|
|
else
|
|
journal = true;
|
|
|
|
bool ret = false;
|
|
|
|
if (journalScript && !journal)
|
|
{
|
|
journal = true;
|
|
journalDepth = execDepth;
|
|
}
|
|
|
|
// Determine the filename we actually want...
|
|
Con::expandScriptFilename(scriptFilenameBuffer, sizeof(scriptFilenameBuffer), fileName);
|
|
|
|
// since this function expects a script file reference, if it's a .dso
|
|
// lets terminate the string before the dso so it will act like a .cs
|
|
if (dStrEndsWith(scriptFilenameBuffer, ".dso"))
|
|
{
|
|
scriptFilenameBuffer[dStrlen(scriptFilenameBuffer) - dStrlen(".dso")] = '\0';
|
|
}
|
|
|
|
// Figure out where to put DSOs
|
|
StringTableEntry dsoPath = Con::getDSOPath(scriptFilenameBuffer);
|
|
|
|
const char *ext = dStrrchr(scriptFilenameBuffer, '.');
|
|
|
|
if (!ext)
|
|
{
|
|
// We need an extension!
|
|
Con::errorf(ConsoleLogEntry::Script, "exec: invalid script file name %s.", scriptFilenameBuffer);
|
|
execDepth--;
|
|
return false;
|
|
}
|
|
|
|
// Check Editor Extensions
|
|
bool isEditorScript = false;
|
|
|
|
// If the script file extension is '.ed.cs' then compile it to a different compiled extension
|
|
if (dStricmp(ext, ".cs") == 0)
|
|
{
|
|
const char* ext2 = ext - 3;
|
|
if (dStricmp(ext2, ".ed.cs") == 0)
|
|
isEditorScript = true;
|
|
}
|
|
else if (dStricmp(ext, ".gui") == 0)
|
|
{
|
|
const char* ext2 = ext - 3;
|
|
if (dStricmp(ext2, ".ed.gui") == 0)
|
|
isEditorScript = true;
|
|
}
|
|
|
|
StringTableEntry scriptFileName = StringTable->insert(scriptFilenameBuffer);
|
|
|
|
// Is this a file we should compile? (anything in the prefs path should not be compiled)
|
|
StringTableEntry prefsPath = Platform::getPrefsPath();
|
|
bool compiled = dStricmp(ext, ".mis") && !journal && !Con::getBoolVariable("Scripts::ignoreDSOs");
|
|
|
|
// [tom, 12/5/2006] stripBasePath() fucks up if the filename is not in the exe
|
|
// path, current directory or prefs path. Thus, getDSOFilename() will also screw
|
|
// up and so this allows the scripts to still load but without a DSO.
|
|
if (Platform::isFullPath(Platform::stripBasePath(scriptFilenameBuffer)))
|
|
compiled = false;
|
|
|
|
// [tom, 11/17/2006] It seems to make sense to not compile scripts that are in the
|
|
// prefs directory. However, getDSOPath() can handle this situation and will put
|
|
// the dso along with the script to avoid name clashes with tools/game dsos.
|
|
if ((dsoPath && *dsoPath == 0) || (prefsPath && prefsPath[0] && dStrnicmp(scriptFileName, prefsPath, dStrlen(prefsPath)) == 0))
|
|
compiled = false;
|
|
|
|
// If we're in a journaling mode, then we will read the script
|
|
// from the journal file.
|
|
if (journal && Journal::IsPlaying())
|
|
{
|
|
char fileNameBuf[256];
|
|
bool fileRead = false;
|
|
U32 fileSize;
|
|
|
|
Journal::ReadString(fileNameBuf);
|
|
Journal::Read(&fileRead);
|
|
|
|
if (!fileRead)
|
|
{
|
|
Con::errorf(ConsoleLogEntry::Script, "Journal script read (failed) for %s", fileNameBuf);
|
|
execDepth--;
|
|
return false;
|
|
}
|
|
Journal::Read(&fileSize);
|
|
char *script = new char[fileSize + 1];
|
|
Journal::Read(fileSize, script);
|
|
script[fileSize] = 0;
|
|
Con::printf("Executing (journal-read) %s.", scriptFileName);
|
|
CodeBlock *newCodeBlock = new CodeBlock();
|
|
newCodeBlock->compileExec(scriptFileName, script, noCalls, 0);
|
|
delete[] script;
|
|
|
|
execDepth--;
|
|
return true;
|
|
}
|
|
|
|
// Ok, we let's try to load and compile the script.
|
|
Torque::FS::FileNodeRef scriptFile = Torque::FS::GetFileNode(scriptFileName);
|
|
Torque::FS::FileNodeRef dsoFile;
|
|
|
|
// ResourceObject *rScr = gResourceManager->find(scriptFileName);
|
|
// ResourceObject *rCom = NULL;
|
|
|
|
char nameBuffer[512];
|
|
char* script = NULL;
|
|
U32 version;
|
|
|
|
Stream *compiledStream = NULL;
|
|
Torque::Time scriptModifiedTime, dsoModifiedTime;
|
|
|
|
// Check here for .edso
|
|
bool edso = false;
|
|
if (dStricmp(ext, ".edso") == 0 && scriptFile != NULL)
|
|
{
|
|
edso = true;
|
|
dsoFile = scriptFile;
|
|
scriptFile = NULL;
|
|
|
|
dsoModifiedTime = dsoFile->getModifiedTime();
|
|
dStrcpy(nameBuffer, scriptFileName, 512);
|
|
}
|
|
|
|
// If we're supposed to be compiling this file, check to see if there's a DSO
|
|
if (compiled && !edso)
|
|
{
|
|
const char *filenameOnly = dStrrchr(scriptFileName, '/');
|
|
if (filenameOnly)
|
|
++filenameOnly;
|
|
else
|
|
filenameOnly = scriptFileName;
|
|
|
|
char pathAndFilename[1024];
|
|
Platform::makeFullPathName(filenameOnly, pathAndFilename, sizeof(pathAndFilename), dsoPath);
|
|
|
|
if (isEditorScript)
|
|
dStrcpyl(nameBuffer, sizeof(nameBuffer), pathAndFilename, ".edso", NULL);
|
|
else
|
|
dStrcpyl(nameBuffer, sizeof(nameBuffer), pathAndFilename, ".dso", NULL);
|
|
|
|
dsoFile = Torque::FS::GetFileNode(nameBuffer);
|
|
|
|
if (scriptFile != NULL)
|
|
scriptModifiedTime = scriptFile->getModifiedTime();
|
|
|
|
if (dsoFile != NULL)
|
|
dsoModifiedTime = dsoFile->getModifiedTime();
|
|
}
|
|
|
|
// Let's do a sanity check to complain about DSOs in the future.
|
|
//
|
|
// MM: This doesn't seem to be working correctly for now so let's just not issue
|
|
// the warning until someone knows how to resolve it.
|
|
//
|
|
//if(compiled && rCom && rScr && Platform::compareFileTimes(comModifyTime, scrModifyTime) < 0)
|
|
//{
|
|
//Con::warnf("exec: Warning! Found a DSO from the future! (%s)", nameBuffer);
|
|
//}
|
|
|
|
// If we had a DSO, let's check to see if we should be reading from it.
|
|
//MGT: fixed bug with dsos not getting recompiled correctly
|
|
//Note: Using Nathan Martin's version from the forums since its easier to read and understand
|
|
if (compiled && dsoFile != NULL && (scriptFile == NULL || (dsoModifiedTime >= scriptModifiedTime)))
|
|
{ //MGT: end
|
|
compiledStream = FileStream::createAndOpen(nameBuffer, Torque::FS::File::Read);
|
|
if (compiledStream)
|
|
{
|
|
// Check the version!
|
|
compiledStream->read(&version);
|
|
if (version != Con::DSOVersion)
|
|
{
|
|
Con::warnf("exec: Found an old DSO (%s, ver %d < %d), ignoring.", nameBuffer, version, Con::DSOVersion);
|
|
delete compiledStream;
|
|
compiledStream = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we're journalling, let's write some info out.
|
|
if (journal && Journal::IsRecording())
|
|
Journal::WriteString(scriptFileName);
|
|
|
|
if (scriptFile != NULL && !compiledStream)
|
|
{
|
|
// If we have source but no compiled version, then we need to compile
|
|
// (and journal as we do so, if that's required).
|
|
|
|
void *data;
|
|
U32 dataSize = 0;
|
|
Torque::FS::ReadFile(scriptFileName, data, dataSize, true);
|
|
|
|
if (journal && Journal::IsRecording())
|
|
Journal::Write(bool(data != NULL));
|
|
|
|
if (data == NULL)
|
|
{
|
|
Con::errorf(ConsoleLogEntry::Script, "exec: invalid script file %s.", scriptFileName);
|
|
execDepth--;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (!dataSize)
|
|
{
|
|
execDepth--;
|
|
return false;
|
|
}
|
|
|
|
script = (char *)data;
|
|
|
|
if (journal && Journal::IsRecording())
|
|
{
|
|
Journal::Write(dataSize);
|
|
Journal::Write(dataSize, data);
|
|
}
|
|
}
|
|
|
|
#ifndef TORQUE_NO_DSO_GENERATION
|
|
if (compiled)
|
|
{
|
|
// compile this baddie.
|
|
#ifdef TORQUE_DEBUG
|
|
Con::printf("Compiling %s...", scriptFileName);
|
|
#endif
|
|
|
|
CodeBlock *code = new CodeBlock();
|
|
code->compile(nameBuffer, scriptFileName, script);
|
|
delete code;
|
|
code = NULL;
|
|
|
|
compiledStream = FileStream::createAndOpen(nameBuffer, Torque::FS::File::Read);
|
|
if (compiledStream)
|
|
{
|
|
compiledStream->read(&version);
|
|
}
|
|
else
|
|
{
|
|
// We have to exit out here, as otherwise we get double error reports.
|
|
delete[] script;
|
|
execDepth--;
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
if (journal && Journal::IsRecording())
|
|
Journal::Write(bool(false));
|
|
}
|
|
|
|
if (compiledStream)
|
|
{
|
|
// Delete the script object first to limit memory used
|
|
// during recursive execs.
|
|
delete[] script;
|
|
script = 0;
|
|
|
|
// We're all compiled, so let's run it.
|
|
#ifdef TORQUE_DEBUG
|
|
Con::printf("Loading compiled script %s.", scriptFileName);
|
|
#endif
|
|
CodeBlock *code = new CodeBlock;
|
|
code->read(scriptFileName, *compiledStream);
|
|
delete compiledStream;
|
|
code->exec(0, scriptFileName, NULL, 0, NULL, noCalls, NULL, 0);
|
|
ret = true;
|
|
}
|
|
else
|
|
if (scriptFile)
|
|
{
|
|
// No compiled script, let's just try executing it
|
|
// directly... this is either a mission file, or maybe
|
|
// we're on a readonly volume.
|
|
#ifdef TORQUE_DEBUG
|
|
Con::printf("Executing %s.", scriptFileName);
|
|
#endif
|
|
|
|
CodeBlock *newCodeBlock = new CodeBlock();
|
|
StringTableEntry name = StringTable->insert(scriptFileName);
|
|
|
|
newCodeBlock->compileExec(name, script, noCalls, 0);
|
|
ret = true;
|
|
}
|
|
else
|
|
{
|
|
// Don't have anything.
|
|
Con::warnf(ConsoleLogEntry::Script, "Missing file: %s!", scriptFileName);
|
|
ret = false;
|
|
}
|
|
|
|
delete[] script;
|
|
execDepth--;
|
|
return ret;
|
|
}
|
|
|
|
ConsoleValueRef evaluate(const char* string, bool echo, const char *fileName)
|
|
{
|
|
ConsoleStackFrameSaver stackSaver;
|
|
stackSaver.save();
|
|
|
|
if (echo)
|
|
{
|
|
if (string[0] == '%')
|
|
Con::printf("%s", string);
|
|
else
|
|
Con::printf("%s%s", getVariable( "$Con::Prompt" ), string);
|
|
}
|
|
|
|
if(fileName)
|
|
fileName = StringTable->insert(fileName);
|
|
|
|
CodeBlock *newCodeBlock = new CodeBlock();
|
|
return newCodeBlock->compileExec(fileName, string, false, fileName ? -1 : 0);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
ConsoleValueRef evaluatef(const char* string, ...)
|
|
{
|
|
ConsoleStackFrameSaver stackSaver;
|
|
stackSaver.save();
|
|
|
|
char buffer[4096];
|
|
va_list args;
|
|
va_start(args, string);
|
|
dVsprintf(buffer, sizeof(buffer), string, args);
|
|
va_end(args);
|
|
CodeBlock *newCodeBlock = new CodeBlock();
|
|
return newCodeBlock->compileExec(NULL, buffer, false, 0);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Internal execute for global function which does not save the stack
|
|
ConsoleValueRef _internalExecute(S32 argc, ConsoleValueRef argv[])
|
|
{
|
|
Namespace::Entry *ent;
|
|
StringTableEntry funcName = StringTable->insert(argv[0]);
|
|
ent = Namespace::global()->lookup(funcName);
|
|
|
|
if(!ent)
|
|
{
|
|
warnf(ConsoleLogEntry::Script, "%s: Unknown command.", (const char*)argv[0]);
|
|
|
|
STR.clearFunctionOffset();
|
|
return ConsoleValueRef();
|
|
}
|
|
return ent->execute(argc, argv, &gEvalState);
|
|
}
|
|
|
|
ConsoleValueRef execute(S32 argc, ConsoleValueRef argv[])
|
|
{
|
|
#ifdef TORQUE_MULTITHREAD
|
|
if(isMainThread())
|
|
{
|
|
#endif
|
|
ConsoleStackFrameSaver stackSaver;
|
|
stackSaver.save();
|
|
return _internalExecute(argc, argv);
|
|
#ifdef TORQUE_MULTITHREAD
|
|
}
|
|
else
|
|
{
|
|
SimConsoleThreadExecCallback cb;
|
|
SimConsoleThreadExecEvent *evt = new SimConsoleThreadExecEvent(argc, argv, false, &cb);
|
|
Sim::postEvent(Sim::getRootGroup(), evt, Sim::getCurrentTime());
|
|
|
|
return cb.waitForResult();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
ConsoleValueRef execute(S32 argc, const char *argv[])
|
|
{
|
|
ConsoleStackFrameSaver stackSaver;
|
|
stackSaver.save();
|
|
StringStackConsoleWrapper args(argc, argv);
|
|
return execute(args.count(), args);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Internal execute for object method which does not save the stack
|
|
ConsoleValueRef _internalExecute(SimObject *object, S32 argc, ConsoleValueRef argv[], bool thisCallOnly)
|
|
{
|
|
if(argc < 2)
|
|
{
|
|
STR.clearFunctionOffset();
|
|
return ConsoleValueRef();
|
|
}
|
|
|
|
// [neo, 10/05/2007 - #3010]
|
|
// Make sure we don't get recursive calls, respect the flag!
|
|
// Should we be calling handlesMethod() first?
|
|
if( !thisCallOnly )
|
|
{
|
|
ICallMethod *com = dynamic_cast<ICallMethod *>(object);
|
|
if(com)
|
|
{
|
|
STR.pushFrame();
|
|
CSTK.pushFrame();
|
|
com->callMethodArgList(argc, argv, false);
|
|
STR.popFrame();
|
|
CSTK.popFrame();
|
|
}
|
|
}
|
|
|
|
if(object->getNamespace())
|
|
{
|
|
U32 ident = object->getId();
|
|
ConsoleValueRef oldIdent(argv[1]);
|
|
|
|
StringTableEntry funcName = StringTable->insert(argv[0]);
|
|
Namespace::Entry *ent = object->getNamespace()->lookup(funcName);
|
|
|
|
if(ent == NULL)
|
|
{
|
|
//warnf(ConsoleLogEntry::Script, "%s: undefined for object '%s' - id %d", funcName, object->getName(), object->getId());
|
|
|
|
STR.clearFunctionOffset();
|
|
return ConsoleValueRef();
|
|
}
|
|
|
|
// Twiddle %this argument
|
|
ConsoleValue func_ident;
|
|
func_ident.setIntValue((S32)ident);
|
|
argv[1] = ConsoleValueRef::fromValue(&func_ident);
|
|
|
|
SimObject *save = gEvalState.thisObject;
|
|
gEvalState.thisObject = object;
|
|
ConsoleValueRef ret = ent->execute(argc, argv, &gEvalState);
|
|
gEvalState.thisObject = save;
|
|
|
|
// Twiddle it back
|
|
argv[1] = oldIdent;
|
|
|
|
return ret;
|
|
}
|
|
|
|
warnf(ConsoleLogEntry::Script, "Con::execute - %d has no namespace: %s", object->getId(), (const char*)argv[0]);
|
|
STR.clearFunctionOffset();
|
|
return ConsoleValueRef();
|
|
}
|
|
|
|
|
|
ConsoleValueRef execute(SimObject *object, S32 argc, ConsoleValueRef argv[], bool thisCallOnly)
|
|
{
|
|
if(argc < 2)
|
|
{
|
|
STR.clearFunctionOffset();
|
|
return ConsoleValueRef();
|
|
}
|
|
|
|
ConsoleStackFrameSaver stackSaver;
|
|
stackSaver.save();
|
|
|
|
if (object->getNamespace() || !thisCallOnly)
|
|
{
|
|
if (isMainThread())
|
|
{
|
|
return _internalExecute(object, argc, argv, thisCallOnly);
|
|
}
|
|
else
|
|
{
|
|
SimConsoleThreadExecCallback cb;
|
|
SimConsoleThreadExecEvent *evt = new SimConsoleThreadExecEvent(argc, argv, true, &cb);
|
|
Sim::postEvent(object, evt, Sim::getCurrentTime());
|
|
}
|
|
}
|
|
|
|
warnf(ConsoleLogEntry::Script, "Con::execute - %d has no namespace: %s", object->getId(), (const char*)argv[0]);
|
|
STR.clearFunctionOffset();
|
|
return ConsoleValueRef();
|
|
}
|
|
|
|
ConsoleValueRef execute(SimObject *object, S32 argc, const char *argv[], bool thisCallOnly)
|
|
{
|
|
ConsoleStackFrameSaver stackSaver;
|
|
stackSaver.save();
|
|
StringStackConsoleWrapper args(argc, argv);
|
|
return execute(object, args.count(), args, thisCallOnly);
|
|
}
|
|
|
|
inline ConsoleValueRef _executef(SimObject *obj, S32 checkArgc, S32 argc, ConsoleValueRef *argv)
|
|
{
|
|
const U32 maxArg = 12;
|
|
AssertWarn(checkArgc == argc, "Incorrect arg count passed to Con::executef(SimObject*)");
|
|
AssertFatal(argc <= maxArg - 1, "Too many args passed to Con::_executef(SimObject*). Please update the function to handle more.");
|
|
return execute(obj, argc, argv);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
inline ConsoleValueRef _executef(S32 checkArgc, S32 argc, ConsoleValueRef *argv)
|
|
{
|
|
const U32 maxArg = 10;
|
|
AssertFatal(checkArgc == argc, "Incorrect arg count passed to Con::executef()");
|
|
AssertFatal(argc <= maxArg, "Too many args passed to Con::_executef(). Please update the function to handle more.");
|
|
return execute(argc, argv);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool isFunction(const char *fn)
|
|
{
|
|
const char *string = StringTable->lookup(fn);
|
|
if(!string)
|
|
return false;
|
|
else
|
|
return Namespace::global()->lookup(string) != NULL;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void setLogMode(S32 newMode)
|
|
{
|
|
if ((newMode & 0x3) != (consoleLogMode & 0x3)) {
|
|
if (newMode && !consoleLogMode) {
|
|
// Enabling logging when it was previously disabled.
|
|
newLogFile = true;
|
|
}
|
|
if ((consoleLogMode & 0x3) == 2) {
|
|
// Changing away from mode 2, must close logfile.
|
|
consoleLogFile.close();
|
|
}
|
|
else if ((newMode & 0x3) == 2) {
|
|
#ifdef _XBOX
|
|
// Xbox is not going to support logging to a file. Use the OutputDebugStr
|
|
// log consumer
|
|
Platform::debugBreak();
|
|
#endif
|
|
// Starting mode 2, must open logfile.
|
|
consoleLogFile.open(defLogFileName, Torque::FS::File::Write);
|
|
}
|
|
consoleLogMode = newMode;
|
|
}
|
|
}
|
|
|
|
Namespace *lookupNamespace(const char *ns)
|
|
{
|
|
if(!ns)
|
|
return Namespace::global();
|
|
return Namespace::find(StringTable->insert(ns));
|
|
}
|
|
|
|
bool linkNamespaces(const char *parent, const char *child)
|
|
{
|
|
Namespace *pns = lookupNamespace(parent);
|
|
Namespace *cns = lookupNamespace(child);
|
|
if(pns && cns)
|
|
return cns->classLinkTo(pns);
|
|
return false;
|
|
}
|
|
|
|
bool unlinkNamespaces(const char *parent, const char *child)
|
|
{
|
|
Namespace *pns = lookupNamespace(parent);
|
|
Namespace *cns = lookupNamespace(child);
|
|
|
|
if(pns == cns)
|
|
{
|
|
Con::warnf("Con::unlinkNamespaces - trying to unlink '%s' from itself, aborting.", parent);
|
|
return false;
|
|
}
|
|
|
|
if(pns && cns)
|
|
return cns->unlinkClass(pns);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool classLinkNamespaces(Namespace *parent, Namespace *child)
|
|
{
|
|
if(parent && child)
|
|
return child->classLinkTo(parent);
|
|
return false;
|
|
}
|
|
|
|
void setData(S32 type, void *dptr, S32 index, S32 argc, const char **argv, const EnumTable *tbl, BitSet32 flag)
|
|
{
|
|
ConsoleBaseType *cbt = ConsoleBaseType::getType(type);
|
|
AssertFatal(cbt, "Con::setData - could not resolve type ID!");
|
|
cbt->setData((void *) (((const char *)dptr) + index * cbt->getTypeSize()),argc, argv, tbl, flag);
|
|
}
|
|
|
|
const char *getData(S32 type, void *dptr, S32 index, const EnumTable *tbl, BitSet32 flag)
|
|
{
|
|
ConsoleBaseType *cbt = ConsoleBaseType::getType(type);
|
|
AssertFatal(cbt, "Con::getData - could not resolve type ID!");
|
|
return cbt->getData((void *) (((const char *)dptr) + index * cbt->getTypeSize()), tbl, flag);
|
|
}
|
|
|
|
const char *getFormattedData(S32 type, const char *data, const EnumTable *tbl, BitSet32 flag)
|
|
{
|
|
ConsoleBaseType *cbt = ConsoleBaseType::getType( type );
|
|
AssertFatal(cbt, "Con::getData - could not resolve type ID!");
|
|
|
|
// Datablock types are just a datablock
|
|
// name and don't ever need formatting.
|
|
if ( cbt->isDatablock() )
|
|
return data;
|
|
|
|
bool currWarn = gWarnUndefinedScriptVariables;
|
|
gWarnUndefinedScriptVariables = false;
|
|
|
|
const char* globalValue = Con::getVariable(data);
|
|
|
|
gWarnUndefinedScriptVariables = currWarn;
|
|
|
|
if (dStrlen(globalValue) > 0)
|
|
return globalValue;
|
|
|
|
void* variable = cbt->getNativeVariable();
|
|
|
|
if (variable)
|
|
{
|
|
Con::setData(type, variable, 0, 1, &data, tbl, flag);
|
|
const char* formattedVal = Con::getData(type, variable, 0, tbl, flag);
|
|
|
|
static const U32 bufSize = 2048;
|
|
char* returnBuffer = Con::getReturnBuffer(bufSize);
|
|
dSprintf(returnBuffer, bufSize, "%s\0", formattedVal );
|
|
|
|
cbt->deleteNativeVariable(variable);
|
|
|
|
return returnBuffer;
|
|
}
|
|
else
|
|
return data;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
bool isCurrentScriptToolScript()
|
|
{
|
|
// With a player build we ALWAYS return false
|
|
#ifndef TORQUE_TOOLS
|
|
return false;
|
|
#else
|
|
const StringTableEntry cbFullPath = CodeBlock::getCurrentCodeBlockFullPath();
|
|
if(cbFullPath == NULL)
|
|
return false;
|
|
const StringTableEntry exePath = Platform::getMainDotCsDir();
|
|
|
|
return dStrnicmp(exePath, cbFullPath, dStrlen(exePath)) == 0;
|
|
#endif
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
StringTableEntry getModNameFromPath(const char *path)
|
|
{
|
|
if(path == NULL || *path == 0)
|
|
return NULL;
|
|
|
|
char buf[1024];
|
|
buf[0] = 0;
|
|
|
|
if(path[0] == '/' || path[1] == ':')
|
|
{
|
|
// It's an absolute path
|
|
const StringTableEntry exePath = Platform::getMainDotCsDir();
|
|
U32 len = dStrlen(exePath);
|
|
if(dStrnicmp(exePath, path, len) == 0)
|
|
{
|
|
const char *ptr = path + len + 1;
|
|
const char *slash = dStrchr(ptr, '/');
|
|
if(slash)
|
|
{
|
|
dStrncpy(buf, ptr, slash - ptr);
|
|
buf[slash - ptr] = 0;
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
const char *slash = dStrchr(path, '/');
|
|
if(slash)
|
|
{
|
|
dStrncpy(buf, path, slash - path);
|
|
buf[slash - path] = 0;
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
return StringTable->insert(buf);
|
|
}
|
|
|
|
void postConsoleInput( RawData data )
|
|
{
|
|
// Schedule this to happen at the next time event.
|
|
ConsoleValue values[2];
|
|
ConsoleValueRef argv[2];
|
|
|
|
values[0].init();
|
|
values[0].setStringValue("eval");
|
|
values[1].init();
|
|
values[1].setStringValue((const char*)data.data);
|
|
argv[0].value = &values[0];
|
|
argv[1].value = &values[1];
|
|
|
|
Sim::postCurrentEvent(Sim::getRootGroup(), new SimConsoleEvent(2, argv, false));
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void pushInstantGroup( String name )
|
|
{
|
|
sInstantGroupStack.push_back( gInstantGroup );
|
|
gInstantGroup = name;
|
|
}
|
|
|
|
void popInstantGroup()
|
|
{
|
|
if( sInstantGroupStack.empty() )
|
|
gInstantGroup = String::EmptyString;
|
|
else
|
|
{
|
|
gInstantGroup = sInstantGroupStack.last();
|
|
sInstantGroupStack.pop_back();
|
|
}
|
|
}
|
|
|
|
|
|
typedef HashMap<StringTableEntry, StringTableEntry> typePathExpandoMap;
|
|
static typePathExpandoMap PathExpandos;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void addPathExpando(const char* pExpandoName, const char* pPath)
|
|
{
|
|
// Sanity!
|
|
AssertFatal(pExpandoName != NULL, "Expando name cannot be NULL.");
|
|
AssertFatal(pPath != NULL, "Expando path cannot be NULL.");
|
|
|
|
// Fetch expando name.
|
|
StringTableEntry expandoName = StringTable->insert(pExpandoName);
|
|
|
|
// Fetch the length of the path.
|
|
S32 pathLength = dStrlen(pPath);
|
|
|
|
char pathBuffer[1024];
|
|
|
|
// Sanity!
|
|
if (pathLength == 0 || pathLength >= sizeof(pathBuffer))
|
|
{
|
|
Con::warnf("Cannot add path expando '%s' with path '%s' as the path is an invalid length.", pExpandoName, pPath);
|
|
return;
|
|
}
|
|
|
|
// Strip repeat slashes.
|
|
if (!Con::stripRepeatSlashes(pathBuffer, pPath, sizeof(pathBuffer)))
|
|
{
|
|
Con::warnf("Cannot add path expando '%s' with path '%s' as the path is an invalid length.", pExpandoName, pPath);
|
|
return;
|
|
}
|
|
|
|
// Fetch new path length.
|
|
pathLength = dStrlen(pathBuffer);
|
|
|
|
// Sanity!
|
|
if (pathLength == 0)
|
|
{
|
|
Con::warnf("Cannot add path expando '%s' with path '%s' as the path is an invalid length.", pExpandoName, pPath);
|
|
return;
|
|
}
|
|
|
|
// Remove any terminating slash.
|
|
if (pathBuffer[pathLength - 1] == '/')
|
|
pathBuffer[pathLength - 1] = 0;
|
|
|
|
// Fetch expanded path.
|
|
StringTableEntry expandedPath = StringTable->insert(pathBuffer);
|
|
|
|
// Info.
|
|
#if defined(TORQUE_DEBUG)
|
|
Con::printf("Adding path expando of '%s' as '%s'.", expandoName, expandedPath);
|
|
#endif
|
|
|
|
// Find any existing path expando.
|
|
typePathExpandoMap::iterator expandoItr = PathExpandos.find(pExpandoName);
|
|
|
|
// Does the expando exist?
|
|
if (expandoItr != PathExpandos.end())
|
|
{
|
|
// Yes, so modify the path.
|
|
expandoItr->value = expandedPath;
|
|
return;
|
|
}
|
|
|
|
// Insert expando.
|
|
PathExpandos.insert(expandoName, expandedPath);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
StringTableEntry getPathExpando(const char* pExpandoName)
|
|
{
|
|
// Sanity!
|
|
AssertFatal(pExpandoName != NULL, "Expando name cannot be NULL.");
|
|
|
|
// Fetch expando name.
|
|
StringTableEntry expandoName = StringTable->insert(pExpandoName);
|
|
|
|
// Find any existing path expando.
|
|
typePathExpandoMap::iterator expandoItr = PathExpandos.find(expandoName);
|
|
|
|
// Does the expando exist?
|
|
if (expandoItr != PathExpandos.end())
|
|
{
|
|
// Yes, so return it.
|
|
return expandoItr->value;
|
|
}
|
|
|
|
// Not found.
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void removePathExpando(const char* pExpandoName)
|
|
{
|
|
// Sanity!
|
|
AssertFatal(pExpandoName != NULL, "Expando name cannot be NULL.");
|
|
|
|
// Fetch expando name.
|
|
StringTableEntry expandoName = StringTable->insert(pExpandoName);
|
|
|
|
// Find any existing path expando.
|
|
typePathExpandoMap::iterator expandoItr = PathExpandos.find(expandoName);
|
|
|
|
// Does the expando exist?
|
|
if (expandoItr == PathExpandos.end())
|
|
{
|
|
// No, so warn.
|
|
#if defined(TORQUE_DEBUG)
|
|
Con::warnf("Removing path expando of '%s' but it does not exist.", expandoName);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// Info.
|
|
#if defined(TORQUE_DEBUG)
|
|
Con::printf("Removing path expando of '%s' as '%s'.", expandoName, expandoItr->value);
|
|
#endif
|
|
// Remove expando.
|
|
PathExpandos.erase(expandoItr);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool isPathExpando(const char* pExpandoName)
|
|
{
|
|
// Sanity!
|
|
AssertFatal(pExpandoName != NULL, "Expando name cannot be NULL.");
|
|
|
|
// Fetch expando name.
|
|
StringTableEntry expandoName = StringTable->insert(pExpandoName);
|
|
|
|
return PathExpandos.contains(expandoName);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
U32 getPathExpandoCount(void)
|
|
{
|
|
return PathExpandos.size();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
StringTableEntry getPathExpandoKey(U32 expandoIndex)
|
|
{
|
|
// Finish if index is out of range.
|
|
if (expandoIndex >= PathExpandos.size())
|
|
return NULL;
|
|
|
|
// Find indexed iterator.
|
|
typePathExpandoMap::iterator expandoItr = PathExpandos.begin();
|
|
while (expandoIndex > 0) { ++expandoItr; --expandoIndex; }
|
|
|
|
return expandoItr->key;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
StringTableEntry getPathExpandoValue(U32 expandoIndex)
|
|
{
|
|
// Finish if index is out of range.
|
|
if (expandoIndex >= PathExpandos.size())
|
|
return NULL;
|
|
|
|
// Find indexed iterator.
|
|
typePathExpandoMap::iterator expandoItr = PathExpandos.begin();
|
|
while (expandoIndex > 0) { ++expandoItr; --expandoIndex; }
|
|
|
|
return expandoItr->value;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool expandPath(char* pDstPath, U32 size, const char* pSrcPath, const char* pWorkingDirectoryHint, const bool ensureTrailingSlash)
|
|
{
|
|
char pathBuffer[2048];
|
|
const char* pSrc = pSrcPath;
|
|
char* pSlash;
|
|
|
|
// Fetch leading character.
|
|
const char leadingToken = *pSrc;
|
|
|
|
// Fetch following token.
|
|
const char followingToken = leadingToken != 0 ? pSrc[1] : 0;
|
|
|
|
// Expando.
|
|
if (leadingToken == '^')
|
|
{
|
|
// Initial prefix search.
|
|
const char* pPrefixSrc = pSrc + 1;
|
|
char* pPrefixDst = pathBuffer;
|
|
|
|
// Search for end of expando.
|
|
while (*pPrefixSrc != '/' && *pPrefixSrc != 0)
|
|
{
|
|
// Copy prefix character.
|
|
*pPrefixDst++ = *pPrefixSrc++;
|
|
}
|
|
|
|
// Yes, so terminate the expando string.
|
|
*pPrefixDst = 0;
|
|
|
|
// Fetch the expando path.
|
|
StringTableEntry expandoPath = getPathExpando(pathBuffer);
|
|
|
|
// Does the expando exist?
|
|
if (expandoPath == NULL)
|
|
{
|
|
// No, so error.
|
|
Con::errorf("expandPath() : Could not find path expando '%s' for path '%s'.", pathBuffer, pSrcPath);
|
|
|
|
// Are we ensuring the trailing slash?
|
|
if (ensureTrailingSlash)
|
|
{
|
|
// Yes, so ensure it.
|
|
Con::ensureTrailingSlash(pDstPath, pSrcPath, size);
|
|
}
|
|
else
|
|
{
|
|
// No, so just use the source path.
|
|
dStrcpy(pDstPath, pSrcPath, size);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Skip the expando and the following slash.
|
|
pSrc += dStrlen(pathBuffer) + 1;
|
|
|
|
// Format the output path.
|
|
dSprintf(pathBuffer, sizeof(pathBuffer), "%s/%s", expandoPath, pSrc);
|
|
|
|
// Are we ensuring the trailing slash?
|
|
if (ensureTrailingSlash)
|
|
{
|
|
// Yes, so ensure it.
|
|
Con::ensureTrailingSlash(pathBuffer, pathBuffer, size);
|
|
}
|
|
|
|
// Strip repeat slashes.
|
|
Con::stripRepeatSlashes(pDstPath, pathBuffer, size);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Script-Relative.
|
|
if (leadingToken == '.')
|
|
{
|
|
// Fetch the code-block file-path.
|
|
const StringTableEntry codeblockFullPath = CodeBlock::getCurrentCodeBlockFullPath();
|
|
|
|
// Do we have a code block full path?
|
|
if (codeblockFullPath == NULL)
|
|
{
|
|
// No, so error.
|
|
Con::errorf("expandPath() : Could not find relative path from code-block for path '%s'.", pSrcPath);
|
|
|
|
// Are we ensuring the trailing slash?
|
|
if (ensureTrailingSlash)
|
|
{
|
|
// Yes, so ensure it.
|
|
Con::ensureTrailingSlash(pDstPath, pSrcPath, size);
|
|
}
|
|
else
|
|
{
|
|
// No, so just use the source path.
|
|
dStrcpy(pDstPath, pSrcPath, size);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Yes, so use it as the prefix.
|
|
dStrncpy(pathBuffer, codeblockFullPath, sizeof(pathBuffer) - 1);
|
|
|
|
// Find the final slash in the code-block.
|
|
pSlash = dStrrchr(pathBuffer, '/');
|
|
|
|
// Is this a parent directory token?
|
|
if (followingToken == '.')
|
|
{
|
|
// Yes, so terminate after the slash so we include it.
|
|
pSlash[1] = 0;
|
|
}
|
|
else
|
|
{
|
|
// No, it's a current directory token so terminate at the slash so we don't include it.
|
|
pSlash[0] = 0;
|
|
|
|
// Skip the current directory token.
|
|
pSrc++;
|
|
}
|
|
|
|
// Format the output path.
|
|
dStrncat(pathBuffer, "/", sizeof(pathBuffer) - 1 - strlen(pathBuffer));
|
|
dStrncat(pathBuffer, pSrc, sizeof(pathBuffer) - 1 - strlen(pathBuffer));
|
|
|
|
// Are we ensuring the trailing slash?
|
|
if (ensureTrailingSlash)
|
|
{
|
|
// Yes, so ensure it.
|
|
Con::ensureTrailingSlash(pathBuffer, pathBuffer, size);
|
|
}
|
|
|
|
// Strip repeat slashes.
|
|
Con::stripRepeatSlashes(pDstPath, pathBuffer, size);
|
|
|
|
return true;
|
|
}
|
|
|
|
// All else.
|
|
|
|
//Using a special case here because the code below barfs on trying to build a full path for apk reading
|
|
#ifdef TORQUE_OS_ANDROID
|
|
if (leadingToken == '/' || strstr(pSrcPath, "/") == NULL)
|
|
Platform::makeFullPathName(pSrcPath, pathBuffer, sizeof(pathBuffer), pWorkingDirectoryHint);
|
|
else
|
|
dSprintf(pathBuffer, sizeof(pathBuffer), "/%s", pSrcPath);
|
|
#else
|
|
Platform::makeFullPathName(pSrcPath, pathBuffer, sizeof(pathBuffer), pWorkingDirectoryHint);
|
|
#endif
|
|
|
|
// Are we ensuring the trailing slash?
|
|
if (ensureTrailingSlash)
|
|
{
|
|
// Yes, so ensure it.
|
|
Con::ensureTrailingSlash(pathBuffer, pathBuffer, size);
|
|
}
|
|
|
|
// Strip repeat slashes.
|
|
Con::stripRepeatSlashes(pDstPath, pathBuffer, size);
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool isBasePath(const char* SrcPath, const char* pBasePath)
|
|
{
|
|
char expandBuffer[1024];
|
|
Con::expandPath(expandBuffer, sizeof(expandBuffer), SrcPath);
|
|
return dStrnicmp(pBasePath, expandBuffer, dStrlen(pBasePath)) == 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void collapsePath(char* pDstPath, U32 size, const char* pSrcPath, const char* pWorkingDirectoryHint)
|
|
{
|
|
// Check path against expandos. If there are multiple matches, choose the
|
|
// expando that produces the shortest relative path.
|
|
|
|
char pathBuffer[2048];
|
|
|
|
// Fetch expando count.
|
|
const U32 expandoCount = getPathExpandoCount();
|
|
|
|
// Iterate expandos.
|
|
U32 expandoRelativePathLength = U32_MAX;
|
|
for (U32 expandoIndex = 0; expandoIndex < expandoCount; ++expandoIndex)
|
|
{
|
|
// Fetch expando value (path).
|
|
StringTableEntry expandoValue = getPathExpandoValue(expandoIndex);
|
|
|
|
// Skip if not the base path.
|
|
if (!isBasePath(pSrcPath, expandoValue))
|
|
continue;
|
|
|
|
// Fetch path relative to expando path.
|
|
StringTableEntry relativePath = Platform::makeRelativePathName(pSrcPath, expandoValue);
|
|
|
|
// If the relative path is simply a period
|
|
if (relativePath[0] == '.')
|
|
relativePath++;
|
|
|
|
if (dStrlen(relativePath) > expandoRelativePathLength)
|
|
{
|
|
// This expando covers less of the path than any previous one found.
|
|
// We will keep the previous one.
|
|
continue;
|
|
}
|
|
|
|
// Keep track of the relative path length
|
|
expandoRelativePathLength = dStrlen(relativePath);
|
|
|
|
// Fetch expando key (name).
|
|
StringTableEntry expandoName = getPathExpandoKey(expandoIndex);
|
|
|
|
// Format against expando.
|
|
dSprintf(pathBuffer, sizeof(pathBuffer), "^%s/%s", expandoName, relativePath);
|
|
}
|
|
|
|
// Check if we've found a suitable expando
|
|
if (expandoRelativePathLength != U32_MAX)
|
|
{
|
|
// Strip repeat slashes.
|
|
Con::stripRepeatSlashes(pDstPath, pathBuffer, size);
|
|
|
|
return;
|
|
}
|
|
|
|
// Fetch the working directory.
|
|
StringTableEntry workingDirectory = pWorkingDirectoryHint != NULL ? pWorkingDirectoryHint : Platform::getCurrentDirectory();
|
|
|
|
// Fetch path relative to current directory.
|
|
StringTableEntry relativePath = Platform::makeRelativePathName(pSrcPath, workingDirectory);
|
|
|
|
// If the relative path is simply a period
|
|
if (relativePath[0] == '.' && relativePath[1] != '.')
|
|
relativePath++;
|
|
|
|
// Format against expando.
|
|
dSprintf(pathBuffer, sizeof(pathBuffer), "%s/%s", workingDirectory, relativePath);
|
|
|
|
// Strip repeat slashes.
|
|
Con::stripRepeatSlashes(pDstPath, pathBuffer, size);
|
|
}
|
|
|
|
|
|
void ensureTrailingSlash(char* pDstPath, const char* pSrcPath, S32 dstSize)
|
|
{
|
|
// Copy to target.
|
|
dStrcpy(pDstPath, pSrcPath, dstSize);
|
|
|
|
// Find trailing character index.
|
|
S32 trailIndex = dStrlen(pDstPath);
|
|
|
|
// Ignore if empty string.
|
|
if (trailIndex == 0)
|
|
return;
|
|
|
|
// Finish if the trailing slash already exists.
|
|
if (pDstPath[trailIndex - 1] == '/')
|
|
return;
|
|
|
|
// Add trailing slash.
|
|
pDstPath[trailIndex++] = '/';
|
|
pDstPath[trailIndex] = 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
StringTableEntry getDSOPath(const char *scriptPath)
|
|
{
|
|
#ifndef TORQUE2D_TOOLS_FIXME
|
|
|
|
// [tom, 11/17/2006] Force old behavior for the player. May not want to do this.
|
|
const char *slash = dStrrchr(scriptPath, '/');
|
|
if (slash != NULL)
|
|
return StringTable->insertn(scriptPath, slash - scriptPath, true);
|
|
|
|
slash = dStrrchr(scriptPath, ':');
|
|
if (slash != NULL)
|
|
return StringTable->insertn(scriptPath, (slash - scriptPath) + 1, true);
|
|
|
|
return "";
|
|
|
|
#else
|
|
|
|
char relPath[1024], dsoPath[1024];
|
|
bool isPrefs = false;
|
|
|
|
// [tom, 11/17/2006] Prefs are handled slightly differently to avoid dso name clashes
|
|
StringTableEntry prefsPath = Platform::getPrefsPath();
|
|
if (dStrnicmp(scriptPath, prefsPath, dStrlen(prefsPath)) == 0)
|
|
{
|
|
relPath[0] = 0;
|
|
isPrefs = true;
|
|
}
|
|
else
|
|
{
|
|
StringTableEntry strippedPath = Platform::stripBasePath(scriptPath);
|
|
dStrcpy(relPath, strippedPath, 1024);
|
|
|
|
char *slash = dStrrchr(relPath, '/');
|
|
if (slash)
|
|
*slash = 0;
|
|
}
|
|
|
|
const char *overridePath;
|
|
if (!isPrefs)
|
|
overridePath = Con::getVariable("$Scripts::OverrideDSOPath");
|
|
else
|
|
overridePath = prefsPath;
|
|
|
|
if (overridePath && *overridePath)
|
|
Platform::makeFullPathName(relPath, dsoPath, sizeof(dsoPath), overridePath);
|
|
else
|
|
{
|
|
char t[1024];
|
|
dSprintf(t, sizeof(t), "compiledScripts/%s", relPath);
|
|
Platform::makeFullPathName(t, dsoPath, sizeof(dsoPath), Platform::getPrefsPath());
|
|
}
|
|
|
|
return StringTable->insert(dsoPath);
|
|
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool stripRepeatSlashes(char* pDstPath, const char* pSrcPath, S32 dstSize)
|
|
{
|
|
// Note original destination.
|
|
char* pOriginalDst = pDstPath;
|
|
|
|
// Reset last source character.
|
|
char lastSrcChar = 0;
|
|
|
|
// Search source...
|
|
while (dstSize > 0)
|
|
{
|
|
// Fetch characters.
|
|
const char srcChar = *pSrcPath++;
|
|
|
|
// Do we have a repeat slash?
|
|
if (srcChar == '/' && lastSrcChar == '/')
|
|
{
|
|
// Yes, so skip it.
|
|
continue;
|
|
}
|
|
|
|
// No, so copy character.
|
|
*pDstPath++ = srcChar;
|
|
|
|
// Finish if end of source.
|
|
if (srcChar == 0)
|
|
return true;
|
|
|
|
// Reduce room left in destination.
|
|
dstSize--;
|
|
|
|
// Set last character.
|
|
lastSrcChar = srcChar;
|
|
}
|
|
|
|
// Terminate the destination string as we ran out of room.
|
|
*pOriginalDst = 0;
|
|
|
|
// Fail!
|
|
return false;
|
|
}
|
|
|
|
} // end of Console namespace
|
|
|
|
#endif
|
|
|
|
//=============================================================================
|
|
// API.
|
|
//=============================================================================
|
|
// MARK: ---- API ----
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineFunction( log, void, ( const char* message ),,
|
|
"@brief Logs a message to the console.\n\n"
|
|
"@param message The message text.\n"
|
|
"@note By default, messages will appear white in the console.\n"
|
|
"@ingroup Logging")
|
|
{
|
|
Con::printf( "%s", message );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineFunction( logError, void, ( const char* message ),,
|
|
"@brief Logs an error message to the console.\n\n"
|
|
"@param message The message text.\n"
|
|
"@note By default, errors will appear red in the console.\n"
|
|
"@ingroup Logging")
|
|
{
|
|
Con::errorf( "%s", message );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineFunction( logWarning, void, ( const char* message ),,
|
|
"@brief Logs a warning message to the console.\n\n"
|
|
"@param message The message text.\n\n"
|
|
"@note By default, warnings will appear turquoise in the console.\n"
|
|
"@ingroup Logging")
|
|
{
|
|
Con::warnf( "%s", message );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
extern ConsoleValueStack CSTK;
|
|
|
|
ConsoleValueRef::ConsoleValueRef(const ConsoleValueRef &ref)
|
|
{
|
|
value = ref.value;
|
|
}
|
|
|
|
ConsoleValueRef& ConsoleValueRef::operator=(const ConsoleValueRef &newValue)
|
|
{
|
|
value = newValue.value;
|
|
return *this;
|
|
}
|
|
|
|
ConsoleValueRef& ConsoleValueRef::operator=(const char *newValue)
|
|
{
|
|
AssertFatal(value, "value should not be NULL");
|
|
value->setStringValue(newValue);
|
|
return *this;
|
|
}
|
|
|
|
ConsoleValueRef& ConsoleValueRef::operator=(S32 newValue)
|
|
{
|
|
AssertFatal(value, "value should not be NULL");
|
|
value->setIntValue(newValue);
|
|
return *this;
|
|
}
|
|
|
|
ConsoleValueRef& ConsoleValueRef::operator=(U32 newValue)
|
|
{
|
|
AssertFatal(value, "value should not be NULL");
|
|
value->setIntValue(newValue);
|
|
return *this;
|
|
}
|
|
|
|
ConsoleValueRef& ConsoleValueRef::operator=(F32 newValue)
|
|
{
|
|
AssertFatal(value, "value should not be NULL");
|
|
value->setFloatValue(newValue);
|
|
return *this;
|
|
}
|
|
|
|
ConsoleValueRef& ConsoleValueRef::operator=(F64 newValue)
|
|
{
|
|
AssertFatal(value, "value should not be NULL");
|
|
value->setFloatValue(newValue);
|
|
return *this;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
StringStackWrapper::StringStackWrapper(int targc, ConsoleValueRef targv[])
|
|
{
|
|
argv = new const char*[targc];
|
|
argc = targc;
|
|
|
|
for (int i=0; i<targc; i++)
|
|
{
|
|
argv[i] = dStrdup(targv[i]);
|
|
}
|
|
}
|
|
|
|
StringStackWrapper::~StringStackWrapper()
|
|
{
|
|
for (int i=0; i<argc; i++)
|
|
{
|
|
dFree(argv[i]);
|
|
}
|
|
delete[] argv;
|
|
}
|
|
|
|
|
|
StringStackConsoleWrapper::StringStackConsoleWrapper(int targc, const char** targ)
|
|
{
|
|
argv = new ConsoleValueRef[targc];
|
|
argvValue = new ConsoleValue[targc];
|
|
argc = targc;
|
|
|
|
for (int i=0; i<targc; i++) {
|
|
argvValue[i].init();
|
|
argv[i].value = &argvValue[i];
|
|
argvValue[i].setStackStringValue(targ[i]);
|
|
}
|
|
}
|
|
|
|
StringStackConsoleWrapper::~StringStackConsoleWrapper()
|
|
{
|
|
for (int i=0; i<argc; i++)
|
|
{
|
|
argv[i] = 0;
|
|
}
|
|
delete[] argv;
|
|
delete[] argvValue;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
S32 ConsoleValue::getSignedIntValue()
|
|
{
|
|
if(type <= TypeInternalString)
|
|
return (S32)fval;
|
|
else
|
|
return dAtoi(Con::getData(type, dataPtr, 0, enumTable));
|
|
}
|
|
|
|
U32 ConsoleValue::getIntValue()
|
|
{
|
|
if(type <= TypeInternalString)
|
|
return ival;
|
|
else
|
|
return dAtoi(Con::getData(type, dataPtr, 0, enumTable));
|
|
}
|
|
|
|
F32 ConsoleValue::getFloatValue()
|
|
{
|
|
if(type <= TypeInternalString)
|
|
return fval;
|
|
else
|
|
return dAtof(Con::getData(type, dataPtr, 0, enumTable));
|
|
}
|
|
|
|
const char *ConsoleValue::getStringValue()
|
|
{
|
|
if(type == TypeInternalString || type == TypeInternalStackString)
|
|
return sval;
|
|
else if (type == TypeInternalStringStackPtr)
|
|
return STR.mBuffer + (uintptr_t)sval;
|
|
else
|
|
{
|
|
// We need a string representation, so lets create one
|
|
const char *internalValue = NULL;
|
|
|
|
if(type == TypeInternalFloat)
|
|
internalValue = Con::getData(TypeF32, &fval, 0);
|
|
else if(type == TypeInternalInt)
|
|
internalValue = Con::getData(TypeS32, &ival, 0);
|
|
else
|
|
return Con::getData(type, dataPtr, 0, enumTable); // We can't save sval here since it is the same as dataPtr
|
|
|
|
if (!internalValue)
|
|
return "";
|
|
|
|
U32 stringLen = dStrlen(internalValue);
|
|
U32 newLen = ((stringLen + 1) + 15) & ~15; // pad upto next cache line
|
|
|
|
if (bufferLen == 0)
|
|
sval = (char *) dMalloc(newLen);
|
|
else if(newLen > bufferLen)
|
|
sval = (char *) dRealloc(sval, newLen);
|
|
|
|
dStrcpy(sval, internalValue, newLen);
|
|
bufferLen = newLen;
|
|
|
|
return sval;
|
|
}
|
|
}
|
|
|
|
StringStackPtr ConsoleValue::getStringStackPtr()
|
|
{
|
|
if (type == TypeInternalStringStackPtr)
|
|
return (uintptr_t)sval;
|
|
else
|
|
return (uintptr_t)-1;
|
|
}
|
|
|
|
bool ConsoleValue::getBoolValue()
|
|
{
|
|
if(type == TypeInternalString || type == TypeInternalStackString || type == TypeInternalStringStackPtr)
|
|
return dAtob(getStringValue());
|
|
if(type == TypeInternalFloat)
|
|
return fval > 0;
|
|
else if(type == TypeInternalInt)
|
|
return ival > 0;
|
|
else {
|
|
const char *value = Con::getData(type, dataPtr, 0, enumTable);
|
|
return dAtob(value);
|
|
}
|
|
}
|
|
|
|
void ConsoleValue::setIntValue(S32 val)
|
|
{
|
|
setFloatValue(val);
|
|
}
|
|
|
|
void ConsoleValue::setIntValue(U32 val)
|
|
{
|
|
if(type <= TypeInternalString)
|
|
{
|
|
fval = (F32)val;
|
|
ival = val;
|
|
if(bufferLen > 0)
|
|
{
|
|
dFree(sval);
|
|
bufferLen = 0;
|
|
}
|
|
|
|
sval = typeValueEmpty;
|
|
type = TypeInternalInt;
|
|
}
|
|
else
|
|
{
|
|
const char *dptr = Con::getData(TypeS32, &val, 0);
|
|
Con::setData(type, dataPtr, 0, 1, &dptr, enumTable);
|
|
}
|
|
}
|
|
|
|
void ConsoleValue::setBoolValue(bool val)
|
|
{
|
|
return setIntValue(val ? 1 : 0);
|
|
}
|
|
|
|
void ConsoleValue::setFloatValue(F32 val)
|
|
{
|
|
if(type <= TypeInternalString)
|
|
{
|
|
fval = val;
|
|
ival = static_cast<U32>(val);
|
|
if(bufferLen > 0)
|
|
{
|
|
dFree(sval);
|
|
bufferLen = 0;
|
|
}
|
|
sval = typeValueEmpty;
|
|
type = TypeInternalFloat;
|
|
}
|
|
else
|
|
{
|
|
const char *dptr = Con::getData(TypeF32, &val, 0);
|
|
Con::setData(type, dataPtr, 0, 1, &dptr, enumTable);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
ConsoleValueRef _BaseEngineConsoleCallbackHelper::_exec()
|
|
{
|
|
ConsoleValueRef returnValue;
|
|
if( mThis )
|
|
{
|
|
// Cannot invoke callback until object has been registered
|
|
if (mThis->isProperlyAdded()) {
|
|
returnValue = Con::_internalExecute( mThis, mArgc, mArgv, false );
|
|
} else {
|
|
STR.clearFunctionOffset();
|
|
returnValue = ConsoleValueRef();
|
|
}
|
|
}
|
|
else
|
|
returnValue = Con::_internalExecute( mArgc, mArgv );
|
|
|
|
mArgc = mInitialArgc; // reset args
|
|
return returnValue;
|
|
}
|
|
|
|
ConsoleValueRef _BaseEngineConsoleCallbackHelper::_execLater(SimConsoleThreadExecEvent *evt)
|
|
{
|
|
mArgc = mInitialArgc; // reset args
|
|
Sim::postEvent((SimObject*)Sim::getRootGroup(), evt, Sim::getCurrentTime());
|
|
return evt->getCB().waitForResult();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void ConsoleStackFrameSaver::save()
|
|
{
|
|
CSTK.pushFrame();
|
|
STR.pushFrame();
|
|
mSaved = true;
|
|
}
|
|
|
|
void ConsoleStackFrameSaver::restore()
|
|
{
|
|
if (mSaved)
|
|
{
|
|
CSTK.popFrame();
|
|
STR.popFrame();
|
|
}
|
|
}
|