mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 20:54:46 +00:00
by request, adds a per object-instance onadd for datablocks if an object instance *also* defines a class. be mindful not to mix up which namespace is in use there, as you can not tag two different core class instances the same scripted class implements the same with the same restrictions for simobjects in general
3566 lines
114 KiB
C++
3566 lines
114 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 "core/frameAllocator.h"
|
|
#include "core/stream/fileStream.h"
|
|
#include "core/fileObject.h"
|
|
#include "persistence/taml/tamlCustom.h"
|
|
#include "gui/editor/guiInspector.h"
|
|
#include "console/script.h"
|
|
|
|
#include "sim/netObject.h"
|
|
|
|
ImplementBitfieldType(GameTypeMasksType,
|
|
"The type of animation effect to apply to this material.\n"
|
|
"@ingroup GFX\n\n")
|
|
{ SceneObjectTypes::StaticObjectType, "$TypeMasks::StaticObjectType", "Static Objects.\n" },
|
|
{ SceneObjectTypes::EnvironmentObjectType, "$TypeMasks::EnvironmentObjectType" , "Objects considered part of the background or environment of a level.\n" },
|
|
{ SceneObjectTypes::TerrainObjectType, "$TypeMasks::TerrainObjectType" , "Terrain Objects.\n" },
|
|
{ SceneObjectTypes::WaterObjectType, "$TypeMasks::WaterObjectType", "Water Objects.\n" },
|
|
{ SceneObjectTypes::TriggerObjectType, "$TypeMasks::TriggerObjectType", "Interactive Trigger Objects.\n" },
|
|
{ SceneObjectTypes::MarkerObjectType, "$TypeMasks::MarkerObjectType", "Marker Objects, utilized primarily for tooling.\n" },
|
|
{ SceneObjectTypes::LightObjectType, "$TypeMasks::LightObjectType", "Lights.\n" },
|
|
{ SceneObjectTypes::ZoneObjectType, "$TypeMasks::ZoneObjectType", "zones.\n" },
|
|
{ SceneObjectTypes::StaticShapeObjectType, "$TypeMasks::StaticShapeObjectType", "Static Shape Objects. Distinct from StaticObjectType in that Static Shapes have additional functionality and behaviors.\n" },
|
|
{ SceneObjectTypes::DynamicShapeObjectType, "$TypeMasks::DynamicShapeObjectType", "Any sort of Dynamic Object.\n" },
|
|
{ SceneObjectTypes::GameBaseObjectType, "$TypeMasks::GameBaseObjectType", "Any Gamebase-based Objects. Objects generally associated to gameplay functionality.\n" },
|
|
{ SceneObjectTypes::GameBaseHiFiObjectType, "$TypeMasks::GameBaseHiFiObjectType", "Specialised Gamebase-based Objects. currently narrowly used. if at all.\n" },
|
|
{ SceneObjectTypes::ShapeBaseObjectType, "$TypeMasks::ShapeBaseObjectType", "Any Gamebase-based Objects. Objects generally associated to gameplay functionality.\n" },
|
|
{ SceneObjectTypes::CameraObjectType, "$TypeMasks::CameraObjectType", "Camera Objects.\n" },
|
|
{ SceneObjectTypes::PlayerObjectType, "$TypeMasks::PlayerObjectType", "Player Objects.\n" },
|
|
{ SceneObjectTypes::ItemObjectType, "$TypeMasks::ItemObjectType", "Item Objects.\n" },
|
|
{ SceneObjectTypes::VehicleObjectType, "$TypeMasks::VehicleObjectType", "Any sort of Vehicle Object.\n" },
|
|
{ SceneObjectTypes::VehicleBlockerObjectType, "$TypeMasks::VehicleBlockerObjectType", "\n" },
|
|
{ SceneObjectTypes::ProjectileObjectType, "$TypeMasks::ProjectileObjectType", "Projectiles.\n" },
|
|
{ SceneObjectTypes::ExplosionObjectType, "$TypeMasks::ExplosionObjectType", "Explosion and Effects.\n" },
|
|
{ SceneObjectTypes::CorpseObjectType, "$TypeMasks::CorpseObjectType", "Corpses of controlled objects.\n" },
|
|
{ SceneObjectTypes::DebrisObjectType, "$TypeMasks::DebrisObjectType", "Debris or debris-like things such as shell casings.\n" },
|
|
{ SceneObjectTypes::PhysicalZoneObjectType, "$TypeMasks::PhysicalZoneObjectType", "Physical Zones. Distinct from triggers in that they have physics forces applications.\n" },
|
|
{ SceneObjectTypes::EntityObjectType, "$TypeMasks::EntityObjectType", "A generic entity.\n" },
|
|
{ SceneObjectTypes::InteriorLikeObjectType, "$TypeMasks::InteriorLikeObjectType", "InteriorLikeObjectType (deprecated).\n" },
|
|
{ SceneObjectTypes::TerrainLikeObjectType, "$TypeMasks::TerrainLikeObjectType", "Pseudo-terrains, like groundplanes, or meshroads.\n" },
|
|
#if defined(AFX_CAP_AFXMODEL_TYPE)
|
|
{ SceneObjectTypes::afxModelObjectType, "afxModelObjectType", "afx-specific model typemask.\n" },
|
|
#else
|
|
{ SceneObjectTypes::N_A_27, "$TypeMasks::N_A_27", "unused 27th bit.\n" },
|
|
#endif
|
|
{ SceneObjectTypes::N_A_28, "$TypeMasks::N_A_28", "unused 28th bit.\n" },
|
|
{ SceneObjectTypes::PathShapeObjectType, "$TypeMasks::PathShapeObjectType", "Path-following Objects.\n" },
|
|
{ SceneObjectTypes::TurretObjectType, "$TypeMasks::TurretObjectType", "Turret Objects.\n" },
|
|
{ SceneObjectTypes::N_A_31, "$TypeMasks::N_A_31", "unused 31st bit.\n" },
|
|
{ SceneObjectTypes::AIObjectType, "$TypeMasks::AIObjectType", "AIObjectType.\n" },
|
|
|
|
EndImplementBitfieldType;
|
|
|
|
IMPLEMENT_CONOBJECT( SimObject );
|
|
|
|
IMPLEMENT_CALLBACK(SimObject, onAdd, void, (SimObjectId ID), (ID),
|
|
"Called when this SimObject is added to the system, if the className is set to something\n"
|
|
"@param ID Unique object ID assigned when created (%this in script).\n"
|
|
);
|
|
|
|
IMPLEMENT_CALLBACK(SimObject, onRemove, void, (SimObjectId ID), (ID),
|
|
"Called when this SimObject is removed from the system, if the className is set to something\n"
|
|
"@param ID Unique object ID assigned when created (%this in script).\n"
|
|
);
|
|
|
|
// 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");
|
|
IMPLEMENT_CALLBACK(SimObject, onSelected, void, (SimObject* obj), (obj), "Generic callback for when an object is selected");
|
|
IMPLEMENT_CALLBACK(SimObject, onUnselected, void, (SimObject* obj), (obj), "Generic callback for when an object is un-selected");
|
|
|
|
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;
|
|
mInheritFrom = 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 );
|
|
mPrototype = true;
|
|
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()
|
|
{
|
|
docsURL;
|
|
addGroup( "Ungrouped" );
|
|
|
|
addProtectedField( "name", TypeName, Offset(mObjectName, SimObject), &setProtectedName, &defaultProtectedGetFn,
|
|
"Optional global name of this object." );
|
|
|
|
addProtectedField("inheritFrom", TypeString, Offset(mInheritFrom, SimObject), &setInheritFrom, &defaultProtectedGetFn,
|
|
"Optional Name of object to inherit from as a parent.");
|
|
|
|
addProtectedField("Prototype", TypeBool, Offset(mPrototype, SimObject), &_doPrototype, &defaultProtectedGetFn, "Prototype Methods", AbstractClassRep::FieldFlags::FIELD_ComponentInspectors);
|
|
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, 0,
|
|
&_setHidden, &_getHidden,
|
|
"Whether the object is visible." );
|
|
addProtectedField( "locked", TypeBool, 0,
|
|
&_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();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool SimObject::_doPrototype(void* object, const char* index, const char* data)
|
|
{
|
|
if (!Con::isFunction("PrototypeClass")) return false;
|
|
if (dAtoi(data) != 1) return false;
|
|
SimObject* obj = reinterpret_cast<SimObject*>(object);
|
|
String command = String("PrototypeClass(") + (obj->getName()? String(obj->getName()) : String::ToString(obj->getId())) + ");";
|
|
Con::evaluate(command.c_str());
|
|
return false;
|
|
}
|
|
|
|
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.
|
|
|
|
// Create a default object of the same type
|
|
ConsoleObject* defaultConObject = ConsoleObject::create(getClassName());
|
|
SimObject* defaultObject = dynamic_cast<SimObject*>(defaultConObject);
|
|
|
|
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 || f->flag.test(AbstractClassRep::FieldFlags::FIELD_ComponentInspectors) || f->flag.test(AbstractClassRep::FieldFlags::FIELD_DontWriteToFile))
|
|
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;
|
|
|
|
//If the field hasn't been changed from the default value, then don't bother writing it out
|
|
const char* defaultValueCheck = defaultObject->getDataField(f->pFieldname, array);
|
|
if (defaultValueCheck && defaultValueCheck[0] != '\0' && dStricmp(defaultValueCheck, valCopy) == 0)
|
|
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 ||
|
|
f->type == TypeSoundFilename )
|
|
{
|
|
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);
|
|
|
|
// Cleanup our created default object
|
|
delete defaultConObject;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
bool SimObject::saveAppend(const char* pcFileName, bool bOnlySelected, const char* preappend)
|
|
{
|
|
|
|
|
|
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)
|
|
{
|
|
setPrefixedDataField(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) override
|
|
{
|
|
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 String& name)
|
|
{
|
|
assignName(name.c_str());
|
|
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;
|
|
|
|
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];
|
|
|
|
if (f->pFieldname == StringTable->insert("docsURL"))
|
|
continue;
|
|
|
|
// Skip the special field types.
|
|
if ( f->type >= AbstractClassRep::ARCFirstCustomField || f->flag.test(AbstractClassRep::FieldFlags::FIELD_ComponentInspectors))
|
|
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 we have an index worth mentioning, process it for pass-along as well to ensure we set stuff correctly
|
|
char* elementIdxBuffer = nullptr;
|
|
if (f->elementCount > 1)
|
|
{
|
|
elementIdxBuffer = Con::getArgBuffer(256);
|
|
dSprintf(elementIdxBuffer, 256, "%i", j);
|
|
}
|
|
|
|
if((*f->setDataFn)( this, elementIdxBuffer, 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, fld->pFieldname, (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, fld->pFieldname, (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, _array, 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, _array, 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, _array, value);
|
|
return;
|
|
}
|
|
|
|
// Yes, so set the data excluding the prefix.
|
|
setDataField(fieldName, _array, 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.c_str() ) )
|
|
{
|
|
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();
|
|
|
|
onAdd_callback(getId());
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SimObject::onRemove()
|
|
{
|
|
mFlags.clear(Added);
|
|
|
|
onRemove_callback(getId());
|
|
|
|
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();
|
|
onSelected_callback(this);
|
|
}
|
|
else
|
|
{
|
|
mFlags.clear( Selected );
|
|
_onUnselected();
|
|
onUnselected_callback(this);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
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;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SimObject::setInheritFrom(void* obj, const char* index, const char* data)
|
|
{
|
|
SimObject* object = static_cast<SimObject*>(obj);
|
|
|
|
SimObject* parent;
|
|
if (Sim::findObject(data, parent))
|
|
{
|
|
object->setCopySource(parent);
|
|
object->assignFieldsFrom(parent);
|
|
|
|
// copy any substitution statements
|
|
SimDataBlock* parent_db = dynamic_cast<SimDataBlock*>(parent);
|
|
if (parent_db)
|
|
{
|
|
SimDataBlock* currentNewObject_db = dynamic_cast<SimDataBlock*>(object);
|
|
if (currentNewObject_db)
|
|
currentNewObject_db->copySubstitutionsFrom(parent_db);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
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 ----
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void SimObject::onInspect(GuiInspector* inspector)
|
|
{
|
|
if (isMethod("onInspect"))
|
|
Con::executef(this, "onInspect", inspector);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
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::executef(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->mModule && e->mModule->getPath() )
|
|
str.append( e->mModule->getPath() );
|
|
str.append( '\n' );
|
|
if( e->mModule )
|
|
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;
|
|
}
|
|
|
|
DefineEngineMethod(SimObject, getMethodSigs, ArrayObject*, (bool commands), (false),
|
|
"List the methods defined on this object.\n\n"
|
|
"Each description is a newline-separated vector with the following elements:\n"
|
|
"- method prototype string.\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 (commands)
|
|
{
|
|
if ((e->mType < Namespace::Entry::ConsoleFunctionType))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if ((e->mType > Namespace::Entry::ScriptCallbackType))
|
|
continue;
|
|
}
|
|
StringBuilder str;
|
|
str.append("function ");
|
|
str.append(ns->getName());
|
|
str.append("::");
|
|
str.append(e->getPrototypeSig());
|
|
str.append('\n');
|
|
str.append("{");
|
|
String docs = e->getDocString();
|
|
if (!docs.isEmpty())
|
|
{
|
|
str.append("\n/*");
|
|
str.append(docs);
|
|
str.append("\n*/");
|
|
}
|
|
str.append('\n');
|
|
str.append("}");
|
|
dictionary->push_back(e->mFunctionName, str.end());
|
|
}
|
|
|
|
return dictionary;
|
|
}
|
|
|
|
DefineEngineFunction(getMethodSigsNS, ArrayObject*, (StringTableEntry className, bool commands), (false),
|
|
"List the methods defined on this object.\n\n"
|
|
"Each description is a newline-separated vector with the following elements:\n"
|
|
"- method prototype string.\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 = Con::lookupNamespace(className);
|
|
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 (commands)
|
|
{
|
|
if ((e->mType < Namespace::Entry::ConsoleFunctionType))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if ((e->mType > Namespace::Entry::ScriptCallbackType))
|
|
continue;
|
|
}
|
|
StringBuilder str;
|
|
str.append("function ");
|
|
str.append(ns->getName());
|
|
str.append("::");
|
|
str.append(e->getPrototypeSig());
|
|
str.append('\n');
|
|
str.append("{");
|
|
String docs = e->getDocString();
|
|
if (!docs.isEmpty())
|
|
{
|
|
str.append("\n/*");
|
|
str.append(docs);
|
|
str.append("\n*/");
|
|
}
|
|
str.append('\n');
|
|
str.append("}");
|
|
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 ];
|
|
|
|
if( !fieldName || !fieldName[0] )
|
|
{
|
|
AssertFatal(false, "SimObject::setFieldValue - Invalid field name.");
|
|
Con::errorf( "SimObject::setFieldValue - Invalid field name." );
|
|
return false;
|
|
}
|
|
|
|
// 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].getString());
|
|
|
|
ConsoleValue returnValue = Con::execute(object, argc - 1, argv + 1);
|
|
return Con::getReturnBuffer(returnValue.getString());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
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." )
|
|
{
|
|
if (!object->isProperlyAdded() || object->isRemoved())
|
|
return;
|
|
|
|
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 || f->flag.test(AbstractClassRep::FieldFlags::FIELD_ComponentInspectors))
|
|
numDummyEntries++;
|
|
}
|
|
|
|
return list.size() - numDummyEntries;
|
|
}
|
|
|
|
DefineEngineFunction(getFieldCountNS, S32, (StringTableEntry className), ,
|
|
"Get the number of static fields on the name space.\n"
|
|
"@return The number of static fields defined on the object.")
|
|
{
|
|
Namespace* ns = Con::lookupNamespace(className);
|
|
if (!ns)
|
|
return 0;
|
|
AbstractClassRep* rep = ns->mClassRep;
|
|
if (!rep)
|
|
return 0;
|
|
|
|
const AbstractClassRep::FieldList& list = rep->mFieldList;
|
|
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 || f->flag.test(AbstractClassRep::FieldFlags::FIELD_ComponentInspectors))
|
|
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 || f->flag.test(AbstractClassRep::FieldFlags::FIELD_ComponentInspectors))
|
|
continue;
|
|
|
|
if(currentField == index)
|
|
return f->pFieldname;
|
|
|
|
currentField++;
|
|
}
|
|
|
|
// if we found nada, return nada.
|
|
return "";
|
|
}
|
|
|
|
DefineEngineFunction(getFieldNS, const char*, (StringTableEntry className,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 \"\".")
|
|
{
|
|
Namespace* ns = Con::lookupNamespace(className);
|
|
if (!ns)
|
|
return 0;
|
|
AbstractClassRep* rep = ns->mClassRep;
|
|
if (!rep)
|
|
return 0;
|
|
|
|
const AbstractClassRep::FieldList& list = rep->mFieldList;
|
|
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 || f->flag.test(AbstractClassRep::FieldFlags::FIELD_ComponentInspectors))
|
|
continue;
|
|
|
|
if (currentField == index)
|
|
return f->pFieldname;
|
|
|
|
currentField++;
|
|
}
|
|
|
|
// if we found nada, return nada.
|
|
return "";
|
|
}
|
|
|
|
DefineEngineFunction(getClassHierarchy, const char*, (const char* name), ,
|
|
"Returns the inheritance hierarchy for a given class.")
|
|
{
|
|
AbstractClassRep* pRep = AbstractClassRep::findClassRep(name);
|
|
if (!pRep)
|
|
{
|
|
//Con::errorf("%s does not exist", name);
|
|
return StringTable->EmptyString();
|
|
}
|
|
|
|
StringBuilder buffer;
|
|
|
|
while (pRep != NULL)
|
|
{
|
|
StringTableEntry className = pRep->getClassName();
|
|
buffer.append(className);
|
|
buffer.append(" ");
|
|
pRep = pRep->getParentClass();
|
|
}
|
|
|
|
String result = buffer.end().trim();
|
|
//Con::printf("getClassHierarchy for %s=%s", name, result.c_str());
|
|
return Con::getReturnBuffer(result.c_str());
|
|
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#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() ) : String("") );
|
|
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::MemInfo memInfo;
|
|
Memory::getMemoryInfo( object, memInfo );
|
|
|
|
array->push_back( "Memory|AllocNumber", String::ToString( memInfo.allocId) );
|
|
array->push_back( "Memory|AllocSize", String::ToString( (U32)memInfo.size) );
|
|
array->push_back( "Memory|AllocFile", memInfo.file);
|
|
array->push_back( "Memory|AllocLine", String::ToString( memInfo.line) );
|
|
#endif
|
|
|
|
return array;
|
|
}
|
|
|
|
#endif
|