JSVM.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. #include "Precompiled.h"
  2. #include <Duktape/duktape.h>
  3. #include "../Core/Profiler.h"
  4. #include "../Core/CoreEvents.h"
  5. #include "../IO/File.h"
  6. #include "../IO/Log.h"
  7. #include "../IO/FileSystem.h"
  8. #include "../IO/PackageFile.h"
  9. #include "../Resource/ResourceCache.h"
  10. #include "../Javascript/JSEvents.h"
  11. #include "../Javascript/JSFile.h"
  12. #include "../Javascript/JSVM.h"
  13. #include "../Javascript/JSAtomic.h"
  14. namespace Atomic
  15. {
  16. JSVM* JSVM::instance_ = NULL;
  17. JSVM::JSVM(Context* context) :
  18. Object(context),
  19. jsContext_(0),
  20. gcTime_(0.0f)
  21. {
  22. assert(!instance_);
  23. instance_ = this;
  24. jsContext_ = duk_create_heap_default();
  25. // create root Atomic Object
  26. duk_push_global_object(jsContext_);
  27. duk_push_object(jsContext_);
  28. duk_put_prop_string(jsContext_, -2, "Atomic");
  29. duk_pop(jsContext_);
  30. duk_push_global_stash(jsContext_);
  31. duk_push_object(jsContext_);
  32. duk_put_prop_index(jsContext_, -2, JS_GLOBALSTASH_INDEX_COMPONENTS);
  33. duk_pop(jsContext_);
  34. duk_get_global_string(jsContext_, "Duktape");
  35. duk_push_c_function(jsContext_, js_module_search, 1);
  36. duk_put_prop_string(jsContext_, -2, "modSearch");
  37. duk_pop(jsContext_);
  38. jsapi_init_atomic(this);
  39. InitScripts();
  40. // handle this elsewhere?
  41. SubscribeToEvents();
  42. }
  43. JSVM::~JSVM()
  44. {
  45. duk_destroy_heap(jsContext_);
  46. instance_ = NULL;
  47. }
  48. void JSVM::SubscribeToEvents()
  49. {
  50. SubscribeToEvent(E_UPDATE, HANDLER(JSVM, HandleUpdate));
  51. }
  52. void JSVM::HandleUpdate(StringHash eventType, VariantMap& eventData)
  53. {
  54. PROFILE(JSVM_HandleUpdate);
  55. using namespace Update;
  56. // Take the frame time step, which is stored as a float
  57. float timeStep = eventData[P_TIMESTEP].GetFloat();
  58. gcTime_ += timeStep;
  59. if (gcTime_ > 5.0f)
  60. {
  61. PROFILE(JSVM_GC);
  62. // run twice to call finalizers
  63. // see duktape docs
  64. // also ensure #define DUK_OPT_NO_VOLUNTARY_GC
  65. // is enabled in duktape.h
  66. duk_gc(jsContext_, 0);
  67. duk_gc(jsContext_, 0);
  68. gcTime_ = 0;
  69. }
  70. duk_get_global_string(jsContext_, "__js_atomicgame_update");
  71. if (duk_is_function(jsContext_, -1))
  72. {
  73. duk_push_number(jsContext_, timeStep);
  74. duk_pcall(jsContext_, 1);
  75. duk_pop(jsContext_);
  76. }
  77. else
  78. {
  79. duk_pop(jsContext_);
  80. }
  81. }
  82. bool JSVM::ExecuteFunction(const String& functionName)
  83. {
  84. duk_get_global_string(jsContext_, functionName.CString());
  85. if (duk_is_function(jsContext_, -1))
  86. {
  87. bool ok = true;
  88. if (duk_pcall(jsContext_, 0) != 0)
  89. {
  90. ok = false;
  91. if (duk_is_object(jsContext_, -1))
  92. {
  93. SendJSErrorEvent();
  94. }
  95. else
  96. {
  97. assert(0);
  98. }
  99. }
  100. duk_pop(jsContext_);
  101. return ok;
  102. }
  103. else
  104. {
  105. duk_pop(jsContext_);
  106. }
  107. return false;
  108. }
  109. void JSVM::GenerateComponent(const String &cname, const String &jsfilename, const String& csource)
  110. {
  111. String source = "(function() {\n function __component_function(self) {\n";
  112. source += csource;
  113. source += "self.node.components = self.node.components || {};\n";
  114. source.AppendWithFormat("self.node.components[\"%s\"] = self.node.components[\"%s\"] || [];\n",
  115. cname.CString(), cname.CString());
  116. source += "if (typeof start != \"undefined\") self.start = start; " \
  117. "if (typeof update != \"undefined\") self.update = update; "\
  118. "if (typeof fixedUpdate != \"undefined\") self.fixedUpdate = fixedUpdate; " \
  119. "if (typeof postUpdate != \"undefined\") self.postUpdate = postUpdate;\n";
  120. String scriptName = cname;
  121. scriptName[0] = tolower(scriptName[0]);
  122. source.AppendWithFormat("self.node.%s = self.node.%s || self;\n",
  123. scriptName.CString(), scriptName.CString());
  124. source.AppendWithFormat("self.node.components[\"%s\"].push(self);\n",
  125. cname.CString());
  126. source += "}\n return __component_function;\n});";
  127. duk_push_string(jsContext_, jsfilename.CString());
  128. if (duk_eval_raw(jsContext_, source.CString(), source.Length(),
  129. DUK_COMPILE_EVAL | DUK_COMPILE_NOSOURCE | DUK_COMPILE_SAFE) != 0)
  130. {
  131. if (duk_is_object(jsContext_, -1))
  132. {
  133. SendJSErrorEvent();
  134. duk_pop(jsContext_);
  135. }
  136. else
  137. {
  138. assert(0);
  139. }
  140. }
  141. else if (duk_pcall(jsContext_, 0) != 0)
  142. {
  143. if (duk_is_object(jsContext_, -1))
  144. {
  145. SendJSErrorEvent();
  146. duk_pop(jsContext_);
  147. }
  148. else
  149. {
  150. assert(0);
  151. }
  152. }
  153. else
  154. {
  155. if (!duk_is_function(jsContext_, -1))
  156. {
  157. const char* error = duk_to_string(jsContext_, -1);
  158. assert(false);
  159. }
  160. duk_put_prop_string(jsContext_, -2, cname.CString());
  161. }
  162. }
  163. void JSVM::InitPackageScripts()
  164. {
  165. ResourceCache* cache = GetSubsystem<ResourceCache>();
  166. duk_push_global_stash(jsContext_);
  167. duk_get_prop_index(jsContext_, -1, JS_GLOBALSTASH_INDEX_COMPONENTS);
  168. const Vector<SharedPtr<PackageFile> >& packageFiles = cache->GetPackageFiles();
  169. for (unsigned i = 0; i < packageFiles.Size(); i++)
  170. {
  171. SharedPtr<PackageFile> package = packageFiles[i];
  172. const Vector<String>& files = package->GetCaseEntryNames();
  173. for (unsigned j = 0; j < files.Size(); j++)
  174. {
  175. String name = files[j];
  176. if (!name.StartsWith("Components/"))
  177. continue;
  178. String cname = GetFileName(name);
  179. String jsname = name;
  180. JSFile* jsfile = cache->GetResource<JSFile>(name);
  181. String csource = jsfile->GetSource();
  182. GenerateComponent(cname, jsname, csource);
  183. }
  184. }
  185. // pop stash and component object
  186. duk_pop_2(jsContext_);
  187. }
  188. void JSVM::InitScripts()
  189. {
  190. ResourceCache* cache = GetSubsystem<ResourceCache>();
  191. if (cache->GetPackageFiles().Size())
  192. {
  193. InitPackageScripts();
  194. return;
  195. }
  196. FileSystem* fileSystem = GetSubsystem<FileSystem>();
  197. const Vector<String>& dirs = cache->GetResourceDirs();
  198. duk_push_global_stash(jsContext_);
  199. duk_get_prop_index(jsContext_, -1, JS_GLOBALSTASH_INDEX_COMPONENTS);
  200. for (unsigned i = 0; i < dirs.Size(); i++)
  201. {
  202. Vector<String> files;
  203. fileSystem->ScanDir(files ,dirs[i]+"/Components", "*.js", SCAN_FILES, true );
  204. for (unsigned j = 0; j < files.Size(); j++)
  205. {
  206. String cname = GetFileName(files[j]);
  207. String jsname = dirs[i]+"Components/" + files[j];
  208. JSFile* jsfile = cache->GetResource<JSFile>("Components/" + files[j]);
  209. String csource = jsfile->GetSource();
  210. GenerateComponent(cname, jsname, csource);
  211. }
  212. }
  213. // pop stash and component object
  214. duk_pop_2(jsContext_);
  215. }
  216. bool JSVM::ExecuteFile(JSFile* jsfile)
  217. {
  218. PROFILE(ExecuteFile);
  219. bool ok = true;
  220. if (duk_peval_string(jsContext_, jsfile->GetSource()) != 0)
  221. {
  222. ok = false;
  223. printf("Error: %s\n", duk_safe_to_string(jsContext_, -1));
  224. }
  225. // ignore result
  226. duk_pop(jsContext_);
  227. return ok;
  228. }
  229. int JSVM::js_module_search(duk_context* ctx)
  230. {
  231. JSVM* vm = GetJSVM(ctx);
  232. ResourceCache* cache = vm->GetContext()->GetSubsystem<ResourceCache>();
  233. String path = duk_to_string(ctx, 0);
  234. // first check if this is an AtomicModule
  235. // attempting to avoid module search paths
  236. // so have AtomicEditor, Atomic, and the projects "Modules"
  237. String pathName, fileName, extension;
  238. SplitPath(path, pathName, fileName, extension);
  239. if (fileName.StartsWith("AtomicEditor"))
  240. {
  241. path = "AtomicEditor/Modules/" + path + ".js";
  242. }
  243. else if (fileName.StartsWith("Atomic"))
  244. {
  245. path = "AtomicModules/" + path + ".js";
  246. }
  247. else
  248. {
  249. path = vm->moduleSearchPath_ + "/" + path + ".js";
  250. }
  251. JSFile* jsfile = cache->GetResource<JSFile>(path);
  252. if (!jsfile)
  253. {
  254. duk_push_null(ctx);
  255. }
  256. else
  257. {
  258. duk_push_string(ctx, jsfile->GetSource());
  259. }
  260. return 1;
  261. }
  262. /*
  263. name standard Name of error, e.g. TypeError, inherited
  264. message standard Optional message of error, own property, empty message inherited if absent
  265. fileName Rhino Filename related to error source, inherited accessor
  266. lineNumber Rhino Linenumber related to error source, inherited accessor
  267. stack V8 Traceback as a multi-line human redable string, inherited accessor
  268. EVENT(E_JSERROR, JSError)
  269. {
  270. PARAM(P_ERRORNAME, ErrorName); // string
  271. PARAM(P_ERRORMESSAGE, ErrorMessage); // string
  272. PARAM(P_ERRORFILENAME, ErrorFileName); // string
  273. PARAM(P_ERRORLINENUMBER, ErrorLineNumber); // int
  274. PARAM(P_ERRORSTACK, ErrorStack); // string
  275. }
  276. */
  277. void JSVM::SendJSErrorEvent()
  278. {
  279. duk_context* ctx = GetJSContext();
  280. assert(duk_is_object(ctx, -1));
  281. using namespace JSError;
  282. VariantMap eventData;
  283. duk_get_prop_string(ctx, -1, "fileName");
  284. eventData[P_ERRORFILENAME] = duk_to_string(ctx, -1);
  285. // Component script are wrapped within a closure, the line number
  286. // needs to be offset by this header
  287. duk_get_prop_string(ctx, -2, "lineNumber");
  288. int lineNumber = (int) (duk_to_number(ctx, -1));
  289. eventData[P_ERRORLINENUMBER] = lineNumber;
  290. duk_get_prop_string(ctx, -3, "name");
  291. eventData[P_ERRORNAME] = duk_to_string(ctx, -1);
  292. duk_get_prop_string(ctx, -4, "message");
  293. eventData[P_ERRORMESSAGE] = duk_to_string(ctx, -1);
  294. duk_get_prop_string(ctx, -5, "stack");
  295. eventData[P_ERRORSTACK] = duk_to_string(ctx, -1);
  296. duk_pop_n(ctx, 5);
  297. SendEvent(E_JSERROR, eventData);
  298. }
  299. bool JSVM::ExecuteMain(const String &mainPath)
  300. {
  301. SharedPtr<JSFile> jsFile;
  302. jsFile = GetSubsystem<ResourceCache>()->GetResource<JSFile>("Scripts/main.js");
  303. if (jsFile.Null())
  304. return false;
  305. int top = duk_get_top(jsContext_);
  306. // wrap in a closure
  307. String source = "(function() {\n function __main_function(self) {\n";
  308. source += "require(\"AtomicGame\");\n";
  309. source += jsFile->GetSource();
  310. source += "\nAtomic.game.init(Start, Update);\n";
  311. source += "}\n return __main_function;\n});";
  312. duk_push_string(jsContext_, mainPath.CString());
  313. if (duk_eval_raw(jsContext_, source.CString(), source.Length(),
  314. DUK_COMPILE_EVAL | DUK_COMPILE_NOSOURCE | DUK_COMPILE_SAFE) != 0)
  315. {
  316. if (duk_is_object(jsContext_, -1))
  317. {
  318. SendJSErrorEvent();
  319. duk_pop(jsContext_);
  320. return false;
  321. }
  322. else
  323. {
  324. assert(0);
  325. }
  326. }
  327. else if (duk_pcall(jsContext_, 0) != 0)
  328. {
  329. if (duk_is_object(jsContext_, -1))
  330. {
  331. SendJSErrorEvent();
  332. duk_pop(jsContext_);
  333. return false;
  334. }
  335. else
  336. {
  337. assert(0);
  338. }
  339. }
  340. if (!duk_is_function(jsContext_, -1))
  341. {
  342. duk_pop(jsContext_);
  343. return false;
  344. }
  345. if (duk_pcall(jsContext_, 0) != 0)
  346. {
  347. if (duk_is_object(jsContext_, -1))
  348. {
  349. SendJSErrorEvent();
  350. duk_pop(jsContext_);
  351. return false;
  352. }
  353. else
  354. {
  355. assert(0);
  356. }
  357. }
  358. duk_pop(jsContext_);
  359. ExecuteFunction("__js_atomicgame_start");
  360. assert(top == duk_get_top(jsContext_));
  361. return true;
  362. }
  363. /*
  364. void JSVM::DumpJavascriptObjects()
  365. {
  366. HashMap<String, unsigned> counts;
  367. List<Object*>::Iterator itr = jsObjects_.Begin();
  368. while (itr != jsObjects_.End())
  369. {
  370. const String& tname = (*itr)->GetTypeName();
  371. if (!counts.Contains(tname))
  372. {
  373. counts[tname] = 0;
  374. }
  375. else
  376. {
  377. counts[tname]++;
  378. }
  379. itr++;
  380. }
  381. HashMap<String, unsigned>::Iterator oitr = counts.Begin();
  382. while (oitr != counts.End())
  383. {
  384. // currently outputting in destructor so log might already be gone
  385. //LOGINFOF("%s: %u", oitr->first_.CString(), oitr->second_);
  386. printf("%s: %u\n", oitr->first_.CString(), oitr->second_);
  387. oitr++;
  388. }
  389. }
  390. */
  391. }