simDatablock.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
  23. // Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
  24. // Copyright (C) 2015 Faust Logic, Inc.
  25. //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
  26. #include "platform/platform.h"
  27. #include "console/simDatablock.h"
  28. #include "console/console.h"
  29. #include "console/consoleInternal.h"
  30. #include "console/engineAPI.h"
  31. #include "T3D/gameBase/gameConnectionEvents.h"
  32. #include "T3D/gameBase/gameConnection.h"
  33. #include "core/stream/bitStream.h"
  34. #include "console/compiler.h"
  35. IMPLEMENT_CO_DATABLOCK_V1(SimDataBlock);
  36. SimObjectId SimDataBlock::sNextObjectId = DataBlockObjectIdFirst;
  37. S32 SimDataBlock::sNextModifiedKey = 0;
  38. ConsoleDocClass( SimDataBlock,
  39. "@brief \n"
  40. "@ingroup \n"
  41. "@section Datablock_Networking Datablocks and Networking\n"
  42. "@section Datablock_ClientSide Client-Side Datablocks\n"
  43. );
  44. //-----------------------------------------------------------------------------
  45. SimDataBlock::SimDataBlock()
  46. {
  47. modifiedKey = 0;
  48. setModDynamicFields(true);
  49. setModStaticFields(true);
  50. }
  51. // this implements a simple structure for managing substitution statements.
  52. SimDataBlock::SubstitutionStatement::SubstitutionStatement(StringTableEntry slot, S32 idx, const char* value)
  53. {
  54. this->mSlot = slot;
  55. this->mIdx = idx;
  56. this->mValue = dStrdup(value);
  57. }
  58. SimDataBlock::SubstitutionStatement::~SubstitutionStatement()
  59. {
  60. dFree(mValue);
  61. }
  62. void SimDataBlock::SubstitutionStatement::replaceValue(const char* value)
  63. {
  64. dFree(this->mValue);
  65. this->mValue = dStrdup(value);
  66. }
  67. // this is the copy-constructor for creating temp-clones.
  68. SimDataBlock::SimDataBlock(const SimDataBlock& other, bool temp_clone) : SimObject(other, temp_clone)
  69. {
  70. modifiedKey = other.modifiedKey;
  71. }
  72. // a destructor is added to SimDataBlock so that we can delete any substitutions.
  73. SimDataBlock::~SimDataBlock()
  74. {
  75. clear_substitutions();
  76. }
  77. void SimDataBlock::clear_substitutions()
  78. {
  79. for (S32 i = 0; i < substitutions.size(); i++)
  80. delete substitutions[i];
  81. substitutions.clear();
  82. }
  83. void SimDataBlock::addSubstitution(StringTableEntry slot, S32 idx, const char* subst)
  84. {
  85. AssertFatal(subst != 0 && subst[0] == '$' && subst[1] == '$', "Bad substition statement string added");
  86. subst += 2;
  87. while (dIsspace(*subst))
  88. subst++;
  89. bool empty_subs = (*subst == '\0');
  90. for (S32 i = 0; i < substitutions.size(); i++)
  91. {
  92. if (substitutions[i] && substitutions[i]->mSlot == slot && substitutions[i]->mIdx == idx)
  93. {
  94. if (empty_subs)
  95. {
  96. delete substitutions[i];
  97. substitutions[i] = 0;
  98. onRemoveSubstitution(slot, idx);
  99. }
  100. else
  101. {
  102. substitutions[i]->replaceValue(subst);
  103. onAddSubstitution(slot, idx, subst);
  104. }
  105. return;
  106. }
  107. }
  108. if (!empty_subs)
  109. {
  110. substitutions.push_back(new SubstitutionStatement(slot, idx, subst));
  111. onAddSubstitution(slot, idx, subst);
  112. }
  113. }
  114. const char* SimDataBlock::getSubstitution(StringTableEntry slot, S32 idx)
  115. {
  116. for (S32 i = 0; i < substitutions.size(); i++)
  117. {
  118. if (substitutions[i] && substitutions[i]->mSlot == slot && substitutions[i]->mIdx == idx)
  119. return substitutions[i]->mValue;
  120. }
  121. return 0;
  122. }
  123. bool SimDataBlock::fieldHasSubstitution(StringTableEntry slot)
  124. {
  125. for (S32 i = 0; i < substitutions.size(); i++)
  126. if (substitutions[i] && substitutions[i]->mSlot == slot)
  127. return true;
  128. return false;
  129. }
  130. void SimDataBlock::printSubstitutions()
  131. {
  132. for (S32 i = 0; i < substitutions.size(); i++)
  133. if (substitutions[i])
  134. Con::errorf("SubstitutionStatement[%s] = \"%s\" -- %d", substitutions[i]->mSlot, substitutions[i]->mValue, i);
  135. }
  136. void SimDataBlock::copySubstitutionsFrom(SimDataBlock* other)
  137. {
  138. clear_substitutions();
  139. if (!other)
  140. return;
  141. for (S32 i = 0; i < other->substitutions.size(); i++)
  142. {
  143. if (other->substitutions[i])
  144. {
  145. SubstitutionStatement* subs = other->substitutions[i];
  146. substitutions.push_back(new SubstitutionStatement(subs->mSlot, subs->mIdx, subs->mValue));
  147. }
  148. }
  149. }
  150. // This is the method that evaluates any substitution statements on a datablock and does the
  151. // actual replacement of substituted datablock fields.
  152. //
  153. // Much of the work is done by passing the statement to Con::evaluate() but first there are
  154. // some key operations performed on the statement.
  155. // -- Instances of "%%" in the statement are replaced with the id of the <obj> object.
  156. // -- Instances of "##" are replaced with the value of <index>.
  157. //
  158. // There are also some return values that get special treatment.
  159. // -- An empty result will produce a realtime error message.
  160. // -- A result of "~~" will leave the original field value unchanged.
  161. // -- A result of "~0" will clear the original field to "" without producing an error message.
  162. //
  163. void SimDataBlock::performSubstitutions(SimDataBlock* dblock, const SimObject* obj, S32 index)
  164. {
  165. if (!dblock || !dblock->getClassRep())
  166. {
  167. // error message
  168. return;
  169. }
  170. char obj_str[32];
  171. dStrcpy(obj_str, Con::getIntArg(obj->getId()), 32);
  172. char index_str[32];
  173. dStrcpy(index_str, Con::getIntArg(index), 32);
  174. for (S32 i = 0; i < substitutions.size(); i++)
  175. {
  176. if (substitutions[i])
  177. {
  178. static char buffer[1024];
  179. static char* b_oob = &buffer[1024];
  180. char* b = buffer;
  181. // perform special token expansion (%% and ##)
  182. const char* v = substitutions[i]->mValue;
  183. while (*v != '\0')
  184. {
  185. // identify "%%" tokens and replace with <obj> id
  186. if (v[0] == '%' && v[1] == '%')
  187. {
  188. const char* s = obj_str;
  189. while (*s != '\0')
  190. {
  191. b[0] = s[0];
  192. b++;
  193. s++;
  194. }
  195. v += 2;
  196. }
  197. // identify "##" tokens and replace with <index> value
  198. else if (v[0] == '#' && v[1] == '#')
  199. {
  200. const char* s = index_str;
  201. while (*s != '\0')
  202. {
  203. b[0] = s[0];
  204. b++;
  205. s++;
  206. }
  207. v += 2;
  208. }
  209. else
  210. {
  211. b[0] = v[0];
  212. b++;
  213. v++;
  214. }
  215. }
  216. AssertFatal((uintptr_t)b < (uintptr_t)b_oob, "Substitution buffer overflowed");
  217. b[0] = '\0';
  218. // perform the statement evaluation
  219. Compiler::gSyntaxError = false;
  220. //Con::errorf("EVAL [%s]", avar("return %s;", buffer));
  221. const char *result = Con::evaluate(avar("return %s;", buffer), false, 0);
  222. if (Compiler::gSyntaxError)
  223. {
  224. Con::errorf("Field Substitution Failed: field=\"%s\" substitution=\"%s\" -- syntax error",
  225. substitutions[i]->mSlot, substitutions[i]->mValue);
  226. Compiler::gSyntaxError = false;
  227. return;
  228. }
  229. // output a runtime console error when a substitution produces and empty result.
  230. if (result == 0 || result[0] == '\0')
  231. {
  232. Con::errorf("Field Substitution Failed: field=\"%s\" substitution=\"%s\" -- empty result",
  233. substitutions[i]->mSlot, substitutions[i]->mValue);
  234. return;
  235. }
  236. // handle special return values
  237. if (result[0] == '~')
  238. {
  239. // if value is "~~" then keep the existing value
  240. if (result[1] == '~' && result[2] == '\0')
  241. continue;
  242. // if "~0" then clear it
  243. if (result[1] == '0' && result[2] == '\0')
  244. result = "";
  245. }
  246. const AbstractClassRep::Field* field = dblock->getClassRep()->findField(substitutions[i]->mSlot);
  247. if (!field)
  248. {
  249. // this should be very unlikely...
  250. Con::errorf("Field Substitution Failed: unknown field, \"%s\".", substitutions[i]->mSlot);
  251. continue;
  252. }
  253. if (field->keepClearSubsOnly && result[0] != '\0')
  254. {
  255. Con::errorf("Field Substitution Failed: field \"%s\" of datablock %s only allows \"$$ ~~\" (keep) and \"$$ ~0\" (clear) field substitutions. [%s]",
  256. substitutions[i]->mSlot, this->getClassName(), this->getName());
  257. continue;
  258. }
  259. // substitute the field value with its replacement
  260. Con::setData(field->type, (void*)(((const char*)(dblock)) + field->offset), substitutions[i]->mIdx, 1, &result, field->table, field->flag);
  261. //dStrncpy(buffer, result, 255);
  262. //Con::errorf("SUBSTITUTION %s.%s[%d] = %s idx=%s", Con::getIntArg(getId()), substitutions[i]->slot, substitutions[i]->idx, buffer, index_str);
  263. // notify subclasses of a field modification
  264. dblock->onStaticModified(substitutions[i]->mSlot);
  265. }
  266. }
  267. // notify subclasses of substitution operation
  268. if (substitutions.size() > 0)
  269. dblock->onPerformSubstitutions();
  270. }
  271. //-----------------------------------------------------------------------------
  272. bool SimDataBlock::onAdd()
  273. {
  274. Parent::onAdd();
  275. // This initialization is done here, and not in the constructor,
  276. // because some jokers like to construct and destruct objects
  277. // (without adding them to the manager) to check what class
  278. // they are.
  279. modifiedKey = ++sNextModifiedKey;
  280. AssertFatal(sNextObjectId <= DataBlockObjectIdLast,
  281. "Exceeded maximum number of data blocks");
  282. // add DataBlock to the DataBlockGroup unless it is client side ONLY DataBlock
  283. if ( !isClientOnly() )
  284. if (SimGroup* grp = Sim::getDataBlockGroup())
  285. grp->addObject(this);
  286. Sim::getDataBlockSet()->addObject( this );
  287. return true;
  288. }
  289. //-----------------------------------------------------------------------------
  290. void SimDataBlock::assignId()
  291. {
  292. // We don't want the id assigned by the manager, but it may have
  293. // already been assigned a correct data block id.
  294. if ( isClientOnly() )
  295. setId(sNextObjectId++);
  296. }
  297. //-----------------------------------------------------------------------------
  298. void SimDataBlock::onStaticModified(const char* slotName, const char* newValue)
  299. {
  300. modifiedKey = sNextModifiedKey++;
  301. }
  302. //-----------------------------------------------------------------------------
  303. // packData() and unpackData() do nothing in the stock implementation, but here
  304. // they've been modified to pack and unpack any substitution statements.
  305. //
  306. void SimDataBlock::packData(BitStream* stream)
  307. {
  308. for (S32 i = 0; i < substitutions.size(); i++)
  309. {
  310. if (substitutions[i])
  311. {
  312. stream->writeFlag(true);
  313. stream->writeString(substitutions[i]->mSlot);
  314. stream->write(substitutions[i]->mIdx);
  315. stream->writeString(substitutions[i]->mValue);
  316. }
  317. }
  318. stream->writeFlag(false);
  319. }
  320. void SimDataBlock::unpackData(BitStream* stream)
  321. {
  322. clear_substitutions();
  323. while(stream->readFlag())
  324. {
  325. char slotName[256];
  326. S32 idx;
  327. char value[256];
  328. stream->readString(slotName);
  329. stream->read(&idx);
  330. stream->readString(value);
  331. substitutions.push_back(new SubstitutionStatement(StringTable->insert(slotName), idx, value));
  332. }
  333. }
  334. //-----------------------------------------------------------------------------
  335. bool SimDataBlock::preload(bool, String&)
  336. {
  337. return true;
  338. }
  339. //-----------------------------------------------------------------------------
  340. void SimDataBlock::write(Stream &stream, U32 tabStop, U32 flags)
  341. {
  342. // Only output selected objects if they want that.
  343. if((flags & SelectedOnly) && !isSelected())
  344. return;
  345. stream.writeTabs(tabStop);
  346. char buffer[1024];
  347. // Client side datablocks are created with 'new' while
  348. // regular server datablocks use the 'datablock' keyword.
  349. if ( isClientOnly() )
  350. dSprintf(buffer, sizeof(buffer), "new %s(%s) {\r\n", getClassName(), getName() ? getName() : "");
  351. else
  352. dSprintf(buffer, sizeof(buffer), "datablock %s(%s) {\r\n", getClassName(), getName() ? getName() : "");
  353. stream.write(dStrlen(buffer), buffer);
  354. writeFields(stream, tabStop + 1);
  355. stream.writeTabs(tabStop);
  356. stream.write(4, "};\r\n");
  357. }
  358. //=============================================================================
  359. // API.
  360. //=============================================================================
  361. // MARK: ---- API ----
  362. //-----------------------------------------------------------------------------
  363. DefineEngineMethod( SimDataBlock, reloadOnLocalClient, void, (),,
  364. "Reload the datablock. This can only be used with a local client configuration." )
  365. {
  366. // Make sure we're running a local client.
  367. GameConnection* localClient = GameConnection::getLocalClientConnection();
  368. if( !localClient )
  369. return;
  370. // Do an in-place pack/unpack/preload.
  371. if( !object->preload( true, NetConnection::getErrorBuffer() ) )
  372. {
  373. Con::errorf( NetConnection::getErrorBuffer() );
  374. return;
  375. }
  376. U8 buffer[ 16384 ];
  377. BitStream stream( buffer, 16384 );
  378. object->packData( &stream );
  379. stream.setPosition(0);
  380. object->unpackData( &stream );
  381. if( !object->preload( false, NetConnection::getErrorBuffer() ) )
  382. {
  383. Con::errorf( NetConnection::getErrorBuffer() );
  384. return;
  385. }
  386. // Trigger a post-apply so that change notifications respond.
  387. object->inspectPostApply();
  388. }
  389. //-----------------------------------------------------------------------------
  390. DefineEngineFunction( preloadClientDataBlocks, void, (),,
  391. "Preload all datablocks in client mode.\n\n"
  392. "(Server parameter is set to false). This will take some time to complete.")
  393. {
  394. // we go from last to first because we cut 'n pasted the loop from deleteDataBlocks
  395. SimGroup *grp = Sim::getDataBlockGroup();
  396. String errorStr;
  397. for(S32 i = grp->size() - 1; i >= 0; i--)
  398. {
  399. AssertFatal(dynamic_cast<SimDataBlock*>((*grp)[i]), "Doh! non-datablock in datablock group!");
  400. SimDataBlock *obj = (SimDataBlock*)(*grp)[i];
  401. if (!obj->preload(false, errorStr))
  402. Con::errorf("Failed to preload client datablock, %s: %s", obj->getName(), errorStr.c_str());
  403. }
  404. }
  405. //-----------------------------------------------------------------------------
  406. DefineEngineFunction( deleteDataBlocks, void, (),,
  407. "Delete all the datablocks we've downloaded.\n\n"
  408. "This is usually done in preparation of downloading a new set of datablocks, "
  409. "such as occurs on a mission change, but it's also good post-mission cleanup." )
  410. {
  411. // delete from last to first:
  412. SimGroup *grp = Sim::getDataBlockGroup();
  413. grp->deleteAllObjects();
  414. SimDataBlock::sNextObjectId = DataBlockObjectIdFirst;
  415. SimDataBlock::sNextModifiedKey = 0;
  416. }