mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 12:44:46 +00:00
- Hides the special-case direct filepath field for ShapeAsset persist fields macro - Shifts the handling of TSStatics so the shape instance will load materials on the server as well as the client. This opens gameplay options as well as allowing rebaking of meshes functionality more easily - Expands AssetBase's isValidAsset utility function to actually check validity instead of just returning true - Adds isValid utility function to AssetPtr - Added new field flag that makes the field not write out to file - Removed legacy iconBitmap field from GuiIconButtonCtrl because it was causing errors - Fixed group filtering check of guiInspector to ignore case - Removed unneeded isFile checks for common datablock script files in Prototyping module script - Removed test datablocks from Prototyping module - Removed unnecessary container control from AssetBrowser - Adjusted preview regen logic of AssetBrowser so it doesn't trip if you're simply resizing the window - Fixed issue where row-vs-column layout logic for AssetBrowser when resizing window was fiddly - Added handling for when Dragging and Dropping datablock from AssetBrowser to spawn, it'll prompt if it spawns the actual object, or a spawnsphere that spawns said object. In the event of an PlayerData will also prompt if it should spawn an AIPlayer - Added ability to take a TSStatic that uses a baked down mesh and are able to restore it to the cache prefab, or trigger and in-place rebake to refresh it if something has changed in the original contents via RMB menu on the scene tree - Added ability to explode prefab to RMB menu on scene tree - Added ability to convert selection to prefab or bake to mesh in RMB menu on scene tree - Tweaked sizing of the DatablockEditorCreatePrompt window to not have cut off elements and easier to see/work with - Added sanity check to datablock editor creation - Fixed preview display of material in Decal Editor - Made compositeTextureEditor use the cached preview of images - Fixed sizing/spacing of gui selection dropdown as well as resolution dropdown of GuiEditor - Fixes MaterialEditor to properly save the group collapse state when editing - Adds ability to in-flow edit and create datablocks in the NavMesh Editor for the testing panel, and makes the datablock dropdown searchable - Fixed issue where opening the ShapeEditor via the edit button on a ShapeAsset field would cause the action buttons on the top bar to not show - Fixed error in shape editor where when exiting it was erroneously checking for a clear value of -1 rather than 0 - Removed unneeded top tabbook and tab page for main editor panel - Fixed issue where reset button of TerrainBrush Softness Curve editor didn't actually reset - Resized Object Builder window to not cut off elements and have enough width to show more data - Added a TypeCommand field type to Object Builder and changed spawnscript field of SpawnSphere to use it rather than a simple text edit field - Allow SpawnSphere in ObjectBuilder to be passed in spawn class and spawn datablock default info - Injects button to controllable objects when Inspecting them to make it easy to toggle if you're in control of it or not -
2516 lines
76 KiB
C++
2516 lines
76 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Copyright (c) 2012 GarageGames, LLC
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "persistenceManager.h"
|
|
#include "console/simSet.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "console/engineAPI.h"
|
|
#include "core/stream/fileStream.h"
|
|
#include "gui/core/guiTypes.h"
|
|
#include "materials/customMaterialDefinition.h"
|
|
#include "ts/tsShapeConstruct.h"
|
|
#include "sim/netStringTable.h"
|
|
|
|
|
|
IMPLEMENT_CONOBJECT(PersistenceManager);
|
|
|
|
ConsoleDocClass( PersistenceManager,
|
|
"@brief this class manages updating SimObjects in the file they were "
|
|
"created in non-destructively (mostly aimed at datablocks and materials).\n\n"
|
|
|
|
"Basic scripting interface:\n\n"
|
|
" - Creation: new PersistenceManager(FooManager);\n"
|
|
" - Flag objects as dirty: FooManager.setDirty(<object name or id>);\n"
|
|
" - Remove objects from dirty list: FooManager.removeDirty(<object name or id>);\n"
|
|
" - List all currently dirty objects: FooManager.listDirty();\n"
|
|
" - Check to see if an object is dirty: FooManager.isDirty(<object name or id>);\n"
|
|
" - Save dirty objects to their files: FooManager.saveDirty();\n\n"
|
|
"@note Dirty objects don't update their files until saveDirty() is "
|
|
"called so you can change their properties after you flag them as dirty\n\n"
|
|
"@note Currently only used by editors, not intended for actual game development\n\n"
|
|
"@ingroup Console\n"
|
|
"@ingroup Editors\n"
|
|
"@internal");
|
|
|
|
PersistenceManager::PersistenceManager()
|
|
{
|
|
mCurrentObject = NULL;
|
|
mCurrentFile = NULL;
|
|
|
|
VECTOR_SET_ASSOCIATION(mLineBuffer);
|
|
|
|
mLineBuffer.reserve(2048);
|
|
}
|
|
|
|
PersistenceManager::~PersistenceManager()
|
|
{
|
|
mDirtyObjects.clear();
|
|
}
|
|
|
|
bool PersistenceManager::onAdd()
|
|
{
|
|
if (!Parent::onAdd())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void PersistenceManager::onRemove()
|
|
{
|
|
Parent::onRemove();
|
|
}
|
|
|
|
void PersistenceManager::clearLineBuffer()
|
|
{
|
|
for (U32 i = 0; i < mLineBuffer.size(); i++)
|
|
{
|
|
dFree( mLineBuffer[ i ] );
|
|
mLineBuffer[ i ] = NULL;
|
|
}
|
|
|
|
mLineBuffer.clear();
|
|
}
|
|
|
|
void PersistenceManager::deleteObject(ParsedObject* object)
|
|
{
|
|
if (object)
|
|
{
|
|
// Clear up used property memory
|
|
for (U32 j = 0; j < object->properties.size(); j++)
|
|
{
|
|
ParsedProperty& prop = object->properties[j];
|
|
|
|
if (prop.value)
|
|
{
|
|
dFree( prop.value );
|
|
prop.value = NULL;
|
|
}
|
|
}
|
|
|
|
object->properties.clear();
|
|
|
|
// Delete the parsed object
|
|
SAFE_DELETE(object);
|
|
}
|
|
}
|
|
|
|
void PersistenceManager::clearObjects()
|
|
{
|
|
// Clean up the object buffer
|
|
for (U32 i = 0; i < mObjectBuffer.size(); i++)
|
|
deleteObject(mObjectBuffer[i]);
|
|
|
|
mObjectBuffer.clear();
|
|
|
|
// We shouldn't have anything in the object stack
|
|
// but let's clean it up just in case
|
|
// Clean up the object buffer
|
|
for (U32 i = 0; i < mObjectStack.size(); i++)
|
|
deleteObject(mObjectStack[i]);
|
|
|
|
mObjectStack.clear();
|
|
|
|
// Finally make sure there isn't a current object
|
|
deleteObject(mCurrentObject);
|
|
}
|
|
|
|
void PersistenceManager::clearFileData()
|
|
{
|
|
// Clear the active file name
|
|
if (mCurrentFile)
|
|
{
|
|
dFree( mCurrentFile );
|
|
mCurrentFile = NULL;
|
|
}
|
|
|
|
// Clear the file objects
|
|
clearObjects();
|
|
|
|
// Clear the line buffer
|
|
clearLineBuffer();
|
|
|
|
// Clear the tokenizer data
|
|
mParser.clear();
|
|
}
|
|
|
|
void PersistenceManager::clearAll()
|
|
{
|
|
// Clear the file data in case it hasn't cleared yet
|
|
clearFileData();
|
|
|
|
// Clear the dirty object list
|
|
mDirtyObjects.clear();
|
|
|
|
// Clear the remove field list
|
|
mRemoveFields.clear();
|
|
}
|
|
|
|
bool PersistenceManager::readFile(const char* fileName)
|
|
{
|
|
// Clear our previous file buffers just in
|
|
// case saveDirtyFile() didn't catch it
|
|
clearFileData();
|
|
|
|
// Handle an object writing out to a new file
|
|
if ( !Torque::FS::IsFile( fileName ) )
|
|
{
|
|
// Set our current file
|
|
mCurrentFile = dStrdup(fileName);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Try to open the file
|
|
FileStream stream;
|
|
stream.open( fileName, Torque::FS::File::Read );
|
|
|
|
if ( stream.getStatus() != Stream::Ok )
|
|
{
|
|
Con::errorf("PersistenceManager::readFile() - Failed to open %s", fileName);
|
|
|
|
return false;
|
|
}
|
|
|
|
// The file is good so read it in
|
|
mCurrentFile = dStrdup(fileName);
|
|
|
|
while(stream.getStatus() != Stream::EOS)
|
|
{
|
|
U8* buffer = ( U8* ) dMalloc( 2048 );
|
|
dMemset(buffer, 0, 2048);
|
|
|
|
stream.readLine(buffer, 2048);
|
|
|
|
mLineBuffer.push_back((const char*)buffer);
|
|
}
|
|
|
|
// Because of the way that writeLine() works we need to
|
|
// make sure we don't have an empty last line or else
|
|
// we will get an extra line break
|
|
if (mLineBuffer.size() > 0)
|
|
{
|
|
if (mLineBuffer.last() && mLineBuffer.last()[0] == 0)
|
|
{
|
|
dFree(mLineBuffer.last());
|
|
|
|
mLineBuffer.pop_back();
|
|
}
|
|
}
|
|
|
|
stream.close();
|
|
|
|
//Con::printf("Successfully opened and read %s", mCurrentFile);
|
|
|
|
return true;
|
|
}
|
|
|
|
void PersistenceManager::killObject()
|
|
{
|
|
// Don't save this object
|
|
SAFE_DELETE(mCurrentObject);
|
|
|
|
// If there is an object in the stack restore it
|
|
if (mObjectStack.size() > 0)
|
|
{
|
|
mCurrentObject = mObjectStack.last();
|
|
mObjectStack.pop_back();
|
|
}
|
|
}
|
|
|
|
void PersistenceManager::saveObject()
|
|
{
|
|
// Now that we have all of the data attempt to
|
|
// find the corresponding SimObject
|
|
mCurrentObject->simObject = Sim::findObject(mCurrentFile, mCurrentObject->endLine + 1);
|
|
|
|
// Save this object
|
|
mObjectBuffer.push_back(mCurrentObject);
|
|
|
|
mCurrentObject = NULL;
|
|
|
|
// If there is an object in the stack restore it
|
|
if (mObjectStack.size() > 0)
|
|
{
|
|
mCurrentObject = mObjectStack.last();
|
|
mObjectStack.pop_back();
|
|
}
|
|
}
|
|
|
|
void PersistenceManager::parseObject()
|
|
{
|
|
// We *should* already be in position but just in case...
|
|
if (!mParser.tokenICmp("new") &&
|
|
!mParser.tokenICmp("singleton") &&
|
|
!mParser.tokenICmp("datablock"))
|
|
{
|
|
Con::errorf("PersistenceManager::parseObject() - handed a position that doesn't point to an object \
|
|
creation keyword (new, singleton, datablock)");
|
|
|
|
return;
|
|
}
|
|
|
|
// If there is an object already being parsed then
|
|
// push it into the stack to finish later
|
|
if (mCurrentObject)
|
|
mObjectStack.push_back(mCurrentObject);
|
|
|
|
mCurrentObject = new ParsedObject;
|
|
|
|
//// If this object declaration is being assigned to a variable then
|
|
//// consider that the "start" of the declaration (otherwise we could
|
|
//// get a script compile error if we delete the object declaration)
|
|
mParser.regressToken(true);
|
|
|
|
if (mParser.tokenICmp("="))
|
|
{
|
|
// Ok, we are at an '='...back up to the beginning of that variable
|
|
mParser.regressToken(true);
|
|
|
|
// Get the startLine and startPosition
|
|
mCurrentObject->startLine = mParser.getCurrentLine();
|
|
mCurrentObject->startPosition = mParser.getTokenLineOffset();
|
|
|
|
// Advance back to the object declaration
|
|
mParser.advanceToken(true);
|
|
mParser.advanceToken(true);
|
|
}
|
|
else
|
|
{
|
|
// Advance back to the object declaration
|
|
mParser.advanceToken(true);
|
|
|
|
// Get the startLine and startPosition
|
|
mCurrentObject->startLine = mParser.getCurrentLine();
|
|
mCurrentObject->startPosition = mParser.getTokenLineOffset();
|
|
}
|
|
|
|
if (mObjectStack.size() > 0)
|
|
mCurrentObject->parentObject = mObjectStack.last();
|
|
|
|
// The next token should be the className
|
|
mCurrentObject->className = StringTable->insert(mParser.getNextToken());
|
|
|
|
// Advance to '('
|
|
mParser.advanceToken(true);
|
|
|
|
if (!mParser.tokenICmp("("))
|
|
{
|
|
Con::errorf("PersistenceManager::parseObject() - badly formed object \
|
|
declaration on line %d - was expecting a '(' character", mParser.getCurrentLine()+1);
|
|
|
|
// Remove this object without saving it
|
|
killObject();
|
|
|
|
return;
|
|
}
|
|
|
|
// The next token should either be the object name or ')'
|
|
mParser.advanceToken(true);
|
|
|
|
if (mParser.tokenICmp(")"))
|
|
{
|
|
mCurrentObject->name = StringTable->EmptyString();
|
|
|
|
mCurrentObject->nameLine = mParser.getCurrentLine();
|
|
mCurrentObject->namePosition = mParser.getTokenLineOffset();
|
|
}
|
|
else
|
|
{
|
|
mCurrentObject->name = StringTable->insert(mParser.getToken());
|
|
|
|
mCurrentObject->nameLine = mParser.getCurrentLine();
|
|
mCurrentObject->namePosition = mParser.getTokenLineOffset();
|
|
|
|
// Advance to either ')' or ':'
|
|
mParser.advanceToken(true);
|
|
|
|
if (mParser.tokenICmp(":"))
|
|
{
|
|
// Advance past the object we are copying from
|
|
mParser.advanceToken(true);
|
|
|
|
// Advance to ')'
|
|
mParser.advanceToken(true);
|
|
}
|
|
|
|
if (!mParser.tokenICmp(")"))
|
|
{
|
|
Con::errorf("PersistenceManager::parseObject() - badly formed object \
|
|
declaration on line %d - was expecting a ')' character", mParser.getCurrentLine()+1);
|
|
|
|
// Remove this object without saving it
|
|
killObject();
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// The next token should either be a ';' or a '{'
|
|
mParser.advanceToken(true);
|
|
|
|
if (mParser.tokenICmp(";"))
|
|
{
|
|
// Save the end line number
|
|
mCurrentObject->endLine = mParser.getCurrentLine();
|
|
|
|
// Save the end position
|
|
mCurrentObject->endPosition = mParser.getTokenLineOffset();
|
|
|
|
// Flag this object as not having braces
|
|
mCurrentObject->hasBraces = false;
|
|
|
|
saveObject(); // Object has no fields
|
|
|
|
return;
|
|
}
|
|
else if (!mParser.tokenICmp("{"))
|
|
{
|
|
Con::errorf("PersistenceManager::parseObject() - badly formed object \
|
|
declaration on line %d - was expecting a '{' character", mParser.getCurrentLine()+1);
|
|
|
|
// Remove this object without saving it
|
|
killObject();
|
|
|
|
return;
|
|
}
|
|
|
|
while (mParser.advanceToken(true))
|
|
{
|
|
// Check for a subobject
|
|
if (mParser.tokenICmp("new") ||
|
|
mParser.tokenICmp("singleton") ||
|
|
mParser.tokenICmp("datablock"))
|
|
{
|
|
parseObject();
|
|
}
|
|
|
|
// Check to see if we have a property
|
|
if (mParser.tokenICmp("="))
|
|
{
|
|
// Ok, we are at an '='...back up to find out
|
|
// what variable is getting assigned
|
|
mParser.regressToken(true);
|
|
|
|
const char* variable = mParser.getToken();
|
|
|
|
if (variable && dStrlen(variable) > 0)
|
|
{
|
|
// See if it is a global or a local variable
|
|
if (variable[0] == '%' || variable[0] == '$')
|
|
{
|
|
// We ignore this variable and go
|
|
// back to our previous place
|
|
mParser.advanceToken(true);
|
|
}
|
|
// Could also potentially be a <object>.<variable>
|
|
// assignment which we don't care about either
|
|
else if (dStrchr(variable, '.'))
|
|
{
|
|
// We ignore this variable and go
|
|
// back to our previous place
|
|
mParser.advanceToken(true);
|
|
}
|
|
// If we made it to here assume it is a variable
|
|
// for the current object
|
|
else
|
|
{
|
|
// Create our new property
|
|
mCurrentObject->properties.increment();
|
|
|
|
ParsedProperty& prop = mCurrentObject->properties.last();
|
|
|
|
// Check to see if this is an array variable
|
|
if (dStrlen(variable) > 3 && variable[dStrlen(variable) - 1] == ']')
|
|
{
|
|
// The last character is a ']' which *should* mean
|
|
// there is also a corresponding '['
|
|
const char* arrayPosStart = dStrrchr(variable, '[');
|
|
|
|
if (!arrayPosStart)
|
|
{
|
|
Con::errorf("PersistenceManager::parseObject() - error parsing array position - \
|
|
was expecting a '[' character");
|
|
}
|
|
else
|
|
{
|
|
// Parse the array position for the variable name
|
|
S32 arrayPos = -1;
|
|
|
|
dSscanf(arrayPosStart, "[%d]", &arrayPos);
|
|
|
|
// If we got a valid array position then set it
|
|
if (arrayPos > -1)
|
|
prop.arrayPos = arrayPos;
|
|
|
|
// Trim off the [<pos>] from the variable name
|
|
char* variableName = dStrdup(variable);
|
|
variableName[arrayPosStart - variable] = 0;
|
|
|
|
// Set the variable name to our new shortened name
|
|
variable = StringTable->insert(variableName, true);
|
|
|
|
// Cleanup our variableName buffer
|
|
dFree( variableName );
|
|
}
|
|
}
|
|
|
|
|
|
// Set back the variable name
|
|
prop.name = StringTable->insert(variable, true);
|
|
|
|
// Store the start position for this variable
|
|
prop.startLine = mParser.getCurrentLine();
|
|
prop.startPosition = mParser.getTokenLineOffset();
|
|
|
|
// Advance back to the '='
|
|
mParser.advanceToken(true);
|
|
|
|
// Sanity check
|
|
if (!mParser.tokenICmp("="))
|
|
Con::errorf("PersistenceManager::parseObject() - somehow we aren't \
|
|
pointing at the expected '=' character");
|
|
else
|
|
{
|
|
// The next token should be the value
|
|
// being assigned to the variable
|
|
mParser.advanceToken(true);
|
|
|
|
// Store the line number for this value
|
|
prop.valueLine = mParser.getCurrentLine();
|
|
|
|
// Store the values beginning position
|
|
prop.valuePosition = mParser.getTokenLineOffset();
|
|
|
|
// Read tokens up to the semicolon.
|
|
// Quoted tokens skip the leading and trailing quote characters. eg.
|
|
// "this" becomes: this
|
|
// "this" TAB "that" becomes: this" TAB "that
|
|
// "this" TAB "that" TAB "other" becomes: this" TAB "that" TAB "other
|
|
String value;
|
|
bool wasQuoted = false;
|
|
while (!mParser.endOfFile() && !mParser.tokenICmp(";"))
|
|
{
|
|
// Join tokens together (skipped first time through when string is empty)
|
|
if (value.length() > 0)
|
|
{
|
|
if (wasQuoted)
|
|
value += "\" "; // quoted followed by non-quoted
|
|
else if (mParser.tokenIsQuoted())
|
|
value += " \""; // non-quoted followed by quoted
|
|
else
|
|
value += " "; // non-quoted followed by non-quoted
|
|
}
|
|
|
|
value += mParser.getToken();
|
|
wasQuoted = mParser.tokenIsQuoted();
|
|
mParser.advanceToken(true);
|
|
}
|
|
|
|
// TODO: make sure this doesn't leak
|
|
prop.value = dStrdup(value.c_str());
|
|
|
|
if (!mParser.tokenICmp(";"))
|
|
Con::errorf("PersistenceManager::parseObject() - badly formed variable "
|
|
"assignment on line %d - was expecting a ';' character", mParser.getCurrentLine()+1);
|
|
|
|
// Store the end position for this variable
|
|
prop.endLine = mParser.getCurrentLine();
|
|
prop.endPosition = mParser.getTokenLineOffset();
|
|
if (wasQuoted)
|
|
prop.endPosition -= 1;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for the end of the object declaration
|
|
if (mParser.tokenICmp("}"))
|
|
{
|
|
// See if the next token is a ';'
|
|
mParser.advanceToken(true);
|
|
|
|
if (mParser.tokenICmp(";"))
|
|
{
|
|
// Save the end line number
|
|
mCurrentObject->endLine = mParser.getCurrentLine();
|
|
|
|
// Save the end position
|
|
mCurrentObject->endPosition = mParser.getTokenLineOffset();
|
|
|
|
saveObject();
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool PersistenceManager::parseFile(const char* fileName)
|
|
{
|
|
// Read the file into the line buffer
|
|
if (!readFile(fileName))
|
|
return false;
|
|
|
|
// Load it into our Tokenizer parser
|
|
if (!mParser.openFile(fileName))
|
|
{
|
|
// Handle an object writing out to a new file
|
|
if ( !Torque::FS::IsFile( fileName ) )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Set our reserved "single" tokens
|
|
mParser.setSingleTokens("(){};=:");
|
|
|
|
// Search object declarations
|
|
while (mParser.advanceToken(true))
|
|
{
|
|
if (mParser.tokenICmp("new") ||
|
|
mParser.tokenICmp("singleton") ||
|
|
mParser.tokenICmp("datablock"))
|
|
{
|
|
parseObject();
|
|
}
|
|
}
|
|
|
|
// If we had an object that didn't end properly
|
|
// then we could have objects on the stack
|
|
while (mCurrentObject)
|
|
saveObject();
|
|
|
|
//Con::errorf("Parsed Results:");
|
|
|
|
//for (U32 i = 0; i < mObjectBuffer.size(); i++)
|
|
//{
|
|
// ParsedObject* parsedObject = mObjectBuffer[i];
|
|
|
|
// Con::warnf(" mObjectBuffer[%d]:", i);
|
|
// Con::warnf(" name = %s", parsedObject->name);
|
|
// Con::warnf(" className = %s", parsedObject->className);
|
|
// Con::warnf(" startLine = %d", parsedObject->startLine + 1);
|
|
// Con::warnf(" endLine = %d", parsedObject->endLine + 1);
|
|
|
|
// //if (mObjectBuffer[i]->properties.size() > 0)
|
|
// //{
|
|
// // Con::warnf(" properties:");
|
|
// // for (U32 j = 0; j < mObjectBuffer[i]->properties.size(); j++)
|
|
// // Con::warnf(" %s = %s;", mObjectBuffer[i]->properties[j].name,
|
|
// // mObjectBuffer[i]->properties[j].value);
|
|
// //}
|
|
|
|
// if (!parsedObject->simObject.isNull())
|
|
// {
|
|
// SimObject* simObject = parsedObject->simObject;
|
|
|
|
// Con::warnf(" SimObject(%s) %d:", simObject->getName(), simObject->getId());
|
|
// Con::warnf(" declaration line = %d", simObject->getDeclarationLine());
|
|
// }
|
|
//}
|
|
|
|
return true;
|
|
}
|
|
|
|
S32 PersistenceManager::getPropertyIndex(ParsedObject* parsedObject, const char* fieldName, U32 arrayPos)
|
|
{
|
|
S32 propertyIndex = -1;
|
|
|
|
if (!parsedObject)
|
|
return propertyIndex;
|
|
|
|
for (U32 i = 0; i < parsedObject->properties.size(); i++)
|
|
{
|
|
if (dStricmp(fieldName, parsedObject->properties[i].name) == 0 &&
|
|
parsedObject->properties[i].arrayPos == arrayPos)
|
|
{
|
|
propertyIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return propertyIndex;
|
|
}
|
|
|
|
S32 PersistenceManager::getSpecialPropertyAtOffset(ParsedObject* parsedObject, const char* fieldName, U32 offsetPos)
|
|
{
|
|
S32 propertyIndex = -1;
|
|
|
|
if (!parsedObject)
|
|
return propertyIndex;
|
|
|
|
U32 hitCount = -1;
|
|
for (U32 i = 0; i < parsedObject->properties.size(); i++)
|
|
{
|
|
if (dStricmp(fieldName, parsedObject->properties[i].name) == 0)
|
|
{
|
|
hitCount++;
|
|
|
|
if (hitCount == offsetPos)
|
|
{
|
|
propertyIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return propertyIndex;
|
|
}
|
|
|
|
char* PersistenceManager::getObjectIndent(ParsedObject* object)
|
|
{
|
|
char* indent = Con::getReturnBuffer(2048);
|
|
indent[0] = 0;
|
|
|
|
if (!object)
|
|
return indent;
|
|
|
|
if (object->startLine < 0 || object->startLine >= mLineBuffer.size())
|
|
return indent;
|
|
|
|
const char* line = mLineBuffer[object->startLine];
|
|
|
|
if (line)
|
|
{
|
|
const char* nonSpace = line;
|
|
|
|
U32 strLen = dStrlen(line);
|
|
|
|
for (U32 i = 0; i < strLen; i++)
|
|
{
|
|
if (*nonSpace != ' ')
|
|
break;
|
|
|
|
nonSpace++;
|
|
}
|
|
|
|
dStrncpy(indent, line, nonSpace - line);
|
|
|
|
indent[nonSpace - line] = 0;
|
|
}
|
|
|
|
return indent;
|
|
}
|
|
|
|
void PersistenceManager::updatePositions(U32 lineNumber, U32 startPos, S32 diff)
|
|
{
|
|
if (diff == 0)
|
|
return;
|
|
|
|
for (U32 i = 0; i < mObjectBuffer.size(); i++)
|
|
{
|
|
ParsedObject* object = mObjectBuffer[i];
|
|
|
|
if (object->nameLine == lineNumber && object->namePosition > startPos)
|
|
object->namePosition += diff;
|
|
|
|
if (object->endLine == lineNumber && object->endPosition > startPos)
|
|
object->endPosition += diff;
|
|
|
|
if (lineNumber >= object->startLine && lineNumber <= object->endLine)
|
|
{
|
|
for (U32 j = 0; j < object->properties.size(); j++)
|
|
{
|
|
ParsedProperty& prop = object->properties[j];
|
|
|
|
S32 propStartPos = prop.startPosition;
|
|
S32 endPos = prop.endPosition;
|
|
S32 valuePos = prop.valuePosition;
|
|
|
|
if (lineNumber == prop.startLine && propStartPos > startPos)
|
|
{
|
|
propStartPos += diff;
|
|
|
|
if (propStartPos < 0)
|
|
propStartPos = 0;
|
|
|
|
prop.startPosition = valuePos;
|
|
}
|
|
if (lineNumber == prop.endLine && endPos > startPos)
|
|
{
|
|
endPos += diff;
|
|
|
|
if (endPos < 0)
|
|
endPos = 0;
|
|
|
|
prop.endPosition = endPos;
|
|
}
|
|
if (lineNumber == prop.valueLine && valuePos > startPos)
|
|
{
|
|
valuePos += diff;
|
|
|
|
if (valuePos < 0)
|
|
valuePos = 0;
|
|
|
|
prop.valuePosition = valuePos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PersistenceManager::updateLineOffsets(U32 startLine, S32 diff, ParsedObject* skipObject)
|
|
{
|
|
if (diff == 0)
|
|
return;
|
|
|
|
if (startLine >= mLineBuffer.size())
|
|
return;
|
|
|
|
if (startLine + diff >= mLineBuffer.size())
|
|
return;
|
|
|
|
// Make sure we don't double offset a SimObject's
|
|
// declaration line
|
|
SimObjectList updated;
|
|
|
|
if (skipObject && !skipObject->simObject.isNull())
|
|
updated.push_back_unique(skipObject->simObject);
|
|
|
|
for (U32 i = 0; i < mObjectBuffer.size(); i++)
|
|
{
|
|
ParsedObject* object = mObjectBuffer[i];
|
|
|
|
// See if this is the skipObject
|
|
if (skipObject && skipObject == object)
|
|
continue;
|
|
|
|
// We can safely ignore objects that
|
|
// came earlier in the file
|
|
if (object->endLine < startLine)
|
|
continue;
|
|
|
|
if (object->startLine >= startLine)
|
|
object->startLine += diff;
|
|
|
|
if (object->nameLine >= startLine)
|
|
object->nameLine += diff;
|
|
|
|
for (U32 j = 0; j < object->properties.size(); j++)
|
|
{
|
|
if (object->properties[j].startLine >= startLine)
|
|
object->properties[j].startLine += diff;
|
|
if (object->properties[j].endLine >= startLine)
|
|
object->properties[j].endLine += diff;
|
|
if (object->properties[j].valueLine >= startLine)
|
|
object->properties[j].valueLine += diff;
|
|
}
|
|
|
|
if (object->endLine >= startLine)
|
|
object->endLine += diff;
|
|
|
|
if (!object->simObject.isNull() &&
|
|
object->simObject->getDeclarationLine() > startLine)
|
|
{
|
|
// Check for already updated SimObject's
|
|
U32 currSize = updated.size();
|
|
updated.push_back_unique(object->simObject);
|
|
|
|
if (updated.size() == currSize)
|
|
continue;
|
|
|
|
S32 newDeclLine = object->simObject->getDeclarationLine() + diff;
|
|
|
|
if (newDeclLine < 0)
|
|
newDeclLine = 0;
|
|
|
|
object->simObject->setDeclarationLine(newDeclLine);
|
|
}
|
|
}
|
|
}
|
|
|
|
PersistenceManager::ParsedObject* PersistenceManager::findParentObject(SimObject* object, ParsedObject* parentObject)
|
|
{
|
|
ParsedObject* ret = NULL;
|
|
|
|
if (!object)
|
|
return ret;
|
|
|
|
// First test for the SimGroup it belongs to
|
|
ret = findParsedObject(object->getGroup(), parentObject);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
// TODO: Test all of the SimSet's that this object belongs to
|
|
|
|
return ret;
|
|
}
|
|
|
|
PersistenceManager::ParsedObject* PersistenceManager::findParsedObject(SimObject* object, ParsedObject* parentObject)
|
|
{
|
|
if (!object)
|
|
return NULL;
|
|
|
|
// See if our object belongs to a parent
|
|
if (!parentObject)
|
|
parentObject = findParentObject(object, parentObject);
|
|
|
|
// First let's compare the object to the SimObject's that
|
|
// we matched to our ParsedObject's when we loaded them
|
|
for (U32 i = 0; i < mObjectBuffer.size(); i++)
|
|
{
|
|
ParsedObject* testObj = mObjectBuffer[i];
|
|
|
|
if (testObj->simObject == object)
|
|
{
|
|
// Deal with children objects
|
|
if (testObj->parentObject != parentObject)
|
|
continue;
|
|
|
|
return testObj;
|
|
}
|
|
}
|
|
|
|
// Didn't find it in our ParsedObject's SimObject's
|
|
// so see if we can find one that corresponds to the
|
|
// same name and className
|
|
const char *originalName = object->getOriginalName();
|
|
|
|
// Find the corresponding ParsedObject
|
|
if (originalName && originalName[0])
|
|
{
|
|
for (U32 i = 0; i < mObjectBuffer.size(); i++)
|
|
{
|
|
ParsedObject* testObj = mObjectBuffer[i];
|
|
|
|
if (testObj->name == originalName)
|
|
{
|
|
// Deal with children objects
|
|
if (testObj->parentObject != parentObject)
|
|
continue;
|
|
|
|
return testObj;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Check internal names
|
|
if (object->getInternalName())
|
|
{
|
|
for (U32 i = 0; i < mObjectBuffer.size(); i++)
|
|
{
|
|
ParsedObject* testObj = mObjectBuffer[i];
|
|
for (U32 j = 0; j < testObj->properties.size(); j++)
|
|
{
|
|
const ParsedProperty &prop = testObj->properties[j];
|
|
|
|
if ( String::compare( prop.name, "internalName" ) == 0 &&
|
|
String::compare( prop.value, object->getInternalName() ) == 0 )
|
|
return testObj;
|
|
else if ( String::compare(prop.name, "internalName") == 0)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void PersistenceManager::updateToken( const U32 lineNumber, const U32 linePosition, const U32 oldValueLen, const char* newValue, bool addQuotes )
|
|
{
|
|
// Make sure we have a valid lineNumber
|
|
if (lineNumber < 0 || linePosition < 0 ||
|
|
lineNumber >= mLineBuffer.size())
|
|
return;
|
|
|
|
// Grab the line that the value is on
|
|
const char* line = mLineBuffer[lineNumber];
|
|
|
|
U32 newValueLen = ( newValue ) ? dStrlen(newValue) : 0;
|
|
|
|
// Make sure we have a valid linePosition
|
|
if (linePosition >= dStrlen(line) ||
|
|
linePosition + oldValueLen > dStrlen(line))
|
|
return;
|
|
|
|
// Get all of the characters up to the value position
|
|
U32 preStringLen = linePosition;
|
|
|
|
bool needQuotes = false;
|
|
if( addQuotes && line[ linePosition - 1 ] != '"' )
|
|
{
|
|
preStringLen ++;
|
|
needQuotes = true;
|
|
}
|
|
|
|
char* preString = ( char* ) dMalloc( preStringLen + 1 );
|
|
dMemcpy( preString, line, linePosition );
|
|
|
|
if( needQuotes )
|
|
{
|
|
preString[ linePosition ] = '"';
|
|
preString[ linePosition + 1 ] = 0;
|
|
}
|
|
else
|
|
preString[ linePosition ] = 0;
|
|
|
|
// Get all of the characters that occur after the value
|
|
|
|
const char* postStringSrc = line + linePosition + oldValueLen;
|
|
U32 postStringLen = dStrlen( postStringSrc );
|
|
if( needQuotes )
|
|
postStringLen ++;
|
|
|
|
char* postString = ( char* ) dMalloc( postStringLen + 1 );
|
|
if( needQuotes )
|
|
postString[ 0 ] = '"';
|
|
dStrcpy( &postString[ needQuotes ? 1 : 0 ], postStringSrc, postStringLen + (needQuotes ? 0 : 1) );
|
|
postString[ postStringLen ] = 0;
|
|
|
|
// Calculate the length of our new line
|
|
U32 newLineLen = 0;
|
|
|
|
newLineLen += preStringLen;
|
|
newLineLen += newValueLen;
|
|
newLineLen += postStringLen;
|
|
|
|
// Create a buffer for our new line and
|
|
// null terminate it
|
|
char* newLine = ( char* ) dMalloc( newLineLen + 1 );
|
|
newLine[0] = 0;
|
|
|
|
// Build the new line with the
|
|
// preString + newValue + postString
|
|
dStrcat(newLine, preString, newLineLen + 1);
|
|
if ( newValue )
|
|
dStrcat(newLine, newValue, newLineLen + 1);
|
|
dStrcat(newLine, postString, newLineLen + 1);
|
|
|
|
// Clear our existing line
|
|
if (mLineBuffer[lineNumber])
|
|
{
|
|
dFree( mLineBuffer[ lineNumber ] );
|
|
mLineBuffer[ lineNumber ] = NULL;
|
|
}
|
|
|
|
// Set the new line
|
|
mLineBuffer[lineNumber] = newLine;
|
|
|
|
// Figure out the size difference of the old value
|
|
// and new value in case we need to update any else
|
|
// on the line after it
|
|
S32 diff = newValueLen - oldValueLen;
|
|
|
|
// Update anything that is on the line after this that needs
|
|
// to change its offsets to reflect the new line
|
|
updatePositions(lineNumber, linePosition, diff);
|
|
|
|
// Clean up our buffers
|
|
dFree( preString );
|
|
dFree( postString );
|
|
}
|
|
|
|
const char* PersistenceManager::getFieldValue(SimObject* object, const char* fieldName, U32 arrayPos)
|
|
{
|
|
// Our return string
|
|
char* ret = NULL;
|
|
|
|
// Buffer to hold the string equivalent of the arrayPos
|
|
char arrayPosStr[8];
|
|
dSprintf(arrayPosStr, 8, "%d", arrayPos);
|
|
|
|
// Get the object's value
|
|
const char *value = object->getDataField(fieldName, arrayPosStr );
|
|
if (value)
|
|
ret = dStrdup(value);
|
|
|
|
return ret;
|
|
}
|
|
|
|
const char* PersistenceManager::createNewProperty(const char* name, const char* value, bool isArray, U32 arrayPos)
|
|
{
|
|
if (!name || !value)
|
|
return NULL;
|
|
|
|
AssertFatal( value[0] != StringTagPrefixByte, "Got tagged string!" );
|
|
|
|
char* newProp = ( char* ) dMalloc( 2048 );
|
|
dMemset(newProp, 0, 2048);
|
|
|
|
if (value)
|
|
{
|
|
if (isArray)
|
|
dSprintf(newProp, 2048, "%s[%d] = \"%s\";", name, arrayPos, value);
|
|
else
|
|
dSprintf(newProp, 2048, "%s = \"%s\";", name, value);
|
|
}
|
|
else
|
|
{
|
|
if (isArray)
|
|
dSprintf(newProp, 2048, "%s[%d] = \"\";", name, arrayPos);
|
|
else
|
|
dSprintf(newProp, 2048, "%s = \"\";", name);
|
|
}
|
|
|
|
return newProp;
|
|
}
|
|
|
|
bool PersistenceManager::isEmptyLine(const char* line)
|
|
{
|
|
// Simple test first
|
|
if (!line || dStrlen(line) == 0)
|
|
return true;
|
|
|
|
U32 len = dStrlen(line);
|
|
|
|
for (U32 i = 0; i < len; i++)
|
|
{
|
|
const char& c = line[i];
|
|
|
|
// Skip "empty" characters
|
|
if (c == ' ' ||
|
|
c == '\t' ||
|
|
c == '\r' ||
|
|
c == '\n')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If we have made it to the an end of the line
|
|
// comment then consider this an empty line
|
|
if (c == '/')
|
|
{
|
|
if (i < len - 1)
|
|
{
|
|
if (line[i + 1] == '/')
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// If it isn't an "empty" character or a comment then
|
|
// we have a valid character on the line and it isn't empty
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void PersistenceManager::removeLine(U32 lineNumber)
|
|
{
|
|
if (lineNumber >= mLineBuffer.size())
|
|
return;
|
|
|
|
if (mLineBuffer[lineNumber])
|
|
{
|
|
dFree( mLineBuffer[ lineNumber ] );
|
|
mLineBuffer[ lineNumber ] = NULL;
|
|
}
|
|
|
|
mLineBuffer.erase(lineNumber);
|
|
|
|
updateLineOffsets(lineNumber, -1);
|
|
}
|
|
|
|
void PersistenceManager::removeTextBlock(U32 startLine, U32 endLine, U32 startPos, U32 endPos, bool removeEmptyLines)
|
|
{
|
|
// Make sure we have valid lines
|
|
if (startLine >= mLineBuffer.size() || endLine >= mLineBuffer.size())
|
|
return;
|
|
|
|
// We assume that the startLine is before the endLine
|
|
if (startLine > endLine)
|
|
return;
|
|
|
|
// Grab the lines (they may be the same)
|
|
const char* startLineText = mLineBuffer[startLine];
|
|
const char* endLineText = mLineBuffer[endLine];
|
|
|
|
// Make sure we have a valid startPos
|
|
if (startPos >= dStrlen(startLineText))
|
|
return;
|
|
|
|
// Make sure we have a valid endPos
|
|
if (endPos > dStrlen(endLineText))
|
|
return;
|
|
|
|
if (startLine == endLine)
|
|
{
|
|
// Now let updateToken do the heavy lifting on removing it
|
|
updateToken(startLine, startPos, endPos - startPos, "");
|
|
|
|
// Handle removing an empty lines if desired
|
|
if (removeEmptyLines)
|
|
{
|
|
const char* line = mLineBuffer[startLine];
|
|
|
|
if (isEmptyLine(line))
|
|
removeLine(startLine);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Start with clearing the startLine from startPos to the end
|
|
updateToken(startLine, startPos, dStrlen(startLineText + startPos), "");
|
|
|
|
// Then clear the endLine from beginning to endPos
|
|
updateToken(endLine, 0, endPos, "");
|
|
|
|
// Handle removing an empty endLine if desired
|
|
if (removeEmptyLines)
|
|
{
|
|
const char* line = mLineBuffer[endLine];
|
|
|
|
if (isEmptyLine(line))
|
|
removeLine(endLine);
|
|
}
|
|
|
|
// Handle removing any lines between the startLine and endLine
|
|
for (U32 i = startLine + 1; i < endLine; i++)
|
|
removeLine(startLine + 1);
|
|
|
|
// Handle removing an empty startLine if desired
|
|
if (removeEmptyLines)
|
|
{
|
|
const char* line = mLineBuffer[startLine];
|
|
|
|
if (isEmptyLine(line))
|
|
removeLine(startLine);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PersistenceManager::removeParsedObject(ParsedObject* parsedObject)
|
|
{
|
|
if (!parsedObject)
|
|
return;
|
|
|
|
if (parsedObject->startLine < 0 || parsedObject->startLine >= mLineBuffer.size())
|
|
return;
|
|
|
|
if (parsedObject->endLine < 0 || parsedObject->startLine >= mLineBuffer.size())
|
|
return;
|
|
|
|
removeTextBlock(parsedObject->startLine, parsedObject->endLine,
|
|
parsedObject->startPosition, parsedObject->endPosition+1, true); // +1 to remove trailing semicolon as well
|
|
|
|
parsedObject->parentObject = NULL;
|
|
parsedObject->simObject = NULL;
|
|
}
|
|
|
|
void PersistenceManager::removeField(const ParsedProperty& prop)
|
|
{
|
|
if (prop.startLine < 0 || prop.startLine >= mLineBuffer.size())
|
|
return;
|
|
|
|
if (prop.endLine < 0 || prop.endLine >= mLineBuffer.size())
|
|
return;
|
|
|
|
S32 endPosition = prop.endPosition+1; // +1 to remove trailing semicolon as well
|
|
if ((endPosition < dStrlen(mLineBuffer[prop.endLine])) &&
|
|
(mLineBuffer[prop.endLine][endPosition] == ';')) // adjust end position for quoted values (otherwise a trailing semicolon will remain)
|
|
endPosition++;
|
|
|
|
removeTextBlock(prop.startLine, prop.endLine, prop.startPosition, endPosition, true);
|
|
}
|
|
|
|
U32 PersistenceManager::writeProperties(const Vector<String>& properties, const U32 insertLine, const char* objectIndent)
|
|
{
|
|
U32 currInsertLine = insertLine;
|
|
|
|
for (U32 i = 0; i < properties.size(); i++)
|
|
{
|
|
const char* prop = properties[i].c_str();
|
|
|
|
if (!prop || dStrlen(prop) == 0)
|
|
continue;
|
|
|
|
U32 len = dStrlen(objectIndent) + dStrlen(prop) + 4;
|
|
|
|
char* newLine = ( char* ) dMalloc( len );
|
|
|
|
dSprintf(newLine, len, "%s %s", objectIndent, prop);
|
|
|
|
mLineBuffer.insert(currInsertLine, newLine);
|
|
currInsertLine++;
|
|
}
|
|
|
|
return currInsertLine - insertLine;
|
|
}
|
|
|
|
PersistenceManager::ParsedObject* PersistenceManager::writeNewObject(SimObject* object, const Vector<String>& properties, const U32 insertLine, ParsedObject* parentObject)
|
|
{
|
|
ParsedObject* parsedObject = new ParsedObject;
|
|
|
|
parsedObject->name = object->getName();
|
|
parsedObject->className = object->getClassName();
|
|
parsedObject->simObject = object;
|
|
|
|
U32 currInsertLine = insertLine;
|
|
|
|
// If the parentObject isn't set see if
|
|
// we can find it in the file
|
|
if (!parentObject)
|
|
parentObject = findParentObject(object);
|
|
|
|
parsedObject->parentObject = parentObject;
|
|
|
|
char* indent = getObjectIndent(parentObject);
|
|
|
|
if (parentObject)
|
|
dStrcat(indent, " \0", 2048);
|
|
|
|
// Write out the beginning of the object declaration
|
|
const char* dclToken = "new";
|
|
|
|
if (dynamic_cast<Material*>(object) ||
|
|
dynamic_cast<CustomMaterial*>(object) ||
|
|
dynamic_cast<GuiControlProfile*>(object) ||
|
|
dynamic_cast<TSShapeConstructor*>(object))
|
|
dclToken = "singleton";
|
|
else if( dynamic_cast< SimDataBlock* >( object ) )
|
|
dclToken = "datablock";
|
|
|
|
char newLine[ 4096 ];
|
|
dMemset(newLine, 0, sizeof( newLine));
|
|
|
|
// New line before an object declaration
|
|
dSprintf(newLine, sizeof( newLine ), "");
|
|
|
|
mLineBuffer.insert(currInsertLine, dStrdup(newLine));
|
|
currInsertLine++;
|
|
dMemset(newLine, 0, sizeof( newLine ));
|
|
|
|
parsedObject->startLine = currInsertLine;
|
|
parsedObject->nameLine = currInsertLine;
|
|
parsedObject->namePosition = dStrlen(indent) + dStrlen(dclToken) + dStrlen(object->getClassName()) + 2;
|
|
|
|
// Objects that had no name were getting saved out as: Object((null))
|
|
if ( object->getName() != NULL )
|
|
{
|
|
if( object->getCopySource() )
|
|
dSprintf(newLine, sizeof( newLine ), "%s%s %s(%s : %s)", indent, dclToken, object->getClassName(), object->getName(),
|
|
object->getCopySource() ? object->getCopySource()->getName() : "" );
|
|
else
|
|
dSprintf(newLine, sizeof( newLine ), "%s%s %s(%s)", indent, dclToken, object->getClassName(), object->getName());
|
|
}
|
|
else
|
|
dSprintf(newLine, sizeof( newLine ), "%s%s %s()", indent, dclToken, object->getClassName() );
|
|
|
|
mLineBuffer.insert(currInsertLine, dStrdup(newLine));
|
|
currInsertLine++;
|
|
dMemset(newLine, 0, sizeof( newLine ));
|
|
|
|
dSprintf(newLine, sizeof( newLine ), "%s{", indent);
|
|
|
|
mLineBuffer.insert(currInsertLine, dStrdup(newLine));
|
|
currInsertLine++;
|
|
dMemset(newLine, 0, sizeof( newLine ));
|
|
|
|
currInsertLine += writeProperties(properties, currInsertLine, indent);
|
|
|
|
parsedObject->endLine = currInsertLine;
|
|
parsedObject->updated = true;
|
|
|
|
dSprintf(newLine, sizeof( newLine ), "%s};", indent);
|
|
|
|
mLineBuffer.insert(currInsertLine, dStrdup(newLine));
|
|
currInsertLine++;
|
|
|
|
updateLineOffsets(insertLine, currInsertLine - insertLine, parsedObject);
|
|
|
|
mObjectBuffer.push_back(parsedObject);
|
|
|
|
// Update the SimObject to reflect its saved name and declaration line.
|
|
// These values should always reflect what is in the file, even if the object
|
|
// has not actually been re-created from an execution of that file yet.
|
|
object->setOriginalName( object->getName() );
|
|
object->setDeclarationLine( currInsertLine );
|
|
|
|
if (mCurrentFile)
|
|
object->setFilename(mCurrentFile);
|
|
|
|
return parsedObject;
|
|
}
|
|
|
|
void PersistenceManager::updateObject(SimObject* object, ParsedObject* parentObject)
|
|
{
|
|
// Create a default object of the same type
|
|
ConsoleObject *defaultConObject = ConsoleObject::create(object->getClassName());
|
|
SimObject* defaultObject = dynamic_cast<SimObject*>(defaultConObject);
|
|
|
|
// ***Really*** shouldn't happen
|
|
if (!defaultObject)
|
|
return;
|
|
|
|
Vector<String> newLines;
|
|
|
|
ParsedObject* parsedObject = findParsedObject(object, parentObject);
|
|
|
|
// If we don't already have an association between the ParsedObject
|
|
// and the SimObject then go ahead and create it
|
|
if (parsedObject && parsedObject->simObject.isNull())
|
|
parsedObject->simObject = object;
|
|
|
|
// Kill all fields on the remove list.
|
|
|
|
for( U32 i = 0; i < mRemoveFields.size(); ++ i )
|
|
{
|
|
RemoveField& field = mRemoveFields[ i ];
|
|
if( field.object != object )
|
|
continue;
|
|
|
|
S32 propertyIndex = getPropertyIndex( parsedObject, field.fieldName, field.arrayPos );
|
|
if( propertyIndex != -1 )
|
|
removeField( parsedObject->properties[ propertyIndex ] );
|
|
}
|
|
|
|
// Get our field list
|
|
const AbstractClassRep::FieldList &list = object->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;
|
|
|
|
if (f->flag.test(AbstractClassRep::FIELD_SpecialtyArrayField))
|
|
{
|
|
U32 fieldArraySize = object->getSpecialFieldSize(f->pFieldname);
|
|
|
|
for(U32 j = 0; j < fieldArraySize; j++)
|
|
{
|
|
String value = object->getSpecialFieldOut(f->pFieldname, j);
|
|
|
|
// Make sure we got a value
|
|
if (value.isEmpty())
|
|
continue;
|
|
|
|
// Let's see if this field is already in the file
|
|
S32 propertyIndex = getSpecialPropertyAtOffset(parsedObject, f->pFieldname, j);
|
|
|
|
if (propertyIndex > -1)
|
|
{
|
|
ParsedProperty& prop = parsedObject->properties[propertyIndex];
|
|
|
|
// If this field is on the remove list then remove it and continue
|
|
if (findRemoveField(object, f->pFieldname, j))
|
|
{
|
|
removeField(parsedObject->properties[propertyIndex]);
|
|
continue;
|
|
}
|
|
|
|
// Run the parsed value through the console system conditioners so
|
|
// that it will better match the data we got back from the object.
|
|
String evalue = Con::getFormattedData(f->type, prop.value, f->table, f->flag);
|
|
|
|
// If our data doesn't match then we get to update it.
|
|
//
|
|
// As for copy-sources, we just assume here that if a property setting
|
|
// is there in the file, the user does not want it inherited from the copy-source
|
|
// even in the case the actual values are identical.
|
|
|
|
if (value != evalue)
|
|
{
|
|
if (value.isEmpty() &&
|
|
dStricmp(getFieldValue(defaultObject, f->pFieldname, j), value.c_str()) == 0 &&
|
|
(!object->getCopySource() || dStricmp(getFieldValue(object->getCopySource(), f->pFieldname, j), value.c_str()) == 0))
|
|
{
|
|
removeField(prop);
|
|
}
|
|
else
|
|
{
|
|
updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, value.c_str(), true);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No need to process a removed field that doesn't exist in the file
|
|
if (findRemoveField(object, f->pFieldname, j))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool mustUpdate = false;
|
|
|
|
// If we didn't find the property in the ParsedObject
|
|
// then we need to compare against the default value
|
|
// for this property and save it out if it is different
|
|
|
|
String defaultValue = defaultObject->getSpecialFieldOut(f->pFieldname, j);
|
|
if (defaultValue.isEmpty() || value != defaultValue)
|
|
{
|
|
// Value differs. Check whether it also differs from the
|
|
// value in the copy source if there is one.
|
|
|
|
if (object->getCopySource())
|
|
{
|
|
String copySourceValue = getFieldValue(object->getCopySource(), f->pFieldname, j);
|
|
if (copySourceValue.isEmpty() || copySourceValue != value)
|
|
mustUpdate = true;
|
|
}
|
|
else
|
|
mustUpdate = true;
|
|
}
|
|
else
|
|
{
|
|
// Value does not differ. If it differs from the copy source's value,
|
|
// though, we still want to write it out as otherwise we'll see the
|
|
// copy source's value override us.
|
|
|
|
if (object->getCopySource())
|
|
{
|
|
String copySourceValue = getFieldValue(object->getCopySource(), f->pFieldname, j);
|
|
if (!copySourceValue.isEmpty() && copySourceValue != value)
|
|
mustUpdate = true;
|
|
}
|
|
}
|
|
|
|
// The default value for most string type fields is
|
|
// NULL so we can't just continue here or we'd never ever
|
|
// write them out...
|
|
//
|
|
//if (!defaultValue)
|
|
// continue;
|
|
|
|
// If the object's value is different from the default
|
|
// value then add it to the ParsedObject's newLines
|
|
if (mustUpdate)
|
|
{
|
|
newLines.push_back(value);
|
|
}
|
|
}
|
|
|
|
//dFree(value);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (U32 j = 0; S32(j) < f->elementCount; j++)
|
|
{
|
|
String value = getFieldValue(object, f->pFieldname, j);
|
|
|
|
// Make sure we got a value
|
|
if (value.isEmpty())
|
|
continue;
|
|
|
|
// Let's see if this field is already in the file
|
|
S32 propertyIndex = getPropertyIndex(parsedObject, f->pFieldname, j);
|
|
|
|
if (propertyIndex > -1)
|
|
{
|
|
ParsedProperty& prop = parsedObject->properties[propertyIndex];
|
|
|
|
// If this field is on the remove list then remove it and continue
|
|
if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value.c_str()))
|
|
{
|
|
removeField(parsedObject->properties[propertyIndex]);
|
|
continue;
|
|
}
|
|
|
|
// Run the parsed value through the console system conditioners so
|
|
// that it will better match the data we got back from the object.
|
|
String evalue = Con::getFormattedData(f->type, prop.value, f->table, f->flag);
|
|
|
|
// If our data doesn't match then we get to update it.
|
|
//
|
|
// As for copy-sources, we just assume here that if a property setting
|
|
// is there in the file, the user does not want it inherited from the copy-source
|
|
// even in the case the actual values are identical.
|
|
|
|
if (value != evalue)
|
|
{
|
|
if (value.isEmpty() &&
|
|
dStricmp(getFieldValue(defaultObject, f->pFieldname, j), value.c_str()) == 0 &&
|
|
(!object->getCopySource() || dStricmp(getFieldValue(object->getCopySource(), f->pFieldname, j), value.c_str()) == 0))
|
|
{
|
|
removeField(prop);
|
|
}
|
|
else
|
|
{
|
|
// TODO: This should be wrapped in a helper method... probably.
|
|
// Detect and collapse relative path information
|
|
if (f->type == TypeFilename ||
|
|
f->type == TypeStringFilename ||
|
|
f->type == TypeImageFilename ||
|
|
f->type == TypePrefabFilename ||
|
|
f->type == TypeShapeFilename ||
|
|
f->type == TypeSoundFilename)
|
|
{
|
|
char fnBuf[1024];
|
|
Con::collapseScriptFilename(fnBuf, 1024, value.c_str());
|
|
|
|
updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, fnBuf, true);
|
|
}
|
|
else if (f->type == TypeCommand || f->type == TypeString || f->type == TypeRealString)
|
|
{
|
|
char cmdBuf[1024];
|
|
expandEscape(cmdBuf, value.c_str());
|
|
|
|
updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, cmdBuf, true);
|
|
}
|
|
else
|
|
updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, value, true);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No need to process a removed field that doesn't exist in the file
|
|
if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value.c_str()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool mustUpdate = false;
|
|
|
|
// If we didn't find the property in the ParsedObject
|
|
// then we need to compare against the default value
|
|
// for this property and save it out if it is different
|
|
|
|
String defaultValue = getFieldValue(defaultObject, f->pFieldname, j);
|
|
if (defaultValue.isEmpty() || value != defaultValue)
|
|
{
|
|
// Value differs. Check whether it also differs from the
|
|
// value in the copy source if there is one.
|
|
|
|
if (object->getCopySource())
|
|
{
|
|
String copySourceValue = getFieldValue(object->getCopySource(), f->pFieldname, j);
|
|
if (copySourceValue.isEmpty() || copySourceValue != value)
|
|
mustUpdate = true;
|
|
}
|
|
else
|
|
mustUpdate = true;
|
|
}
|
|
else
|
|
{
|
|
// Value does not differ. If it differs from the copy source's value,
|
|
// though, we still want to write it out as otherwise we'll see the
|
|
// copy source's value override us.
|
|
|
|
if (object->getCopySource())
|
|
{
|
|
String copySourceValue = getFieldValue(object->getCopySource(), f->pFieldname, j);
|
|
if (!copySourceValue.isEmpty() && copySourceValue != value)
|
|
mustUpdate = true;
|
|
}
|
|
}
|
|
|
|
// The default value for most string type fields is
|
|
// NULL so we can't just continue here or we'd never ever
|
|
// write them out...
|
|
//
|
|
//if (!defaultValue)
|
|
// continue;
|
|
|
|
// If the object's value is different from the default
|
|
// value then add it to the ParsedObject's newLines
|
|
if (mustUpdate)
|
|
{
|
|
// TODO: This should be wrapped in a helper method... probably.
|
|
// Detect and collapse relative path information
|
|
if (f->type == TypeFilename ||
|
|
f->type == TypeStringFilename ||
|
|
f->type == TypeImageFilename ||
|
|
f->type == TypePrefabFilename ||
|
|
f->type == TypeShapeFilename ||
|
|
f->type == TypeSoundFilename)
|
|
{
|
|
char fnBuf[1024];
|
|
Con::collapseScriptFilename(fnBuf, 1024, value.c_str());
|
|
|
|
newLines.push_back(createNewProperty(f->pFieldname, fnBuf, f->elementCount > 1, j));
|
|
}
|
|
else if (f->type == TypeCommand)
|
|
{
|
|
char cmdBuf[1024];
|
|
expandEscape(cmdBuf, value.c_str());
|
|
|
|
newLines.push_back(createNewProperty(f->pFieldname, cmdBuf, f->elementCount > 1, j));
|
|
}
|
|
else
|
|
newLines.push_back(createNewProperty(f->pFieldname, value, f->elementCount > 1, j));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle dynamic fields
|
|
SimFieldDictionary* fieldDict = object->getFieldDictionary();
|
|
|
|
for(SimFieldDictionaryIterator itr(fieldDict); *itr; ++itr)
|
|
{
|
|
SimFieldDictionary::Entry * entry = (*itr);
|
|
if( !entry->value )
|
|
continue;
|
|
|
|
// Let's see if this field is already in the file
|
|
S32 propertyIndex = getPropertyIndex(parsedObject, entry->slotName);
|
|
|
|
if (propertyIndex > -1)
|
|
{
|
|
ParsedProperty& prop = parsedObject->properties[propertyIndex];
|
|
|
|
// If this field is on the remove list then remove it and continue
|
|
if (findRemoveField(object, entry->slotName) || !object->writeField(entry->slotName, entry->value))
|
|
{
|
|
removeField( parsedObject->properties[ propertyIndex ] );
|
|
continue;
|
|
}
|
|
|
|
if( object->getCopySource() )
|
|
{
|
|
const char* copySourceFieldValue = object->getCopySource()->getDataField( entry->slotName, NULL );
|
|
if( String::compare( copySourceFieldValue, entry->value ) == 0 )
|
|
{
|
|
removeField( prop );
|
|
continue;
|
|
}
|
|
}
|
|
|
|
const char* evalue = prop.value;
|
|
|
|
const char *entryVal = entry->value;
|
|
if ( entryVal[0] == StringTagPrefixByte )
|
|
entryVal = gNetStringTable->lookupString( dAtoi( entryVal+1 ) );
|
|
else
|
|
{
|
|
// Run the parsed value through the console system conditioners so
|
|
// that it will better match the data we got back from the object.
|
|
evalue = Con::getFormattedData(TypeString, evalue);
|
|
}
|
|
|
|
// If our data doesn't match then we get to update it
|
|
if (dStricmp(entryVal, evalue) != 0)
|
|
updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, entryVal);
|
|
}
|
|
else
|
|
{
|
|
// No need to process a removed field that doesn't exist in the file
|
|
if (findRemoveField(object, entry->slotName) || !object->writeField(entry->slotName, entry->value))
|
|
continue;
|
|
|
|
if( object->getCopySource() )
|
|
{
|
|
const char* copySourceFieldValue = object->getCopySource()->getDataField( entry->slotName, NULL );
|
|
if( String::compare( copySourceFieldValue, entry->value ) == 0 )
|
|
continue;
|
|
}
|
|
|
|
newLines.push_back(createNewProperty(entry->slotName, entry->value));
|
|
}
|
|
}
|
|
|
|
// If we have a parsedObject and the name changed
|
|
// then update the parsedObject to the new name.
|
|
// NOTE: an object 'can' have a NULL name which crashes in dStricmp.
|
|
if (parsedObject && parsedObject->name != StringTable->insert(object->getName(), true) )
|
|
{
|
|
StringTableEntry objectName = StringTable->insert(object->getName(), true);
|
|
|
|
if (parsedObject->name != objectName)
|
|
{
|
|
// Update the name in the file
|
|
updateToken(parsedObject->nameLine, parsedObject->namePosition, dStrlen(parsedObject->name), object->getName());
|
|
|
|
// Updated the parsedObject's name
|
|
parsedObject->name = objectName;
|
|
|
|
// Updated the object's "original" name to the one that is now in the file
|
|
object->setOriginalName(objectName);
|
|
}
|
|
}
|
|
|
|
if (parsedObject && newLines.size() > 0)
|
|
{
|
|
U32 lastPropLine = parsedObject->endLine;
|
|
|
|
if (parsedObject->properties.size() > 0)
|
|
lastPropLine = parsedObject->properties.last().valueLine + 1;
|
|
|
|
U32 currInsertLine = lastPropLine;
|
|
|
|
const char* indent = getObjectIndent(parsedObject);
|
|
|
|
// This should handle adding the opening { to an object
|
|
// that formerly did not have {};
|
|
if (!parsedObject->hasBraces)
|
|
{
|
|
updateToken(parsedObject->endLine, parsedObject->endPosition, 1, "\r\n{");
|
|
|
|
currInsertLine++;
|
|
}
|
|
|
|
currInsertLine += writeProperties(newLines, currInsertLine, indent);
|
|
|
|
// This should handle adding the opening } to an object
|
|
// that formerly did not have {};
|
|
if (!parsedObject->hasBraces)
|
|
{
|
|
U32 len = dStrlen(indent) + 3;
|
|
char* newLine = ( char* ) dMalloc( len );
|
|
|
|
dSprintf(newLine, len, "%s};", indent);
|
|
|
|
mLineBuffer.insert(currInsertLine, newLine);
|
|
currInsertLine++;
|
|
}
|
|
|
|
// Update the line offsets to account for the new lines
|
|
updateLineOffsets(lastPropLine, currInsertLine - lastPropLine);
|
|
}
|
|
else if (!parsedObject)
|
|
{
|
|
U32 insertLine = mLineBuffer.size();
|
|
|
|
if (!parentObject)
|
|
parentObject = findParentObject(object, parentObject);
|
|
|
|
if (parentObject && parentObject->endLine > -1)
|
|
insertLine = parentObject->endLine;
|
|
|
|
parsedObject = writeNewObject(object, newLines, insertLine, parentObject);
|
|
}
|
|
|
|
// Clean up the newLines memory
|
|
newLines.clear();
|
|
|
|
SimSet* set = dynamic_cast<SimSet*>(object);
|
|
|
|
if (set)
|
|
{
|
|
for(SimSet::iterator i = set->begin(); i != set->end(); i++)
|
|
{
|
|
SimObject* subObject = (SimObject*)(*i);
|
|
updateObject(subObject, parsedObject);
|
|
}
|
|
}
|
|
|
|
// Loop through the children of this parsedObject
|
|
// If they haven't been updated then assume that they
|
|
// don't exist in the file anymore
|
|
if (parsedObject)
|
|
{
|
|
for (S32 i = 0; i < mObjectBuffer.size(); i++)
|
|
{
|
|
ParsedObject* removeObj = mObjectBuffer[i];
|
|
|
|
if (removeObj->parentObject == parsedObject && !removeObj->updated)
|
|
{
|
|
removeParsedObject(removeObj);
|
|
|
|
mObjectBuffer.erase(i);
|
|
i--;
|
|
|
|
deleteObject(removeObj);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Flag this as an updated object
|
|
if (parsedObject)
|
|
parsedObject->updated = true;
|
|
|
|
// Cleanup our created default object
|
|
delete defaultConObject;
|
|
}
|
|
|
|
bool PersistenceManager::saveDirtyFile()
|
|
{
|
|
FileStream stream;
|
|
stream.open( mCurrentFile, Torque::FS::File::Write );
|
|
|
|
if ( stream.getStatus() != Stream::Ok )
|
|
{
|
|
clearFileData();
|
|
|
|
return false;
|
|
}
|
|
|
|
for (U32 i = 0; i < mLineBuffer.size(); i++)
|
|
stream.writeLine((const U8*)mLineBuffer[i]);
|
|
|
|
stream.close();
|
|
|
|
//Con::printf("Successfully opened and wrote %s", mCurrentFile);
|
|
|
|
//Con::errorf("Updated Results:");
|
|
|
|
//for (U32 i = 0; i < mObjectBuffer.size(); i++)
|
|
//{
|
|
// ParsedObject* parsedObject = mObjectBuffer[i];
|
|
|
|
// Con::warnf(" mObjectBuffer[%d]:", i);
|
|
// Con::warnf(" name = %s", parsedObject->name);
|
|
// Con::warnf(" className = %s", parsedObject->className);
|
|
// Con::warnf(" startLine = %d", parsedObject->startLine + 1);
|
|
// Con::warnf(" endLine = %d", parsedObject->endLine + 1);
|
|
|
|
// //if (mObjectBuffer[i]->properties.size() > 0)
|
|
// //{
|
|
// // Con::warnf(" properties:");
|
|
// // for (U32 j = 0; j < mObjectBuffer[i]->properties.size(); j++)
|
|
// // Con::warnf(" %s = %s;", mObjectBuffer[i]->properties[j].name,
|
|
// // mObjectBuffer[i]->properties[j].value);
|
|
// //}
|
|
|
|
// if (!parsedObject->simObject.isNull())
|
|
// {
|
|
// SimObject* simObject = parsedObject->simObject;
|
|
|
|
// Con::warnf(" SimObject(%s) %d:", simObject->getName(), simObject->getId());
|
|
// Con::warnf(" declaration line = %d", simObject->getDeclarationLine());
|
|
// }
|
|
//}
|
|
|
|
// Clear our file data
|
|
clearFileData();
|
|
|
|
return true;
|
|
}
|
|
|
|
S32 QSORT_CALLBACK PersistenceManager::compareFiles(const void* a,const void* b)
|
|
{
|
|
DirtyObject* objectA = (DirtyObject*)(a);
|
|
DirtyObject* objectB = (DirtyObject*)(b);
|
|
|
|
if (objectA->isNull())
|
|
return -1;
|
|
else if (objectB->isNull())
|
|
return 1;
|
|
|
|
if (objectA->fileName == objectB->fileName)
|
|
return objectA->getObject()->getDeclarationLine() - objectB->getObject()->getDeclarationLine();
|
|
|
|
return dStricmp(objectA->fileName, objectB->fileName);
|
|
}
|
|
|
|
bool PersistenceManager::setDirty(SimObject* inObject, const char* inFileName)
|
|
{
|
|
// Check if the object is already in the dirty list...
|
|
DirtyObject *pDirty = findDirtyObject( inObject );
|
|
|
|
// The filename we will save this object to (later)..
|
|
String saveFile;
|
|
|
|
// Expand the script filename if we were passed one.
|
|
if ( inFileName )
|
|
{
|
|
char buffer[4096];
|
|
Con::expandScriptFilename( buffer, 4096, inFileName );
|
|
saveFile = buffer;
|
|
}
|
|
|
|
// If no filename was passed in, and the object was already dirty,
|
|
// we have nothing to do.
|
|
if ( saveFile.isEmpty() && pDirty )
|
|
return true;
|
|
|
|
// Otherwise default to the simObject's filename.
|
|
if ( saveFile.isEmpty() )
|
|
saveFile = inObject->getFilename();
|
|
|
|
// Error if still no filename.
|
|
if ( saveFile.isEmpty() )
|
|
{
|
|
if (inObject->getName())
|
|
Con::errorf("PersistenceManager::setDirty() - SimObject %s has no file name associated with it - can not save", inObject->getName());
|
|
else
|
|
Con::errorf("PersistenceManager::setDirty() - SimObject %d has no file name associated with it - can not save", inObject->getId());
|
|
|
|
return false;
|
|
}
|
|
|
|
// Update the DirtyObject's fileName if we have it
|
|
// else create a new one.
|
|
|
|
if ( pDirty )
|
|
pDirty->fileName = StringTable->insert( saveFile );
|
|
else
|
|
{
|
|
// Add the newly dirty object.
|
|
mDirtyObjects.increment();
|
|
mDirtyObjects.last().setObject( inObject );
|
|
mDirtyObjects.last().fileName = StringTable->insert( saveFile );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void PersistenceManager::removeDirty(SimObject* object)
|
|
{
|
|
for (U32 i = 0; i < mDirtyObjects.size(); i++)
|
|
{
|
|
const DirtyObject& dirtyObject = mDirtyObjects[i];
|
|
|
|
if (dirtyObject.isNull())
|
|
continue;
|
|
|
|
if (dirtyObject.getObject() == object)
|
|
{
|
|
mDirtyObjects.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (U32 i = 0; i < mRemoveFields.size(); i++)
|
|
{
|
|
const RemoveField& field = mRemoveFields[i];
|
|
|
|
if (field.object != object)
|
|
continue;
|
|
|
|
mRemoveFields.erase(i);
|
|
|
|
if (i > 0)
|
|
i--;
|
|
}
|
|
}
|
|
|
|
void PersistenceManager::addRemoveField(SimObject* object, const char* fieldName)
|
|
{
|
|
// Check to see if this is an array variable
|
|
U32 arrayPos = 0;
|
|
const char* name = fieldName;
|
|
|
|
if (dStrlen(fieldName) > 3 && fieldName[dStrlen(fieldName) - 1] == ']')
|
|
{
|
|
// The last character is a ']' which *should* mean
|
|
// there is also a corresponding '['
|
|
const char* arrayPosStart = dStrrchr(fieldName, '[');
|
|
|
|
if (!arrayPosStart)
|
|
{
|
|
Con::errorf("PersistenceManager::addRemoveField() - error parsing array position - \
|
|
was expecting a '[' character");
|
|
}
|
|
else
|
|
{
|
|
// Parse the array position for the variable name
|
|
dSscanf(arrayPosStart, "[%d]", &arrayPos);
|
|
|
|
// Trim off the [<pos>] from the variable name
|
|
char* variableName = dStrdup(fieldName);
|
|
variableName[arrayPosStart - fieldName] = 0;
|
|
|
|
// Set the variable name to our new shortened name
|
|
name = StringTable->insert(variableName, true);
|
|
|
|
// Cleanup our variableName buffer
|
|
dFree( variableName );
|
|
}
|
|
}
|
|
|
|
// Make sure this field isn't already on the list
|
|
if (!findRemoveField(object, name, arrayPos))
|
|
{
|
|
mRemoveFields.increment();
|
|
|
|
RemoveField& field = mRemoveFields.last();
|
|
|
|
field.object = object;
|
|
field.fieldName = StringTable->insert(name);
|
|
field.arrayPos = arrayPos;
|
|
}
|
|
}
|
|
|
|
bool PersistenceManager::isDirty(SimObject* object)
|
|
{
|
|
return ( findDirtyObject( object ) != NULL );
|
|
}
|
|
|
|
PersistenceManager::DirtyObject* PersistenceManager::findDirtyObject(SimObject* object)
|
|
{
|
|
for (U32 i = 0; i < mDirtyObjects.size(); i++)
|
|
{
|
|
const DirtyObject& dirtyObject = mDirtyObjects[i];
|
|
|
|
if (dirtyObject.isNull())
|
|
continue;
|
|
|
|
if (dirtyObject.getObject() == object)
|
|
return &mDirtyObjects[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool PersistenceManager::findRemoveField(SimObject* object, const char* fieldName, U32 arrayPos)
|
|
{
|
|
for (U32 i = 0; i < mRemoveFields.size(); i++)
|
|
{
|
|
if (mRemoveFields[i].object == object &&
|
|
mRemoveFields[i].arrayPos == arrayPos &&
|
|
dStricmp(mRemoveFields[i].fieldName, fieldName) == 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool PersistenceManager::saveDirty()
|
|
{
|
|
// Remove any null SimObject's first
|
|
for (S32 i = 0; i < mDirtyObjects.size(); i++)
|
|
{
|
|
const DirtyObject& dirtyObject = mDirtyObjects[i];
|
|
|
|
if (dirtyObject.isNull())
|
|
{
|
|
mDirtyObjects.erase(i);
|
|
i--;
|
|
}
|
|
}
|
|
|
|
// Sort by filename and declaration lines
|
|
dQsort(mDirtyObjects.address(), mDirtyObjects.size(), sizeof(DirtyList::value_type), compareFiles);
|
|
|
|
for (U32 i = 0; i < mDirtyObjects.size(); i++)
|
|
{
|
|
const DirtyObject& dirtyObject = mDirtyObjects[i];
|
|
|
|
if (dirtyObject.isNull())
|
|
continue;
|
|
|
|
SimObject* object = dirtyObject.getObject();
|
|
|
|
if (!mCurrentFile || dStricmp(mCurrentFile, dirtyObject.fileName) != 0)
|
|
{
|
|
// If mCurrentFile is set then that means we
|
|
// changed file names so save our previous one
|
|
if (mCurrentFile)
|
|
saveDirtyFile();
|
|
|
|
// Open our new file and parse it
|
|
bool success = parseFile(dirtyObject.fileName);
|
|
|
|
if (!success)
|
|
{
|
|
const char *name = object->getName();
|
|
if (name)
|
|
{
|
|
Con::errorf("PersistenceManager::saveDirty(): Unable to open %s to save %s %s (%d)",
|
|
dirtyObject.fileName, object->getClassName(), name, object->getId());
|
|
}
|
|
else
|
|
{
|
|
Con::errorf("PersistenceManager::saveDirty(): Unable to open %s to save %s (%d)",
|
|
dirtyObject.fileName, object->getClassName(), object->getId());
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Update this object's properties
|
|
//
|
|
// An empty script file (with 1 line) gets here with zero
|
|
// elements in the linebuffer, so this would prevent us from
|
|
// ever writing to it... Or is this test preventing bad things from
|
|
// happening if the file didn't exist at all?
|
|
//
|
|
if (mCurrentFile /*&& mLineBuffer.size() > 0*/)
|
|
updateObject(object);
|
|
}
|
|
|
|
// Save out our last file
|
|
if (mCurrentFile)
|
|
saveDirtyFile();
|
|
|
|
// Done writing out our dirty objects so reset everything
|
|
clearAll();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PersistenceManager::saveDirtyObject(SimObject* object)
|
|
{
|
|
// find our object passed in
|
|
for (U32 i = 0; i < mDirtyObjects.size(); i++)
|
|
{
|
|
const DirtyObject& dirtyObject = mDirtyObjects[i];
|
|
|
|
if (dirtyObject.isNull())
|
|
continue;
|
|
|
|
if (dirtyObject.getObject() == object)
|
|
{
|
|
// Open our new file and parse it
|
|
bool success = parseFile(dirtyObject.fileName);
|
|
|
|
if (!success)
|
|
{
|
|
const char *name = object->getName();
|
|
if (name)
|
|
{
|
|
Con::errorf("PersistenceManager::saveDirtyObject(): Unable to open %s to save %s %s (%d)",
|
|
dirtyObject.fileName, object->getClassName(), name, object->getId());
|
|
}
|
|
else
|
|
{
|
|
Con::errorf("PersistenceManager::saveDirtyObject(): Unable to open %s to save %s (%d)",
|
|
dirtyObject.fileName, object->getClassName(), object->getId());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// if the file exists then lets update and save
|
|
if(mCurrentFile)
|
|
{
|
|
updateObject(object);
|
|
saveDirtyFile();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// remove this object from the dirty list
|
|
removeDirty(object);
|
|
|
|
return true;
|
|
}
|
|
|
|
void PersistenceManager::removeObjectFromFile(SimObject* object, const char* fileName)
|
|
{
|
|
if (mCurrentFile)
|
|
{
|
|
Con::errorf("PersistenceManager::removeObjectFromFile(): Can't remove an object from a \
|
|
file while another is currently opened");
|
|
|
|
return;
|
|
}
|
|
|
|
const char* file = object->getFilename();
|
|
if (fileName)
|
|
{
|
|
char buffer[1024];
|
|
Con::expandScriptFilename( buffer, 1024, fileName );
|
|
|
|
file = StringTable->insert(buffer);
|
|
}
|
|
|
|
bool success = false;
|
|
|
|
if ( file && file[ 0 ] )
|
|
success = parseFile(file);
|
|
|
|
if (!success)
|
|
{
|
|
const char *name = object->getName();
|
|
|
|
String errorNameStr;
|
|
if ( name )
|
|
errorNameStr = String::ToString( "%s %s (%d)", object->getClassName(), name, object->getId() );
|
|
else
|
|
errorNameStr = String::ToString( "%s (%d)", object->getClassName(), object->getId() );
|
|
|
|
if ( !file )
|
|
Con::errorf("PersistenceManager::removeObjectFromFile(): File was null trying to save %s", errorNameStr.c_str() );
|
|
else
|
|
Con::errorf("PersistenceManager::removeObjectFromFile(): Unable to open %s to save %s", file, errorNameStr.c_str() );
|
|
|
|
// Reset everything
|
|
clearAll();
|
|
|
|
return;
|
|
}
|
|
|
|
ParsedObject* parsedObject = findParsedObject(object);
|
|
|
|
if (!parsedObject)
|
|
{
|
|
const char *name = object->getName();
|
|
if (name)
|
|
{
|
|
Con::errorf("PersistenceManager::removeObjectFromFile(): Unable to find %s %s (%d) in %s",
|
|
object->getClassName(), name, object->getId(), file);
|
|
}
|
|
else
|
|
{
|
|
Con::errorf("PersistenceManager::removeObjectFromFile(): Unable to find %s (%d) in %s",
|
|
object->getClassName(), object->getId(), file);
|
|
}
|
|
|
|
// Reset everything
|
|
clearAll();
|
|
|
|
return;
|
|
}
|
|
|
|
removeParsedObject(parsedObject);
|
|
|
|
for (U32 i = 0; i < mObjectBuffer.size(); i++)
|
|
{
|
|
ParsedObject* removeObj = mObjectBuffer[i];
|
|
|
|
if (removeObj == parsedObject)
|
|
{
|
|
mObjectBuffer.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
deleteObject(parsedObject);
|
|
|
|
// Save out the file
|
|
if (mCurrentFile)
|
|
saveDirtyFile();
|
|
|
|
// Reset everything
|
|
clearAll();
|
|
}
|
|
|
|
void PersistenceManager::deleteObjectsFromFile(const char* fileName)
|
|
{
|
|
if ( mCurrentFile )
|
|
{
|
|
Con::errorf( "PersistenceManager::deleteObjectsFromFile(): Cannot process while file while another is currently open." );
|
|
return;
|
|
}
|
|
|
|
// Expand Script File.
|
|
char buffer[1024];
|
|
Con::expandScriptFilename( buffer, 1024, fileName );
|
|
|
|
// Parse File.
|
|
if ( !parseFile( StringTable->insert(buffer) ) )
|
|
{
|
|
// Invalid.
|
|
return;
|
|
}
|
|
|
|
// Iterate over the objects.
|
|
for ( Vector<ParsedObject*>::iterator itr = mObjectBuffer.begin(); itr != mObjectBuffer.end(); itr++ )
|
|
{
|
|
SimObject *object;
|
|
if ( Sim::findObject( ( *itr )->name, object ) )
|
|
{
|
|
// Delete the Object.
|
|
object->deleteObject();
|
|
}
|
|
}
|
|
|
|
// Clear.
|
|
clearAll();
|
|
}
|
|
|
|
DefineEngineMethod( PersistenceManager, deleteObjectsFromFile, void, ( const char * fileName ), , "( fileName )"
|
|
"Delete all of the objects that are created from the given file." )
|
|
{
|
|
// Delete Objects.
|
|
object->deleteObjectsFromFile( fileName );
|
|
}
|
|
|
|
DefineEngineMethod( PersistenceManager, setDirty, void, ( const char * objName, const char * fileName ), (""), "(SimObject object, [filename])"
|
|
"Mark an existing SimObject as dirty (will be written out when saveDirty() is called).")
|
|
{
|
|
SimObject *dirtyObject = NULL;
|
|
if (String::compare(objName,"") != 0)
|
|
{
|
|
if (!Sim::findObject(objName, dirtyObject))
|
|
{
|
|
Con::printf("PersistenceManager::setDirty(): Invalid SimObject: %s", objName);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Prevent ourselves from shooting us in the foot.
|
|
if( dirtyObject == Sim::getRootGroup() )
|
|
{
|
|
Con::errorf( "PersistenceManager::setDirty(): Cannot save RootGroup" );
|
|
return;
|
|
}
|
|
|
|
if (dirtyObject)
|
|
{
|
|
if (String::compare( fileName,"")!=0)
|
|
object->setDirty(dirtyObject, fileName);
|
|
else
|
|
object->setDirty(dirtyObject);
|
|
}
|
|
}
|
|
|
|
DefineEngineMethod( PersistenceManager, removeDirty, void, ( const char * objName ), , "(SimObject object)"
|
|
"Remove a SimObject from the dirty list.")
|
|
{
|
|
SimObject *dirtyObject = NULL;
|
|
if (String::compare( objName,"")!=0)
|
|
{
|
|
if (!Sim::findObject(objName, dirtyObject))
|
|
{
|
|
Con::printf("PersistenceManager::removeDirty(): Invalid SimObject: %s", objName);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (dirtyObject)
|
|
object->removeDirty(dirtyObject);
|
|
}
|
|
|
|
DefineEngineMethod( PersistenceManager, isDirty, bool, ( const char * objName ), , "(SimObject object)"
|
|
"Returns true if the SimObject is on the dirty list.")
|
|
{
|
|
SimObject *dirtyObject = NULL;
|
|
if (String::compare ( objName,"")!=0)
|
|
{
|
|
if (!Sim::findObject(objName, dirtyObject))
|
|
{
|
|
Con::printf("PersistenceManager::isDirty(): Invalid SimObject: %s", objName);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (dirtyObject)
|
|
return object->isDirty(dirtyObject);
|
|
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( PersistenceManager, hasDirty, bool, (), , "()"
|
|
"Returns true if the manager has dirty objects to save." )
|
|
{
|
|
return object->hasDirty();
|
|
}
|
|
|
|
DefineEngineMethod( PersistenceManager, getDirtyObjectCount, S32, (), , "()"
|
|
"Returns the number of dirty objects." )
|
|
{
|
|
return object->getDirtyList().size();
|
|
}
|
|
|
|
DefineEngineMethod( PersistenceManager, getDirtyObject, S32, (S32 index), , "( index )"
|
|
"Returns the ith dirty object." )
|
|
{
|
|
if ( index < 0 || index >= object->getDirtyList().size() )
|
|
{
|
|
Con::warnf( "PersistenceManager::getDirtyObject() - Index (%s) out of range.", index );
|
|
return 0;
|
|
}
|
|
|
|
// Fetch Object.
|
|
const PersistenceManager::DirtyObject& dirtyObject = object->getDirtyList()[index];
|
|
|
|
// Return Id.
|
|
return ( dirtyObject.getObject() ) ? dirtyObject.getObject()->getId() : 0;
|
|
}
|
|
|
|
DefineEngineMethod( PersistenceManager, listDirty, void, (), , "()"
|
|
"Prints the dirty list to the console.")
|
|
{
|
|
const PersistenceManager::DirtyList dirtyList = object->getDirtyList();
|
|
|
|
for(U32 i = 0; i < dirtyList.size(); i++)
|
|
{
|
|
const PersistenceManager::DirtyObject& dirtyObject = dirtyList[i];
|
|
|
|
if (dirtyObject.isNull())
|
|
continue;
|
|
|
|
SimObject *obj = dirtyObject.getObject();
|
|
bool isSet = dynamic_cast<SimSet *>(obj) != 0;
|
|
const char *name = obj->getName();
|
|
if (name)
|
|
{
|
|
Con::printf(" %d,\"%s\": %s %s %s", obj->getId(), name,
|
|
obj->getClassName(), dirtyObject.fileName, isSet ? "(g)":"");
|
|
}
|
|
else
|
|
{
|
|
Con::printf(" %d: %s %s, %s", obj->getId(), obj->getClassName(),
|
|
dirtyObject.fileName, isSet ? "(g)" : "");
|
|
}
|
|
}
|
|
}
|
|
|
|
DefineEngineMethod( PersistenceManager, saveDirty, bool, (), , "()"
|
|
"Saves all of the SimObject's on the dirty list to their respective files.")
|
|
{
|
|
return object->saveDirty();
|
|
}
|
|
|
|
DefineEngineMethod( PersistenceManager, saveDirtyObject, bool, (const char * objName), , "(SimObject object)"
|
|
"Save a dirty SimObject to it's file.")
|
|
{
|
|
SimObject *dirtyObject = NULL;
|
|
if (String::compare ( objName, "")!=0)
|
|
{
|
|
if (!Sim::findObject(objName, dirtyObject))
|
|
{
|
|
Con::printf("%s(): Invalid SimObject: %s", object->getName(), objName);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (dirtyObject)
|
|
return object->saveDirtyObject(dirtyObject);
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( PersistenceManager, clearAll, void, (), , "()"
|
|
"Clears all the tracked objects without saving them." )
|
|
{
|
|
object->clearAll();
|
|
}
|
|
|
|
DefineEngineMethod( PersistenceManager, removeObjectFromFile, void, (const char * objName, const char * filename),("") , "(SimObject object, [filename])"
|
|
"Remove an existing SimObject from a file (can optionally specify a different file than \
|
|
the one it was created in.")
|
|
{
|
|
SimObject *dirtyObject = NULL;
|
|
if (String::compare ( objName , "")!=0)
|
|
{
|
|
if (!Sim::findObject(objName, dirtyObject))
|
|
{
|
|
Con::printf("PersistenceManager::removeObjectFromFile(): Invalid SimObject: %s", objName);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (dirtyObject)
|
|
{
|
|
if (String::compare( filename,"")!=0)
|
|
object->removeObjectFromFile(dirtyObject, filename);
|
|
else
|
|
object->removeObjectFromFile(dirtyObject);
|
|
}
|
|
}
|
|
|
|
DefineEngineMethod( PersistenceManager, removeField, void, (const char * objName, const char * fieldName), , "(SimObject object, string fieldName)"
|
|
"Remove a specific field from an object declaration.")
|
|
{
|
|
SimObject *dirtyObject = NULL;
|
|
if (String::compare(objName,"")!=0)
|
|
{
|
|
if (!Sim::findObject(objName, dirtyObject))
|
|
{
|
|
Con::printf("PersistenceManager::removeField(): Invalid SimObject: %s", objName);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (dirtyObject)
|
|
{
|
|
if (String::compare(fieldName,"") != 0)
|
|
object->addRemoveField(dirtyObject, fieldName);
|
|
}
|
|
}
|