persistenceManager.cpp
Engine/source/console/persistenceManager.cpp
Public Functions
ConsoleDocClass(PersistenceManager , "@brief this class manages updating SimObjects in the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> they were " "created in non-destructively (mostly aimed at datablocks and materials).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "Basic scripting <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">interface:\n\n</a>" " - Creation: <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classpersistencemanager/">PersistenceManager</a>(FooManager);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - Flag objects as dirty: FooManager.setDirty(<object name or <a href="/coding/file/win32cursorcontroller_8cpp/#win32cursorcontroller_8cpp_1ab38592509822a5f4674447022cc62efe">id</a>>);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - Remove objects from dirty list: FooManager.removeDirty(<object name or <a href="/coding/file/win32cursorcontroller_8cpp/#win32cursorcontroller_8cpp_1ab38592509822a5f4674447022cc62efe">id</a>>);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - List all currently dirty objects: FooManager.listDirty();\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - Check <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> see <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> an object is dirty: FooManager.isDirty(<object name or <a href="/coding/file/win32cursorcontroller_8cpp/#win32cursorcontroller_8cpp_1ab38592509822a5f4674447022cc62efe">id</a>>);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - Save dirty objects <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> their files: FooManager.saveDirty();\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "@note Dirty objects don'<a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1aded116371789db1fd63c90ef00c95a3d">t</a> update their files until saveDirty() is " "called so you can change their properties after you flag them as <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">dirty\n\n</a>" "@note Currently only used by editors, not intended <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> actual game <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">development\n\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Console\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Editors\n</a>" " @internal" )
DefineEngineMethod(PersistenceManager , clearAll , void , () , "()" "Clears all the tracked objects without saving them." )
DefineEngineMethod(PersistenceManager , deleteObjectsFromFile , void , (const char *fileName) , "( fileName )" "Delete all of the objects that are created from the given file." )
DefineEngineMethod(PersistenceManager , getDirtyObject , S32 , (S32 index) , "( index )" "Returns the ith dirty object." )
DefineEngineMethod(PersistenceManager , getDirtyObjectCount , S32 , () , "()" "Returns the number of dirty objects." )
DefineEngineMethod(PersistenceManager , hasDirty , bool , () , "()" "Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the manager has dirty objects <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> save." )
DefineEngineMethod(PersistenceManager , isDirty , bool , (const char *objName) , "(SimObject object)" "Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the <a href="/coding/class/classsimobject/">SimObject</a> is on the dirty list." )
DefineEngineMethod(PersistenceManager , listDirty , void , () , "()" "Prints the dirty list <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the console." )
DefineEngineMethod(PersistenceManager , removeDirty , void , (const char *objName) , "(SimObject object)" "Remove <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classsimobject/">SimObject</a> from the dirty list." )
DefineEngineMethod(PersistenceManager , removeField , void , (const char *objName, const char *fieldName) , "(SimObject object, string fieldName)" "Remove <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> specific field from an object declaration." )
DefineEngineMethod(PersistenceManager , removeObjectFromFile , void , (const char *objName, const char *filename) , ("") )
DefineEngineMethod(PersistenceManager , saveDirty , bool , () , "()" "Saves all of the <a href="/coding/class/classsimobject/">SimObject</a>'s on the dirty list <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> their respective files." )
DefineEngineMethod(PersistenceManager , saveDirtyObject , bool , (const char *objName) , "(SimObject object)" "Save <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> dirty <a href="/coding/class/classsimobject/">SimObject</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> it's file." )
DefineEngineMethod(PersistenceManager , setDirty , void , (const char *objName, const char *fileName) , ("") , "(SimObject object, [filename])" "Mark an existing <a href="/coding/class/classsimobject/">SimObject</a> as dirty (will be written out when saveDirty() is called)." )
Detailed Description
Public Functions
ConsoleDocClass(PersistenceManager , "@brief this class manages updating SimObjects in the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> they were " "created in non-destructively (mostly aimed at datablocks and materials).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "Basic scripting <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">interface:\n\n</a>" " - Creation: <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classpersistencemanager/">PersistenceManager</a>(FooManager);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - Flag objects as dirty: FooManager.setDirty(<object name or <a href="/coding/file/win32cursorcontroller_8cpp/#win32cursorcontroller_8cpp_1ab38592509822a5f4674447022cc62efe">id</a>>);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - Remove objects from dirty list: FooManager.removeDirty(<object name or <a href="/coding/file/win32cursorcontroller_8cpp/#win32cursorcontroller_8cpp_1ab38592509822a5f4674447022cc62efe">id</a>>);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - List all currently dirty objects: FooManager.listDirty();\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - Check <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> see <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> an object is dirty: FooManager.isDirty(<object name or <a href="/coding/file/win32cursorcontroller_8cpp/#win32cursorcontroller_8cpp_1ab38592509822a5f4674447022cc62efe">id</a>>);\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " - Save dirty objects <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> their files: FooManager.saveDirty();\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "@note Dirty objects don'<a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1aded116371789db1fd63c90ef00c95a3d">t</a> update their files until saveDirty() is " "called so you can change their properties after you flag them as <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">dirty\n\n</a>" "@note Currently only used by editors, not intended <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> actual game <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">development\n\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Console\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Editors\n</a>" " @internal" )
DefineEngineMethod(PersistenceManager , clearAll , void , () , "()" "Clears all the tracked objects without saving them." )
DefineEngineMethod(PersistenceManager , deleteObjectsFromFile , void , (const char *fileName) , "( fileName )" "Delete all of the objects that are created from the given file." )
DefineEngineMethod(PersistenceManager , getDirtyObject , S32 , (S32 index) , "( index )" "Returns the ith dirty object." )
DefineEngineMethod(PersistenceManager , getDirtyObjectCount , S32 , () , "()" "Returns the number of dirty objects." )
DefineEngineMethod(PersistenceManager , hasDirty , bool , () , "()" "Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the manager has dirty objects <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> save." )
DefineEngineMethod(PersistenceManager , isDirty , bool , (const char *objName) , "(SimObject object)" "Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the <a href="/coding/class/classsimobject/">SimObject</a> is on the dirty list." )
DefineEngineMethod(PersistenceManager , listDirty , void , () , "()" "Prints the dirty list <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the console." )
DefineEngineMethod(PersistenceManager , removeDirty , void , (const char *objName) , "(SimObject object)" "Remove <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classsimobject/">SimObject</a> from the dirty list." )
DefineEngineMethod(PersistenceManager , removeField , void , (const char *objName, const char *fieldName) , "(SimObject object, string fieldName)" "Remove <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> specific field from an object declaration." )
DefineEngineMethod(PersistenceManager , removeObjectFromFile , void , (const char *objName, const char *filename) , ("") )
DefineEngineMethod(PersistenceManager , saveDirty , bool , () , "()" "Saves all of the <a href="/coding/class/classsimobject/">SimObject</a>'s on the dirty list <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> their respective files." )
DefineEngineMethod(PersistenceManager , saveDirtyObject , bool , (const char *objName) , "(SimObject object)" "Save <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> dirty <a href="/coding/class/classsimobject/">SimObject</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> it's file." )
DefineEngineMethod(PersistenceManager , setDirty , void , (const char *objName, const char *fileName) , ("") , "(SimObject object, [filename])" "Mark an existing <a href="/coding/class/classsimobject/">SimObject</a> as dirty (will be written out when saveDirty() is called)." )
IMPLEMENT_CONOBJECT(PersistenceManager )
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "persistenceManager.h" 25#include "console/simSet.h" 26#include "console/consoleTypes.h" 27#include "console/engineAPI.h" 28#include "core/stream/fileStream.h" 29#include "gui/core/guiTypes.h" 30#include "materials/customMaterialDefinition.h" 31#include "ts/tsShapeConstruct.h" 32#include "sim/netStringTable.h" 33 34 35IMPLEMENT_CONOBJECT(PersistenceManager); 36 37ConsoleDocClass( PersistenceManager, 38 "@brief this class manages updating SimObjects in the file they were " 39 "created in non-destructively (mostly aimed at datablocks and materials).\n\n" 40 41 "Basic scripting interface:\n\n" 42 " - Creation: new PersistenceManager(FooManager);\n" 43 " - Flag objects as dirty: FooManager.setDirty(<object name or id>);\n" 44 " - Remove objects from dirty list: FooManager.removeDirty(<object name or id>);\n" 45 " - List all currently dirty objects: FooManager.listDirty();\n" 46 " - Check to see if an object is dirty: FooManager.isDirty(<object name or id>);\n" 47 " - Save dirty objects to their files: FooManager.saveDirty();\n\n" 48 "@note Dirty objects don't update their files until saveDirty() is " 49 "called so you can change their properties after you flag them as dirty\n\n" 50 "@note Currently only used by editors, not intended for actual game development\n\n" 51 "@ingroup Console\n" 52 "@ingroup Editors\n" 53 "@internal"); 54 55PersistenceManager::PersistenceManager() 56{ 57 mCurrentObject = NULL; 58 mCurrentFile = NULL; 59 60 VECTOR_SET_ASSOCIATION(mLineBuffer); 61 62 mLineBuffer.reserve(2048); 63} 64 65PersistenceManager::~PersistenceManager() 66{ 67 mDirtyObjects.clear(); 68} 69 70bool PersistenceManager::onAdd() 71{ 72 if (!Parent::onAdd()) 73 return false; 74 75 return true; 76} 77 78void PersistenceManager::onRemove() 79{ 80 Parent::onRemove(); 81} 82 83void PersistenceManager::clearLineBuffer() 84{ 85 for (U32 i = 0; i < mLineBuffer.size(); i++) 86 { 87 dFree( mLineBuffer[ i ] ); 88 mLineBuffer[ i ] = NULL; 89 } 90 91 mLineBuffer.clear(); 92} 93 94void PersistenceManager::deleteObject(ParsedObject* object) 95{ 96 if (object) 97 { 98 // Clear up used property memory 99 for (U32 j = 0; j < object->properties.size(); j++) 100 { 101 ParsedProperty& prop = object->properties[j]; 102 103 if (prop.value) 104 { 105 dFree( prop.value ); 106 prop.value = NULL; 107 } 108 } 109 110 object->properties.clear(); 111 112 // Delete the parsed object 113 SAFE_DELETE(object); 114 } 115} 116 117void PersistenceManager::clearObjects() 118{ 119 // Clean up the object buffer 120 for (U32 i = 0; i < mObjectBuffer.size(); i++) 121 deleteObject(mObjectBuffer[i]); 122 123 mObjectBuffer.clear(); 124 125 // We shouldn't have anything in the object stack 126 // but let's clean it up just in case 127 // Clean up the object buffer 128 for (U32 i = 0; i < mObjectStack.size(); i++) 129 deleteObject(mObjectStack[i]); 130 131 mObjectStack.clear(); 132 133 // Finally make sure there isn't a current object 134 deleteObject(mCurrentObject); 135} 136 137void PersistenceManager::clearFileData() 138{ 139 // Clear the active file name 140 if (mCurrentFile) 141 { 142 dFree( mCurrentFile ); 143 mCurrentFile = NULL; 144 } 145 146 // Clear the file objects 147 clearObjects(); 148 149 // Clear the line buffer 150 clearLineBuffer(); 151 152 // Clear the tokenizer data 153 mParser.clear(); 154} 155 156void PersistenceManager::clearAll() 157{ 158 // Clear the file data in case it hasn't cleared yet 159 clearFileData(); 160 161 // Clear the dirty object list 162 mDirtyObjects.clear(); 163 164 // Clear the remove field list 165 mRemoveFields.clear(); 166} 167 168bool PersistenceManager::readFile(const char* fileName) 169{ 170 // Clear our previous file buffers just in 171 // case saveDirtyFile() didn't catch it 172 clearFileData(); 173 174 // Handle an object writing out to a new file 175 if ( !Torque::FS::IsFile( fileName ) ) 176 { 177 // Set our current file 178 mCurrentFile = dStrdup(fileName); 179 180 return true; 181 } 182 183 // Try to open the file 184 FileStream stream; 185 stream.open( fileName, Torque::FS::File::Read ); 186 187 if ( stream.getStatus() != Stream::Ok ) 188 { 189 Con::errorf("PersistenceManager::readFile() - Failed to open %s", fileName); 190 191 return false; 192 } 193 194 // The file is good so read it in 195 mCurrentFile = dStrdup(fileName); 196 197 while(stream.getStatus() != Stream::EOS) 198 { 199 U8* buffer = ( U8* ) dMalloc( 2048 ); 200 dMemset(buffer, 0, 2048); 201 202 stream.readLine(buffer, 2048); 203 204 mLineBuffer.push_back((const char*)buffer); 205 } 206 207 // Because of the way that writeLine() works we need to 208 // make sure we don't have an empty last line or else 209 // we will get an extra line break 210 if (mLineBuffer.size() > 0) 211 { 212 if (mLineBuffer.last() && mLineBuffer.last()[0] == 0) 213 { 214 dFree(mLineBuffer.last()); 215 216 mLineBuffer.pop_back(); 217 } 218 } 219 220 stream.close(); 221 222 //Con::printf("Successfully opened and read %s", mCurrentFile); 223 224 return true; 225} 226 227void PersistenceManager::killObject() 228{ 229 // Don't save this object 230 SAFE_DELETE(mCurrentObject); 231 232 // If there is an object in the stack restore it 233 if (mObjectStack.size() > 0) 234 { 235 mCurrentObject = mObjectStack.last(); 236 mObjectStack.pop_back(); 237 } 238} 239 240void PersistenceManager::saveObject() 241{ 242 // Now that we have all of the data attempt to 243 // find the corresponding SimObject 244 mCurrentObject->simObject = Sim::findObject(mCurrentFile, mCurrentObject->endLine + 1); 245 246 // Save this object 247 mObjectBuffer.push_back(mCurrentObject); 248 249 mCurrentObject = NULL; 250 251 // If there is an object in the stack restore it 252 if (mObjectStack.size() > 0) 253 { 254 mCurrentObject = mObjectStack.last(); 255 mObjectStack.pop_back(); 256 } 257} 258 259void PersistenceManager::parseObject() 260{ 261 // We *should* already be in position but just in case... 262 if (!mParser.tokenICmp("new") && 263 !mParser.tokenICmp("singleton") && 264 !mParser.tokenICmp("datablock")) 265 { 266 Con::errorf("PersistenceManager::parseObject() - handed a position that doesn't point to an object \ 267 creation keyword (new, singleton, datablock)"); 268 269 return; 270 } 271 272 // If there is an object already being parsed then 273 // push it into the stack to finish later 274 if (mCurrentObject) 275 mObjectStack.push_back(mCurrentObject); 276 277 mCurrentObject = new ParsedObject; 278 279 //// If this object declaration is being assigned to a variable then 280 //// consider that the "start" of the declaration (otherwise we could 281 //// get a script compile error if we delete the object declaration) 282 mParser.regressToken(true); 283 284 if (mParser.tokenICmp("=")) 285 { 286 // Ok, we are at an '='...back up to the beginning of that variable 287 mParser.regressToken(true); 288 289 // Get the startLine and startPosition 290 mCurrentObject->startLine = mParser.getCurrentLine(); 291 mCurrentObject->startPosition = mParser.getTokenLineOffset(); 292 293 // Advance back to the object declaration 294 mParser.advanceToken(true); 295 mParser.advanceToken(true); 296 } 297 else 298 { 299 // Advance back to the object declaration 300 mParser.advanceToken(true); 301 302 // Get the startLine and startPosition 303 mCurrentObject->startLine = mParser.getCurrentLine(); 304 mCurrentObject->startPosition = mParser.getTokenLineOffset(); 305 } 306 307 if (mObjectStack.size() > 0) 308 mCurrentObject->parentObject = mObjectStack.last(); 309 310 // The next token should be the className 311 mCurrentObject->className = StringTable->insert(mParser.getNextToken()); 312 313 // Advance to '(' 314 mParser.advanceToken(true); 315 316 if (!mParser.tokenICmp("(")) 317 { 318 Con::errorf("PersistenceManager::parseObject() - badly formed object \ 319 declaration on line %d - was expecting a '(' character", mParser.getCurrentLine()+1); 320 321 // Remove this object without saving it 322 killObject(); 323 324 return; 325 } 326 327 // The next token should either be the object name or ')' 328 mParser.advanceToken(true); 329 330 if (mParser.tokenICmp(")")) 331 { 332 mCurrentObject->name = StringTable->EmptyString(); 333 334 mCurrentObject->nameLine = mParser.getCurrentLine(); 335 mCurrentObject->namePosition = mParser.getTokenLineOffset(); 336 } 337 else 338 { 339 mCurrentObject->name = StringTable->insert(mParser.getToken()); 340 341 mCurrentObject->nameLine = mParser.getCurrentLine(); 342 mCurrentObject->namePosition = mParser.getTokenLineOffset(); 343 344 // Advance to either ')' or ':' 345 mParser.advanceToken(true); 346 347 if (mParser.tokenICmp(":")) 348 { 349 // Advance past the object we are copying from 350 mParser.advanceToken(true); 351 352 // Advance to ')' 353 mParser.advanceToken(true); 354 } 355 356 if (!mParser.tokenICmp(")")) 357 { 358 Con::errorf("PersistenceManager::parseObject() - badly formed object \ 359 declaration on line %d - was expecting a ')' character", mParser.getCurrentLine()+1); 360 361 // Remove this object without saving it 362 killObject(); 363 364 return; 365 } 366 } 367 368 // The next token should either be a ';' or a '{' 369 mParser.advanceToken(true); 370 371 if (mParser.tokenICmp(";")) 372 { 373 // Save the end line number 374 mCurrentObject->endLine = mParser.getCurrentLine(); 375 376 // Save the end position 377 mCurrentObject->endPosition = mParser.getTokenLineOffset(); 378 379 // Flag this object as not having braces 380 mCurrentObject->hasBraces = false; 381 382 saveObject(); // Object has no fields 383 384 return; 385 } 386 else if (!mParser.tokenICmp("{")) 387 { 388 Con::errorf("PersistenceManager::parseObject() - badly formed object \ 389 declaration on line %d - was expecting a '{' character", mParser.getCurrentLine()+1); 390 391 // Remove this object without saving it 392 killObject(); 393 394 return; 395 } 396 397 while (mParser.advanceToken(true)) 398 { 399 // Check for a subobject 400 if (mParser.tokenICmp("new") || 401 mParser.tokenICmp("singleton") || 402 mParser.tokenICmp("datablock")) 403 { 404 parseObject(); 405 } 406 407 // Check to see if we have a property 408 if (mParser.tokenICmp("=")) 409 { 410 // Ok, we are at an '='...back up to find out 411 // what variable is getting assigned 412 mParser.regressToken(true); 413 414 const char* variable = mParser.getToken(); 415 416 if (variable && dStrlen(variable) > 0) 417 { 418 // See if it is a global or a local variable 419 if (variable[0] == '%' || variable[0] == '$') 420 { 421 // We ignore this variable and go 422 // back to our previous place 423 mParser.advanceToken(true); 424 } 425 // Could also potentially be a <object>.<variable> 426 // assignment which we don't care about either 427 else if (dStrchr(variable, '.')) 428 { 429 // We ignore this variable and go 430 // back to our previous place 431 mParser.advanceToken(true); 432 } 433 // If we made it to here assume it is a variable 434 // for the current object 435 else 436 { 437 // Create our new property 438 mCurrentObject->properties.increment(); 439 440 ParsedProperty& prop = mCurrentObject->properties.last(); 441 442 // Check to see if this is an array variable 443 if (dStrlen(variable) > 3 && variable[dStrlen(variable) - 1] == ']') 444 { 445 // The last character is a ']' which *should* mean 446 // there is also a corresponding '[' 447 const char* arrayPosStart = dStrrchr(variable, '['); 448 449 if (!arrayPosStart) 450 { 451 Con::errorf("PersistenceManager::parseObject() - error parsing array position - \ 452 was expecting a '[' character"); 453 } 454 else 455 { 456 // Parse the array position for the variable name 457 S32 arrayPos = -1; 458 459 dSscanf(arrayPosStart, "[%d]", &arrayPos); 460 461 // If we got a valid array position then set it 462 if (arrayPos > -1) 463 prop.arrayPos = arrayPos; 464 465 // Trim off the [<pos>] from the variable name 466 char* variableName = dStrdup(variable); 467 variableName[arrayPosStart - variable] = 0; 468 469 // Set the variable name to our new shortened name 470 variable = StringTable->insert(variableName, true); 471 472 // Cleanup our variableName buffer 473 dFree( variableName ); 474 } 475 } 476 477 478 // Set back the variable name 479 prop.name = StringTable->insert(variable, true); 480 481 // Store the start position for this variable 482 prop.startLine = mParser.getCurrentLine(); 483 prop.startPosition = mParser.getTokenLineOffset(); 484 485 // Advance back to the '=' 486 mParser.advanceToken(true); 487 488 // Sanity check 489 if (!mParser.tokenICmp("=")) 490 Con::errorf("PersistenceManager::parseObject() - somehow we aren't \ 491 pointing at the expected '=' character"); 492 else 493 { 494 // The next token should be the value 495 // being assigned to the variable 496 mParser.advanceToken(true); 497 498 // Store the line number for this value 499 prop.valueLine = mParser.getCurrentLine(); 500 501 // Store the values beginning position 502 prop.valuePosition = mParser.getTokenLineOffset(); 503 504 // Read tokens up to the semicolon. 505 // Quoted tokens skip the leading and trailing quote characters. eg. 506 // "this" becomes: this 507 // "this" TAB "that" becomes: this" TAB "that 508 // "this" TAB "that" TAB "other" becomes: this" TAB "that" TAB "other 509 String value; 510 bool wasQuoted = false; 511 while (!mParser.endOfFile() && !mParser.tokenICmp(";")) 512 { 513 // Join tokens together (skipped first time through when string is empty) 514 if (value.length() > 0) 515 { 516 if (wasQuoted) 517 value += "\" "; // quoted followed by non-quoted 518 else if (mParser.tokenIsQuoted()) 519 value += " \""; // non-quoted followed by quoted 520 else 521 value += " "; // non-quoted followed by non-quoted 522 } 523 524 value += mParser.getToken(); 525 wasQuoted = mParser.tokenIsQuoted(); 526 mParser.advanceToken(true); 527 } 528 529 // TODO: make sure this doesn't leak 530 prop.value = dStrdup(value.c_str()); 531 532 if (!mParser.tokenICmp(";")) 533 Con::errorf("PersistenceManager::parseObject() - badly formed variable " 534 "assignment on line %d - was expecting a ';' character", mParser.getCurrentLine()+1); 535 536 // Store the end position for this variable 537 prop.endLine = mParser.getCurrentLine(); 538 prop.endPosition = mParser.getTokenLineOffset(); 539 if (wasQuoted) 540 prop.endPosition -= 1; 541 542 } 543 } 544 } 545 } 546 547 // Check for the end of the object declaration 548 if (mParser.tokenICmp("}")) 549 { 550 // See if the next token is a ';' 551 mParser.advanceToken(true); 552 553 if (mParser.tokenICmp(";")) 554 { 555 // Save the end line number 556 mCurrentObject->endLine = mParser.getCurrentLine(); 557 558 // Save the end position 559 mCurrentObject->endPosition = mParser.getTokenLineOffset(); 560 561 saveObject(); 562 563 break; 564 } 565 } 566 } 567} 568 569bool PersistenceManager::parseFile(const char* fileName) 570{ 571 // Read the file into the line buffer 572 if (!readFile(fileName)) 573 return false; 574 575 // Load it into our Tokenizer parser 576 if (!mParser.openFile(fileName)) 577 { 578 // Handle an object writing out to a new file 579 if ( !Torque::FS::IsFile( fileName ) ) 580 return true; 581 582 return false; 583 } 584 585 // Set our reserved "single" tokens 586 mParser.setSingleTokens("(){};=:"); 587 588 // Search object declarations 589 while (mParser.advanceToken(true)) 590 { 591 if (mParser.tokenICmp("new") || 592 mParser.tokenICmp("singleton") || 593 mParser.tokenICmp("datablock")) 594 { 595 parseObject(); 596 } 597 } 598 599 // If we had an object that didn't end properly 600 // then we could have objects on the stack 601 while (mCurrentObject) 602 saveObject(); 603 604 //Con::errorf("Parsed Results:"); 605 606 //for (U32 i = 0; i < mObjectBuffer.size(); i++) 607 //{ 608 // ParsedObject* parsedObject = mObjectBuffer[i]; 609 610 // Con::warnf(" mObjectBuffer[%d]:", i); 611 // Con::warnf(" name = %s", parsedObject->name); 612 // Con::warnf(" className = %s", parsedObject->className); 613 // Con::warnf(" startLine = %d", parsedObject->startLine + 1); 614 // Con::warnf(" endLine = %d", parsedObject->endLine + 1); 615 616 // //if (mObjectBuffer[i]->properties.size() > 0) 617 // //{ 618 // // Con::warnf(" properties:"); 619 // // for (U32 j = 0; j < mObjectBuffer[i]->properties.size(); j++) 620 // // Con::warnf(" %s = %s;", mObjectBuffer[i]->properties[j].name, 621 // // mObjectBuffer[i]->properties[j].value); 622 // //} 623 624 // if (!parsedObject->simObject.isNull()) 625 // { 626 // SimObject* simObject = parsedObject->simObject; 627 628 // Con::warnf(" SimObject(%s) %d:", simObject->getName(), simObject->getId()); 629 // Con::warnf(" declaration line = %d", simObject->getDeclarationLine()); 630 // } 631 //} 632 633 return true; 634} 635 636S32 PersistenceManager::getPropertyIndex(ParsedObject* parsedObject, const char* fieldName, U32 arrayPos) 637{ 638 S32 propertyIndex = -1; 639 640 if (!parsedObject) 641 return propertyIndex; 642 643 for (U32 i = 0; i < parsedObject->properties.size(); i++) 644 { 645 if (dStricmp(fieldName, parsedObject->properties[i].name) == 0 && 646 parsedObject->properties[i].arrayPos == arrayPos) 647 { 648 propertyIndex = i; 649 break; 650 } 651 } 652 653 return propertyIndex; 654} 655 656char* PersistenceManager::getObjectIndent(ParsedObject* object) 657{ 658 char* indent = Con::getReturnBuffer(2048); 659 indent[0] = 0; 660 661 if (!object) 662 return indent; 663 664 if (object->startLine < 0 || object->startLine >= mLineBuffer.size()) 665 return indent; 666 667 const char* line = mLineBuffer[object->startLine]; 668 669 if (line) 670 { 671 const char* nonSpace = line; 672 673 U32 strLen = dStrlen(line); 674 675 for (U32 i = 0; i < strLen; i++) 676 { 677 if (*nonSpace != ' ') 678 break; 679 680 nonSpace++; 681 } 682 683 dStrncpy(indent, line, nonSpace - line); 684 685 indent[nonSpace - line] = 0; 686 } 687 688 return indent; 689} 690 691void PersistenceManager::updatePositions(U32 lineNumber, U32 startPos, S32 diff) 692{ 693 if (diff == 0) 694 return; 695 696 for (U32 i = 0; i < mObjectBuffer.size(); i++) 697 { 698 ParsedObject* object = mObjectBuffer[i]; 699 700 if (object->nameLine == lineNumber && object->namePosition > startPos) 701 object->namePosition += diff; 702 703 if (object->endLine == lineNumber && object->endPosition > startPos) 704 object->endPosition += diff; 705 706 if (lineNumber >= object->startLine && lineNumber <= object->endLine) 707 { 708 for (U32 j = 0; j < object->properties.size(); j++) 709 { 710 ParsedProperty& prop = object->properties[j]; 711 712 S32 propStartPos = prop.startPosition; 713 S32 endPos = prop.endPosition; 714 S32 valuePos = prop.valuePosition; 715 716 if (lineNumber == prop.startLine && propStartPos > startPos) 717 { 718 propStartPos += diff; 719 720 if (propStartPos < 0) 721 propStartPos = 0; 722 723 prop.startPosition = valuePos; 724 } 725 if (lineNumber == prop.endLine && endPos > startPos) 726 { 727 endPos += diff; 728 729 if (endPos < 0) 730 endPos = 0; 731 732 prop.endPosition = endPos; 733 } 734 if (lineNumber == prop.valueLine && valuePos > startPos) 735 { 736 valuePos += diff; 737 738 if (valuePos < 0) 739 valuePos = 0; 740 741 prop.valuePosition = valuePos; 742 } 743 } 744 } 745 } 746} 747 748void PersistenceManager::updateLineOffsets(U32 startLine, S32 diff, ParsedObject* skipObject) 749{ 750 if (diff == 0) 751 return; 752 753 if (startLine >= mLineBuffer.size()) 754 return; 755 756 if (startLine + diff >= mLineBuffer.size()) 757 return; 758 759 // Make sure we don't double offset a SimObject's 760 // declaration line 761 SimObjectList updated; 762 763 if (skipObject && !skipObject->simObject.isNull()) 764 updated.push_back_unique(skipObject->simObject); 765 766 for (U32 i = 0; i < mObjectBuffer.size(); i++) 767 { 768 ParsedObject* object = mObjectBuffer[i]; 769 770 // See if this is the skipObject 771 if (skipObject && skipObject == object) 772 continue; 773 774 // We can safely ignore objects that 775 // came earlier in the file 776 if (object->endLine < startLine) 777 continue; 778 779 if (object->startLine >= startLine) 780 object->startLine += diff; 781 782 if (object->nameLine >= startLine) 783 object->nameLine += diff; 784 785 for (U32 j = 0; j < object->properties.size(); j++) 786 { 787 if (object->properties[j].startLine >= startLine) 788 object->properties[j].startLine += diff; 789 if (object->properties[j].endLine >= startLine) 790 object->properties[j].endLine += diff; 791 if (object->properties[j].valueLine >= startLine) 792 object->properties[j].valueLine += diff; 793 } 794 795 if (object->endLine >= startLine) 796 object->endLine += diff; 797 798 if (!object->simObject.isNull() && 799 object->simObject->getDeclarationLine() > startLine) 800 { 801 // Check for already updated SimObject's 802 U32 currSize = updated.size(); 803 updated.push_back_unique(object->simObject); 804 805 if (updated.size() == currSize) 806 continue; 807 808 S32 newDeclLine = object->simObject->getDeclarationLine() + diff; 809 810 if (newDeclLine < 0) 811 newDeclLine = 0; 812 813 object->simObject->setDeclarationLine(newDeclLine); 814 } 815 } 816} 817 818PersistenceManager::ParsedObject* PersistenceManager::findParentObject(SimObject* object, ParsedObject* parentObject) 819{ 820 ParsedObject* ret = NULL; 821 822 if (!object) 823 return ret; 824 825 // First test for the SimGroup it belongs to 826 ret = findParsedObject(object->getGroup(), parentObject); 827 828 if (ret) 829 return ret; 830 831 // TODO: Test all of the SimSet's that this object belongs to 832 833 return ret; 834} 835 836PersistenceManager::ParsedObject* PersistenceManager::findParsedObject(SimObject* object, ParsedObject* parentObject) 837{ 838 if (!object) 839 return NULL; 840 841 // See if our object belongs to a parent 842 if (!parentObject) 843 parentObject = findParentObject(object, parentObject); 844 845 // First let's compare the object to the SimObject's that 846 // we matched to our ParsedObject's when we loaded them 847 for (U32 i = 0; i < mObjectBuffer.size(); i++) 848 { 849 ParsedObject* testObj = mObjectBuffer[i]; 850 851 if (testObj->simObject == object) 852 { 853 // Deal with children objects 854 if (testObj->parentObject != parentObject) 855 continue; 856 857 return testObj; 858 } 859 } 860 861 // Didn't find it in our ParsedObject's SimObject's 862 // so see if we can find one that corresponds to the 863 // same name and className 864 const char *originalName = object->getOriginalName(); 865 866 // Find the corresponding ParsedObject 867 if (originalName && originalName[0]) 868 { 869 for (U32 i = 0; i < mObjectBuffer.size(); i++) 870 { 871 ParsedObject* testObj = mObjectBuffer[i]; 872 873 if (testObj->name == originalName) 874 { 875 // Deal with children objects 876 if (testObj->parentObject != parentObject) 877 continue; 878 879 return testObj; 880 } 881 } 882 } 883 884 //Check internal names 885 if (object->getInternalName()) 886 { 887 for (U32 i = 0; i < mObjectBuffer.size(); i++) 888 { 889 ParsedObject* testObj = mObjectBuffer[i]; 890 for (U32 j = 0; j < testObj->properties.size(); j++) 891 { 892 const ParsedProperty &prop = testObj->properties[j]; 893 894 if ( String::compare( prop.name, "internalName" ) == 0 && 895 String::compare( prop.value, object->getInternalName() ) == 0 ) 896 return testObj; 897 else if ( String::compare(prop.name, "internalName") == 0) 898 break; 899 } 900 } 901 } 902 903 return NULL; 904} 905 906void PersistenceManager::updateToken( const U32 lineNumber, const U32 linePosition, const U32 oldValueLen, const char* newValue, bool addQuotes ) 907{ 908 // Make sure we have a valid lineNumber 909 if (lineNumber < 0 || linePosition < 0 || 910 lineNumber >= mLineBuffer.size()) 911 return; 912 913 // Grab the line that the value is on 914 const char* line = mLineBuffer[lineNumber]; 915 916 U32 newValueLen = ( newValue ) ? dStrlen(newValue) : 0; 917 918 // Make sure we have a valid linePosition 919 if (linePosition >= dStrlen(line) || 920 linePosition + oldValueLen > dStrlen(line)) 921 return; 922 923 // Get all of the characters up to the value position 924 U32 preStringLen = linePosition; 925 926 bool needQuotes = false; 927 if( addQuotes && line[ linePosition - 1 ] != '"' ) 928 { 929 preStringLen ++; 930 needQuotes = true; 931 } 932 933 char* preString = ( char* ) dMalloc( preStringLen + 1 ); 934 dMemcpy( preString, line, linePosition ); 935 936 if( needQuotes ) 937 { 938 preString[ linePosition ] = '"'; 939 preString[ linePosition + 1 ] = 0; 940 } 941 else 942 preString[ linePosition ] = 0; 943 944 // Get all of the characters that occur after the value 945 946 const char* postStringSrc = line + linePosition + oldValueLen; 947 U32 postStringLen = dStrlen( postStringSrc ); 948 if( needQuotes ) 949 postStringLen ++; 950 951 char* postString = ( char* ) dMalloc( postStringLen + 1 ); 952 if( needQuotes ) 953 postString[ 0 ] = '"'; 954 dStrcpy( &postString[ needQuotes ? 1 : 0 ], postStringSrc, postStringLen + (needQuotes ? 0 : 1) ); 955 postString[ postStringLen ] = 0; 956 957 // Calculate the length of our new line 958 U32 newLineLen = 0; 959 960 newLineLen += preStringLen; 961 newLineLen += newValueLen; 962 newLineLen += postStringLen; 963 964 // Create a buffer for our new line and 965 // null terminate it 966 char* newLine = ( char* ) dMalloc( newLineLen + 1 ); 967 newLine[0] = 0; 968 969 // Build the new line with the 970 // preString + newValue + postString 971 dStrcat(newLine, preString, newLineLen + 1); 972 if ( newValue ) 973 dStrcat(newLine, newValue, newLineLen + 1); 974 dStrcat(newLine, postString, newLineLen + 1); 975 976 // Clear our existing line 977 if (mLineBuffer[lineNumber]) 978 { 979 dFree( mLineBuffer[ lineNumber ] ); 980 mLineBuffer[ lineNumber ] = NULL; 981 } 982 983 // Set the new line 984 mLineBuffer[lineNumber] = newLine; 985 986 // Figure out the size difference of the old value 987 // and new value in case we need to update any else 988 // on the line after it 989 S32 diff = newValueLen - oldValueLen; 990 991 // Update anything that is on the line after this that needs 992 // to change its offsets to reflect the new line 993 updatePositions(lineNumber, linePosition, diff); 994 995 // Clean up our buffers 996 dFree( preString ); 997 dFree( postString ); 998} 999 1000const char* PersistenceManager::getFieldValue(SimObject* object, const char* fieldName, U32 arrayPos) 1001{ 1002 // Our return string 1003 char* ret = NULL; 1004 1005 // Buffer to hold the string equivalent of the arrayPos 1006 char arrayPosStr[8]; 1007 dSprintf(arrayPosStr, 8, "%d", arrayPos); 1008 1009 // Get the object's value 1010 const char *value = object->getDataField(fieldName, arrayPosStr ); 1011 if (value) 1012 ret = dStrdup(value); 1013 1014 return ret; 1015} 1016 1017const char* PersistenceManager::createNewProperty(const char* name, const char* value, bool isArray, U32 arrayPos) 1018{ 1019 if (!name || !value) 1020 return NULL; 1021 1022 AssertFatal( value[0] != StringTagPrefixByte, "Got tagged string!" ); 1023 1024 char* newProp = ( char* ) dMalloc( 2048 ); 1025 dMemset(newProp, 0, 2048); 1026 1027 if (value) 1028 { 1029 if (isArray) 1030 dSprintf(newProp, 2048, "%s[%d] = \"%s\";", name, arrayPos, value); 1031 else 1032 dSprintf(newProp, 2048, "%s = \"%s\";", name, value); 1033 } 1034 else 1035 { 1036 if (isArray) 1037 dSprintf(newProp, 2048, "%s[%d] = \"\";", name, arrayPos); 1038 else 1039 dSprintf(newProp, 2048, "%s = \"\";", name); 1040 } 1041 1042 return newProp; 1043} 1044 1045bool PersistenceManager::isEmptyLine(const char* line) 1046{ 1047 // Simple test first 1048 if (!line || dStrlen(line) == 0) 1049 return true; 1050 1051 U32 len = dStrlen(line); 1052 1053 for (U32 i = 0; i < len; i++) 1054 { 1055 const char& c = line[i]; 1056 1057 // Skip "empty" characters 1058 if (c == ' ' || 1059 c == '\t' || 1060 c == '\r' || 1061 c == '\n') 1062 { 1063 continue; 1064 } 1065 1066 // If we have made it to the an end of the line 1067 // comment then consider this an empty line 1068 if (c == '/') 1069 { 1070 if (i < len - 1) 1071 { 1072 if (line[i + 1] == '/') 1073 return true; 1074 } 1075 } 1076 1077 // If it isn't an "empty" character or a comment then 1078 // we have a valid character on the line and it isn't empty 1079 return false; 1080 } 1081 1082 return true; 1083} 1084 1085void PersistenceManager::removeLine(U32 lineNumber) 1086{ 1087 if (lineNumber >= mLineBuffer.size()) 1088 return; 1089 1090 if (mLineBuffer[lineNumber]) 1091 { 1092 dFree( mLineBuffer[ lineNumber ] ); 1093 mLineBuffer[ lineNumber ] = NULL; 1094 } 1095 1096 mLineBuffer.erase(lineNumber); 1097 1098 updateLineOffsets(lineNumber, -1); 1099} 1100 1101void PersistenceManager::removeTextBlock(U32 startLine, U32 endLine, U32 startPos, U32 endPos, bool removeEmptyLines) 1102{ 1103 // Make sure we have valid lines 1104 if (startLine >= mLineBuffer.size() || endLine >= mLineBuffer.size()) 1105 return; 1106 1107 // We assume that the startLine is before the endLine 1108 if (startLine > endLine) 1109 return; 1110 1111 // Grab the lines (they may be the same) 1112 const char* startLineText = mLineBuffer[startLine]; 1113 const char* endLineText = mLineBuffer[endLine]; 1114 1115 // Make sure we have a valid startPos 1116 if (startPos >= dStrlen(startLineText)) 1117 return; 1118 1119 // Make sure we have a valid endPos 1120 if (endPos > dStrlen(endLineText)) 1121 return; 1122 1123 if (startLine == endLine) 1124 { 1125 // Now let updateToken do the heavy lifting on removing it 1126 updateToken(startLine, startPos, endPos - startPos, ""); 1127 1128 // Handle removing an empty lines if desired 1129 if (removeEmptyLines) 1130 { 1131 const char* line = mLineBuffer[startLine]; 1132 1133 if (isEmptyLine(line)) 1134 removeLine(startLine); 1135 } 1136 } 1137 else 1138 { 1139 // Start with clearing the startLine from startPos to the end 1140 updateToken(startLine, startPos, dStrlen(startLineText + startPos), ""); 1141 1142 // Then clear the endLine from beginning to endPos 1143 updateToken(endLine, 0, endPos, ""); 1144 1145 // Handle removing an empty endLine if desired 1146 if (removeEmptyLines) 1147 { 1148 const char* line = mLineBuffer[endLine]; 1149 1150 if (isEmptyLine(line)) 1151 removeLine(endLine); 1152 } 1153 1154 // Handle removing any lines between the startLine and endLine 1155 for (U32 i = startLine + 1; i < endLine; i++) 1156 removeLine(startLine + 1); 1157 1158 // Handle removing an empty startLine if desired 1159 if (removeEmptyLines) 1160 { 1161 const char* line = mLineBuffer[startLine]; 1162 1163 if (isEmptyLine(line)) 1164 removeLine(startLine); 1165 } 1166 } 1167} 1168 1169void PersistenceManager::removeParsedObject(ParsedObject* parsedObject) 1170{ 1171 if (!parsedObject) 1172 return; 1173 1174 if (parsedObject->startLine < 0 || parsedObject->startLine >= mLineBuffer.size()) 1175 return; 1176 1177 if (parsedObject->endLine < 0 || parsedObject->startLine >= mLineBuffer.size()) 1178 return; 1179 1180 removeTextBlock(parsedObject->startLine, parsedObject->endLine, 1181 parsedObject->startPosition, parsedObject->endPosition+1, true); // +1 to remove trailing semicolon as well 1182 1183 parsedObject->parentObject = NULL; 1184 parsedObject->simObject = NULL; 1185} 1186 1187void PersistenceManager::removeField(const ParsedProperty& prop) 1188{ 1189 if (prop.startLine < 0 || prop.startLine >= mLineBuffer.size()) 1190 return; 1191 1192 if (prop.endLine < 0 || prop.endLine >= mLineBuffer.size()) 1193 return; 1194 1195 S32 endPosition = prop.endPosition+1; // +1 to remove trailing semicolon as well 1196 if ((endPosition < dStrlen(mLineBuffer[prop.endLine])) && 1197 (mLineBuffer[prop.endLine][endPosition] == ';')) // adjust end position for quoted values (otherwise a trailing semicolon will remain) 1198 endPosition++; 1199 1200 removeTextBlock(prop.startLine, prop.endLine, prop.startPosition, endPosition, true); 1201} 1202 1203U32 PersistenceManager::writeProperties(const Vector<const char*>& properties, const U32 insertLine, const char* objectIndent) 1204{ 1205 U32 currInsertLine = insertLine; 1206 1207 for (U32 i = 0; i < properties.size(); i++) 1208 { 1209 const char* prop = properties[i]; 1210 1211 if (!prop || dStrlen(prop) == 0) 1212 continue; 1213 1214 U32 len = dStrlen(objectIndent) + dStrlen(prop) + 4; 1215 1216 char* newLine = ( char* ) dMalloc( len ); 1217 1218 dSprintf(newLine, len, "%s %s", objectIndent, prop); 1219 1220 mLineBuffer.insert(currInsertLine, newLine); 1221 currInsertLine++; 1222 } 1223 1224 return currInsertLine - insertLine; 1225} 1226 1227PersistenceManager::ParsedObject* PersistenceManager::writeNewObject(SimObject* object, const Vector<const char*>& properties, const U32 insertLine, ParsedObject* parentObject) 1228{ 1229 ParsedObject* parsedObject = new ParsedObject; 1230 1231 parsedObject->name = object->getName(); 1232 parsedObject->className = object->getClassName(); 1233 parsedObject->simObject = object; 1234 1235 U32 currInsertLine = insertLine; 1236 1237 // If the parentObject isn't set see if 1238 // we can find it in the file 1239 if (!parentObject) 1240 parentObject = findParentObject(object); 1241 1242 parsedObject->parentObject = parentObject; 1243 1244 char* indent = getObjectIndent(parentObject); 1245 1246 if (parentObject) 1247 dStrcat(indent, " \0", 2048); 1248 1249 // Write out the beginning of the object declaration 1250 const char* dclToken = "new"; 1251 1252 if (dynamic_cast<Material*>(object) || 1253 dynamic_cast<CustomMaterial*>(object) || 1254 dynamic_cast<GuiControlProfile*>(object) || 1255 dynamic_cast<TSShapeConstructor*>(object)) 1256 dclToken = "singleton"; 1257 else if( dynamic_cast< SimDataBlock* >( object ) ) 1258 { 1259 SimDataBlock* db = static_cast<SimDataBlock*>(object); 1260 1261 if( db->isClientOnly() ) 1262 { 1263 if( db->getName() && db->getName()[ 0 ] ) 1264 dclToken = "singleton"; 1265 } 1266 else 1267 dclToken = "datablock"; 1268 } 1269 1270 char newLine[ 4096 ]; 1271 dMemset(newLine, 0, sizeof( newLine)); 1272 1273 // New line before an object declaration 1274 dSprintf(newLine, sizeof( newLine ), ""); 1275 1276 mLineBuffer.insert(currInsertLine, dStrdup(newLine)); 1277 currInsertLine++; 1278 dMemset(newLine, 0, sizeof( newLine )); 1279 1280 parsedObject->startLine = currInsertLine; 1281 parsedObject->nameLine = currInsertLine; 1282 parsedObject->namePosition = dStrlen(indent) + dStrlen(dclToken) + dStrlen(object->getClassName()) + 2; 1283 1284 // Objects that had no name were getting saved out as: Object((null)) 1285 if ( object->getName() != NULL ) 1286 { 1287 if( object->getCopySource() ) 1288 dSprintf(newLine, sizeof( newLine ), "%s%s %s(%s : %s)", indent, dclToken, object->getClassName(), object->getName(), 1289 object->getCopySource() ? object->getCopySource()->getName() : "" ); 1290 else 1291 dSprintf(newLine, sizeof( newLine ), "%s%s %s(%s)", indent, dclToken, object->getClassName(), object->getName()); 1292 } 1293 else 1294 dSprintf(newLine, sizeof( newLine ), "%s%s %s()", indent, dclToken, object->getClassName() ); 1295 1296 mLineBuffer.insert(currInsertLine, dStrdup(newLine)); 1297 currInsertLine++; 1298 dMemset(newLine, 0, sizeof( newLine )); 1299 1300 dSprintf(newLine, sizeof( newLine ), "%s{", indent); 1301 1302 mLineBuffer.insert(currInsertLine, dStrdup(newLine)); 1303 currInsertLine++; 1304 dMemset(newLine, 0, sizeof( newLine )); 1305 1306 currInsertLine += writeProperties(properties, currInsertLine, indent); 1307 1308 parsedObject->endLine = currInsertLine; 1309 parsedObject->updated = true; 1310 1311 dSprintf(newLine, sizeof( newLine ), "%s};", indent); 1312 1313 mLineBuffer.insert(currInsertLine, dStrdup(newLine)); 1314 currInsertLine++; 1315 1316 updateLineOffsets(insertLine, currInsertLine - insertLine, parsedObject); 1317 1318 mObjectBuffer.push_back(parsedObject); 1319 1320 // Update the SimObject to reflect its saved name and declaration line. 1321 // These values should always reflect what is in the file, even if the object 1322 // has not actually been re-created from an execution of that file yet. 1323 object->setOriginalName( object->getName() ); 1324 object->setDeclarationLine( currInsertLine ); 1325 1326 if (mCurrentFile) 1327 object->setFilename(mCurrentFile); 1328 1329 return parsedObject; 1330} 1331 1332void PersistenceManager::updateObject(SimObject* object, ParsedObject* parentObject) 1333{ 1334 // Create a default object of the same type 1335 ConsoleObject *defaultConObject = ConsoleObject::create(object->getClassName()); 1336 SimObject* defaultObject = dynamic_cast<SimObject*>(defaultConObject); 1337 1338 // ***Really*** shouldn't happen 1339 if (!defaultObject) 1340 return; 1341 1342 Vector<const char*> newLines; 1343 1344 ParsedObject* parsedObject = findParsedObject(object, parentObject); 1345 1346 // If we don't already have an association between the ParsedObject 1347 // and the SimObject then go ahead and create it 1348 if (parsedObject && parsedObject->simObject.isNull()) 1349 parsedObject->simObject = object; 1350 1351 // Kill all fields on the remove list. 1352 1353 for( U32 i = 0; i < mRemoveFields.size(); ++ i ) 1354 { 1355 RemoveField& field = mRemoveFields[ i ]; 1356 if( field.object != object ) 1357 continue; 1358 1359 S32 propertyIndex = getPropertyIndex( parsedObject, field.fieldName, field.arrayPos ); 1360 if( propertyIndex != -1 ) 1361 removeField( parsedObject->properties[ propertyIndex ] ); 1362 } 1363 1364 // Get our field list 1365 const AbstractClassRep::FieldList &list = object->getFieldList(); 1366 1367 for(U32 i = 0; i < list.size(); i++) 1368 { 1369 const AbstractClassRep::Field* f = &list[i]; 1370 1371 // Skip the special field types. 1372 if ( f->type >= AbstractClassRep::ARCFirstCustomField ) 1373 continue; 1374 1375 for(U32 j = 0; S32(j) < f->elementCount; j++) 1376 { 1377 const char* value = getFieldValue(object, f->pFieldname, j); 1378 1379 // Make sure we got a value 1380 if (!value) 1381 continue; 1382 1383 // Let's see if this field is already in the file 1384 S32 propertyIndex = getPropertyIndex(parsedObject, f->pFieldname, j); 1385 1386 if (propertyIndex > -1) 1387 { 1388 ParsedProperty& prop = parsedObject->properties[propertyIndex]; 1389 1390 // If this field is on the remove list then remove it and continue 1391 if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value)) 1392 { 1393 removeField( parsedObject->properties[ propertyIndex ] ); 1394 dFree( value ); 1395 continue; 1396 } 1397 1398 // Run the parsed value through the console system conditioners so 1399 // that it will better match the data we got back from the object. 1400 const char* evalue = Con::getFormattedData(f->type, prop.value, f->table, f->flag); 1401 1402 // If our data doesn't match then we get to update it. 1403 // 1404 // As for copy-sources, we just assume here that if a property setting 1405 // is there in the file, the user does not want it inherited from the copy-source 1406 // even in the case the actual values are identical. 1407 1408 if( dStricmp(value, evalue) != 0 ) 1409 { 1410 if( value[ 0 ] == '\0' && 1411 dStricmp( getFieldValue( defaultObject, f->pFieldname, j ), value ) == 0 && 1412 ( !object->getCopySource() || dStricmp( getFieldValue( object->getCopySource(), f->pFieldname, j ), value ) == 0 ) ) 1413 { 1414 removeField( prop ); 1415 } 1416 else 1417 { 1418 // TODO: This should be wrapped in a helper method... probably. 1419 // Detect and collapse relative path information 1420 if (f->type == TypeFilename || 1421 f->type == TypeStringFilename || 1422 f->type == TypeImageFilename || 1423 f->type == TypePrefabFilename || 1424 f->type == TypeShapeFilename) 1425 { 1426 char fnBuf[1024]; 1427 Con::collapseScriptFilename(fnBuf, 1024, value); 1428 1429 updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, fnBuf, true); 1430 } 1431 else 1432 updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, value, true); 1433 } 1434 } 1435 } 1436 else 1437 { 1438 // No need to process a removed field that doesn't exist in the file 1439 if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value)) 1440 { 1441 dFree( value ); 1442 continue; 1443 } 1444 1445 bool mustUpdate = false; 1446 1447 // If we didn't find the property in the ParsedObject 1448 // then we need to compare against the default value 1449 // for this property and save it out if it is different 1450 1451 const char* defaultValue = getFieldValue(defaultObject, f->pFieldname, j); 1452 if( !defaultValue || dStricmp( value, defaultValue ) != 0 ) 1453 { 1454 // Value differs. Check whether it also differs from the 1455 // value in the copy source if there is one. 1456 1457 if( object->getCopySource() ) 1458 { 1459 const char* copySourceValue = getFieldValue( object->getCopySource(), f->pFieldname, j ); 1460 if( !copySourceValue || dStricmp( copySourceValue, value ) != 0 ) 1461 mustUpdate = true; 1462 1463 if( copySourceValue ) 1464 dFree( copySourceValue ); 1465 } 1466 else 1467 mustUpdate = true; 1468 } 1469 else 1470 { 1471 // Value does not differ. If it differs from the copy source's value, 1472 // though, we still want to write it out as otherwise we'll see the 1473 // copy source's value override us. 1474 1475 if( object->getCopySource() ) 1476 { 1477 const char* copySourceValue = getFieldValue( object->getCopySource(), f->pFieldname, j ); 1478 if( copySourceValue && dStricmp( copySourceValue, value ) != 0 ) 1479 mustUpdate = true; 1480 1481 if( copySourceValue ) 1482 dFree( copySourceValue ); 1483 } 1484 } 1485 1486 // The default value for most string type fields is 1487 // NULL so we can't just continue here or we'd never ever 1488 // write them out... 1489 // 1490 //if (!defaultValue) 1491 // continue; 1492 1493 // If the object's value is different from the default 1494 // value then add it to the ParsedObject's newLines 1495 if ( mustUpdate ) 1496 { 1497 // TODO: This should be wrapped in a helper method... probably. 1498 // Detect and collapse relative path information 1499 if (f->type == TypeFilename || 1500 f->type == TypeStringFilename || 1501 f->type == TypeImageFilename || 1502 f->type == TypePrefabFilename || 1503 f->type == TypeShapeFilename) 1504 { 1505 char fnBuf[1024]; 1506 Con::collapseScriptFilename(fnBuf, 1024, value); 1507 1508 newLines.push_back(createNewProperty(f->pFieldname, fnBuf, f->elementCount > 1, j)); 1509 } 1510 else 1511 newLines.push_back(createNewProperty(f->pFieldname, value, f->elementCount > 1, j)); 1512 } 1513 1514 if (defaultValue) 1515 dFree( defaultValue ); 1516 } 1517 1518 dFree( value ); 1519 } 1520 } 1521 1522 // Handle dynamic fields 1523 SimFieldDictionary* fieldDict = object->getFieldDictionary(); 1524 1525 for(SimFieldDictionaryIterator itr(fieldDict); *itr; ++itr) 1526 { 1527 SimFieldDictionary::Entry * entry = (*itr); 1528 if( !entry->value ) 1529 continue; 1530 1531 // Let's see if this field is already in the file 1532 S32 propertyIndex = getPropertyIndex(parsedObject, entry->slotName); 1533 1534 if (propertyIndex > -1) 1535 { 1536 ParsedProperty& prop = parsedObject->properties[propertyIndex]; 1537 1538 // If this field is on the remove list then remove it and continue 1539 if (findRemoveField(object, entry->slotName) || !object->writeField(entry->slotName, entry->value)) 1540 { 1541 removeField( parsedObject->properties[ propertyIndex ] ); 1542 continue; 1543 } 1544 1545 if( object->getCopySource() ) 1546 { 1547 const char* copySourceFieldValue = object->getCopySource()->getDataField( entry->slotName, NULL ); 1548 if( String::compare( copySourceFieldValue, entry->value ) == 0 ) 1549 { 1550 removeField( prop ); 1551 continue; 1552 } 1553 } 1554 1555 const char* evalue = prop.value; 1556 1557 const char *entryVal = entry->value; 1558 if ( entryVal[0] == StringTagPrefixByte ) 1559 entryVal = gNetStringTable->lookupString( dAtoi( entryVal+1 ) ); 1560 else 1561 { 1562 // Run the parsed value through the console system conditioners so 1563 // that it will better match the data we got back from the object. 1564 evalue = Con::getFormattedData(TypeString, evalue); 1565 } 1566 1567 // If our data doesn't match then we get to update it 1568 if (dStricmp(entryVal, evalue) != 0) 1569 updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, entryVal); 1570 } 1571 else 1572 { 1573 // No need to process a removed field that doesn't exist in the file 1574 if (findRemoveField(object, entry->slotName) || !object->writeField(entry->slotName, entry->value)) 1575 continue; 1576 1577 if( object->getCopySource() ) 1578 { 1579 const char* copySourceFieldValue = object->getCopySource()->getDataField( entry->slotName, NULL ); 1580 if( String::compare( copySourceFieldValue, entry->value ) == 0 ) 1581 continue; 1582 } 1583 1584 newLines.push_back(createNewProperty(entry->slotName, entry->value)); 1585 } 1586 } 1587 1588 // If we have a parsedObject and the name changed 1589 // then update the parsedObject to the new name. 1590 // NOTE: an object 'can' have a NULL name which crashes in dStricmp. 1591 if (parsedObject && parsedObject->name != StringTable->insert(object->getName(), true) ) 1592 { 1593 StringTableEntry objectName = StringTable->insert(object->getName(), true); 1594 1595 if (parsedObject->name != objectName) 1596 { 1597 // Update the name in the file 1598 updateToken(parsedObject->nameLine, parsedObject->namePosition, dStrlen(parsedObject->name), object->getName()); 1599 1600 // Updated the parsedObject's name 1601 parsedObject->name = objectName; 1602 1603 // Updated the object's "original" name to the one that is now in the file 1604 object->setOriginalName(objectName); 1605 } 1606 } 1607 1608 if (parsedObject && newLines.size() > 0) 1609 { 1610 U32 lastPropLine = parsedObject->endLine; 1611 1612 if (parsedObject->properties.size() > 0) 1613 lastPropLine = parsedObject->properties.last().valueLine + 1; 1614 1615 U32 currInsertLine = lastPropLine; 1616 1617 const char* indent = getObjectIndent(parsedObject); 1618 1619 // This should handle adding the opening { to an object 1620 // that formerly did not have {}; 1621 if (!parsedObject->hasBraces) 1622 { 1623 updateToken(parsedObject->endLine, parsedObject->endPosition, 1, "\r\n{"); 1624 1625 currInsertLine++; 1626 } 1627 1628 currInsertLine += writeProperties(newLines, currInsertLine, indent); 1629 1630 // This should handle adding the opening } to an object 1631 // that formerly did not have {}; 1632 if (!parsedObject->hasBraces) 1633 { 1634 U32 len = dStrlen(indent) + 3; 1635 char* newLine = ( char* ) dMalloc( len ); 1636 1637 dSprintf(newLine, len, "%s};", indent); 1638 1639 mLineBuffer.insert(currInsertLine, newLine); 1640 currInsertLine++; 1641 } 1642 1643 // Update the line offsets to account for the new lines 1644 updateLineOffsets(lastPropLine, currInsertLine - lastPropLine); 1645 } 1646 else if (!parsedObject) 1647 { 1648 U32 insertLine = mLineBuffer.size(); 1649 1650 if (!parentObject) 1651 parentObject = findParentObject(object, parentObject); 1652 1653 if (parentObject && parentObject->endLine > -1) 1654 insertLine = parentObject->endLine; 1655 1656 parsedObject = writeNewObject(object, newLines, insertLine, parentObject); 1657 } 1658 1659 // Clean up the newLines memory 1660 for (U32 i = 0; i < newLines.size(); i++) 1661 { 1662 if (newLines[i]) 1663 { 1664 dFree(newLines[i]); 1665 newLines[ i ] = NULL; 1666 } 1667 } 1668 1669 newLines.clear(); 1670 1671 SimSet* set = dynamic_cast<SimSet*>(object); 1672 1673 if (set) 1674 { 1675 for(SimSet::iterator i = set->begin(); i != set->end(); i++) 1676 { 1677 SimObject* subObject = (SimObject*)(*i); 1678 updateObject(subObject, parsedObject); 1679 } 1680 } 1681 1682 // Loop through the children of this parsedObject 1683 // If they haven't been updated then assume that they 1684 // don't exist in the file anymore 1685 if (parsedObject) 1686 { 1687 for (S32 i = 0; i < mObjectBuffer.size(); i++) 1688 { 1689 ParsedObject* removeObj = mObjectBuffer[i]; 1690 1691 if (removeObj->parentObject == parsedObject && !removeObj->updated) 1692 { 1693 removeParsedObject(removeObj); 1694 1695 mObjectBuffer.erase(i); 1696 i--; 1697 1698 deleteObject(removeObj); 1699 } 1700 } 1701 } 1702 1703 // Flag this as an updated object 1704 if (parsedObject) 1705 parsedObject->updated = true; 1706 1707 // Cleanup our created default object 1708 delete defaultConObject; 1709} 1710 1711bool PersistenceManager::saveDirtyFile() 1712{ 1713 FileStream stream; 1714 stream.open( mCurrentFile, Torque::FS::File::Write ); 1715 1716 if ( stream.getStatus() != Stream::Ok ) 1717 { 1718 clearFileData(); 1719 1720 return false; 1721 } 1722 1723 for (U32 i = 0; i < mLineBuffer.size(); i++) 1724 stream.writeLine((const U8*)mLineBuffer[i]); 1725 1726 stream.close(); 1727 1728 //Con::printf("Successfully opened and wrote %s", mCurrentFile); 1729 1730 //Con::errorf("Updated Results:"); 1731 1732 //for (U32 i = 0; i < mObjectBuffer.size(); i++) 1733 //{ 1734 // ParsedObject* parsedObject = mObjectBuffer[i]; 1735 1736 // Con::warnf(" mObjectBuffer[%d]:", i); 1737 // Con::warnf(" name = %s", parsedObject->name); 1738 // Con::warnf(" className = %s", parsedObject->className); 1739 // Con::warnf(" startLine = %d", parsedObject->startLine + 1); 1740 // Con::warnf(" endLine = %d", parsedObject->endLine + 1); 1741 1742 // //if (mObjectBuffer[i]->properties.size() > 0) 1743 // //{ 1744 // // Con::warnf(" properties:"); 1745 // // for (U32 j = 0; j < mObjectBuffer[i]->properties.size(); j++) 1746 // // Con::warnf(" %s = %s;", mObjectBuffer[i]->properties[j].name, 1747 // // mObjectBuffer[i]->properties[j].value); 1748 // //} 1749 1750 // if (!parsedObject->simObject.isNull()) 1751 // { 1752 // SimObject* simObject = parsedObject->simObject; 1753 1754 // Con::warnf(" SimObject(%s) %d:", simObject->getName(), simObject->getId()); 1755 // Con::warnf(" declaration line = %d", simObject->getDeclarationLine()); 1756 // } 1757 //} 1758 1759 // Clear our file data 1760 clearFileData(); 1761 1762 return true; 1763} 1764 1765S32 QSORT_CALLBACK PersistenceManager::compareFiles(const void* a,const void* b) 1766{ 1767 DirtyObject* objectA = (DirtyObject*)(a); 1768 DirtyObject* objectB = (DirtyObject*)(b); 1769 1770 if (objectA->isNull()) 1771 return -1; 1772 else if (objectB->isNull()) 1773 return 1; 1774 1775 if (objectA->fileName == objectB->fileName) 1776 return objectA->getObject()->getDeclarationLine() - objectB->getObject()->getDeclarationLine(); 1777 1778 return dStricmp(objectA->fileName, objectB->fileName); 1779} 1780 1781bool PersistenceManager::setDirty(SimObject* inObject, const char* inFileName) 1782{ 1783 // Check if the object is already in the dirty list... 1784 DirtyObject *pDirty = findDirtyObject( inObject ); 1785 1786 // The filename we will save this object to (later).. 1787 String saveFile; 1788 1789 // Expand the script filename if we were passed one. 1790 if ( inFileName ) 1791 { 1792 char buffer[4096]; 1793 Con::expandScriptFilename( buffer, 4096, inFileName ); 1794 saveFile = buffer; 1795 } 1796 1797 // If no filename was passed in, and the object was already dirty, 1798 // we have nothing to do. 1799 if ( saveFile.isEmpty() && pDirty ) 1800 return true; 1801 1802 // Otherwise default to the simObject's filename. 1803 if ( saveFile.isEmpty() ) 1804 saveFile = inObject->getFilename(); 1805 1806 // Error if still no filename. 1807 if ( saveFile.isEmpty() ) 1808 { 1809 if (inObject->getName()) 1810 Con::errorf("PersistenceManager::setDirty() - SimObject %s has no file name associated with it - can not save", inObject->getName()); 1811 else 1812 Con::errorf("PersistenceManager::setDirty() - SimObject %d has no file name associated with it - can not save", inObject->getId()); 1813 1814 return false; 1815 } 1816 1817 // Update the DirtyObject's fileName if we have it 1818 // else create a new one. 1819 1820 if ( pDirty ) 1821 pDirty->fileName = StringTable->insert( saveFile ); 1822 else 1823 { 1824 // Add the newly dirty object. 1825 mDirtyObjects.increment(); 1826 mDirtyObjects.last().setObject( inObject ); 1827 mDirtyObjects.last().fileName = StringTable->insert( saveFile ); 1828 } 1829 1830 return true; 1831} 1832 1833void PersistenceManager::removeDirty(SimObject* object) 1834{ 1835 for (U32 i = 0; i < mDirtyObjects.size(); i++) 1836 { 1837 const DirtyObject& dirtyObject = mDirtyObjects[i]; 1838 1839 if (dirtyObject.isNull()) 1840 continue; 1841 1842 if (dirtyObject.getObject() == object) 1843 { 1844 mDirtyObjects.erase(i); 1845 break; 1846 } 1847 } 1848 1849 for (U32 i = 0; i < mRemoveFields.size(); i++) 1850 { 1851 const RemoveField& field = mRemoveFields[i]; 1852 1853 if (field.object != object) 1854 continue; 1855 1856 mRemoveFields.erase(i); 1857 1858 if (i > 0) 1859 i--; 1860 } 1861} 1862 1863void PersistenceManager::addRemoveField(SimObject* object, const char* fieldName) 1864{ 1865 // Check to see if this is an array variable 1866 U32 arrayPos = 0; 1867 const char* name = fieldName; 1868 1869 if (dStrlen(fieldName) > 3 && fieldName[dStrlen(fieldName) - 1] == ']') 1870 { 1871 // The last character is a ']' which *should* mean 1872 // there is also a corresponding '[' 1873 const char* arrayPosStart = dStrrchr(fieldName, '['); 1874 1875 if (!arrayPosStart) 1876 { 1877 Con::errorf("PersistenceManager::addRemoveField() - error parsing array position - \ 1878 was expecting a '[' character"); 1879 } 1880 else 1881 { 1882 // Parse the array position for the variable name 1883 dSscanf(arrayPosStart, "[%d]", &arrayPos); 1884 1885 // Trim off the [<pos>] from the variable name 1886 char* variableName = dStrdup(fieldName); 1887 variableName[arrayPosStart - fieldName] = 0; 1888 1889 // Set the variable name to our new shortened name 1890 name = StringTable->insert(variableName, true); 1891 1892 // Cleanup our variableName buffer 1893 dFree( variableName ); 1894 } 1895 } 1896 1897 // Make sure this field isn't already on the list 1898 if (!findRemoveField(object, name, arrayPos)) 1899 { 1900 mRemoveFields.increment(); 1901 1902 RemoveField& field = mRemoveFields.last(); 1903 1904 field.object = object; 1905 field.fieldName = StringTable->insert(name); 1906 field.arrayPos = arrayPos; 1907 } 1908} 1909 1910bool PersistenceManager::isDirty(SimObject* object) 1911{ 1912 return ( findDirtyObject( object ) != NULL ); 1913} 1914 1915PersistenceManager::DirtyObject* PersistenceManager::findDirtyObject(SimObject* object) 1916{ 1917 for (U32 i = 0; i < mDirtyObjects.size(); i++) 1918 { 1919 const DirtyObject& dirtyObject = mDirtyObjects[i]; 1920 1921 if (dirtyObject.isNull()) 1922 continue; 1923 1924 if (dirtyObject.getObject() == object) 1925 return &mDirtyObjects[i]; 1926 } 1927 1928 return NULL; 1929} 1930 1931bool PersistenceManager::findRemoveField(SimObject* object, const char* fieldName, U32 arrayPos) 1932{ 1933 for (U32 i = 0; i < mRemoveFields.size(); i++) 1934 { 1935 if (mRemoveFields[i].object == object && 1936 mRemoveFields[i].arrayPos == arrayPos && 1937 dStricmp(mRemoveFields[i].fieldName, fieldName) == 0) 1938 { 1939 return true; 1940 } 1941 } 1942 1943 return false; 1944} 1945 1946bool PersistenceManager::saveDirty() 1947{ 1948 // Remove any null SimObject's first 1949 for (S32 i = 0; i < mDirtyObjects.size(); i++) 1950 { 1951 const DirtyObject& dirtyObject = mDirtyObjects[i]; 1952 1953 if (dirtyObject.isNull()) 1954 { 1955 mDirtyObjects.erase(i); 1956 i--; 1957 } 1958 } 1959 1960 // Sort by filename and declaration lines 1961 dQsort(mDirtyObjects.address(), mDirtyObjects.size(), sizeof(DirtyList::value_type), compareFiles); 1962 1963 for (U32 i = 0; i < mDirtyObjects.size(); i++) 1964 { 1965 const DirtyObject& dirtyObject = mDirtyObjects[i]; 1966 1967 if (dirtyObject.isNull()) 1968 continue; 1969 1970 SimObject* object = dirtyObject.getObject(); 1971 1972 if (!mCurrentFile || dStricmp(mCurrentFile, dirtyObject.fileName) != 0) 1973 { 1974 // If mCurrentFile is set then that means we 1975 // changed file names so save our previous one 1976 if (mCurrentFile) 1977 saveDirtyFile(); 1978 1979 // Open our new file and parse it 1980 bool success = parseFile(dirtyObject.fileName); 1981 1982 if (!success) 1983 { 1984 const char *name = object->getName(); 1985 if (name) 1986 { 1987 Con::errorf("PersistenceManager::saveDirty(): Unable to open %s to save %s %s (%d)", 1988 dirtyObject.fileName, object->getClassName(), name, object->getId()); 1989 } 1990 else 1991 { 1992 Con::errorf("PersistenceManager::saveDirty(): Unable to open %s to save %s (%d)", 1993 dirtyObject.fileName, object->getClassName(), object->getId()); 1994 } 1995 1996 continue; 1997 } 1998 } 1999 2000 // Update this object's properties 2001 // 2002 // An empty script file (with 1 line) gets here with zero 2003 // elements in the linebuffer, so this would prevent us from 2004 // ever writing to it... Or is this test preventing bad things from 2005 // happening if the file didn't exist at all? 2006 // 2007 if (mCurrentFile /*&& mLineBuffer.size() > 0*/) 2008 updateObject(object); 2009 } 2010 2011 // Save out our last file 2012 if (mCurrentFile) 2013 saveDirtyFile(); 2014 2015 // Done writing out our dirty objects so reset everything 2016 clearAll(); 2017 2018 return true; 2019} 2020 2021bool PersistenceManager::saveDirtyObject(SimObject* object) 2022{ 2023 // find our object passed in 2024 for (U32 i = 0; i < mDirtyObjects.size(); i++) 2025 { 2026 const DirtyObject& dirtyObject = mDirtyObjects[i]; 2027 2028 if (dirtyObject.isNull()) 2029 continue; 2030 2031 if (dirtyObject.getObject() == object) 2032 { 2033 // Open our new file and parse it 2034 bool success = parseFile(dirtyObject.fileName); 2035 2036 if (!success) 2037 { 2038 const char *name = object->getName(); 2039 if (name) 2040 { 2041 Con::errorf("PersistenceManager::saveDirtyObject(): Unable to open %s to save %s %s (%d)", 2042 dirtyObject.fileName, object->getClassName(), name, object->getId()); 2043 } 2044 else 2045 { 2046 Con::errorf("PersistenceManager::saveDirtyObject(): Unable to open %s to save %s (%d)", 2047 dirtyObject.fileName, object->getClassName(), object->getId()); 2048 } 2049 2050 return false; 2051 } 2052 2053 // if the file exists then lets update and save 2054 if(mCurrentFile) 2055 { 2056 updateObject(object); 2057 saveDirtyFile(); 2058 } 2059 2060 break; 2061 } 2062 } 2063 2064 // remove this object from the dirty list 2065 removeDirty(object); 2066 2067 return true; 2068} 2069 2070void PersistenceManager::removeObjectFromFile(SimObject* object, const char* fileName) 2071{ 2072 if (mCurrentFile) 2073 { 2074 Con::errorf("PersistenceManager::removeObjectFromFile(): Can't remove an object from a \ 2075 file while another is currently opened"); 2076 2077 return; 2078 } 2079 2080 const char* file = object->getFilename(); 2081 if (fileName) 2082 { 2083 char buffer[1024]; 2084 Con::expandScriptFilename( buffer, 1024, fileName ); 2085 2086 file = StringTable->insert(buffer); 2087 } 2088 2089 bool success = false; 2090 2091 if ( file && file[ 0 ] ) 2092 success = parseFile(file); 2093 2094 if (!success) 2095 { 2096 const char *name = object->getName(); 2097 2098 String errorNameStr; 2099 if ( name ) 2100 errorNameStr = String::ToString( "%s %s (%d)", object->getClassName(), name, object->getId() ); 2101 else 2102 errorNameStr = String::ToString( "%s (%d)", object->getClassName(), object->getId() ); 2103 2104 if ( !file ) 2105 Con::errorf("PersistenceManager::removeObjectFromFile(): File was null trying to save %s", errorNameStr.c_str() ); 2106 else 2107 Con::errorf("PersistenceManager::removeObjectFromFile(): Unable to open %s to save %s", file, errorNameStr.c_str() ); 2108 2109 // Reset everything 2110 clearAll(); 2111 2112 return; 2113 } 2114 2115 ParsedObject* parsedObject = findParsedObject(object); 2116 2117 if (!parsedObject) 2118 { 2119 const char *name = object->getName(); 2120 if (name) 2121 { 2122 Con::errorf("PersistenceManager::removeObjectFromFile(): Unable to find %s %s (%d) in %s", 2123 object->getClassName(), name, object->getId(), file); 2124 } 2125 else 2126 { 2127 Con::errorf("PersistenceManager::removeObjectFromFile(): Unable to find %s (%d) in %s", 2128 object->getClassName(), object->getId(), file); 2129 } 2130 2131 // Reset everything 2132 clearAll(); 2133 2134 return; 2135 } 2136 2137 removeParsedObject(parsedObject); 2138 2139 for (U32 i = 0; i < mObjectBuffer.size(); i++) 2140 { 2141 ParsedObject* removeObj = mObjectBuffer[i]; 2142 2143 if (removeObj == parsedObject) 2144 { 2145 mObjectBuffer.erase(i); 2146 break; 2147 } 2148 } 2149 2150 deleteObject(parsedObject); 2151 2152 // Save out the file 2153 if (mCurrentFile) 2154 saveDirtyFile(); 2155 2156 // Reset everything 2157 clearAll(); 2158} 2159 2160void PersistenceManager::deleteObjectsFromFile(const char* fileName) 2161{ 2162 if ( mCurrentFile ) 2163 { 2164 Con::errorf( "PersistenceManager::deleteObjectsFromFile(): Cannot process while file while another is currently open." ); 2165 return; 2166 } 2167 2168 // Expand Script File. 2169 char buffer[1024]; 2170 Con::expandScriptFilename( buffer, 1024, fileName ); 2171 2172 // Parse File. 2173 if ( !parseFile( StringTable->insert(buffer) ) ) 2174 { 2175 // Invalid. 2176 return; 2177 } 2178 2179 // Iterate over the objects. 2180 for ( Vector<ParsedObject*>::iterator itr = mObjectBuffer.begin(); itr != mObjectBuffer.end(); itr++ ) 2181 { 2182 SimObject *object; 2183 if ( Sim::findObject( ( *itr )->name, object ) ) 2184 { 2185 // Delete the Object. 2186 object->deleteObject(); 2187 } 2188 } 2189 2190 // Clear. 2191 clearAll(); 2192} 2193 2194DefineEngineMethod( PersistenceManager, deleteObjectsFromFile, void, ( const char * fileName ), , "( fileName )" 2195 "Delete all of the objects that are created from the given file." ) 2196{ 2197 // Delete Objects. 2198 object->deleteObjectsFromFile( fileName ); 2199} 2200 2201DefineEngineMethod( PersistenceManager, setDirty, void, ( const char * objName, const char * fileName ), (""), "(SimObject object, [filename])" 2202 "Mark an existing SimObject as dirty (will be written out when saveDirty() is called).") 2203{ 2204 SimObject *dirtyObject = NULL; 2205 if (String::compare(objName,"") != 0) 2206 { 2207 if (!Sim::findObject(objName, dirtyObject)) 2208 { 2209 Con::printf("PersistenceManager::setDirty(): Invalid SimObject: %s", objName); 2210 return; 2211 } 2212 } 2213 2214 // Prevent ourselves from shooting us in the foot. 2215 if( dirtyObject == Sim::getRootGroup() ) 2216 { 2217 Con::errorf( "PersistenceManager::setDirty(): Cannot save RootGroup" ); 2218 return; 2219 } 2220 2221 if (dirtyObject) 2222 { 2223 if (String::compare( fileName,"")!=0) 2224 object->setDirty(dirtyObject, fileName); 2225 else 2226 object->setDirty(dirtyObject); 2227 } 2228} 2229 2230DefineEngineMethod( PersistenceManager, removeDirty, void, ( const char * objName ), , "(SimObject object)" 2231 "Remove a SimObject from the dirty list.") 2232{ 2233 SimObject *dirtyObject = NULL; 2234 if (String::compare( objName,"")!=0) 2235 { 2236 if (!Sim::findObject(objName, dirtyObject)) 2237 { 2238 Con::printf("PersistenceManager::removeDirty(): Invalid SimObject: %s", objName); 2239 return; 2240 } 2241 } 2242 2243 if (dirtyObject) 2244 object->removeDirty(dirtyObject); 2245} 2246 2247DefineEngineMethod( PersistenceManager, isDirty, bool, ( const char * objName ), , "(SimObject object)" 2248 "Returns true if the SimObject is on the dirty list.") 2249{ 2250 SimObject *dirtyObject = NULL; 2251 if (String::compare ( objName,"")!=0) 2252 { 2253 if (!Sim::findObject(objName, dirtyObject)) 2254 { 2255 Con::printf("PersistenceManager::isDirty(): Invalid SimObject: %s", objName); 2256 return false; 2257 } 2258 } 2259 2260 if (dirtyObject) 2261 return object->isDirty(dirtyObject); 2262 2263 return false; 2264} 2265 2266DefineEngineMethod( PersistenceManager, hasDirty, bool, (), , "()" 2267 "Returns true if the manager has dirty objects to save." ) 2268{ 2269 return object->hasDirty(); 2270} 2271 2272DefineEngineMethod( PersistenceManager, getDirtyObjectCount, S32, (), , "()" 2273 "Returns the number of dirty objects." ) 2274{ 2275 return object->getDirtyList().size(); 2276} 2277 2278DefineEngineMethod( PersistenceManager, getDirtyObject, S32, (S32 index), , "( index )" 2279 "Returns the ith dirty object." ) 2280{ 2281 if ( index < 0 || index >= object->getDirtyList().size() ) 2282 { 2283 Con::warnf( "PersistenceManager::getDirtyObject() - Index (%s) out of range.", index ); 2284 return 0; 2285 } 2286 2287 // Fetch Object. 2288 const PersistenceManager::DirtyObject& dirtyObject = object->getDirtyList()[index]; 2289 2290 // Return Id. 2291 return ( dirtyObject.getObject() ) ? dirtyObject.getObject()->getId() : 0; 2292} 2293 2294DefineEngineMethod( PersistenceManager, listDirty, void, (), , "()" 2295 "Prints the dirty list to the console.") 2296{ 2297 const PersistenceManager::DirtyList dirtyList = object->getDirtyList(); 2298 2299 for(U32 i = 0; i < dirtyList.size(); i++) 2300 { 2301 const PersistenceManager::DirtyObject& dirtyObject = dirtyList[i]; 2302 2303 if (dirtyObject.isNull()) 2304 continue; 2305 2306 SimObject *obj = dirtyObject.getObject(); 2307 bool isSet = dynamic_cast<SimSet *>(obj) != 0; 2308 const char *name = obj->getName(); 2309 if (name) 2310 { 2311 Con::printf(" %d,\"%s\": %s %s %s", obj->getId(), name, 2312 obj->getClassName(), dirtyObject.fileName, isSet ? "(g)":""); 2313 } 2314 else 2315 { 2316 Con::printf(" %d: %s %s, %s", obj->getId(), obj->getClassName(), 2317 dirtyObject.fileName, isSet ? "(g)" : ""); 2318 } 2319 } 2320} 2321 2322DefineEngineMethod( PersistenceManager, saveDirty, bool, (), , "()" 2323 "Saves all of the SimObject's on the dirty list to their respective files.") 2324{ 2325 return object->saveDirty(); 2326} 2327 2328DefineEngineMethod( PersistenceManager, saveDirtyObject, bool, (const char * objName), , "(SimObject object)" 2329 "Save a dirty SimObject to it's file.") 2330{ 2331 SimObject *dirtyObject = NULL; 2332 if (String::compare ( objName, "")!=0) 2333 { 2334 if (!Sim::findObject(objName, dirtyObject)) 2335 { 2336 Con::printf("%s(): Invalid SimObject: %s", object->getName(), objName); 2337 return false; 2338 } 2339 } 2340 2341 if (dirtyObject) 2342 return object->saveDirtyObject(dirtyObject); 2343 return false; 2344} 2345 2346DefineEngineMethod( PersistenceManager, clearAll, void, (), , "()" 2347 "Clears all the tracked objects without saving them." ) 2348{ 2349 object->clearAll(); 2350} 2351 2352DefineEngineMethod( PersistenceManager, removeObjectFromFile, void, (const char * objName, const char * filename),("") , "(SimObject object, [filename])" 2353 "Remove an existing SimObject from a file (can optionally specify a different file than \ 2354 the one it was created in.") 2355{ 2356 SimObject *dirtyObject = NULL; 2357 if (String::compare ( objName , "")!=0) 2358 { 2359 if (!Sim::findObject(objName, dirtyObject)) 2360 { 2361 Con::printf("PersistenceManager::removeObjectFromFile(): Invalid SimObject: %s", objName); 2362 return; 2363 } 2364 } 2365 2366 if (dirtyObject) 2367 { 2368 if (String::compare( filename,"")!=0) 2369 object->removeObjectFromFile(dirtyObject, filename); 2370 else 2371 object->removeObjectFromFile(dirtyObject); 2372 } 2373} 2374 2375DefineEngineMethod( PersistenceManager, removeField, void, (const char * objName, const char * fieldName), , "(SimObject object, string fieldName)" 2376 "Remove a specific field from an object declaration.") 2377{ 2378 SimObject *dirtyObject = NULL; 2379 if (String::compare(objName,"")!=0) 2380 { 2381 if (!Sim::findObject(objName, dirtyObject)) 2382 { 2383 Con::printf("PersistenceManager::removeField(): Invalid SimObject: %s", objName); 2384 return; 2385 } 2386 } 2387 2388 if (dirtyObject) 2389 { 2390 if (String::compare(fieldName,"") != 0) 2391 object->addRemoveField(dirtyObject, fieldName); 2392 } 2393} 2394