mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-19 20:24:49 +00:00
3222 lines
100 KiB
C++
3222 lines
100 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.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
|
|
// Copyright (C) 2015 Faust Logic, Inc.
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
#include "platform/platform.h"
|
|
#include "platform/platformMemory.h"
|
|
#include "console/simObject.h"
|
|
#include "console/console.h"
|
|
#include "console/consoleInternal.h"
|
|
#include "console/engineAPI.h"
|
|
#include "console/simFieldDictionary.h"
|
|
#include "console/simPersistID.h"
|
|
#include "console/typeValidators.h"
|
|
#include "console/arrayObject.h"
|
|
#include "console/codeBlock.h"
|
|
#include "core/frameAllocator.h"
|
|
#include "core/stream/fileStream.h"
|
|
#include "core/fileObject.h"
|
|
#include "persistence/taml/tamlCustom.h"
|
|
|
|
#include "sim/netObject.h"
|
|
#include "cinterface/cinterface.h"
|
|
|
|
IMPLEMENT_CONOBJECT( SimObject );
|
|
|
|
// See full description in the new CHM manual
|
|
ConsoleDocClass( SimObject,
|
|
"@brief Base class for almost all objects involved in the simulation.\n\n"
|
|
|
|
"@ingroup Console\n"
|
|
);
|
|
|
|
bool SimObject::smForceId = false;
|
|
SimObjectId SimObject::smForcedId = 0;
|
|
|
|
bool SimObject::preventNameChanging = false;
|
|
|
|
IMPLEMENT_CALLBACK(SimObject, onInspectPostApply, void, (SimObject* obj), (obj), "Generic callback for when an object is edited");
|
|
|
|
namespace Sim
|
|
{
|
|
// Defined in simManager.cpp
|
|
extern SimGroup *gRootGroup;
|
|
extern SimManagerNameDictionary *gNameDictionary;
|
|
extern SimIdDictionary *gIdDictionary;
|
|
extern U32 gNextObjectId;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
SimObject::SimObject()
|
|
{
|
|
mObjectName = NULL;
|
|
mOriginalName = NULL;
|
|
mInternalName = NULL;
|
|
nextNameObject = nullptr;
|
|
nextManagerNameObject = nullptr;
|
|
nextIdObject = NULL;
|
|
|
|
mFilename = NULL;
|
|
mDeclarationLine = -1;
|
|
|
|
mId = 0;
|
|
mIdString[ 0 ] = '\0';
|
|
mGroup = 0;
|
|
mNameSpace = NULL;
|
|
mNotifyList = NULL;
|
|
mFlags.set( ModStaticFields | ModDynamicFields );
|
|
|
|
mProgenitorFile = StringTable->EmptyString();
|
|
|
|
mFieldDictionary = NULL;
|
|
mCanSaveFieldDictionary = true;
|
|
|
|
mClassName = NULL;
|
|
mSuperClassName = NULL;
|
|
|
|
mCopySource = NULL;
|
|
mPersistentId = NULL;
|
|
is_temp_clone = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
SimObject::~SimObject()
|
|
{
|
|
// if this is a temp-clone, we don't delete any members that were shallow-copied
|
|
// over from the source datablock.
|
|
if (is_temp_clone)
|
|
return;
|
|
if( mFieldDictionary )
|
|
{
|
|
delete mFieldDictionary;
|
|
mFieldDictionary = NULL;
|
|
}
|
|
|
|
// Release persistent ID.
|
|
if( mPersistentId )
|
|
{
|
|
mPersistentId->unresolve();
|
|
mPersistentId->decRefCount();
|
|
mPersistentId = NULL;
|
|
}
|
|
|
|
if( mCopySource )
|
|
mCopySource->unregisterReference( &mCopySource );
|
|
|
|
AssertFatal(nextNameObject == nullptr,avar(
|
|
"SimObject::~SimObject: Not removed from dictionary: name %s, id %i",
|
|
mObjectName, mId));
|
|
AssertFatal(nextManagerNameObject == nullptr,avar(
|
|
"SimObject::~SimObject: Not removed from manager dictionary: name %s, id %i",
|
|
mObjectName,mId));
|
|
AssertFatal(mFlags.test(Added) == 0, "SimObject::object "
|
|
"missing call to SimObject::onRemove");
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::processArguments(S32 argc, ConsoleValue *argv)
|
|
{
|
|
return argc == 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::initPersistFields()
|
|
{
|
|
addGroup( "Ungrouped" );
|
|
|
|
addProtectedField( "name", TypeName, Offset(mObjectName, SimObject), &setProtectedName, &defaultProtectedGetFn,
|
|
"Optional global name of this object." );
|
|
|
|
endGroup( "Ungrouped" );
|
|
|
|
addGroup( "Object" );
|
|
|
|
addField( "internalName", TypeString, Offset(mInternalName, SimObject),
|
|
"Optional name that may be used to lookup this object within a SimSet.");
|
|
|
|
addProtectedField( "parentGroup", TYPEID< SimObject >(), Offset(mGroup, SimObject), &setProtectedParent, &defaultProtectedGetFn,
|
|
"Group hierarchy parent of the object." );
|
|
|
|
addProtectedField( "class", TypeString, Offset(mClassName, SimObject), &setClass, &defaultProtectedGetFn,
|
|
"Script class of object." );
|
|
|
|
addProtectedField( "superClass", TypeString, Offset(mSuperClassName, SimObject), &setSuperClass, &defaultProtectedGetFn,
|
|
"Script super-class of object." );
|
|
|
|
// For legacy support
|
|
addProtectedField( "className", TypeString, Offset(mClassName, SimObject), &setClass, &defaultProtectedGetFn,
|
|
"Script class of object.", AbstractClassRep::FIELD_HideInInspectors );
|
|
|
|
endGroup( "Object" );
|
|
|
|
addGroup( "Editing" );
|
|
|
|
addProtectedField( "hidden", TypeBool, NULL,
|
|
&_setHidden, &_getHidden,
|
|
"Whether the object is visible." );
|
|
addProtectedField( "locked", TypeBool, NULL,
|
|
&_setLocked, &_getLocked,
|
|
"Whether the object can be edited." );
|
|
|
|
endGroup( "Editing" );
|
|
|
|
addGroup( "Persistence" );
|
|
|
|
addProtectedField( "canSave", TypeBool, Offset( mFlags, SimObject ),
|
|
&_setCanSave, &_getCanSave,
|
|
"Whether the object can be saved out. If false, the object is purely transient in nature." );
|
|
|
|
addField( "canSaveDynamicFields", TypeBool, Offset(mCanSaveFieldDictionary, SimObject),
|
|
"True if dynamic fields (added at runtime) should be saved. Defaults to true." );
|
|
|
|
addProtectedField( "persistentId", TypePID, Offset( mPersistentId, SimObject ),
|
|
&_setPersistentID, &defaultProtectedGetFn,
|
|
"The universally unique identifier for the object." );
|
|
|
|
endGroup( "Persistence" );
|
|
|
|
Parent::initPersistFields();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
String SimObject::describeSelf() const
|
|
{
|
|
String desc = Parent::describeSelf();
|
|
|
|
if( mId != 0 )
|
|
desc = avar( "%s|id: %i", desc.c_str(), mId );
|
|
if(mObjectName)
|
|
desc = avar( "%s|name: %s", desc.c_str(), mObjectName);
|
|
if( mInternalName )
|
|
desc = avar( "%s|internal: %s", desc.c_str(), mInternalName );
|
|
if( mNameSpace )
|
|
desc = avar( "%s|nspace: %s", desc.c_str(), mNameSpace->mName );
|
|
if( mGroup )
|
|
desc = avar( "%s|group: %s", desc.c_str(), mGroup->getName() );
|
|
if( mCopySource )
|
|
desc = avar( "%s|copy: %s", desc.c_str(), mCopySource->getName() );
|
|
if( mPersistentId )
|
|
desc = avar( "%s|pid: %s", desc.c_str(), mPersistentId->getUUID().toString().c_str() );
|
|
|
|
return desc;
|
|
}
|
|
|
|
// Copies dynamic fields from one object to another, optionally limited by the settings for
|
|
// <filter> and <no_replace>. When true, <no_replace> prohibits the replacement of fields that
|
|
// already have a value. When <filter> is specified, only fields with leading characters that
|
|
// exactly match the characters in <filter> are copied.
|
|
void SimObject::assignDynamicFieldsFrom(SimObject* from, const char* filter, bool no_replace)
|
|
{
|
|
if (from->mFieldDictionary)
|
|
{
|
|
if( mFieldDictionary == NULL )
|
|
mFieldDictionary = new SimFieldDictionary;
|
|
mFieldDictionary->assignFrom(from->mFieldDictionary, filter, no_replace);
|
|
}
|
|
}
|
|
//=============================================================================
|
|
// Persistence.
|
|
//=============================================================================
|
|
// MARK: ---- Persistence ----
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::writeField(StringTableEntry fieldname, const char* value)
|
|
{
|
|
// Don't write empty fields.
|
|
if (!value || !*value)
|
|
return false;
|
|
|
|
// Don't write owner field for components
|
|
static StringTableEntry sOwner = StringTable->insert( "owner" );
|
|
if( fieldname == sOwner )
|
|
return false;
|
|
|
|
// Don't write ParentGroup
|
|
static StringTableEntry sParentGroup = StringTable->insert( "parentGroup" );
|
|
if( fieldname == sParentGroup )
|
|
return false;
|
|
|
|
// Don't write name, is within the parenthesis already
|
|
static StringTableEntry sName = StringTable->insert( "name" );
|
|
if( fieldname == sName )
|
|
return false;
|
|
|
|
// Don't write className, it is read for legacy support but we
|
|
// write it out as class.
|
|
static StringTableEntry sClassName = StringTable->insert( "className" );
|
|
if( fieldname == sClassName )
|
|
return false;
|
|
|
|
// Write persistent ID only if present.
|
|
static StringTableEntry sPersistentId = StringTable->insert( "persistentId" );
|
|
if( fieldname == sPersistentId && ( !value || !value[ 0 ] ) )
|
|
return false;
|
|
|
|
// Don't write hidden and locked flags if they are at their default value.
|
|
|
|
static StringTableEntry sHidden = StringTable->insert( "hidden" );
|
|
static StringTableEntry sLocked = StringTable->insert( "locked" );
|
|
|
|
if( fieldname == sHidden && !dAtob( value ) )
|
|
return false;
|
|
if( fieldname == sLocked && !dAtob( value ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::writeFields(Stream &stream, U32 tabStop)
|
|
{
|
|
// Write static fields.
|
|
|
|
const AbstractClassRep::FieldList &list = getFieldList();
|
|
|
|
for(U32 i = 0; i < list.size(); i++)
|
|
{
|
|
const AbstractClassRep::Field* f = &list[i];
|
|
|
|
// Skip the special field types.
|
|
if ( f->type >= AbstractClassRep::ARCFirstCustomField )
|
|
continue;
|
|
|
|
for(U32 j = 0; S32(j) < f->elementCount; j++)
|
|
{
|
|
char array[8];
|
|
dSprintf( array, 8, "%d", j );
|
|
const char *val = getDataField(StringTable->insert( f->pFieldname ), array );
|
|
|
|
// Make a copy for the field check.
|
|
if (!val)
|
|
continue;
|
|
|
|
U32 nBufferSize = dStrlen( val ) + 1;
|
|
FrameTemp<char> valCopy( nBufferSize );
|
|
dStrcpy( (char *)valCopy, val, nBufferSize );
|
|
|
|
if (!writeField(f->pFieldname, valCopy))
|
|
continue;
|
|
|
|
val = valCopy;
|
|
|
|
U32 expandedBufferSize = ( nBufferSize * 2 ) + dStrlen(f->pFieldname) + 32;
|
|
FrameTemp<char> expandedBuffer( expandedBufferSize );
|
|
if(f->elementCount == 1)
|
|
dSprintf(expandedBuffer, expandedBufferSize, "%s = \"", f->pFieldname);
|
|
else
|
|
dSprintf(expandedBuffer, expandedBufferSize, "%s[%d] = \"", f->pFieldname, j);
|
|
|
|
// detect and collapse relative path information
|
|
char fnBuf[1024];
|
|
if (f->type == TypeFilename ||
|
|
f->type == TypeStringFilename ||
|
|
f->type == TypeImageFilename ||
|
|
f->type == TypePrefabFilename ||
|
|
f->type == TypeShapeFilename)
|
|
{
|
|
Con::collapseScriptFilename(fnBuf, 1024, val);
|
|
val = fnBuf;
|
|
}
|
|
|
|
expandEscape((char*)expandedBuffer + dStrlen(expandedBuffer), val);
|
|
dStrcat(expandedBuffer, "\";\r\n", expandedBufferSize);
|
|
|
|
stream.writeTabs(tabStop);
|
|
stream.write(dStrlen(expandedBuffer),expandedBuffer);
|
|
}
|
|
}
|
|
|
|
// Write dynamic fields, if enabled.
|
|
|
|
if(mFieldDictionary && mCanSaveFieldDictionary)
|
|
mFieldDictionary->writeFields(this, stream, tabStop);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::write(Stream &stream, U32 tabStop, U32 flags)
|
|
{
|
|
if( !getCanSave() && !( flags & IgnoreCanSave ) )
|
|
return;
|
|
|
|
// Only output selected objects if they want that.
|
|
if((flags & SelectedOnly) && !isSelected())
|
|
return;
|
|
|
|
stream.writeTabs(tabStop);
|
|
char buffer[1024];
|
|
dSprintf(buffer, sizeof(buffer), "new %s(%s) {\r\n", getClassName(), getName() && !(flags & NoName) ? getName() : "");
|
|
stream.write(dStrlen(buffer), buffer);
|
|
writeFields(stream, tabStop + 1);
|
|
|
|
stream.writeTabs(tabStop);
|
|
stream.write(4, "};\r\n");
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::save(const char *pcFileName, bool bOnlySelected, const char *preappend)
|
|
{
|
|
static const char *beginMessage = "//--- OBJECT WRITE BEGIN ---";
|
|
static const char *endMessage = "//--- OBJECT WRITE END ---";
|
|
FileStream *stream;
|
|
FileObject f;
|
|
f.readMemory(pcFileName);
|
|
|
|
// check for flags <selected, ...>
|
|
U32 writeFlags = 0;
|
|
if(bOnlySelected)
|
|
writeFlags |= SimObject::SelectedOnly;
|
|
|
|
if((stream = FileStream::createAndOpen( pcFileName, Torque::FS::File::Write )) == NULL)
|
|
return false;
|
|
|
|
char docRoot[256];
|
|
char modRoot[256];
|
|
|
|
dStrcpy(docRoot, pcFileName, 256);
|
|
char *p = dStrrchr(docRoot, '/');
|
|
if (p) *++p = '\0';
|
|
else docRoot[0] = '\0';
|
|
|
|
dStrcpy(modRoot, pcFileName, 256);
|
|
p = dStrchr(modRoot, '/');
|
|
if (p) *++p = '\0';
|
|
else modRoot[0] = '\0';
|
|
|
|
Con::setVariable("$DocRoot", docRoot);
|
|
Con::setVariable("$ModRoot", modRoot);
|
|
|
|
const char *buffer;
|
|
while(!f.isEOF())
|
|
{
|
|
buffer = (const char *) f.readLine();
|
|
if(!String::compare(buffer, beginMessage))
|
|
break;
|
|
stream->write(dStrlen(buffer), buffer);
|
|
stream->write(2, "\r\n");
|
|
}
|
|
stream->write(dStrlen(beginMessage), beginMessage);
|
|
stream->write(2, "\r\n");
|
|
if ( preappend != NULL )
|
|
stream->write(dStrlen(preappend),preappend);
|
|
write(*stream, 0, writeFlags);
|
|
stream->write(dStrlen(endMessage), endMessage);
|
|
stream->write(2, "\r\n");
|
|
while(!f.isEOF())
|
|
{
|
|
buffer = (const char *) f.readLine();
|
|
if(!String::compare(buffer, endMessage))
|
|
break;
|
|
}
|
|
while(!f.isEOF())
|
|
{
|
|
buffer = (const char *) f.readLine();
|
|
stream->write(dStrlen(buffer), buffer);
|
|
stream->write(2, "\r\n");
|
|
}
|
|
|
|
Con::setVariable("$DocRoot", NULL);
|
|
Con::setVariable("$ModRoot", NULL);
|
|
|
|
delete stream;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
SimPersistID* SimObject::getOrCreatePersistentId()
|
|
{
|
|
if( !mPersistentId )
|
|
{
|
|
mPersistentId = SimPersistID::create( this );
|
|
mPersistentId->incRefCount();
|
|
}
|
|
return mPersistentId;
|
|
}
|
|
|
|
|
|
|
|
void SimObject::onTamlCustomRead(TamlCustomNodes const& customNodes)
|
|
{
|
|
// Debug Profiling.
|
|
//PROFILE_SCOPE(SimObject_OnTamlCustomRead);
|
|
|
|
// Fetch field list.
|
|
const AbstractClassRep::FieldList& fieldList = getFieldList();
|
|
const U32 fieldCount = fieldList.size();
|
|
for (U32 index = 0; index < fieldCount; ++index)
|
|
{
|
|
// Fetch field.
|
|
const AbstractClassRep::Field* pField = &fieldList[index];
|
|
|
|
// Ignore if field not appropriate.
|
|
if (pField->type == AbstractClassRep::StartArrayFieldType || pField->elementCount > 1)
|
|
{
|
|
// Find cell custom node.
|
|
const TamlCustomNode* pCustomCellNodes = NULL;
|
|
if (pField->pGroupname != NULL)
|
|
pCustomCellNodes = customNodes.findNode(pField->pGroupname);
|
|
if (!pCustomCellNodes)
|
|
{
|
|
char* niceFieldName = const_cast<char *>(pField->pFieldname);
|
|
niceFieldName[0] = dToupper(niceFieldName[0]);
|
|
String str_niceFieldName = String(niceFieldName);
|
|
pCustomCellNodes = customNodes.findNode(str_niceFieldName + "s");
|
|
}
|
|
|
|
// Continue if we have explicit cells.
|
|
if (pCustomCellNodes != NULL)
|
|
{
|
|
// Fetch children cell nodes.
|
|
const TamlCustomNodeVector& cellNodes = pCustomCellNodes->getChildren();
|
|
|
|
U8 idx = 0;
|
|
// Iterate cells.
|
|
for (TamlCustomNodeVector::const_iterator cellNodeItr = cellNodes.begin(); cellNodeItr != cellNodes.end(); ++cellNodeItr)
|
|
{
|
|
char buf[5];
|
|
dSprintf(buf, 5, "%d", idx);
|
|
|
|
// Fetch cell node.
|
|
TamlCustomNode* pCellNode = *cellNodeItr;
|
|
|
|
// Fetch node name.
|
|
StringTableEntry nodeName = pCellNode->getNodeName();
|
|
|
|
// Is this a valid alias?
|
|
if (nodeName != pField->pFieldname)
|
|
{
|
|
// No, so warn.
|
|
Con::warnf("SimObject::onTamlCustomRead() - Encountered an unknown custom name of '%s'. Only '%s' is valid.", nodeName, pField->pFieldname);
|
|
continue;
|
|
}
|
|
|
|
// Fetch fields.
|
|
const TamlCustomFieldVector& fields = pCellNode->getFields();
|
|
|
|
// Iterate property fields.
|
|
for (TamlCustomFieldVector::const_iterator fieldItr = fields.begin(); fieldItr != fields.end(); ++fieldItr)
|
|
{
|
|
// Fetch field.
|
|
const TamlCustomField* cField = *fieldItr;
|
|
|
|
// Fetch field name.
|
|
StringTableEntry fieldName = cField->getFieldName();
|
|
|
|
const AbstractClassRep::Field* field = findField(fieldName);
|
|
|
|
// Check common fields.
|
|
if (field)
|
|
{
|
|
setDataField(fieldName, buf, cField->getFieldValue());
|
|
}
|
|
else
|
|
{
|
|
// Unknown name so warn.
|
|
Con::warnf("SimObject::onTamlCustomRead() - Encountered an unknown custom field name of '%s'.", fieldName);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
idx++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::_setPersistentID( void* object, const char* index, const char* data )
|
|
{
|
|
SimObject* simObject = reinterpret_cast< SimObject* >( object );
|
|
|
|
// Make sure we don't already have a PID.
|
|
if( simObject->getPersistentId() )
|
|
{
|
|
Con::errorf( "SimObject::_setPersistentID - cannot set a persistent ID on an object that already has a persistent ID assigned." );
|
|
return false;
|
|
}
|
|
|
|
SimPersistID* pid;
|
|
Con::setData( TypePID, &pid, 0, 1, &data );
|
|
if ( !pid )
|
|
return false;
|
|
|
|
// Make sure it's not already bound to an object.
|
|
if( pid->getObject() )
|
|
{
|
|
AssertWarn( pid->getObject() != simObject, "Sim::_setPersistentID - PID is bound to this object yet not assigned to it!" );
|
|
|
|
SimObject* otherObj = pid->getObject();
|
|
Con::errorf( "SimObject::_setPersistentID - UUID is already used by another object: '%s' -> %i:%s (%s)",
|
|
data, otherObj->getId(), otherObj->getClassName(), otherObj->getName() );
|
|
|
|
return false;
|
|
}
|
|
|
|
pid->resolve( simObject );
|
|
pid->incRefCount();
|
|
simObject->mPersistentId = pid;
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::setFilename( const char* file )
|
|
{
|
|
if( file )
|
|
mFilename = StringTable->insert( file );
|
|
else
|
|
mFilename = StringTable->EmptyString();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::setDeclarationLine(U32 lineNumber)
|
|
{
|
|
mDeclarationLine = lineNumber;
|
|
}
|
|
|
|
//=============================================================================
|
|
// Management.
|
|
//=============================================================================
|
|
// MARK: ---- Management ----
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::registerObject()
|
|
{
|
|
AssertFatal( !mFlags.test( Added ), "reigsterObject - Object already registered!");
|
|
mFlags.clear(Deleted | Removed);
|
|
|
|
if(smForceId)
|
|
{
|
|
setId(smForcedId);
|
|
smForceId = false;
|
|
}
|
|
|
|
if( !mId )
|
|
{
|
|
mId = Sim::gNextObjectId++;
|
|
dSprintf( mIdString, sizeof( mIdString ), "%u", mId );
|
|
}
|
|
|
|
AssertFatal(Sim::gIdDictionary && Sim::gNameDictionary,
|
|
"SimObject::registerObject - tried to register an object before Sim::init()!");
|
|
|
|
Sim::gIdDictionary->insert(this);
|
|
|
|
Sim::gNameDictionary->insert(this);
|
|
|
|
// Notify object
|
|
bool ret = onAdd();
|
|
|
|
if(!ret)
|
|
unregisterObject();
|
|
|
|
AssertFatal(!ret || isProperlyAdded(), "Object did not call SimObject::onAdd()");
|
|
return ret;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::unregisterObject()
|
|
{
|
|
mFlags.set(Removed);
|
|
|
|
// Notify object first
|
|
onRemove();
|
|
|
|
// Clear out any pending notifications before
|
|
// we call our own, just in case they delete
|
|
// something that we have referenced.
|
|
clearAllNotifications();
|
|
|
|
// Notify all objects that are waiting for delete
|
|
// messages
|
|
if (getGroup())
|
|
getGroup()->removeObject(this);
|
|
|
|
processDeleteNotifies();
|
|
|
|
// Do removals from the Sim.
|
|
Sim::gNameDictionary->remove(this);
|
|
Sim::gIdDictionary->remove(this);
|
|
Sim::cancelPendingEvents(this);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::deleteObject()
|
|
{
|
|
Parent::destroySelf();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::_destroySelf()
|
|
{
|
|
AssertFatal( !isDeleted(), "SimObject::destroySelf - Object has already been deleted" );
|
|
AssertFatal( !isRemoved(), "SimObject::destroySelf - Object in the process of being removed" );
|
|
|
|
mFlags.set( Deleted );
|
|
|
|
if( mFlags.test( Added ) )
|
|
unregisterObject();
|
|
|
|
Parent::_destroySelf();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::destroySelf()
|
|
{
|
|
// When using the legacy console interop, we don't delete objects
|
|
// when their reference count drops to zero but rather defer their
|
|
// deletion until deleteObject() is called.
|
|
|
|
if( engineAPI::gUseConsoleInterop )
|
|
return;
|
|
|
|
Parent::destroySelf();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
class SimObjectDeleteEvent : public SimEvent
|
|
{
|
|
public:
|
|
void process(SimObject *object)
|
|
{
|
|
object->deleteObject();
|
|
}
|
|
};
|
|
|
|
void SimObject::safeDeleteObject()
|
|
{
|
|
Sim::postEvent( this, new SimObjectDeleteEvent, Sim::getCurrentTime() + 1 );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::setId(SimObjectId newId)
|
|
{
|
|
if(!mFlags.test(Added))
|
|
mId = newId;
|
|
else
|
|
{
|
|
// get this object out of the id dictionary if it's in it
|
|
Sim::gIdDictionary->remove(this);
|
|
|
|
// Free current Id.
|
|
// Assign new one.
|
|
mId = newId ? newId : Sim::gNextObjectId++;
|
|
Sim::gIdDictionary->insert(this);
|
|
}
|
|
|
|
dSprintf( mIdString, sizeof( mIdString ), "%u", mId );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::assignName(const char *name)
|
|
{
|
|
if(mObjectName && !isNameChangeAllowed() )
|
|
{
|
|
Con::errorf( "SimObject::assignName - not allowed to change name of object '%s'", mObjectName);
|
|
return;
|
|
}
|
|
|
|
// Added this assert 3/30/2007 because it is dumb to try to name
|
|
// a SimObject the same thing as it's class name -patw
|
|
//AssertFatal( dStricmp( getClassName(), name ), "Attempted to assign a name to a SimObject which matches it's type name." );
|
|
if( dStricmp( getClassName(), name ) == 0 )
|
|
Con::errorf( "SimObject::assignName - Assigning name '%s' to instance of object with type '%s'."
|
|
" This can cause namespace linking issues.", getClassName(), name );
|
|
|
|
StringTableEntry newName = NULL;
|
|
if(name[0])
|
|
newName = StringTable->insert(name);
|
|
|
|
onNameChange( newName );
|
|
|
|
if( mGroup )
|
|
mGroup->mNameDictionary.remove( this );
|
|
if( isProperlyAdded() )
|
|
{
|
|
unlinkNamespaces();
|
|
Sim::gNameDictionary->remove( this );
|
|
}
|
|
|
|
mObjectName = newName;
|
|
|
|
if( mGroup )
|
|
mGroup->mNameDictionary.insert( this );
|
|
if( isProperlyAdded() )
|
|
{
|
|
Sim::gNameDictionary->insert( this );
|
|
linkNamespaces();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::registerObject(U32 id)
|
|
{
|
|
setId(id);
|
|
return registerObject();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::registerObject(const char *name)
|
|
{
|
|
assignName(name);
|
|
return registerObject();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::registerObject(const char *name, U32 id)
|
|
{
|
|
setId(id);
|
|
assignName(name);
|
|
return registerObject();
|
|
}
|
|
|
|
//=============================================================================
|
|
// Introspection.
|
|
//=============================================================================
|
|
// MARK: ---- Introspection ----
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::isMethod( const char* methodName )
|
|
{
|
|
if( !methodName || !methodName[0] )
|
|
return false;
|
|
|
|
if (CInterface::isMethod(this->getName(), methodName) || CInterface::isMethod(this->getClassName(), methodName)) {
|
|
return true;
|
|
}
|
|
|
|
StringTableEntry stname = StringTable->insert( methodName );
|
|
|
|
if( getNamespace() )
|
|
return ( getNamespace()->lookup( stname ) != NULL );
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::isField( const char* fieldName, bool includeStatic, bool includeDynamic )
|
|
{
|
|
const char* strFieldName = StringTable->insert( fieldName );
|
|
|
|
if( includeStatic && getClassRep()->findField( strFieldName ) )
|
|
return true;
|
|
|
|
if( includeDynamic && getFieldDictionary() && getFieldDictionary()->findDynamicField( strFieldName ) )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::assignDynamicFieldsFrom(SimObject* parent)
|
|
{
|
|
if(parent->mFieldDictionary)
|
|
{
|
|
if( mFieldDictionary == NULL )
|
|
mFieldDictionary = new SimFieldDictionary;
|
|
mFieldDictionary->assignFrom(parent->mFieldDictionary);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::assignFieldsFrom(SimObject *parent)
|
|
{
|
|
// Only allow field assigns from objects of the same class or
|
|
// a superclass.
|
|
|
|
if( getClassRep()->isClass( parent->getClassRep() ) )
|
|
{
|
|
const AbstractClassRep::FieldList &list = parent->getFieldList();
|
|
|
|
// copy out all the fields:
|
|
for(U32 i = 0; i < list.size(); i++)
|
|
{
|
|
const AbstractClassRep::Field* f = &list[i];
|
|
|
|
// Skip the special field types.
|
|
if ( f->type >= AbstractClassRep::ARCFirstCustomField )
|
|
continue;
|
|
|
|
// Skip certain fields that we don't want to see copied so we don't
|
|
// get error messages from their setters.
|
|
|
|
static StringTableEntry sName = StringTable->insert( "name" );
|
|
static StringTableEntry sPersistentId = StringTable->insert( "persistentId" );
|
|
|
|
if( f->pFieldname == sName || f->pFieldname == sPersistentId )
|
|
continue;
|
|
|
|
S32 lastField = f->elementCount - 1;
|
|
for(S32 j = 0; j <= lastField; j++)
|
|
{
|
|
const char* fieldVal = (*f->getDataFn)( parent, Con::getData(f->type, (void *) (((const char *)parent) + f->offset), j, f->table, f->flag));
|
|
|
|
// Don't assign the field is the pointer is null or if
|
|
// the field is not empty and writing it was disallowed.
|
|
if ( !fieldVal || ( fieldVal[0] && !writeField( f->pFieldname, fieldVal ) ) )
|
|
continue;
|
|
|
|
// code copied from SimObject::setDataField().
|
|
// TODO: paxorr: abstract this into a better setData / getData that considers prot fields.
|
|
FrameTemp<char> buffer(2048);
|
|
FrameTemp<char> bufferSecure(2048); // This buffer is used to make a copy of the data
|
|
ConsoleBaseType *cbt = ConsoleBaseType::getType( f->type );
|
|
const char* szBuffer = cbt->prepData( fieldVal, buffer, 2048 );
|
|
dMemset( bufferSecure, 0, 2048 );
|
|
dMemcpy( bufferSecure, szBuffer, dStrlen( szBuffer ) );
|
|
|
|
if((*f->setDataFn)( this, NULL, bufferSecure ) )
|
|
Con::setData(f->type, (void *) (((const char *)this) + f->offset), j, 1, &fieldVal, f->table);
|
|
|
|
if (f->networkMask != 0)
|
|
{
|
|
NetObject* netObj = static_cast<NetObject*>(this);
|
|
netObj->setMaskBits(f->networkMask);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Con::errorf( "SimObject::assignFieldsFrom() - cannot assigned fields from object of type '%s' to object of type '%s'",
|
|
parent->getClassName(), getClassName()
|
|
);
|
|
}
|
|
|
|
assignDynamicFieldsFrom(parent);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::setDataField(StringTableEntry slotName, const char *array, const char *value)
|
|
{
|
|
// first search the static fields if enabled
|
|
if(mFlags.test(ModStaticFields))
|
|
{
|
|
const AbstractClassRep::Field *fld = findField(slotName);
|
|
if(fld)
|
|
{
|
|
// Skip the special field types as they are not data.
|
|
if ( fld->type >= AbstractClassRep::ARCFirstCustomField )
|
|
return;
|
|
|
|
S32 array1 = array ? dAtoi(array) : 0;
|
|
|
|
// Here we check to see if <this> is a datablock and if <value>
|
|
// starts with "$$". If both true than save value as a runtime substitution.
|
|
if (dynamic_cast<SimDataBlock*>(this) && value[0] == '$' && value[1] == '$')
|
|
{
|
|
if (!this->allowSubstitutions())
|
|
{
|
|
Con::errorf("Substitution Error: %s datablocks do not allow \"$$\" field substitutions. [%s]",
|
|
this->getClassName(), this->getName());
|
|
return;
|
|
}
|
|
|
|
if (fld->doNotSubstitute)
|
|
{
|
|
Con::errorf("Substitution Error: field \"%s\" of datablock %s prohibits \"$$\" field substitutions. [%s]",
|
|
slotName, this->getClassName(), this->getName());
|
|
return;
|
|
}
|
|
|
|
// add the substitution
|
|
((SimDataBlock*)this)->addSubstitution(slotName, array1, value);
|
|
return;
|
|
}
|
|
|
|
if(array1 >= 0 && array1 < fld->elementCount && fld->elementCount >= 1)
|
|
{
|
|
// If the set data notify callback returns true, then go ahead and
|
|
// set the data, otherwise, assume the set notify callback has either
|
|
// already set the data, or has deemed that the data should not
|
|
// be set at all.
|
|
FrameTemp<char> buffer(2048);
|
|
FrameTemp<char> bufferSecure(2048); // This buffer is used to make a copy of the data
|
|
// so that if the prep functions or any other functions use the string stack, the data
|
|
// is not corrupted.
|
|
|
|
ConsoleBaseType *cbt = ConsoleBaseType::getType( fld->type );
|
|
AssertFatal( cbt != NULL, "Could not resolve Type Id." );
|
|
|
|
const char* szBuffer = cbt->prepData( value, buffer, 2048 );
|
|
dMemset( bufferSecure, 0, 2048 );
|
|
dMemcpy( bufferSecure, szBuffer, dStrlen( szBuffer ) );
|
|
|
|
if( (*fld->setDataFn)( this, array, bufferSecure ) )
|
|
Con::setData(fld->type, (void *) (((const char *)this) + fld->offset), array1, 1, &value, fld->table);
|
|
|
|
if(fld->validator)
|
|
fld->validator->validateType(this, (void *) (((const char *)this) + fld->offset));
|
|
|
|
if (fld->networkMask != 0)
|
|
{
|
|
NetObject* netObj = static_cast<NetObject*>(this);
|
|
netObj->setMaskBits(fld->networkMask);
|
|
}
|
|
|
|
onStaticModified( slotName, value );
|
|
|
|
return;
|
|
}
|
|
|
|
if(fld->validator)
|
|
fld->validator->validateType(this, (void *) (((const char *)this) + fld->offset));
|
|
|
|
onStaticModified( slotName, value );
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(mFlags.test(ModDynamicFields))
|
|
{
|
|
if(!mFieldDictionary)
|
|
mFieldDictionary = new SimFieldDictionary;
|
|
|
|
if(!array)
|
|
{
|
|
mFieldDictionary->setFieldValue(slotName, value);
|
|
onDynamicModified( slotName, value );
|
|
}
|
|
else
|
|
{
|
|
char buf[256];
|
|
dStrcpy(buf, slotName, 256);
|
|
dStrcat(buf, array, 256);
|
|
StringTableEntry permanentSlotName = StringTable->insert(buf);
|
|
mFieldDictionary->setFieldValue(permanentSlotName, value);
|
|
onDynamicModified( permanentSlotName, value );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const char *SimObject::getDataField(StringTableEntry slotName, const char *array)
|
|
{
|
|
if(mFlags.test(ModStaticFields))
|
|
{
|
|
S32 array1 = array ? dAtoi(array) : -1;
|
|
const AbstractClassRep::Field *fld = findField(slotName);
|
|
|
|
if(fld)
|
|
{
|
|
if(array1 == -1 && fld->elementCount == 1)
|
|
return (*fld->getDataFn)( this, Con::getData(fld->type, (void *) (((const char *)this) + fld->offset), 0, fld->table, fld->flag) );
|
|
if(array1 >= 0 && array1 < fld->elementCount)
|
|
return (*fld->getDataFn)( this, Con::getData(fld->type, (void *) (((const char *)this) + fld->offset), array1, fld->table, fld->flag) );// + typeSizes[fld.type] * array1));
|
|
return "";
|
|
}
|
|
}
|
|
|
|
if(mFlags.test(ModDynamicFields))
|
|
{
|
|
if(!mFieldDictionary)
|
|
return "";
|
|
|
|
if(!array)
|
|
{
|
|
if (const char* val = mFieldDictionary->getFieldValue(slotName))
|
|
return val;
|
|
}
|
|
else
|
|
{
|
|
static char buf[256];
|
|
dStrcpy(buf, slotName, 256);
|
|
dStrcat(buf, array, 256);
|
|
if (const char* val = mFieldDictionary->getFieldValue(StringTable->insert(buf)))
|
|
return val;
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
|
|
const char *SimObject::getPrefixedDataField(StringTableEntry fieldName, const char *array)
|
|
{
|
|
// Sanity!
|
|
AssertFatal(fieldName != NULL, "Cannot get field value with NULL field name.");
|
|
|
|
// Fetch field value.
|
|
const char* pFieldValue = getDataField(fieldName, array);
|
|
|
|
// Sanity.
|
|
//AssertFatal(pFieldValue != NULL, "Field value cannot be NULL.");
|
|
if (!pFieldValue)
|
|
return NULL;
|
|
|
|
// Return without the prefix if there's no value.
|
|
if (*pFieldValue == 0)
|
|
return StringTable->EmptyString();
|
|
|
|
// Fetch the field prefix.
|
|
StringTableEntry fieldPrefix = getDataFieldPrefix(fieldName);
|
|
|
|
// Sanity!
|
|
AssertFatal(fieldPrefix != NULL, "Field prefix cannot be NULL.");
|
|
|
|
// Calculate a buffer size including prefix.
|
|
const U32 valueBufferSize = dStrlen(fieldPrefix) + dStrlen(pFieldValue) + 1;
|
|
|
|
// Fetch a buffer.
|
|
char* pValueBuffer = Con::getReturnBuffer(valueBufferSize);
|
|
|
|
// Format the value buffer.
|
|
dSprintf(pValueBuffer, valueBufferSize, "%s%s", fieldPrefix, pFieldValue);
|
|
|
|
return pValueBuffer;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::setPrefixedDataField(StringTableEntry fieldName, const char *array, const char *value)
|
|
{
|
|
// Sanity!
|
|
AssertFatal(fieldName != NULL, "Cannot set object field value with NULL field name.");
|
|
AssertFatal(value != NULL, "Field value cannot be NULL.");
|
|
|
|
// Set value without prefix if there's no value.
|
|
if (*value == 0)
|
|
{
|
|
setDataField(fieldName, NULL, value);
|
|
return;
|
|
}
|
|
|
|
// Fetch the field prefix.
|
|
StringTableEntry fieldPrefix = getDataFieldPrefix(fieldName);
|
|
|
|
// Sanity.
|
|
AssertFatal(fieldPrefix != NULL, "Field prefix cannot be NULL.");
|
|
|
|
// Do we have a field prefix?
|
|
if (fieldPrefix == StringTable->EmptyString())
|
|
{
|
|
// No, so set the data field in the usual way.
|
|
setDataField(fieldName, NULL, value);
|
|
return;
|
|
}
|
|
|
|
// Yes, so fetch the length of the field prefix.
|
|
const U32 fieldPrefixLength = dStrlen(fieldPrefix);
|
|
|
|
// Yes, so does it start with the object field prefix?
|
|
if (dStrnicmp(value, fieldPrefix, fieldPrefixLength) != 0)
|
|
{
|
|
// No, so set the data field in the usual way.
|
|
setDataField(fieldName, NULL, value);
|
|
return;
|
|
}
|
|
|
|
// Yes, so set the data excluding the prefix.
|
|
setDataField(fieldName, NULL, value + fieldPrefixLength);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const char *SimObject::getPrefixedDynamicDataField(StringTableEntry fieldName, const char *array, const S32 fieldType)
|
|
{
|
|
// Sanity!
|
|
AssertFatal(fieldName != NULL, "Cannot get field value with NULL field name.");
|
|
|
|
// Fetch field value.
|
|
const char* pFieldValue = getDataField(fieldName, array);
|
|
|
|
// Sanity.
|
|
AssertFatal(pFieldValue != NULL, "Field value cannot be NULL.");
|
|
|
|
// Return the field if no field type is specified.
|
|
if (fieldType == -1)
|
|
return pFieldValue;
|
|
|
|
// Return without the prefix if there's no value.
|
|
if (*pFieldValue == 0)
|
|
return StringTable->EmptyString();
|
|
|
|
// Fetch the console base type.
|
|
ConsoleBaseType* pConsoleBaseType = ConsoleBaseType::getType(fieldType);
|
|
|
|
// Did we find the console base type?
|
|
if (pConsoleBaseType == NULL)
|
|
{
|
|
// No, so warn.
|
|
Con::warnf("getPrefixedDynamicDataField() - Invalid field type '%d' specified for field '%s' with value '%s'.",
|
|
fieldType, fieldName, pFieldValue);
|
|
}
|
|
|
|
// Fetch the field prefix.
|
|
StringTableEntry fieldPrefix = pConsoleBaseType->getTypePrefix();
|
|
|
|
// Sanity!
|
|
AssertFatal(fieldPrefix != NULL, "Field prefix cannot be NULL.");
|
|
|
|
// Calculate a buffer size including prefix.
|
|
const U32 valueBufferSize = dStrlen(fieldPrefix) + dStrlen(pFieldValue) + 1;
|
|
|
|
// Fetch a buffer.
|
|
char* pValueBuffer = Con::getReturnBuffer(valueBufferSize);
|
|
|
|
// Format the value buffer.
|
|
dSprintf(pValueBuffer, valueBufferSize, "%s%s", fieldPrefix, pFieldValue);
|
|
|
|
return pValueBuffer;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::setPrefixedDynamicDataField(StringTableEntry fieldName, const char *array, const char *value, const S32 fieldType)
|
|
{
|
|
// Sanity!
|
|
AssertFatal(fieldName != NULL, "Cannot set object field value with NULL field name.");
|
|
AssertFatal(value != NULL, "Field value cannot be NULL.");
|
|
|
|
// Set value without prefix if no field type was specified.
|
|
if (fieldType == -1)
|
|
{
|
|
setDataField(fieldName, NULL, value);
|
|
return;
|
|
}
|
|
|
|
// Fetch the console base type.
|
|
ConsoleBaseType* pConsoleBaseType = ConsoleBaseType::getType(fieldType);
|
|
|
|
// Did we find the console base type?
|
|
if (pConsoleBaseType == NULL)
|
|
{
|
|
// No, so warn.
|
|
Con::warnf("setPrefixedDynamicDataField() - Invalid field type '%d' specified for field '%s' with value '%s'.",
|
|
fieldType, fieldName, value);
|
|
}
|
|
|
|
// Set value without prefix if there's no value or we didn't find the console base type.
|
|
if (*value == 0 || pConsoleBaseType == NULL)
|
|
{
|
|
setDataField(fieldName, NULL, value);
|
|
return;
|
|
}
|
|
|
|
// Fetch the field prefix.
|
|
StringTableEntry fieldPrefix = pConsoleBaseType->getTypePrefix();
|
|
|
|
// Sanity.
|
|
AssertFatal(fieldPrefix != NULL, "Field prefix cannot be NULL.");
|
|
|
|
// Do we have a field prefix?
|
|
if (fieldPrefix == StringTable->EmptyString())
|
|
{
|
|
// No, so set the data field in the usual way.
|
|
setDataField(fieldName, NULL, value);
|
|
return;
|
|
}
|
|
|
|
// Yes, so fetch the length of the field prefix.
|
|
const U32 fieldPrefixLength = dStrlen(fieldPrefix);
|
|
|
|
// Yes, so does it start with the object field prefix?
|
|
if (dStrnicmp(value, fieldPrefix, fieldPrefixLength) != 0)
|
|
{
|
|
// No, so set the data field in the usual way.
|
|
setDataField(fieldName, NULL, value);
|
|
return;
|
|
}
|
|
|
|
// Yes, so set the data excluding the prefix.
|
|
setDataField(fieldName, NULL, value + fieldPrefixLength);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
StringTableEntry SimObject::getDataFieldPrefix(StringTableEntry fieldName)
|
|
{
|
|
// Sanity!
|
|
AssertFatal(fieldName != NULL, "Cannot get field prefix with NULL field name.");
|
|
|
|
// Find the field.
|
|
const AbstractClassRep::Field* pField = findField(fieldName);
|
|
|
|
// Return nothing if field was not found.
|
|
if (pField == NULL)
|
|
return StringTable->EmptyString();
|
|
|
|
// Yes, so fetch the console base type.
|
|
ConsoleBaseType* pConsoleBaseType = ConsoleBaseType::getType(pField->type);
|
|
|
|
// Fetch the type prefix.
|
|
return pConsoleBaseType->getTypePrefix();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
U32 SimObject::getDataFieldType( StringTableEntry slotName, const char* array )
|
|
{
|
|
const AbstractClassRep::Field* field = findField( slotName );
|
|
if(field)
|
|
return field->type;
|
|
|
|
// Check dynamic fields
|
|
if(!mFieldDictionary)
|
|
return 0;
|
|
|
|
if(array == NULL || *array == 0)
|
|
return mFieldDictionary->getFieldType( slotName );
|
|
else
|
|
{
|
|
static char buf[256];
|
|
dStrcpy( buf, slotName, 256 );
|
|
dStrcat( buf, array, 256 );
|
|
|
|
return mFieldDictionary->getFieldType( StringTable->insert( buf ) );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::setDataFieldType(const U32 fieldTypeId, StringTableEntry slotName, const char *array)
|
|
{
|
|
// This only works on dynamic fields, bail if we have no field dictionary
|
|
if(!mFieldDictionary)
|
|
return;
|
|
|
|
if(array == NULL || *array == 0)
|
|
{
|
|
mFieldDictionary->setFieldType( slotName, fieldTypeId );
|
|
onDynamicModified( slotName, mFieldDictionary->getFieldValue(slotName) );
|
|
}
|
|
else
|
|
{
|
|
static char buf[256];
|
|
dStrcpy( buf, slotName, 256 );
|
|
dStrcat( buf, array, 256 );
|
|
|
|
mFieldDictionary->setFieldType( StringTable->insert( buf ), fieldTypeId );
|
|
onDynamicModified( slotName, mFieldDictionary->getFieldValue(slotName) );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::setDataFieldType(const char *typeName, StringTableEntry slotName, const char *array)
|
|
{
|
|
// This only works on dynamic fields, bail if we have no field dictionary
|
|
if(!mFieldDictionary)
|
|
return;
|
|
|
|
if(array == NULL || *array == 0)
|
|
mFieldDictionary->setFieldType( slotName, typeName );
|
|
else
|
|
{
|
|
static char buf[256];
|
|
dStrcpy( buf, slotName, 256 );
|
|
dStrcat( buf, array, 256 );
|
|
StringTableEntry permanentSlotName = StringTable->insert(buf);
|
|
|
|
mFieldDictionary->setFieldType( permanentSlotName, typeName );
|
|
onDynamicModified( permanentSlotName, mFieldDictionary->getFieldValue(permanentSlotName) );
|
|
}
|
|
}
|
|
|
|
// This is the copy-constructor used to create temporary datablock clones.
|
|
// The <temp_clone> argument is added to distinguish this copy-constructor
|
|
// from any general-purpose copy-constructor that might be needed in the
|
|
// future. <temp_clone> should always be true when creating temporary
|
|
// datablock clones.
|
|
//
|
|
SimObject::SimObject(const SimObject& other, bool temp_clone)
|
|
{
|
|
is_temp_clone = temp_clone;
|
|
|
|
mObjectName = other.mObjectName;
|
|
mOriginalName = other.mOriginalName;
|
|
nextNameObject = other.nextNameObject;
|
|
nextManagerNameObject = other.nextManagerNameObject;
|
|
nextIdObject = other.nextIdObject;
|
|
mGroup = other.mGroup;
|
|
mFlags = other.mFlags;
|
|
mProgenitorFile = other.mProgenitorFile;
|
|
mCopySource = other.mCopySource;
|
|
mFieldDictionary = other.mFieldDictionary;
|
|
//mIdString = other.mIdString; // special treatment (see below)
|
|
mFilename = other.mFilename;
|
|
mDeclarationLine = other.mDeclarationLine;
|
|
mNotifyList = other.mNotifyList;
|
|
mId = other.mId;
|
|
mInternalName = other.mInternalName;
|
|
mCanSaveFieldDictionary = other.mCanSaveFieldDictionary;
|
|
mPersistentId = other.mPersistentId;
|
|
mNameSpace = other.mNameSpace;
|
|
mClassName = other.mClassName;
|
|
mSuperClassName = other.mSuperClassName;
|
|
preventNameChanging = other.preventNameChanging;
|
|
|
|
if (mId)
|
|
dSprintf( mIdString, sizeof( mIdString ), "%u", mId );
|
|
else
|
|
mIdString[ 0 ] = '\0';
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::dumpClassHierarchy()
|
|
{
|
|
AbstractClassRep* pRep = getClassRep();
|
|
while(pRep)
|
|
{
|
|
Con::warnf("%s ->", pRep->getClassName());
|
|
pRep = pRep->getParentClass();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
SimObject* SimObject::clone()
|
|
{
|
|
if( !getClassRep() )
|
|
return NULL;
|
|
|
|
ConsoleObject* conObject = getClassRep()->create();
|
|
if( !conObject )
|
|
return NULL;
|
|
|
|
SimObject* simObject = dynamic_cast< SimObject* >( conObject );
|
|
if( !simObject )
|
|
{
|
|
delete conObject;
|
|
return NULL;
|
|
}
|
|
|
|
simObject->assignFieldsFrom( this );
|
|
|
|
String name = Sim::getUniqueName( getName() );
|
|
if( !simObject->registerObject( name ) )
|
|
{
|
|
delete simObject;
|
|
return NULL;
|
|
}
|
|
|
|
if( getGroup() )
|
|
getGroup()->addObject( simObject );
|
|
|
|
return simObject;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
SimObject* SimObject::deepClone()
|
|
{
|
|
return clone();
|
|
}
|
|
|
|
//=============================================================================
|
|
// Grouping.
|
|
//=============================================================================
|
|
// MARK: ---- Grouping ----
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
SimObject* SimObject::findObject( const char* )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::isChildOfGroup(SimGroup* pGroup)
|
|
{
|
|
if(!pGroup)
|
|
return false;
|
|
|
|
//if we *are* the group in question,
|
|
//return true:
|
|
if(pGroup == dynamic_cast<SimGroup*>(this))
|
|
return true;
|
|
|
|
SimGroup* temp = mGroup;
|
|
while(temp)
|
|
{
|
|
if(temp == pGroup)
|
|
return true;
|
|
temp = temp->mGroup;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::addToSet(SimObjectId spid)
|
|
{
|
|
if (mFlags.test(Added) == false)
|
|
return false;
|
|
|
|
SimObject* ptr = Sim::findObject(spid);
|
|
if (ptr)
|
|
{
|
|
SimSet* sp = dynamic_cast<SimSet*>(ptr);
|
|
AssertFatal(sp != 0,
|
|
"SimObject::addToSet: "
|
|
"ObjectId does not refer to a set object");
|
|
sp->addObject(this);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::addToSet(const char *ObjectName)
|
|
{
|
|
if (mFlags.test(Added) == false)
|
|
return false;
|
|
|
|
SimObject* ptr = Sim::findObject(ObjectName);
|
|
if (ptr)
|
|
{
|
|
SimSet* sp = dynamic_cast<SimSet*>(ptr);
|
|
AssertFatal(sp != 0,
|
|
"SimObject::addToSet: "
|
|
"ObjectName does not refer to a set object");
|
|
sp->addObject(this);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::removeFromSet(SimObjectId sid)
|
|
{
|
|
if (mFlags.test(Added) == false)
|
|
return false;
|
|
|
|
SimSet *set;
|
|
if(Sim::findObject(sid, set))
|
|
{
|
|
set->removeObject(this);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::removeFromSet(const char *objectName)
|
|
{
|
|
if (mFlags.test(Added) == false)
|
|
return false;
|
|
|
|
SimSet *set;
|
|
if(Sim::findObject(objectName, set))
|
|
{
|
|
set->removeObject(this);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::dumpGroupHierarchy()
|
|
{
|
|
String className( getClassName() );
|
|
String objectName( getName() );
|
|
|
|
Con::warnf( "[%i] %s - %s ->", getId(), className.c_str(), objectName.c_str() );
|
|
|
|
if ( mGroup )
|
|
mGroup->dumpGroupHierarchy();
|
|
}
|
|
|
|
//=============================================================================
|
|
// Events.
|
|
//=============================================================================
|
|
// MARK: ---- Events ----
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::onAdd()
|
|
{
|
|
mFlags.set(Added);
|
|
|
|
linkNamespaces();
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::onRemove()
|
|
{
|
|
mFlags.clear(Added);
|
|
|
|
unlinkNamespaces();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::onGroupAdd()
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::onGroupRemove()
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::onDeleteNotify(SimObject*)
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::onNameChange(const char*)
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::onStaticModified(const char* slotName, const char* newValue)
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::onDynamicModified(const char* slotName, const char* newValue)
|
|
{
|
|
}
|
|
|
|
//=============================================================================
|
|
// Notifications.
|
|
//=============================================================================
|
|
// MARK: ---- Notifications ----
|
|
|
|
static Chunker<SimObject::Notify> notifyChunker(128000);
|
|
SimObject::Notify *SimObject::mNotifyFreeList = NULL;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
SimObject::Notify *SimObject::allocNotify()
|
|
{
|
|
if(mNotifyFreeList)
|
|
{
|
|
SimObject::Notify *ret = mNotifyFreeList;
|
|
mNotifyFreeList = ret->next;
|
|
return ret;
|
|
}
|
|
return notifyChunker.alloc();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::freeNotify(SimObject::Notify* note)
|
|
{
|
|
AssertFatal(note->type != SimObject::Notify::Invalid, "Invalid notify");
|
|
note->type = SimObject::Notify::Invalid;
|
|
note->next = mNotifyFreeList;
|
|
mNotifyFreeList = note;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
SimObject::Notify* SimObject::removeNotify(void *ptr, SimObject::Notify::Type type)
|
|
{
|
|
Notify **list = &mNotifyList;
|
|
while(*list)
|
|
{
|
|
if((*list)->ptr == ptr && (*list)->type == type)
|
|
{
|
|
SimObject::Notify *ret = *list;
|
|
*list = ret->next;
|
|
return ret;
|
|
}
|
|
list = &((*list)->next);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::deleteNotify(SimObject* obj)
|
|
{
|
|
AssertFatal(!obj->isDeleted(),
|
|
"SimManager::deleteNotify: Object is being deleted");
|
|
Notify *note = allocNotify();
|
|
note->ptr = (void *) this;
|
|
note->next = obj->mNotifyList;
|
|
note->type = Notify::DeleteNotify;
|
|
obj->mNotifyList = note;
|
|
|
|
note = allocNotify();
|
|
note->ptr = (void *) obj;
|
|
note->next = mNotifyList;
|
|
note->type = Notify::ClearNotify;
|
|
mNotifyList = note;
|
|
|
|
//obj->deleteNotifyList.pushBack(this);
|
|
//clearNotifyList.pushBack(obj);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::registerReference(SimObject **ptr)
|
|
{
|
|
Notify *note = allocNotify();
|
|
note->ptr = (void *) ptr;
|
|
note->next = mNotifyList;
|
|
note->type = Notify::ObjectRef;
|
|
mNotifyList = note;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::unregisterReference(SimObject **ptr)
|
|
{
|
|
Notify *note = removeNotify((void *) ptr, Notify::ObjectRef);
|
|
if(note)
|
|
{
|
|
freeNotify(note);
|
|
|
|
if( mFlags.test( AutoDelete ) )
|
|
{
|
|
for( Notify* n = mNotifyList; n != NULL; n = n->next )
|
|
if( n->type == Notify::ObjectRef )
|
|
return;
|
|
|
|
deleteObject();
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::clearNotify(SimObject* obj)
|
|
{
|
|
Notify *note = obj->removeNotify((void *) this, Notify::DeleteNotify);
|
|
if(note)
|
|
freeNotify(note);
|
|
|
|
note = removeNotify((void *) obj, Notify::ClearNotify);
|
|
if(note)
|
|
freeNotify(note);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::processDeleteNotifies()
|
|
{
|
|
// clear out any delete notifies and
|
|
// object refs.
|
|
|
|
while(mNotifyList)
|
|
{
|
|
Notify *note = mNotifyList;
|
|
mNotifyList = note->next;
|
|
|
|
AssertFatal(note->type != Notify::ClearNotify, "Clear notes should be all gone.");
|
|
|
|
if(note->type == Notify::DeleteNotify)
|
|
{
|
|
SimObject *obj = (SimObject *) note->ptr;
|
|
Notify *cnote = obj->removeNotify((void *)this, Notify::ClearNotify);
|
|
obj->onDeleteNotify(this);
|
|
freeNotify(cnote);
|
|
}
|
|
else
|
|
{
|
|
// it must be an object ref - a pointer refs this object
|
|
*((SimObject **) note->ptr) = NULL;
|
|
}
|
|
freeNotify(note);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::clearAllNotifications()
|
|
{
|
|
for(Notify **cnote = &mNotifyList; *cnote; )
|
|
{
|
|
Notify *temp = *cnote;
|
|
if(temp->type == Notify::ClearNotify)
|
|
{
|
|
*cnote = temp->next;
|
|
Notify *note = ((SimObject *) temp->ptr)->removeNotify((void *) this, Notify::DeleteNotify);
|
|
freeNotify(temp);
|
|
if ( note )
|
|
freeNotify(note);
|
|
}
|
|
else
|
|
cnote = &(temp->next);
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
// Namespaces.
|
|
//=============================================================================
|
|
// MARK: ---- Namespaces ----
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::linkNamespaces()
|
|
{
|
|
// Don't link if we already have a namespace linkage in place.
|
|
// If you want to change namespace linking, first call unlinkNamespaces()
|
|
// while still having the class namespace fields matching the current
|
|
// setup.
|
|
|
|
if (mNameSpace)
|
|
{
|
|
Con::warnf("SimObject::linkNamespaces -- Namespace linkage already in place %s", mNameSpace->getName());
|
|
return;
|
|
}
|
|
// Get the namespace for the C++ class.
|
|
|
|
Namespace* cppNamespace = getClassRep()->getNameSpace();
|
|
|
|
// Parent namespace defaults to namespace of C++ class.
|
|
|
|
Namespace* parentNamespace = cppNamespace;
|
|
|
|
// Perform superclass linking, if requested.
|
|
|
|
if( mSuperClassName && mSuperClassName[ 0 ] )
|
|
{
|
|
// Look up the superclass namespace.
|
|
|
|
Namespace* superClassNamespace = Con::lookupNamespace( mSuperClassName );
|
|
|
|
// If packages are active and adding to the superclass namespace, then we will
|
|
// have multiple packages in a parent chain that all have the same name.
|
|
// Con::lookupNamespace returns the bottom-most package in the chain to us so
|
|
// in order to properly link namespace here without conflicting with the package
|
|
// mechanism, we need to properly link child namespaces to the bottom-most namespace
|
|
// while linking parent namespaces to the topmost namespace. To find the latter
|
|
// one, we walk up the hierarchy here.
|
|
|
|
Namespace* superClassNamespacePackageRoot = superClassNamespace->getPackageRoot();
|
|
|
|
// Link the superclass namespace to the C++ class namespace.
|
|
|
|
if( superClassNamespacePackageRoot->getParent() == NULL )
|
|
{
|
|
// The superclass namespace isn't linked yet so we just
|
|
// link it to the C++ class namespace and make that our parent.
|
|
// No increasing parent reference counts is needed in this case.
|
|
|
|
bool ok = superClassNamespacePackageRoot->classLinkTo( cppNamespace );
|
|
AssertFatal( ok, "SimObject::linkNamespaces - failed to link new namespace to c++ class name" );
|
|
parentNamespace = superClassNamespace;
|
|
}
|
|
else
|
|
{
|
|
// In debug builds, make sure the namespace hierarchy that's been
|
|
// put into place actually makes sense and leads back to the C++
|
|
// class namespace.
|
|
|
|
#ifdef TORQUE_DEBUG
|
|
|
|
bool foundClassNameNS = false;
|
|
for( Namespace* linkWalk = superClassNamespacePackageRoot->getParent(); linkWalk != NULL;
|
|
linkWalk = linkWalk->getParent() )
|
|
{
|
|
if( linkWalk == cppNamespace )
|
|
{
|
|
foundClassNameNS = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !foundClassNameNS )
|
|
{
|
|
// C++ class namespace not in parent link chain. Warn about it.
|
|
|
|
Con::errorf(
|
|
"SimObject::linkNamespaces - cannot link object to superclass %s because c++ class %s is not in the parent namespace chain. Linking object to c++ class.",
|
|
mSuperClassName,
|
|
getClassName()
|
|
);
|
|
|
|
// Clear out superclass name so we don't come across it during
|
|
// unlinking.
|
|
|
|
mSuperClassName = NULL;
|
|
}
|
|
else
|
|
|
|
#endif
|
|
{
|
|
// Super link is ok.
|
|
|
|
parentNamespace = superClassNamespace;
|
|
|
|
// Now increase the reference count of all namespaces in the parent hierarchy
|
|
// (up to the C++ class).
|
|
|
|
for( Namespace* linkWalk = parentNamespace;
|
|
linkWalk != NULL && linkWalk != cppNamespace && linkWalk->getParent() != NULL;
|
|
linkWalk = linkWalk->getParent() )
|
|
{
|
|
// Skip namespaces coming from packages.
|
|
if( linkWalk->getPackage() != NULL )
|
|
continue;
|
|
|
|
linkWalk->incRefCountToParent();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If class name is set, link it in as the new parent
|
|
// which itself inherits from the current parent.
|
|
|
|
if( mClassName && mClassName[ 0 ] )
|
|
{
|
|
Namespace* classNamespace = Con::lookupNamespace( mClassName );
|
|
if( classNamespace && classNamespace->classLinkTo( parentNamespace ) )
|
|
{
|
|
parentNamespace = classNamespace;
|
|
}
|
|
else
|
|
{
|
|
// Clear out class name so we don't perform a bogus unlink
|
|
// in unlinkNamespaces().
|
|
mClassName = NULL;
|
|
}
|
|
}
|
|
|
|
// Finally, if we have an object name, link its namespace
|
|
// as the child to the current parent namespace and let it
|
|
// become the final namespace of this object.
|
|
|
|
StringTableEntry objectName = getName();
|
|
if( objectName && objectName[ 0 ] )
|
|
{
|
|
Namespace* objectNamespace = Con::lookupNamespace( objectName );
|
|
if( objectNamespace && objectNamespace->classLinkTo( parentNamespace ) )
|
|
{
|
|
parentNamespace = objectNamespace;
|
|
}
|
|
}
|
|
|
|
// Store our namespace.
|
|
|
|
mNameSpace = parentNamespace;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::unlinkNamespaces()
|
|
{
|
|
if( !mNameSpace )
|
|
return;
|
|
|
|
Namespace* cppNamespace = getClassRep()->getNameSpace();
|
|
Namespace* parentNamespace = cppNamespace;
|
|
|
|
// Handle superclass.
|
|
|
|
if( mSuperClassName && mSuperClassName[ 0 ] )
|
|
{
|
|
// Get the superclass namespace.
|
|
|
|
Namespace* superClassNamespace = Con::lookupNamespace( mSuperClassName );
|
|
|
|
// Make it the parent namespace.
|
|
|
|
parentNamespace = superClassNamespace;
|
|
|
|
// Decrease parent refcounts on the superclass hierarchy.
|
|
|
|
for( Namespace* linkWalk = superClassNamespace;
|
|
linkWalk != NULL && linkWalk != cppNamespace && linkWalk->getParent() != NULL; )
|
|
{
|
|
// Store the parent link since it may disappear once we
|
|
// decrease the reference count.
|
|
Namespace* parent = linkWalk->getParent();
|
|
|
|
// Decrease the refcount.
|
|
if( linkWalk->getPackage() == NULL ) // Skip namespaces coming from packages.
|
|
linkWalk->decRefCountToParent();
|
|
|
|
// Walk up.
|
|
linkWalk = parent;
|
|
}
|
|
}
|
|
|
|
// Handle class.
|
|
|
|
if( mClassName && mClassName[ 0 ] )
|
|
{
|
|
Namespace* classNamespace = Con::lookupNamespace( mClassName );
|
|
if( classNamespace )
|
|
{
|
|
classNamespace->decRefCountToParent();
|
|
parentNamespace = classNamespace;
|
|
}
|
|
}
|
|
|
|
// Handle object name.
|
|
|
|
StringTableEntry objectName = getName();
|
|
if( objectName && objectName[ 0 ] )
|
|
mNameSpace->decRefCountToParent();
|
|
|
|
mNameSpace = NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::setClassNamespace( const char *classNamespace )
|
|
{
|
|
StringTableEntry oldClassNamespace = mClassName;
|
|
StringTableEntry newClassNamespace = StringTable->insert( classNamespace );
|
|
|
|
if( oldClassNamespace == newClassNamespace )
|
|
return;
|
|
|
|
if( isProperlyAdded() )
|
|
unlinkNamespaces();
|
|
|
|
mClassName = newClassNamespace;
|
|
|
|
if( isProperlyAdded() )
|
|
{
|
|
linkNamespaces();
|
|
|
|
// Restore old namespace setup if linkage failed.
|
|
|
|
if( mClassName != newClassNamespace )
|
|
{
|
|
mClassName = oldClassNamespace;
|
|
linkNamespaces();
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::setSuperClassNamespace( const char *superClassNamespace )
|
|
{
|
|
StringTableEntry oldSuperClassNamespace = mSuperClassName;
|
|
StringTableEntry newSuperClassNamespace = StringTable->insert( superClassNamespace );
|
|
|
|
if( oldSuperClassNamespace == newSuperClassNamespace )
|
|
return;
|
|
|
|
if( isProperlyAdded() )
|
|
unlinkNamespaces();
|
|
|
|
mSuperClassName = newSuperClassNamespace;
|
|
|
|
if( isProperlyAdded() )
|
|
{
|
|
linkNamespaces();
|
|
|
|
// Restore old setup if linkage failed.
|
|
|
|
if( mSuperClassName != newSuperClassNamespace )
|
|
{
|
|
mSuperClassName = oldSuperClassNamespace;
|
|
linkNamespaces();
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
// Misc.
|
|
//=============================================================================
|
|
// MARK: ---- Misc ----
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::setInternalName( const char* newname )
|
|
{
|
|
if( newname )
|
|
mInternalName = StringTable->insert( newname );
|
|
else
|
|
mInternalName = StringTable->EmptyString();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::setOriginalName( const char* originalName )
|
|
{
|
|
if( originalName )
|
|
mOriginalName = StringTable->insert( originalName );
|
|
else
|
|
mOriginalName = StringTable->EmptyString();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const char *SimObject::tabComplete(const char *prevText, S32 baseLen, bool fForward)
|
|
{
|
|
return mNameSpace->tabComplete(prevText, baseLen, fForward);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::setSelected( bool sel )
|
|
{
|
|
if( mFlags.test( Selected ) == sel )
|
|
return; // No change.
|
|
|
|
if( sel )
|
|
{
|
|
mFlags.set( Selected );
|
|
_onSelected();
|
|
}
|
|
else
|
|
{
|
|
mFlags.clear( Selected );
|
|
_onUnselected();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::isSelectedRecursive() const
|
|
{
|
|
const SimObject *walk = this;
|
|
while ( walk )
|
|
{
|
|
if ( walk->isSelected() )
|
|
return true;
|
|
walk = walk->getGroup();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::setLocked( bool b )
|
|
{
|
|
if( b )
|
|
mFlags.set( Locked );
|
|
else
|
|
mFlags.clear( Locked );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::setHidden( bool b )
|
|
{
|
|
if( b )
|
|
mFlags.set( Hidden );
|
|
else
|
|
mFlags.clear( Hidden );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::setCopySource( SimObject* object )
|
|
{
|
|
if( mCopySource )
|
|
mCopySource->unregisterReference( &mCopySource );
|
|
mCopySource = object;
|
|
if( mCopySource )
|
|
mCopySource->registerReference( &mCopySource );
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool SimObject::_setCanSave( void* object, const char* index, const char* data )
|
|
{
|
|
SimObject* obj = reinterpret_cast< SimObject* >( object );
|
|
obj->setCanSave( dAtob( data ) );
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const char* SimObject::_getCanSave( void* object, const char* data )
|
|
{
|
|
SimObject* obj = reinterpret_cast< SimObject* >( object );
|
|
if( obj->getCanSave() )
|
|
return "1";
|
|
else
|
|
return "0";
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Copy SimObject to another SimObject (Originally designed for T2D).
|
|
void SimObject::copyTo( SimObject* object )
|
|
{
|
|
object->mClassName = mClassName;
|
|
object->mSuperClassName = mSuperClassName;
|
|
|
|
linkNamespaces();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::setProtectedParent( void *obj, const char *index, const char *data )
|
|
{
|
|
SimGroup *parent = NULL;
|
|
SimObject *object = static_cast<SimObject*>(obj);
|
|
|
|
if(Sim::findObject(data, parent))
|
|
parent->addObject(object);
|
|
|
|
// always return false, because we've set mGroup when we called addObject
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::setProtectedName(void *obj, const char *index, const char *data)
|
|
{
|
|
if (preventNameChanging)
|
|
return false;
|
|
SimObject *object = static_cast<SimObject*>(obj);
|
|
|
|
if ( object->isProperlyAdded() )
|
|
object->assignName( data );
|
|
|
|
// always return false because we assign the name here
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::inspectPreApply()
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::inspectPostApply()
|
|
{
|
|
onInspectPostApply_callback(this);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
String SimObject::_getLogMessage(const char* fmt, va_list args) const
|
|
{
|
|
String objClass = "UnknownClass";
|
|
if(getClassRep())
|
|
objClass = getClassRep()->getClassName();
|
|
|
|
String objName = getName();
|
|
if(objName.isEmpty())
|
|
objName = "Unnamed";
|
|
|
|
String formattedMessage = String::VToString(fmt, args);
|
|
return String::ToString("%s - %s(%i) - %s",
|
|
objClass.c_str(), objName.c_str(), getId(), formattedMessage.c_str());
|
|
}
|
|
|
|
//=============================================================================
|
|
// API.
|
|
//=============================================================================
|
|
// MARK: ---- API ----
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, dumpGroupHierarchy, void, (),,
|
|
"Dump the hierarchy of this object up to RootGroup to the console." )
|
|
{
|
|
object->dumpGroupHierarchy();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, isMethod, bool, ( const char* methodName ),,
|
|
"Test whether the given method is defined on this object.\n"
|
|
"@param The name of the method.\n"
|
|
"@return True if the object implements the given method." )
|
|
{
|
|
return object->isMethod( methodName );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, isChildOfGroup, bool, ( SimGroup* group ),,
|
|
"Test whether the object belongs directly or indirectly to the given group.\n"
|
|
"@param group The SimGroup object.\n"
|
|
"@return True if the object is a child of the given group or a child of a group that the given group is directly or indirectly a child to." )
|
|
{
|
|
return object->isChildOfGroup( group );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, getClassNamespace, const char*, (),,
|
|
"Get the name of the class namespace assigned to this object.\n"
|
|
"@return The name of the 'class' namespace." )
|
|
{
|
|
return object->getClassNamespace();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, getSuperClassNamespace, const char*, (),,
|
|
"Get the name of the superclass namespace assigned to this object.\n"
|
|
"@return The name of the 'superClass' namespace." )
|
|
{
|
|
return object->getSuperClassNamespace();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, setClassNamespace, void, ( const char* name ),,
|
|
"Assign a class namespace to this object.\n"
|
|
"@param name The name of the 'class' namespace for this object." )
|
|
{
|
|
object->setClassNamespace( name );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, setSuperClassNamespace, void, ( const char* name ),,
|
|
"Assign a superclass namespace to this object.\n"
|
|
"@param name The name of the 'superClass' namespace for this object." )
|
|
{
|
|
object->setSuperClassNamespace( name );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, isSelected, bool, (),,
|
|
"Get whether the object has been marked as selected. (in editor)\n"
|
|
"@return True if the object is currently selected." )
|
|
{
|
|
return object->isSelected();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, setIsSelected, void, ( bool state ), ( true ),
|
|
"Set whether the object has been marked as selected. (in editor)\n"
|
|
"@param state True if object is to be marked selected; false if not." )
|
|
{
|
|
object->setSelected( state );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, isExpanded, bool, (),,
|
|
"Get whether the object has been marked as expanded. (in editor)\n"
|
|
"@return True if the object is marked expanded." )
|
|
{
|
|
return object->isExpanded();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, setIsExpanded, void, ( bool state ), ( true ),
|
|
"Set whether the object has been marked as expanded. (in editor)\n"
|
|
"@param state True if the object is to be marked expanded; false if not." )
|
|
{
|
|
object->setExpanded( state );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, getFilename, const char*, (),,
|
|
"Returns the filename the object is attached to.\n"
|
|
"@return The name of the file the object is associated with; usually the file the object was loaded from." )
|
|
{
|
|
return object->getFilename();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, setFilename, void, ( const char* fileName ),,
|
|
"Sets the object's file name and path\n"
|
|
"@param fileName The name of the file to associate this object with." )
|
|
{
|
|
return object->setFilename( fileName );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, getDeclarationLine, S32, (),,
|
|
"Get the line number at which the object is defined in its file.\n\n"
|
|
"@return The line number of the object's definition in script.\n"
|
|
"@see getFilename()")
|
|
{
|
|
return object->getDeclarationLine();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#ifdef TORQUE_DEBUG
|
|
|
|
static const char* sEnumCallbackFunction;
|
|
static void sEnumCallback( EngineObject* object )
|
|
{
|
|
SimObject* simObject = dynamic_cast< SimObject* >( object );
|
|
if( !simObject )
|
|
return;
|
|
|
|
Con::evaluatef( "%s( %i );", sEnumCallbackFunction, simObject->getId() );
|
|
}
|
|
|
|
DefineEngineFunction( debugEnumInstances, void, ( const char* className, const char* functionName ),,
|
|
"Call the given function for each instance of the given class.\n"
|
|
"@param className Name of the class for which to enumerate instances.\n"
|
|
"@param functionName Name of function to call and pass each instance of the given class.\n"
|
|
"@note This function is only available in debug builds and primarily meant as an aid in debugging."
|
|
"@ingroup Console")
|
|
{
|
|
sEnumCallbackFunction = functionName;
|
|
ConsoleObject::debugEnumInstances( className, sEnumCallback );
|
|
}
|
|
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, assignFieldsFrom, void, ( SimObject* fromObject ),,
|
|
"Copy fields from another object onto this one. The objects must "
|
|
"be of same type. Everything from the object will overwrite what's "
|
|
"in this object; extra fields in this object will remain. This "
|
|
"includes dynamic fields.\n"
|
|
"@param fromObject The object from which to copy fields." )
|
|
{
|
|
if( fromObject )
|
|
object->assignFieldsFrom( fromObject );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, assignPersistentId, void, (),,
|
|
"Assign a persistent ID to the object if it does not already have one." )
|
|
{
|
|
object->getOrCreatePersistentId();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, getCanSave, bool, (),,
|
|
"Get whether the object will be included in saves.\n"
|
|
"@return True if the object will be saved; false otherwise." )
|
|
{
|
|
return object->getCanSave();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, setCanSave, void, ( bool value ), ( true ),
|
|
"Set whether the object will be included in saves.\n"
|
|
"@param value If true, the object will be included in saves; if false, it will be excluded." )
|
|
{
|
|
object->setCanSave( value );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, isEditorOnly, bool, (),,
|
|
"Return true if the object is only used by the editor.\n"
|
|
"@return True if this object exists only for the sake of editing." )
|
|
{
|
|
return object->isEditorOnly();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, setEditorOnly, void, ( bool value ), ( true ),
|
|
"Set/clear the editor-only flag on this object.\n"
|
|
"@param value If true, the object is marked as existing only for the editor." )
|
|
{
|
|
object->setEditorOnly( value );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, isNameChangeAllowed, bool, (),,
|
|
"Get whether this object may be renamed.\n"
|
|
"@return True if this object can be renamed; false otherwise." )
|
|
{
|
|
return object->isNameChangeAllowed();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, setNameChangeAllowed, void, ( bool value ), ( true ),
|
|
"Set whether this object can be renamed from its first name.\n"
|
|
"@param value If true, renaming is allowed for this object; if false, trying to change the name of the object will generate a console error." )
|
|
{
|
|
object->setNameChangeAllowed( value );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, clone, SimObject*, (),,
|
|
"Create a copy of this object.\n"
|
|
"@return An exact duplicate of this object." )
|
|
{
|
|
return object->clone();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, deepClone, SimObject*, (),,
|
|
"Create a copy of this object and all its subobjects.\n"
|
|
"@return An exact duplicate of this object and all objects it references." )
|
|
{
|
|
return object->deepClone();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, setLocked, void, ( bool value ), ( true ),
|
|
"Lock/unlock the object in the editor.\n"
|
|
"@param value If true, the object will be locked; if false, the object will be unlocked." )
|
|
{
|
|
object->setLocked( value );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, setHidden, void, ( bool value ), ( true ),
|
|
"Hide/unhide the object.\n"
|
|
"@param value If true, the object will be hidden; if false, the object will be unhidden." )
|
|
{
|
|
object->setHidden( value );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, dumpMethods, ArrayObject*, (),,
|
|
"List the methods defined on this object.\n\n"
|
|
"Each description is a newline-separated vector with the following elements:\n"
|
|
"- Minimum number of arguments.\n"
|
|
"- Maximum number of arguments.\n"
|
|
"- Prototype string.\n"
|
|
"- Full script file path (if script method).\n"
|
|
"- Line number of method definition in script (if script method).\n\n"
|
|
"- Documentation string (not including prototype). This takes up the remainder of the vector.\n"
|
|
"@return An ArrayObject populated with (name,description) pairs of all methods defined on the object." )
|
|
{
|
|
Namespace *ns = object->getNamespace();
|
|
if( !ns )
|
|
return 0;
|
|
|
|
ArrayObject* dictionary = new ArrayObject();
|
|
dictionary->registerObject();
|
|
|
|
VectorPtr<Namespace::Entry *> vec(__FILE__, __LINE__);
|
|
ns->getEntryList(&vec);
|
|
|
|
for(Vector< Namespace::Entry* >::iterator j = vec.begin(); j != vec.end(); j++)
|
|
{
|
|
Namespace::Entry* e = *j;
|
|
|
|
if( e->mType < 0 )
|
|
continue;
|
|
|
|
StringBuilder str;
|
|
|
|
str.append( String::ToString( e->mMinArgs ) );
|
|
str.append( '\n' );
|
|
str.append( String::ToString( e->mMaxArgs ) );
|
|
str.append( '\n' );
|
|
str.append( e->getPrototypeString() );
|
|
|
|
str.append( '\n' );
|
|
if( e->mCode && e->mCode->fullPath )
|
|
str.append( e->mCode->fullPath );
|
|
str.append( '\n' );
|
|
if( e->mCode )
|
|
str.append( String::ToString( e->mFunctionLineNumber ) );
|
|
|
|
str.append( '\n' );
|
|
String docs = e->getDocString();
|
|
if( !docs.isEmpty() )
|
|
str.append( docs );
|
|
|
|
dictionary->push_back( e->mFunctionName, str.end() );
|
|
}
|
|
|
|
return dictionary;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
namespace {
|
|
S32 QSORT_CALLBACK compareFields( const void* a,const void* b )
|
|
{
|
|
const AbstractClassRep::Field* fa = *((const AbstractClassRep::Field**)a);
|
|
const AbstractClassRep::Field* fb = *((const AbstractClassRep::Field**)b);
|
|
|
|
return dStricmp(fa->pFieldname, fb->pFieldname);
|
|
}
|
|
|
|
struct DocString
|
|
{
|
|
char mPadding[ 8 ];
|
|
String mPrototype;
|
|
String mDescription;
|
|
const char* mReturnType;
|
|
|
|
DocString( Namespace::Entry* entry )
|
|
: mPrototype( entry->getArgumentsString() ),
|
|
mDescription( entry->getBriefDescription() )
|
|
{
|
|
mReturnType = " ";
|
|
mPadding[ 0 ] = 0;
|
|
if( entry->mType == -4 )
|
|
{
|
|
//TODO: need to have script callbacks set up proper return type info
|
|
}
|
|
else
|
|
{
|
|
switch( entry->mType )
|
|
{
|
|
case Namespace::Entry::StringCallbackType:
|
|
mReturnType = "string";
|
|
mPadding[ 0 ] = ' ';
|
|
mPadding[ 1 ] = ' ';
|
|
mPadding[ 2 ] = 0;
|
|
break;
|
|
|
|
case Namespace::Entry::IntCallbackType:
|
|
mReturnType = "int";
|
|
mPadding[ 0 ] = ' ';
|
|
mPadding[ 1 ] = ' ';
|
|
mPadding[ 2 ] = ' ';
|
|
mPadding[ 3 ] = ' ';
|
|
mPadding[ 4 ] = ' ';
|
|
mPadding[ 5 ] = 0;
|
|
break;
|
|
|
|
case Namespace::Entry::FloatCallbackType:
|
|
mReturnType = "float";
|
|
mPadding[ 0 ] = ' ';
|
|
mPadding[ 1 ] = ' ';
|
|
mPadding[ 2 ] = ' ';
|
|
mPadding[ 3 ] = 0;
|
|
break;
|
|
|
|
case Namespace::Entry::VoidCallbackType:
|
|
mReturnType = "void";
|
|
mPadding[ 0 ] = ' ';
|
|
mPadding[ 1 ] = ' ';
|
|
mPadding[ 2 ] = ' ';
|
|
mPadding[ 3 ] = ' ';
|
|
mPadding[ 4 ] = 0;
|
|
break;
|
|
|
|
case Namespace::Entry::BoolCallbackType:
|
|
mReturnType = "bool";
|
|
mPadding[ 0 ] = ' ';
|
|
mPadding[ 1 ] = ' ';
|
|
mPadding[ 2 ] = ' ';
|
|
mPadding[ 3 ] = ' ';
|
|
mPadding[ 4 ] = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
DefineEngineMethod( SimObject, dump, void, ( bool detailed ), ( false ),
|
|
"Dump a description of all fields and methods defined on this object to the console.\n"
|
|
"@param detailed Whether to print detailed information about members." )
|
|
{
|
|
Con::printf( "Class: %s", object->getClassName() );
|
|
|
|
const AbstractClassRep::FieldList &list = object->getFieldList();
|
|
char expandedBuffer[4096];
|
|
|
|
Con::printf( "Static Fields:" );
|
|
Vector<const AbstractClassRep::Field *> flist(__FILE__, __LINE__);
|
|
|
|
for(U32 i = 0; i < list.size(); i++)
|
|
flist.push_back(&list[i]);
|
|
|
|
dQsort(flist.address(),flist.size(),sizeof(AbstractClassRep::Field *),compareFields);
|
|
|
|
for(Vector<const AbstractClassRep::Field *>::iterator itr = flist.begin(); itr != flist.end(); itr++)
|
|
{
|
|
const AbstractClassRep::Field* f = *itr;
|
|
|
|
// The special field types can be skipped.
|
|
if ( f->type >= AbstractClassRep::ARCFirstCustomField )
|
|
continue;
|
|
|
|
for(U32 j = 0; S32(j) < f->elementCount; j++)
|
|
{
|
|
// [neo, 07/05/2007 - #3000]
|
|
// Some objects use dummy vars and projected fields so make sure we call the get functions
|
|
//const char *val = Con::getData(f->type, (void *) (((const char *)object) + f->offset), j, f->table, f->flag);
|
|
const char *val = (*f->getDataFn)( object, Con::getData(f->type, (void *) (((const char *)object) + f->offset), j, f->table, f->flag) );// + typeSizes[fld.type] * array1));
|
|
|
|
ConsoleBaseType* conType = ConsoleBaseType::getType( f->type );
|
|
const char* conTypeName = "<unknown>";
|
|
if( conType )
|
|
conTypeName = conType->getTypeClassName();
|
|
|
|
if( !val /*|| !*val*/ )
|
|
continue;
|
|
if( f->elementCount == 1 )
|
|
dSprintf( expandedBuffer, sizeof( expandedBuffer ), " %s %s = \"", conTypeName, f->pFieldname );
|
|
else
|
|
dSprintf( expandedBuffer, sizeof( expandedBuffer ), " %s %s[ %d ] = \"", conTypeName, f->pFieldname, j );
|
|
expandEscape( expandedBuffer + dStrlen(expandedBuffer), val);
|
|
Con::printf( "%s\"", expandedBuffer );
|
|
|
|
if( detailed && f->pFieldDocs && f->pFieldDocs[ 0 ] )
|
|
Con::printf( " %s", f->pFieldDocs );
|
|
}
|
|
}
|
|
|
|
// If the object is a datablock with substitution statements,
|
|
// they get printed out as part of the dump.
|
|
if (dynamic_cast<SimDataBlock*>(object))
|
|
{
|
|
if (((SimDataBlock*)object)->getSubstitutionCount() > 0)
|
|
{
|
|
Con::printf("Substitution Fields:");
|
|
((SimDataBlock*)object)->printSubstitutions();
|
|
}
|
|
}
|
|
Con::printf( "Dynamic Fields:" );
|
|
if(object->getFieldDictionary())
|
|
object->getFieldDictionary()->printFields(object);
|
|
|
|
Con::printf( "Methods:" );
|
|
Namespace *ns = object->getNamespace();
|
|
VectorPtr<Namespace::Entry *> vec(__FILE__, __LINE__);
|
|
|
|
if(ns)
|
|
ns->getEntryList(&vec);
|
|
|
|
bool sawCBs = false;
|
|
|
|
for(Vector<Namespace::Entry *>::iterator j = vec.begin(); j != vec.end(); j++)
|
|
{
|
|
Namespace::Entry *e = *j;
|
|
|
|
if(e->mType == Namespace::Entry::ScriptCallbackType)
|
|
sawCBs = true;
|
|
|
|
if(e->mType < 0)
|
|
continue;
|
|
|
|
DocString doc( e );
|
|
Con::printf( " %s%s%s%s", doc.mReturnType, doc.mPadding, e->mFunctionName, doc.mPrototype.c_str() );
|
|
|
|
if( detailed && !doc.mDescription.isEmpty() )
|
|
Con::printf( " %s", doc.mDescription.c_str() );
|
|
}
|
|
|
|
if( sawCBs )
|
|
{
|
|
Con::printf( "Callbacks:" );
|
|
|
|
for(Vector<Namespace::Entry *>::iterator j = vec.begin(); j != vec.end(); j++)
|
|
{
|
|
Namespace::Entry *e = *j;
|
|
|
|
if(e->mType != Namespace::Entry::ScriptCallbackType)
|
|
continue;
|
|
|
|
DocString doc( e );
|
|
Con::printf( " %s%s%s%s", doc.mReturnType, doc.mPadding, e->cb.mCallbackName, doc.mPrototype.c_str() );
|
|
|
|
if( detailed && !doc.mDescription.isEmpty() )
|
|
Con::printf( " %s", doc.mDescription.c_str() );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, save, bool, ( const char* fileName, bool selectedOnly, const char* preAppendString ), ( false, "" ),
|
|
"Save out the object to the given file.\n"
|
|
"@param fileName The name of the file to save to."
|
|
"@param selectedOnly If true, only objects marked as selected will be saved out.\n"
|
|
"@param preAppendString Text which will be preprended directly to the object serialization.\n"
|
|
"@param True on success, false on failure." )
|
|
{
|
|
return object->save( fileName, selectedOnly, preAppendString );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, setName, void, ( const char* newName ),,
|
|
"Set the global name of the object.\n"
|
|
"@param newName The new global name to assign to the object.\n"
|
|
"@note If name changing is disallowed on the object, the method will fail with a console error." )
|
|
{
|
|
object->assignName( newName );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, getName, const char*, (),,
|
|
"Get the global name of the object.\n"
|
|
"@return The global name assigned to the object." )
|
|
{
|
|
const char *ret = object->getName();
|
|
return ret ? ret : "";
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, getClassName, const char*, (),,
|
|
"Get the name of the C++ class which the object is an instance of.\n"
|
|
"@return The name of the C++ class of the object." )
|
|
{
|
|
const char *ret = object->getClassName();
|
|
return ret ? ret : "";
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, isField, bool, ( const char* fieldName ),,
|
|
"Test whether the given field is defined on this object.\n"
|
|
"@param fieldName The name of the field.\n"
|
|
"@return True if the object implements the given field." )
|
|
{
|
|
return object->isField( fieldName );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, getFieldValue, const char*, ( const char* fieldName, S32 index ), ( -1 ),
|
|
"Return the value of the given field on this object.\n"
|
|
"@param fieldName The name of the field. If it includes a field index, the index is parsed out.\n"
|
|
"@param index Optional parameter to specify the index of an array field separately.\n"
|
|
"@return The value of the given field or \"\" if undefined." )
|
|
{
|
|
const U32 nameLen = dStrlen( fieldName );
|
|
if (nameLen == 0)
|
|
return "";
|
|
|
|
char fieldNameBuffer[ 1024 ];
|
|
char arrayIndexBuffer[ 64 ];
|
|
|
|
// Parse out index if the field is given in the form of 'name[index]'.
|
|
|
|
const char* arrayIndex = NULL;
|
|
if( fieldName[ nameLen - 1 ] == ']' )
|
|
{
|
|
const char* leftBracket = dStrchr( fieldName, '[' );
|
|
const char* rightBracket = &fieldName[ nameLen - 1 ];
|
|
|
|
const U32 fieldNameLen = getMin( U32( leftBracket - fieldName ), sizeof( fieldNameBuffer ) - 1 );
|
|
const U32 arrayIndexLen = getMin( U32( rightBracket - leftBracket - 1 ), sizeof( arrayIndexBuffer ) - 1 );
|
|
|
|
dMemcpy( fieldNameBuffer, fieldName, fieldNameLen );
|
|
dMemcpy( arrayIndexBuffer, leftBracket + 1, arrayIndexLen );
|
|
|
|
fieldNameBuffer[ fieldNameLen ] = '\0';
|
|
arrayIndexBuffer[ arrayIndexLen ] = '\0';
|
|
|
|
fieldName = fieldNameBuffer;
|
|
arrayIndex = arrayIndexBuffer;
|
|
}
|
|
|
|
fieldName = StringTable->insert( fieldName );
|
|
|
|
if( index != -1 )
|
|
{
|
|
dSprintf( arrayIndexBuffer, sizeof( arrayIndexBuffer ), "%i", index );
|
|
arrayIndex = arrayIndexBuffer;
|
|
}
|
|
|
|
return object->getDataField( fieldName, arrayIndex );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, setFieldValue, bool, ( const char* fieldName, const char* value, S32 index ), ( -1 ),
|
|
"Set the value of the given field on this object.\n"
|
|
"@param fieldName The name of the field to assign to. If it includes an array index, the index will be parsed out.\n"
|
|
"@param value The new value to assign to the field.\n"
|
|
"@param index Optional argument to specify an index for an array field.\n"
|
|
"@return True." )
|
|
{
|
|
char fieldNameBuffer[ 1024 ];
|
|
char arrayIndexBuffer[ 64 ];
|
|
|
|
// Parse out index if the field is given in the form of 'name[index]'.
|
|
|
|
const char* arrayIndex = NULL;
|
|
const U32 nameLen = dStrlen( fieldName );
|
|
if( fieldName[ nameLen - 1 ] == ']' )
|
|
{
|
|
const char* leftBracket = dStrchr( fieldName, '[' );
|
|
const char* rightBracket = &fieldName[ nameLen - 1 ];
|
|
|
|
const U32 fieldNameLen = getMin( U32( leftBracket - fieldName ), sizeof( fieldNameBuffer ) - 1 );
|
|
const U32 arrayIndexLen = getMin( U32( rightBracket - leftBracket - 1 ), sizeof( arrayIndexBuffer ) - 1 );
|
|
|
|
dMemcpy( fieldNameBuffer, fieldName, fieldNameLen );
|
|
dMemcpy( arrayIndexBuffer, leftBracket + 1, arrayIndexLen );
|
|
|
|
fieldNameBuffer[ fieldNameLen ] = '\0';
|
|
arrayIndexBuffer[ arrayIndexLen ] = '\0';
|
|
|
|
fieldName = fieldNameBuffer;
|
|
arrayIndex = arrayIndexBuffer;
|
|
}
|
|
|
|
fieldName = StringTable->insert( fieldName );
|
|
|
|
if( index != -1 )
|
|
{
|
|
dSprintf( arrayIndexBuffer, sizeof( arrayIndexBuffer ), "%i", index );
|
|
arrayIndex = arrayIndexBuffer;
|
|
}
|
|
|
|
object->setDataField( fieldName, arrayIndex, value );
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, getFieldType, const char*, ( const char* fieldName ),,
|
|
"Get the console type code of the given field.\n"
|
|
"@return The numeric type code for the underlying console type of the given field." )
|
|
{
|
|
U32 typeID = object->getDataFieldType( StringTable->insert( fieldName ), NULL );
|
|
ConsoleBaseType* type = ConsoleBaseType::getType( typeID );
|
|
|
|
if( type )
|
|
return type->getTypeName();
|
|
|
|
return "";
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, setFieldType, void, ( const char* fieldName, const char* type ),,
|
|
"Set the console type code for the given field.\n"
|
|
"@param fieldName The name of the dynamic field to change to type for.\n"
|
|
"@param type The name of the console type.\n"
|
|
"@note This only works for dynamic fields. Types of static fields cannot be changed." )
|
|
{
|
|
object->setDataFieldType( type, StringTable->insert( fieldName ), NULL );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineStringlyVariadicMethod( SimObject, call, const char*, 3, 0, "( string method, string args... ) Dynamically call a method on an object.\n"
|
|
"@param method Name of method to call.\n"
|
|
"@param args Zero or more arguments for the method.\n"
|
|
"@return The result of the method call." )
|
|
{
|
|
argv[1].setString(argv[2], dStrlen(argv[2]));
|
|
return Con::execute( object, argc - 1, argv + 1 );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, setInternalName, void, ( const char* newInternalName ),,
|
|
"Set the internal name of the object.\n"
|
|
"@param newInternalName The new internal name for the object." )
|
|
{
|
|
object->setInternalName( newInternalName );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, getInternalName, const char*, (),,
|
|
"Get the internal name of the object.\n"
|
|
"@return The internal name of the object." )
|
|
{
|
|
return object->getInternalName();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, dumpClassHierarchy, void, (),,
|
|
"Dump the native C++ class hierarchy of this object's C++ class to the console." )
|
|
{
|
|
object->dumpClassHierarchy();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, isMemberOfClass, bool, ( const char* className ),,
|
|
"Test whether this object is a member of the specified class.\n"
|
|
"@param className Name of a native C++ class.\n"
|
|
"@return True if this object is an instance of the given C++ class or any of its super classes." )
|
|
{
|
|
AbstractClassRep* pRep = object->getClassRep();
|
|
while(pRep)
|
|
{
|
|
if( !dStricmp(pRep->getClassName(), className ) )
|
|
{
|
|
//matches
|
|
return true;
|
|
}
|
|
|
|
pRep = pRep->getParentClass();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, isInNamespaceHierarchy, bool, ( const char* name ),,
|
|
"Test whether the namespace of this object is a direct or indirect child to the given namespace.\n"
|
|
"@param name The name of a namespace.\n"
|
|
"@return True if the given namespace name is within the namespace hierarchy of this object." )
|
|
{
|
|
Namespace* nspace = object->getNamespace();
|
|
|
|
while( nspace && dStricmp( nspace->mName, name ) != 0 )
|
|
nspace = nspace->mParent;
|
|
|
|
return ( nspace != NULL );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, getId, S32, (),,
|
|
"Get the underlying unique numeric ID of the object.\n"
|
|
"@note Object IDs are unique only during single engine runs.\n"
|
|
"@return The unique numeric ID of the object." )
|
|
{
|
|
return object->getId();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, getGroup, SimGroup*, (),,
|
|
"Get the group that this object is contained in.\n"
|
|
"@note If not assigned to particular SimGroup, an object belongs to RootGroup.\n"
|
|
"@return The SimGroup object to which the object belongs." )
|
|
{
|
|
return object->getGroup();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, delete, void, (),,
|
|
"Delete and remove the object." )
|
|
{
|
|
object->deleteObject();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineStringlyVariadicMethod( SimObject,schedule, S32, 4, 0, "( float time, string method, string args... ) Delay an invocation of a method.\n"
|
|
"@param time The number of milliseconds after which to invoke the method. This is a soft limit.\n"
|
|
"@param method The method to call.\n"
|
|
"@param args The arguments with which to call the method.\n"
|
|
"@return The numeric ID of the created schedule. Can be used to cancel the call.\n" )
|
|
{
|
|
U32 timeDelta = U32(argv[2].getFloat());
|
|
argv[2].setString(argv[3].getString());
|
|
argv[3].setString(argv[1].getString());
|
|
SimConsoleEvent *evt = new SimConsoleEvent(argc - 2, argv + 2, true);
|
|
S32 ret = Sim::postEvent(object, evt, Sim::getCurrentTime() + timeDelta);
|
|
// #ifdef DEBUG
|
|
// Con::printf("obj %s schedule(%s) = %d", argv[3], argv[2], ret);
|
|
// Con::executef( "backtrace");
|
|
// #endif
|
|
return ret;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, getDynamicFieldCount, S32, (),,
|
|
"Get the number of dynamic fields defined on the object.\n"
|
|
"@return The number of dynamic fields defined on the object." )
|
|
{
|
|
S32 count = 0;
|
|
SimFieldDictionary* fieldDictionary = object->getFieldDictionary();
|
|
for (SimFieldDictionaryIterator itr(fieldDictionary); *itr; ++itr)
|
|
count++;
|
|
|
|
return count;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, getDynamicField, const char*, ( S32 index ),,
|
|
"Get a value of a dynamic field by index.\n"
|
|
"@param index The index of the dynamic field.\n"
|
|
"@return The value of the dynamic field at the given index or \"\"." )
|
|
{
|
|
SimFieldDictionary* fieldDictionary = object->getFieldDictionary();
|
|
SimFieldDictionaryIterator itr(fieldDictionary);
|
|
for (S32 i = 0; i < index; i++)
|
|
{
|
|
if (!(*itr))
|
|
{
|
|
Con::warnf("Invalid dynamic field index passed to SimObject::getDynamicField!");
|
|
return NULL;
|
|
}
|
|
++itr;
|
|
}
|
|
|
|
static const U32 bufSize = 256;
|
|
char* buffer = Con::getReturnBuffer(bufSize);
|
|
if (*itr)
|
|
{
|
|
SimFieldDictionary::Entry* entry = *itr;
|
|
dSprintf(buffer, bufSize, "%s\t%s", entry->slotName, entry->value);
|
|
return buffer;
|
|
}
|
|
|
|
Con::warnf("Invalid dynamic field index passed to SimObject::getDynamicField!");
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, getFieldCount, S32, (),,
|
|
"Get the number of static fields on the object.\n"
|
|
"@return The number of static fields defined on the object." )
|
|
{
|
|
const AbstractClassRep::FieldList &list = object->getFieldList();
|
|
const AbstractClassRep::Field* f;
|
|
U32 numDummyEntries = 0;
|
|
|
|
for(S32 i = 0; i < list.size(); i++)
|
|
{
|
|
f = &list[i];
|
|
|
|
// The special field types do not need to be counted.
|
|
if ( f->type >= AbstractClassRep::ARCFirstCustomField )
|
|
numDummyEntries++;
|
|
}
|
|
|
|
return list.size() - numDummyEntries;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( SimObject, getField, const char*, ( S32 index ),,
|
|
"Retrieve the value of a static field by index.\n"
|
|
"@param index The index of the static field.\n"
|
|
"@return The value of the static field with the given index or \"\"." )
|
|
{
|
|
const AbstractClassRep::FieldList &list = object->getFieldList();
|
|
if( ( index < 0 ) || ( index >= list.size() ) )
|
|
return "";
|
|
|
|
const AbstractClassRep::Field* f;
|
|
S32 currentField = 0;
|
|
for ( U32 i = 0; i < list.size() && currentField <= index; i++ )
|
|
{
|
|
f = &list[i];
|
|
|
|
// The special field types can be skipped.
|
|
if ( f->type >= AbstractClassRep::ARCFirstCustomField )
|
|
continue;
|
|
|
|
if(currentField == index)
|
|
return f->pFieldname;
|
|
|
|
currentField++;
|
|
}
|
|
|
|
// if we found nada, return nada.
|
|
return "";
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#ifdef TORQUE_DEBUG
|
|
|
|
DefineEngineMethod( SimObject, getDebugInfo, ArrayObject*, (),,
|
|
"Return some behind-the-scenes information on the object.\n"
|
|
"@return An ArrayObject filled with internal information about the object." )
|
|
{
|
|
ArrayObject* array = new ArrayObject();
|
|
array->registerObject();
|
|
|
|
array->push_back( "C++|Address", String::ToString( "0x%x", object ) );
|
|
array->push_back( "C++|Size", String::ToString( object->getClassRep()->getSizeof() ) );
|
|
array->push_back( "Object|Description", object->describeSelf() );
|
|
array->push_back( "Object|FileName", object->getFilename() );
|
|
array->push_back( "Object|DeclarationLine", String::ToString( object->getDeclarationLine() ) );
|
|
array->push_back( "Object|CopySource", object->getCopySource() ?
|
|
String::ToString( "%i:%s (%s)", object->getCopySource()->getId(), object->getCopySource()->getClassName(), object->getCopySource()->getName() ) : "" );
|
|
array->push_back( "Flag|EditorOnly", object->isEditorOnly() ? "true" : "false" );
|
|
array->push_back( "Flag|NameChangeAllowed", object->isNameChangeAllowed() ? "true" : "false" );
|
|
array->push_back( "Flag|AutoDelete", object->isAutoDeleted() ? "true" : "false" );
|
|
array->push_back( "Flag|Selected", object->isSelected() ? "true" : "false" );
|
|
array->push_back( "Flag|Expanded", object->isExpanded() ? "true" : "false" );
|
|
array->push_back( "Flag|ModStaticFields", object->canModStaticFields() ? "true" : "false" );
|
|
array->push_back( "Flag|ModDynamicFields", object->canModDynamicFields() ? "true" : "false" );
|
|
array->push_back( "Flag|CanSave", object->getCanSave() ? "true" : "false" );
|
|
|
|
#ifndef TORQUE_DISABLE_MEMORY_MANAGER
|
|
Memory::Info memInfo;
|
|
Memory::getMemoryInfo( object, memInfo );
|
|
|
|
array->push_back( "Memory|AllocNumber", String::ToString( memInfo.mAllocNumber ) );
|
|
array->push_back( "Memory|AllocSize", String::ToString( memInfo.mAllocSize ) );
|
|
array->push_back( "Memory|AllocFile", memInfo.mFileName );
|
|
array->push_back( "Memory|AllocLine", String::ToString( memInfo.mLineNumber ) );
|
|
array->push_back( "Memory|IsGlobal", memInfo.mIsGlobal ? "true" : "false" );
|
|
array->push_back( "Memory|IsStatic", memInfo.mIsStatic ? "true" : "false" );
|
|
#endif
|
|
|
|
return array;
|
|
}
|
|
|
|
#endif
|