simDatablock.cpp
Engine/source/console/simDatablock.cpp
Public Functions
ConsoleDocClass(SimDataBlock , "@brief \<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@ingroup \<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@section Datablock_Networking Datablocks and <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Networking\n</a>" "@section Datablock_ClientSide Client-Side <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Datablocks\n</a>" )
DefineEngineFunction(deleteDataBlocks , void , () , "Delete all the datablocks we've <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">downloaded.\n\n</a>" "This is usually done in preparation of downloading <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> set of datablocks, " "such as occurs on <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> mission change, but it 's also good post-mission cleanup." )
DefineEngineFunction(preloadClientDataBlocks , void , () , "Preload all datablocks in client <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">mode.\n\n</a>" "(Server parameter is set <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> false). This will take some time <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> complete." )
DefineEngineMethod(SimDataBlock , reloadOnLocalClient , void , () , "Reload the datablock. This can only be used with <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> local client configuration." )
Detailed Description
Public Functions
ConsoleDocClass(SimDataBlock , "@brief \<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@ingroup \<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@section Datablock_Networking Datablocks and <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Networking\n</a>" "@section Datablock_ClientSide Client-Side <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Datablocks\n</a>" )
DefineEngineFunction(deleteDataBlocks , void , () , "Delete all the datablocks we've <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">downloaded.\n\n</a>" "This is usually done in preparation of downloading <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> set of datablocks, " "such as occurs on <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> mission change, but it 's also good post-mission cleanup." )
DefineEngineFunction(preloadClientDataBlocks , void , () , "Preload all datablocks in client <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">mode.\n\n</a>" "(Server parameter is set <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> false). This will take some time <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> complete." )
DefineEngineMethod(SimDataBlock , reloadOnLocalClient , void , () , "Reload the datablock. This can only be used with <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> local client configuration." )
IMPLEMENT_CO_DATABLOCK_V1(SimDataBlock )
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//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// 25// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames 26// Copyright (C) 2015 Faust Logic, Inc. 27//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// 28#include "platform/platform.h" 29#include "console/simDatablock.h" 30 31#include "console/console.h" 32#include "console/consoleInternal.h" 33#include "console/engineAPI.h" 34#include "T3D/gameBase/gameConnectionEvents.h" 35#include "T3D/gameBase/gameConnection.h" 36 37#include "core/stream/bitStream.h" 38#include "console/compiler.h" 39 40IMPLEMENT_CO_DATABLOCK_V1(SimDataBlock); 41SimObjectId SimDataBlock::sNextObjectId = DataBlockObjectIdFirst; 42S32 SimDataBlock::sNextModifiedKey = 0; 43 44ConsoleDocClass( SimDataBlock, 45 "@brief \n" 46 "@ingroup \n" 47 48 "@section Datablock_Networking Datablocks and Networking\n" 49 50 "@section Datablock_ClientSide Client-Side Datablocks\n" 51); 52 53 54 55//----------------------------------------------------------------------------- 56 57SimDataBlock::SimDataBlock() 58{ 59 modifiedKey = 0; 60 setModDynamicFields(true); 61 setModStaticFields(true); 62} 63// this implements a simple structure for managing substitution statements. 64 65SimDataBlock::SubstitutionStatement::SubstitutionStatement(StringTableEntry slot, S32 idx, const char* value) 66{ 67 this->mSlot = slot; 68 this->mIdx = idx; 69 this->mValue = dStrdup(value); 70} 71 72SimDataBlock::SubstitutionStatement::~SubstitutionStatement() 73{ 74 dFree(mValue); 75} 76 77void SimDataBlock::SubstitutionStatement::replaceValue(const char* value) 78{ 79 dFree(this->mValue); 80 this->mValue = dStrdup(value); 81} 82 83// this is the copy-constructor for creating temp-clones. 84SimDataBlock::SimDataBlock(const SimDataBlock& other, bool temp_clone) : SimObject(other, temp_clone) 85{ 86 modifiedKey = other.modifiedKey; 87} 88 89// a destructor is added to SimDataBlock so that we can delete any substitutions. 90SimDataBlock::~SimDataBlock() 91{ 92 clear_substitutions(); 93} 94 95void SimDataBlock::clear_substitutions() 96{ 97 for (S32 i = 0; i < substitutions.size(); i++) 98 delete substitutions[i]; 99 substitutions.clear(); 100} 101 102void SimDataBlock::addSubstitution(StringTableEntry slot, S32 idx, const char* subst) 103{ 104 AssertFatal(subst != 0 && subst[0] == '$' && subst[1] == '$', "Bad substition statement string added"); 105 106 subst += 2; 107 while (dIsspace(*subst)) 108 subst++; 109 110 bool empty_subs = (*subst == '\0'); 111 112 for (S32 i = 0; i < substitutions.size(); i++) 113 { 114 if (substitutions[i] && substitutions[i]->mSlot == slot && substitutions[i]->mIdx == idx) 115 { 116 if (empty_subs) 117 { 118 delete substitutions[i]; 119 substitutions[i] = 0; 120 onRemoveSubstitution(slot, idx); 121 } 122 else 123 { 124 substitutions[i]->replaceValue(subst); 125 onAddSubstitution(slot, idx, subst); 126 } 127 return; 128 } 129 } 130 131 if (!empty_subs) 132 { 133 substitutions.push_back(new SubstitutionStatement(slot, idx, subst)); 134 onAddSubstitution(slot, idx, subst); 135 } 136} 137 138const char* SimDataBlock::getSubstitution(StringTableEntry slot, S32 idx) 139{ 140 for (S32 i = 0; i < substitutions.size(); i++) 141 { 142 if (substitutions[i] && substitutions[i]->mSlot == slot && substitutions[i]->mIdx == idx) 143 return substitutions[i]->mValue; 144 } 145 146 return 0; 147} 148 149bool SimDataBlock::fieldHasSubstitution(StringTableEntry slot) 150{ 151 for (S32 i = 0; i < substitutions.size(); i++) 152 if (substitutions[i] && substitutions[i]->mSlot == slot) 153 return true; 154 return false; 155} 156 157void SimDataBlock::printSubstitutions() 158{ 159 for (S32 i = 0; i < substitutions.size(); i++) 160 if (substitutions[i]) 161 Con::errorf("SubstitutionStatement[%s] = \"%s\" -- %d", substitutions[i]->mSlot, substitutions[i]->mValue, i); 162} 163 164void SimDataBlock::copySubstitutionsFrom(SimDataBlock* other) 165{ 166 clear_substitutions(); 167 if (!other) 168 return; 169 170 for (S32 i = 0; i < other->substitutions.size(); i++) 171 { 172 if (other->substitutions[i]) 173 { 174 SubstitutionStatement* subs = other->substitutions[i]; 175 substitutions.push_back(new SubstitutionStatement(subs->mSlot, subs->mIdx, subs->mValue)); 176 } 177 } 178} 179 180 181// This is the method that evaluates any substitution statements on a datablock and does the 182// actual replacement of substituted datablock fields. 183// 184// Much of the work is done by passing the statement to Con::evaluate() but first there are 185// some key operations performed on the statement. 186// -- Instances of "%%" in the statement are replaced with the id of the <obj> object. 187// -- Instances of "##" are replaced with the value of <index>. 188// 189// There are also some return values that get special treatment. 190// -- An empty result will produce a realtime error message. 191// -- A result of "~~" will leave the original field value unchanged. 192// -- A result of "~0" will clear the original field to "" without producing an error message. 193// 194void SimDataBlock::performSubstitutions(SimDataBlock* dblock, const SimObject* obj, S32 index) 195{ 196 if (!dblock || !dblock->getClassRep()) 197 { 198 // error message 199 return; 200 } 201 202 char obj_str[32]; 203 dStrcpy(obj_str, Con::getIntArg(obj->getId()), 32); 204 205 char index_str[32]; 206 dStrcpy(index_str, Con::getIntArg(index), 32); 207 208 for (S32 i = 0; i < substitutions.size(); i++) 209 { 210 if (substitutions[i]) 211 { 212 static char buffer[1024]; 213 static char* b_oob = &buffer[1024]; 214 char* b = buffer; 215 216 // perform special token expansion (%% and ##) 217 const char* v = substitutions[i]->mValue; 218 while (*v != '\0') 219 { 220 // identify "%%" tokens and replace with <obj> id 221 if (v[0] == '%' && v[1] == '%') 222 { 223 const char* s = obj_str; 224 while (*s != '\0') 225 { 226 b[0] = s[0]; 227 b++; 228 s++; 229 } 230 v += 2; 231 } 232 // identify "##" tokens and replace with <index> value 233 else if (v[0] == '#' && v[1] == '#') 234 { 235 const char* s = index_str; 236 while (*s != '\0') 237 { 238 b[0] = s[0]; 239 b++; 240 s++; 241 } 242 v += 2; 243 } 244 else 245 { 246 b[0] = v[0]; 247 b++; 248 v++; 249 } 250 } 251 252 AssertFatal((uintptr_t)b < (uintptr_t)b_oob, "Substitution buffer overflowed"); 253 254 b[0] = '\0'; 255 256 // perform the statement evaluation 257 Compiler::gSyntaxError = false; 258 //Con::errorf("EVAL [%s]", avar("return %s;", buffer)); 259 const char *result = Con::evaluate(avar("return %s;", buffer), false, 0); 260 if (Compiler::gSyntaxError) 261 { 262 Con::errorf("Field Substitution Failed: field=\"%s\" substitution=\"%s\" -- syntax error", 263 substitutions[i]->mSlot, substitutions[i]->mValue); 264 Compiler::gSyntaxError = false; 265 return; 266 } 267 268 // output a runtime console error when a substitution produces and empty result. 269 if (result == 0 || result[0] == '\0') 270 { 271 Con::errorf("Field Substitution Failed: field=\"%s\" substitution=\"%s\" -- empty result", 272 substitutions[i]->mSlot, substitutions[i]->mValue); 273 return; 274 } 275 276 // handle special return values 277 if (result[0] == '~') 278 { 279 // if value is "~~" then keep the existing value 280 if (result[1] == '~' && result[2] == '\0') 281 continue; 282 // if "~0" then clear it 283 if (result[1] == '0' && result[2] == '\0') 284 result = ""; 285 } 286 287 const AbstractClassRep::Field* field = dblock->getClassRep()->findField(substitutions[i]->mSlot); 288 if (!field) 289 { 290 // this should be very unlikely... 291 Con::errorf("Field Substitution Failed: unknown field, \"%s\".", substitutions[i]->mSlot); 292 continue; 293 } 294 295 if (field->keepClearSubsOnly && result[0] != '\0') 296 { 297 Con::errorf("Field Substitution Failed: field \"%s\" of datablock %s only allows \"$$ ~~\" (keep) and \"$$ ~0\" (clear) field substitutions. [%s]", 298 substitutions[i]->mSlot, this->getClassName(), this->getName()); 299 continue; 300 } 301 302 // substitute the field value with its replacement 303 Con::setData(field->type, (void*)(((const char*)(dblock)) + field->offset), substitutions[i]->mIdx, 1, &result, field->table, field->flag); 304 305 //dStrncpy(buffer, result, 255); 306 //Con::errorf("SUBSTITUTION %s.%s[%d] = %s idx=%s", Con::getIntArg(getId()), substitutions[i]->slot, substitutions[i]->idx, buffer, index_str); 307 308 // notify subclasses of a field modification 309 dblock->onStaticModified(substitutions[i]->mSlot); 310 } 311 } 312 313 // notify subclasses of substitution operation 314 if (substitutions.size() > 0) 315 dblock->onPerformSubstitutions(); 316} 317 318//----------------------------------------------------------------------------- 319 320bool SimDataBlock::onAdd() 321{ 322 Parent::onAdd(); 323 324 // This initialization is done here, and not in the constructor, 325 // because some jokers like to construct and destruct objects 326 // (without adding them to the manager) to check what class 327 // they are. 328 modifiedKey = ++sNextModifiedKey; 329 AssertFatal(sNextObjectId <= DataBlockObjectIdLast, 330 "Exceeded maximum number of data blocks"); 331 332 // add DataBlock to the DataBlockGroup unless it is client side ONLY DataBlock 333 if ( !isClientOnly() ) 334 if (SimGroup* grp = Sim::getDataBlockGroup()) 335 grp->addObject(this); 336 337 Sim::getDataBlockSet()->addObject( this ); 338 339 return true; 340} 341 342//----------------------------------------------------------------------------- 343 344void SimDataBlock::assignId() 345{ 346 // We don't want the id assigned by the manager, but it may have 347 // already been assigned a correct data block id. 348 if ( isClientOnly() ) 349 setId(sNextObjectId++); 350} 351 352//----------------------------------------------------------------------------- 353 354void SimDataBlock::onStaticModified(const char* slotName, const char* newValue) 355{ 356 modifiedKey = sNextModifiedKey++; 357} 358 359//----------------------------------------------------------------------------- 360 361// packData() and unpackData() do nothing in the stock implementation, but here 362// they've been modified to pack and unpack any substitution statements. 363// 364void SimDataBlock::packData(BitStream* stream) 365{ 366 for (S32 i = 0; i < substitutions.size(); i++) 367 { 368 if (substitutions[i]) 369 { 370 stream->writeFlag(true); 371 stream->writeString(substitutions[i]->mSlot); 372 stream->write(substitutions[i]->mIdx); 373 stream->writeString(substitutions[i]->mValue); 374 } 375 } 376 stream->writeFlag(false); 377} 378 379void SimDataBlock::unpackData(BitStream* stream) 380{ 381 clear_substitutions(); 382 while(stream->readFlag()) 383 { 384 char slotName[256]; 385 S32 idx; 386 char value[256]; 387 stream->readString(slotName); 388 stream->read(&idx); 389 stream->readString(value); 390 substitutions.push_back(new SubstitutionStatement(StringTable->insert(slotName), idx, value)); 391 } 392} 393 394//----------------------------------------------------------------------------- 395 396bool SimDataBlock::preload(bool, String&) 397{ 398 return true; 399} 400 401//----------------------------------------------------------------------------- 402 403void SimDataBlock::write(Stream &stream, U32 tabStop, U32 flags) 404{ 405 // Only output selected objects if they want that. 406 if((flags & SelectedOnly) && !isSelected()) 407 return; 408 409 stream.writeTabs(tabStop); 410 char buffer[1024]; 411 412 // Client side datablocks are created with 'new' while 413 // regular server datablocks use the 'datablock' keyword. 414 if ( isClientOnly() ) 415 dSprintf(buffer, sizeof(buffer), "new %s(%s) {\r\n", getClassName(), getName() ? getName() : ""); 416 else 417 dSprintf(buffer, sizeof(buffer), "datablock %s(%s) {\r\n", getClassName(), getName() ? getName() : ""); 418 419 stream.write(dStrlen(buffer), buffer); 420 writeFields(stream, tabStop + 1); 421 422 stream.writeTabs(tabStop); 423 stream.write(4, "};\r\n"); 424} 425 426//============================================================================= 427// API. 428//============================================================================= 429// MARK: ---- API ---- 430 431//----------------------------------------------------------------------------- 432 433DefineEngineMethod( SimDataBlock, reloadOnLocalClient, void, (),, 434 "Reload the datablock. This can only be used with a local client configuration." ) 435{ 436 // Make sure we're running a local client. 437 438 GameConnection* localClient = GameConnection::getLocalClientConnection(); 439 if( !localClient ) 440 return; 441 442 // Do an in-place pack/unpack/preload. 443 444 if( !object->preload( true, NetConnection::getErrorBuffer() ) ) 445 { 446 Con::errorf( NetConnection::getErrorBuffer() ); 447 return; 448 } 449 450 U8 buffer[ 16384 ]; 451 BitStream stream( buffer, 16384 ); 452 453 object->packData( &stream ); 454 stream.setPosition(0); 455 object->unpackData( &stream ); 456 457 if( !object->preload( false, NetConnection::getErrorBuffer() ) ) 458 { 459 Con::errorf( NetConnection::getErrorBuffer() ); 460 return; 461 } 462 463 // Trigger a post-apply so that change notifications respond. 464 object->inspectPostApply(); 465} 466 467//----------------------------------------------------------------------------- 468 469DefineEngineFunction( preloadClientDataBlocks, void, (),, 470 "Preload all datablocks in client mode.\n\n" 471 "(Server parameter is set to false). This will take some time to complete.") 472{ 473 // we go from last to first because we cut 'n pasted the loop from deleteDataBlocks 474 SimGroup *grp = Sim::getDataBlockGroup(); 475 String errorStr; 476 for(S32 i = grp->size() - 1; i >= 0; i--) 477 { 478 AssertFatal(dynamic_cast<SimDataBlock*>((*grp)[i]), "Doh! non-datablock in datablock group!"); 479 SimDataBlock *obj = (SimDataBlock*)(*grp)[i]; 480 if (!obj->preload(false, errorStr)) 481 Con::errorf("Failed to preload client datablock, %s: %s", obj->getName(), errorStr.c_str()); 482 } 483} 484 485//----------------------------------------------------------------------------- 486 487DefineEngineFunction( deleteDataBlocks, void, (),, 488 "Delete all the datablocks we've downloaded.\n\n" 489 "This is usually done in preparation of downloading a new set of datablocks, " 490 "such as occurs on a mission change, but it's also good post-mission cleanup." ) 491{ 492 // delete from last to first: 493 SimGroup *grp = Sim::getDataBlockGroup(); 494 grp->deleteAllObjects(); 495 SimDataBlock::sNextObjectId = DataBlockObjectIdFirst; 496 SimDataBlock::sNextModifiedKey = 0; 497} 498