JSVM.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. //
  2. // Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
  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 deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // 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 FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. #include <Duktape/duktape.h>
  23. #include <Atomic/Core/Profiler.h>
  24. #include <Atomic/Core/CoreEvents.h>
  25. #include <Atomic/IO/File.h>
  26. #include <Atomic/IO/Log.h>
  27. #include <Atomic/IO/FileSystem.h>
  28. #include <Atomic/IO/PackageFile.h>
  29. #include <Atomic/Resource/ResourceCache.h>
  30. #include "JSRequire.h"
  31. #include "JSPlugin.h"
  32. #include "JSEvents.h"
  33. #include "JSVM.h"
  34. #include "JSAtomic.h"
  35. #include "JSUI.h"
  36. #include "JSMetrics.h"
  37. #include "JSEventHelper.h"
  38. namespace Atomic
  39. {
  40. JSVM* JSVM::instance_ = NULL;
  41. Vector<JSVM::JSAPIPackageRegistration*> JSVM::packageRegistrations_;
  42. JSVM::JSVM(Context* context) :
  43. Object(context),
  44. ctx_(0),
  45. gcTime_(0.0f),
  46. stashCount_(0),
  47. totalStashCount_(0),
  48. totalUnstashCount_(0)
  49. {
  50. assert(!instance_);
  51. instance_ = this;
  52. metrics_ = new JSMetrics(context, this);
  53. SharedPtr<JSEventDispatcher> dispatcher(new JSEventDispatcher(context_));
  54. context_->RegisterSubsystem(dispatcher);
  55. context_->AddGlobalEventListener(dispatcher);
  56. RefCounted::AddRefCountChangedFunction(OnRefCountChanged);
  57. }
  58. JSVM::~JSVM()
  59. {
  60. context_->RemoveGlobalEventListener(context_->GetSubsystem<JSEventDispatcher>());
  61. context_->RemoveSubsystem(JSEventDispatcher::GetTypeStatic());
  62. duk_destroy_heap(ctx_);
  63. RefCounted::RemoveRefCountChangedFunction(OnRefCountChanged);
  64. // assert(stashCount_ == 0);
  65. instance_ = NULL;
  66. }
  67. void JSVM::InitJSContext()
  68. {
  69. ctx_ = duk_create_heap_default();
  70. jsapi_init_atomic(this);
  71. // register whether we are in the editor
  72. duk_get_global_string(ctx_, "Atomic");
  73. duk_push_boolean(ctx_, context_->GetEditorContext() ? 1 : 0);
  74. duk_put_prop_string(ctx_, -2, "editor");
  75. duk_pop(ctx_);
  76. js_init_require(this);
  77. js_init_jsplugin(this);
  78. ui_ = new JSUI(context_);
  79. InitializePackages();
  80. // handle this elsewhere?
  81. SubscribeToEvents();
  82. }
  83. void JSVM::InitializePackages()
  84. {
  85. for (unsigned i = 0; i < packageRegistrations_.Size(); i++)
  86. {
  87. JSAPIPackageRegistration* pkgReg = packageRegistrations_.At(i);
  88. if (pkgReg->registrationFunction)
  89. {
  90. pkgReg->registrationFunction(this);
  91. }
  92. else
  93. {
  94. pkgReg->registrationSettingsFunction(this, pkgReg->settings);
  95. }
  96. delete pkgReg;
  97. }
  98. packageRegistrations_.Clear();
  99. }
  100. void JSVM::RegisterPackage(JSVMPackageRegistrationFunction regFunction)
  101. {
  102. packageRegistrations_.Push(new JSAPIPackageRegistration(regFunction));
  103. }
  104. void JSVM::RegisterPackage(JSVMPackageRegistrationSettingsFunction regFunction, const VariantMap& settings)
  105. {
  106. packageRegistrations_.Push(new JSAPIPackageRegistration(regFunction, settings));
  107. }
  108. void JSVM::SubscribeToEvents()
  109. {
  110. SubscribeToEvent(E_UPDATE, ATOMIC_HANDLER(JSVM, HandleUpdate));
  111. }
  112. void JSVM::OnRefCountChanged(RefCounted* refCounted, int refCount)
  113. {
  114. assert(instance_);
  115. assert(refCounted->JSGetHeapPtr());
  116. if (refCount == 1)
  117. {
  118. // only script reference is left, so unstash
  119. instance_->Unstash(refCounted);
  120. }
  121. else if (refCount == 2)
  122. {
  123. // We are going from solely having a script reference to having another reference
  124. instance_->Stash(refCounted);
  125. }
  126. }
  127. void JSVM::Stash(RefCounted* refCounted)
  128. {
  129. assert(refCounted);
  130. assert(refCounted->JSGetHeapPtr());
  131. totalStashCount_++;
  132. stashCount_++;
  133. duk_push_global_stash(ctx_);
  134. duk_get_prop_index(ctx_, -1, JS_GLOBALSTASH_INDEX_REFCOUNTED_REGISTRY);
  135. // can't use instance as key, as this coerces to [Object] for
  136. // string property, pointer will be string representation of
  137. // address, so, unique key
  138. /*
  139. duk_push_pointer(ctx_, refCounted);
  140. duk_get_prop(ctx_, -2);
  141. assert(duk_is_undefined(ctx_, -1));
  142. duk_pop(ctx_);
  143. */
  144. duk_push_pointer(ctx_, refCounted);
  145. duk_push_heapptr(ctx_, refCounted->JSGetHeapPtr());
  146. duk_put_prop(ctx_, -3);
  147. duk_pop_2(ctx_);
  148. }
  149. void JSVM::Unstash(RefCounted* refCounted)
  150. {
  151. assert(refCounted);
  152. assert(refCounted->JSGetHeapPtr());
  153. assert(stashCount_ > 0);
  154. stashCount_--;
  155. totalUnstashCount_++;
  156. duk_push_global_stash(ctx_);
  157. duk_get_prop_index(ctx_, -1, JS_GLOBALSTASH_INDEX_REFCOUNTED_REGISTRY);
  158. // can't use instance as key, as this coerces to [Object] for
  159. // string property, pointer will be string representation of
  160. // address, so, unique key
  161. /*
  162. duk_push_pointer(ctx_, refCounted);
  163. duk_get_prop(ctx_, -2);
  164. assert(!duk_is_undefined(ctx_, -1));
  165. duk_pop(ctx_);
  166. */
  167. duk_push_pointer(ctx_, refCounted);
  168. duk_del_prop(ctx_, -2);
  169. duk_pop_2(ctx_);
  170. }
  171. // Returns if the given object is stashed
  172. bool JSVM::GetStashed(RefCounted* refcounted) const
  173. {
  174. duk_push_global_stash(ctx_);
  175. duk_get_prop_index(ctx_, -1, JS_GLOBALSTASH_INDEX_REFCOUNTED_REGISTRY);
  176. duk_push_pointer(ctx_, refcounted);
  177. duk_get_prop(ctx_, -2);
  178. bool result = !duk_is_undefined(ctx_, -1);
  179. duk_pop_3(ctx_);
  180. return result;
  181. }
  182. void JSVM::HandleUpdate(StringHash eventType, VariantMap& eventData)
  183. {
  184. ATOMIC_PROFILE(JSVM_HandleUpdate);
  185. using namespace Update;
  186. // Take the frame time step, which is stored as a float
  187. float timeStep = eventData[P_TIMESTEP].GetFloat();
  188. gcTime_ += timeStep;
  189. if (gcTime_ > 5.0f)
  190. {
  191. ATOMIC_PROFILE(JSVM_GC);
  192. // run twice to call finalizers
  193. // see duktape docs
  194. // also ensure #define DUK_OPT_NO_VOLUNTARY_GC
  195. // is enabled in duktape.h
  196. duk_gc(ctx_, 0);
  197. duk_gc(ctx_, 0);
  198. gcTime_ = 0;
  199. // DumpJavascriptObjects();
  200. }
  201. duk_get_global_string(ctx_, "__js_atomic_main_update");
  202. if (duk_is_function(ctx_, -1))
  203. {
  204. duk_push_number(ctx_, timeStep);
  205. duk_pcall(ctx_, 1);
  206. duk_pop(ctx_);
  207. }
  208. else
  209. {
  210. duk_pop(ctx_);
  211. }
  212. }
  213. bool JSVM::ExecuteFunction(const String& functionName)
  214. {
  215. duk_get_global_string(ctx_, functionName.CString());
  216. if (duk_is_function(ctx_, -1))
  217. {
  218. bool ok = true;
  219. if (duk_pcall(ctx_, 0) != 0)
  220. {
  221. ok = false;
  222. if (duk_is_object(ctx_, -1))
  223. {
  224. SendJSErrorEvent();
  225. }
  226. else
  227. {
  228. assert(0);
  229. }
  230. }
  231. duk_pop(ctx_);
  232. return ok;
  233. }
  234. else
  235. {
  236. duk_pop(ctx_);
  237. }
  238. return false;
  239. }
  240. void JSVM::SendJSErrorEvent(const String& filename)
  241. {
  242. duk_context* ctx = GetJSContext();
  243. using namespace JSError;
  244. VariantMap eventData;
  245. if (duk_is_string(ctx, -1))
  246. {
  247. eventData[P_ERRORNAME] = "(Unknown Error Name)";
  248. eventData[P_ERRORFILENAME] = "(Unknown Filename)";
  249. eventData[P_ERRORLINENUMBER] = -1;
  250. eventData[P_ERRORMESSAGE] = duk_to_string(ctx, -1);
  251. eventData[P_ERRORSTACK] = "";
  252. SendEvent(E_JSERROR, eventData);
  253. return;
  254. }
  255. assert(duk_is_object(ctx, -1));
  256. duk_get_prop_string(ctx, -1, "fileName");
  257. if (duk_is_string(ctx, -1))
  258. {
  259. eventData[P_ERRORFILENAME] = duk_to_string(ctx, -1);
  260. }
  261. else
  262. {
  263. eventData[P_ERRORFILENAME] = filename;
  264. }
  265. // Component script are wrapped within a closure, the line number
  266. // needs to be offset by this header
  267. duk_get_prop_string(ctx, -2, "lineNumber");
  268. int lineNumber = (int) (duk_to_number(ctx, -1));
  269. eventData[P_ERRORLINENUMBER] = lineNumber;
  270. duk_get_prop_string(ctx, -3, "name");
  271. String name = duk_to_string(ctx, -1);
  272. eventData[P_ERRORNAME] = name;
  273. duk_get_prop_string(ctx, -4, "message");
  274. String message = duk_to_string(ctx, -1);
  275. eventData[P_ERRORMESSAGE] = message;
  276. // we're not getting good file/line from duktape on parser errors
  277. if (name == "SyntaxError")
  278. {
  279. lineNumber = -1;
  280. // parse line if we have it
  281. if (message.Contains("(line "))
  282. {
  283. if (!filename.Length())
  284. eventData[P_ERRORFILENAME] = lastModuleSearchFilename_;
  285. unsigned pos = message.Find("(line ");
  286. const char* parse = message.CString() + pos + 6;
  287. String number;
  288. while (*parse >= '0' && *parse<='9')
  289. {
  290. number += *parse;
  291. parse++;
  292. }
  293. lineNumber = ToInt(number);
  294. }
  295. eventData[P_ERRORLINENUMBER] = lineNumber;
  296. }
  297. duk_get_prop_string(ctx, -5, "stack");
  298. String stack = duk_to_string(ctx, -1);
  299. eventData[P_ERRORSTACK] = stack;
  300. duk_pop_n(ctx, 5);
  301. ATOMIC_LOGERRORF("JSErrorEvent: %s : Line %i\n Name: %s\n Message: %s\n Stack:%s",
  302. filename.CString(), lineNumber, name.CString(), message.CString(), stack.CString());
  303. SendEvent(E_JSERROR, eventData);
  304. }
  305. int JSVM::GetRealLineNumber(const String& fileName, const int lineNumber) {
  306. int realLineNumber = lineNumber;
  307. String mapPath = fileName;
  308. if (!mapPath.EndsWith(".js.map"))
  309. mapPath += ".js.map";
  310. if (mapPath.EndsWith(".js")) {
  311. return realLineNumber;
  312. }
  313. ResourceCache* cache = GetSubsystem<ResourceCache>();
  314. String path;
  315. const Vector<String>& searchPaths = GetModuleSearchPaths();
  316. for (unsigned i = 0; i < searchPaths.Size(); i++)
  317. {
  318. String checkPath = searchPaths[i] + mapPath;
  319. if (cache->Exists(checkPath))
  320. {
  321. path = checkPath;
  322. break;
  323. }
  324. }
  325. if (!path.Length())
  326. return realLineNumber;
  327. SharedPtr<File> mapFile(GetSubsystem<ResourceCache>()->GetFile(path));
  328. //if there's no source map file, maybe you use a pure js, so give an error, or maybe forgot to generate source-maps :(
  329. if (mapFile.Null())
  330. {
  331. return realLineNumber;
  332. }
  333. String map;
  334. mapFile->ReadText(map);
  335. int top = duk_get_top(ctx_);
  336. duk_get_global_string(ctx_, "require");
  337. duk_push_string(ctx_, "AtomicEditor/JavaScript/Lib/jsutils");
  338. if (duk_pcall(ctx_, 1))
  339. {
  340. printf("Error: %s\n", duk_safe_to_string(ctx_, -1));
  341. duk_set_top(ctx_, top);
  342. return false;
  343. }
  344. duk_get_prop_string(ctx_, -1, "getRealLineNumber");
  345. duk_push_string(ctx_, map.CString());
  346. duk_push_int(ctx_, lineNumber);
  347. bool ok = true;
  348. if (duk_pcall(ctx_, 2))
  349. {
  350. ok = false;
  351. printf("Error: %s\n", duk_safe_to_string(ctx_, -1));
  352. }
  353. else
  354. {
  355. realLineNumber = duk_to_int(ctx_, -1);
  356. }
  357. duk_set_top(ctx_, top);
  358. return realLineNumber;
  359. }
  360. bool JSVM::ExecuteScript(const String& scriptPath)
  361. {
  362. String path = scriptPath;
  363. if (!path.StartsWith("Scripts/"))
  364. path = "Scripts/" + path;
  365. if (!path.EndsWith(".js"))
  366. path += ".js";
  367. SharedPtr<File> file (GetSubsystem<ResourceCache>()->GetFile(path));
  368. if (file.Null())
  369. {
  370. return false;
  371. }
  372. String source;
  373. file->ReadText(source);
  374. source.Append('\n');
  375. duk_push_string(ctx_, file->GetFullPath().CString());
  376. if (duk_eval_raw(ctx_, source.CString(), 0,
  377. DUK_COMPILE_EVAL | DUK_COMPILE_SAFE | DUK_COMPILE_NOSOURCE | DUK_COMPILE_STRLEN) != 0)
  378. {
  379. if (duk_is_object(ctx_, -1))
  380. SendJSErrorEvent(path);
  381. duk_pop(ctx_);
  382. return false;
  383. }
  384. duk_pop(ctx_);
  385. return true;
  386. }
  387. bool JSVM::ExecuteFile(File *file)
  388. {
  389. if (!file)
  390. return false;
  391. String source;
  392. file->ReadText(source);
  393. source.Append('\n');
  394. duk_push_string(ctx_, file->GetFullPath().CString());
  395. if (duk_eval_raw(ctx_, source.CString(), 0,
  396. DUK_COMPILE_EVAL | DUK_COMPILE_SAFE | DUK_COMPILE_NOSOURCE | DUK_COMPILE_STRLEN) != 0)
  397. {
  398. SendJSErrorEvent(file->GetFullPath());
  399. duk_pop(ctx_);
  400. return false;
  401. }
  402. duk_pop(ctx_);
  403. return true;
  404. }
  405. void JSVM::GC()
  406. {
  407. // run twice to ensure finalizers are run
  408. duk_gc(ctx_, 0);
  409. duk_gc(ctx_, 0);
  410. }
  411. bool JSVM::ExecuteMain()
  412. {
  413. if (!GetSubsystem<ResourceCache>()->Exists("Scripts/main.js"))
  414. return true;
  415. duk_get_global_string(ctx_, "require");
  416. duk_push_string(ctx_, "Scripts/main");
  417. if (duk_pcall(ctx_, 1) != 0)
  418. {
  419. SendJSErrorEvent();
  420. return false;
  421. }
  422. if (duk_is_object(ctx_, -1))
  423. {
  424. duk_get_prop_string(ctx_, -1, "update");
  425. if (duk_is_function(ctx_, -1))
  426. {
  427. // store function for main loop
  428. duk_put_global_string(ctx_, "__js_atomic_main_update");
  429. }
  430. else
  431. {
  432. duk_pop(ctx_);
  433. }
  434. }
  435. // pop main module
  436. duk_pop(ctx_);
  437. return true;
  438. }
  439. void JSVM::DumpJavascriptObjects()
  440. {
  441. ATOMIC_LOGINFOF("--- JS Objects ---");
  442. ATOMIC_LOGINFOF("Stash Count: %u, Total Stash: %u, Total Unstash: %u", stashCount_, totalStashCount_, totalUnstashCount_);
  443. HashMap<StringHash, String> strLookup;
  444. HashMap<StringHash, unsigned> totalClassCount;
  445. HashMap<StringHash, unsigned> stashedClassCount;
  446. HashMap<StringHash, int> maxRefCount;
  447. StringHash refCountedTypeHash("RefCounted");
  448. strLookup[refCountedTypeHash] = "RefCounted";
  449. duk_push_global_stash(ctx_);
  450. duk_get_prop_index(ctx_, -1, JS_GLOBALSTASH_INDEX_REFCOUNTED_REGISTRY);
  451. HashMap<void*, RefCounted*>::ConstIterator itr = heapToObject_.Begin();
  452. while (itr != heapToObject_.End())
  453. {
  454. void* heapPtr = itr->first_;
  455. RefCounted* refCounted = itr->second_;
  456. // TODO: need a lookup for refcounted classid to typename
  457. const String& className = refCounted->IsObject() ? ((Object*)refCounted)->GetTypeName() : "RefCounted";
  458. StringHash typeHash = refCounted->IsObject() ? ((Object*)refCounted)->GetType() : refCountedTypeHash;
  459. strLookup.InsertNew(typeHash, className);
  460. totalClassCount.InsertNew(typeHash, 0);
  461. totalClassCount[typeHash]++;
  462. maxRefCount.InsertNew(typeHash, 0);
  463. if (refCounted->Refs() > maxRefCount[typeHash])
  464. maxRefCount[typeHash] = refCounted->Refs();
  465. duk_push_pointer(ctx_, refCounted);
  466. duk_get_prop(ctx_, -2);
  467. if (!duk_is_undefined(ctx_, -1))
  468. {
  469. stashedClassCount.InsertNew(typeHash, 0);
  470. stashedClassCount[typeHash]++;
  471. }
  472. duk_pop(ctx_);
  473. itr++;
  474. }
  475. HashMap<StringHash, String>::ConstIterator itr2 = strLookup.Begin();
  476. while (itr2 != strLookup.End())
  477. {
  478. StringHash typeHash = itr2->first_;
  479. const String& className = itr2->second_;
  480. unsigned totalCount = totalClassCount[typeHash];
  481. unsigned stashedCount = stashedClassCount[typeHash];
  482. int _maxRefCount = maxRefCount[typeHash];
  483. ATOMIC_LOGINFOF("Classname: %s, Total: %u, Stashed: %u, Max Refs: %i", className.CString(), totalCount, stashedCount, _maxRefCount);
  484. itr2++;
  485. }
  486. duk_pop_2(ctx_);
  487. }
  488. }