simDatablock.cpp 16 KB

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