JSComponent.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. // Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
  2. // Please see LICENSE.md in repository root for license information
  3. // https://github.com/AtomicGameEngine/AtomicGameEngine
  4. #include <Atomic/Core/Context.h>
  5. #include <Atomic/IO/Log.h>
  6. #ifdef ATOMIC_PHYSICS
  7. #include <Atomic/Physics/PhysicsEvents.h>
  8. #include <Atomic/Physics/PhysicsWorld.h>
  9. #endif
  10. #include <Atomic/Core/Profiler.h>
  11. #include <Atomic/IO/MemoryBuffer.h>
  12. #include <Atomic/Resource/ResourceCache.h>
  13. #include <Atomic/Resource/ResourceEvents.h>
  14. #include <Atomic/Scene/Scene.h>
  15. #include <Atomic/Scene/SceneEvents.h>
  16. #include <Atomic/Atomic2D/PhysicsEvents2D.h>
  17. #include <Atomic/Atomic2D/PhysicsWorld2D.h>
  18. #include <Atomic/Atomic2D/RigidBody2D.h>
  19. #include <Atomic/UI/UIEvents.h>
  20. #include <Atomic/UI/UIElement.h>
  21. #include "Javascript.h"
  22. #include "JSEvents.h"
  23. #include "JSComponent.h"
  24. #include "JSAPI.h"
  25. namespace Atomic
  26. {
  27. static const char* methodDeclarations[] = {
  28. "start",
  29. "stop",
  30. "delayedStart",
  31. "update",
  32. "postUpdate",
  33. "fixedUpdate",
  34. "fixedPostUpdate",
  35. "load",
  36. "save",
  37. "readNetworkUpdate",
  38. "writeNetworkUpdate",
  39. "applyAttributes",
  40. "transformChanged"
  41. };
  42. extern const char* LOGIC_CATEGORY;
  43. JSComponent::JSComponent(Context* context) :
  44. Component(context),
  45. script_(GetSubsystem<Javascript>()),
  46. scriptObject_(0),
  47. subscribed_(false),
  48. subscribedPostFixed_(false),
  49. started_(false),
  50. destroyed_(false)
  51. {
  52. vm_ = JSVM::GetJSVM(NULL);
  53. ClearScriptMethods();
  54. }
  55. JSComponent::~JSComponent()
  56. {
  57. }
  58. void JSComponent::OnNodeSet(Node *node)
  59. {
  60. Component::OnNodeSet(node);
  61. if (node)
  62. {
  63. assert(node->JSGetHeapPtr());
  64. duk_context* ctx = vm_->GetJSContext();
  65. duk_push_global_stash(ctx);
  66. duk_get_prop_index(ctx, -1, JS_GLOBALSTASH_INDEX_NODE_REGISTRY);
  67. // can't use instance as key, as this coerces to [Object] for
  68. // string property, pointer will be string representation of
  69. // address, so, unique key
  70. duk_push_pointer(ctx, node->JSGetHeapPtr());
  71. js_push_class_object_instance(ctx, node);
  72. duk_put_prop(ctx, -3);
  73. duk_pop_2(ctx);
  74. }
  75. }
  76. void JSComponent::RegisterObject(Context* context)
  77. {
  78. context->RegisterFactory<JSComponent>(LOGIC_CATEGORY);
  79. //ACCESSOR_ATTRIBUTE(JSComponent, VAR_BOOL, "Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
  80. //REF_ACCESSOR_ATTRIBUTE(JSComponent, VAR_STRING, "Class Name", GetClassName, SetClassName, String, String::EMPTY, AM_DEFAULT);
  81. //ACCESSOR_ATTRIBUTE(JSComponent, VAR_RESOURCEREF, "Script File", GetScriptFileAttr, SetScriptFileAttr, ResourceRef, ResourceRef(JSFile::GetTypeStatic()), AM_DEFAULT);
  82. //ACCESSOR_ATTRIBUTE(JSComponent, VAR_BUFFER, "Delayed Method Calls", GetDelayedCallsAttr, SetDelayedCallsAttr, PODVector<unsigned char>, Variant::emptyBuffer, AM_FILE | AM_NOEDIT);
  83. //ACCESSOR_ATTRIBUTE(JSComponent, VAR_BUFFER, "Script Data", GetScriptDataAttr, SetScriptDataAttr, PODVector<unsigned char>, Variant::emptyBuffer, AM_FILE | AM_NOEDIT);
  84. //ACCESSOR_ATTRIBUTE(JSComponent, VAR_BUFFER, "Script Network Data", GetScriptNetworkDataAttr, SetScriptNetworkDataAttr, PODVector<unsigned char>, Variant::emptyBuffer, AM_NET | AM_NOEDIT);
  85. }
  86. void JSComponent::ClearScriptMethods()
  87. {
  88. for (unsigned i = 0; i < MAX_JSSCRIPT_METHODS; ++i)
  89. methods_[i] = 0;
  90. //delayedCalls_.Clear();
  91. }
  92. void JSComponent::OnSetEnabled()
  93. {
  94. UpdateEventSubscription();
  95. }
  96. void JSComponent::ListenToEvent(Object* sender, StringHash eventType, JS_HEAP_PTR __duk_function)
  97. {
  98. duk_context* ctx = vm_->GetJSContext();
  99. duk_push_heapptr(ctx, __duk_function);
  100. assert(duk_is_function(ctx, -1));
  101. duk_pop(ctx);
  102. scriptEventFunctions_[eventType] = __duk_function;
  103. if (sender)
  104. SubscribeToEvent(sender, eventType, HANDLER(JSComponent, HandleScriptEvent));
  105. else
  106. SubscribeToEvent(eventType, HANDLER(JSComponent, HandleScriptEvent));
  107. }
  108. bool JSComponent::CreateObject(JSFile* scriptFile, const String& className)
  109. {
  110. className_ = String::EMPTY; // Do not create object during SetScriptFile()
  111. SetScriptFile(scriptFile);
  112. SetClassName(className);
  113. return scriptObject_ != 0;
  114. }
  115. void JSComponent::SetClassName(const String& className)
  116. {
  117. if (className == className_ && scriptObject_)
  118. return;
  119. ReleaseObject();
  120. className_ = className;
  121. CreateObject();
  122. MarkNetworkUpdate();
  123. }
  124. void JSComponent::ReleaseObject()
  125. {
  126. if (scriptObject_)
  127. {
  128. //if (methods_[JSMETHOD_STOP])
  129. // scriptFile_->Execute(scriptObject_, methods_[JSMETHOD_STOP]);
  130. PODVector<StringHash> exceptions;
  131. exceptions.Push(E_RELOADSTARTED);
  132. exceptions.Push(E_RELOADFINISHED);
  133. UnsubscribeFromAllEventsExcept(exceptions, false);
  134. if (node_)
  135. node_->RemoveListener(this);
  136. subscribed_ = false;
  137. subscribedPostFixed_ = false;
  138. ClearScriptMethods();
  139. scriptObject_ = 0;
  140. }
  141. }
  142. void JSComponent::SetScriptFile(JSFile* scriptFile)
  143. {
  144. ReleaseObject();
  145. CreateObject();
  146. MarkNetworkUpdate();
  147. }
  148. void JSComponent::CreateObject()
  149. {
  150. if (className_.Empty())
  151. return;
  152. PROFILE(CreateScriptObject);
  153. duk_context* ctx = vm_->GetJSContext();
  154. duk_push_global_stash(ctx);
  155. duk_get_prop_index(ctx, -1, JS_GLOBALSTASH_INDEX_COMPONENTS);
  156. duk_get_prop_string(ctx, -1, className_.CString());
  157. assert(duk_is_function(ctx, -1));
  158. js_push_class_object_instance(ctx, this);
  159. if (duk_pcall(ctx, 1) != 0)
  160. {
  161. vm_->SendJSErrorEvent();
  162. }
  163. else
  164. {
  165. scriptObject_ = this->JSGetHeapPtr();
  166. }
  167. if (scriptObject_)
  168. {
  169. GetScriptMethods();
  170. UpdateEventSubscription();
  171. }
  172. duk_pop_n(ctx, 2);
  173. }
  174. void JSComponent::HandleSceneUpdate(StringHash eventType, VariantMap& eventData)
  175. {
  176. if (!scriptObject_)
  177. return;
  178. assert(!destroyed_);
  179. assert(JSGetHeapPtr());
  180. using namespace SceneUpdate;
  181. float timeStep = eventData[P_TIMESTEP].GetFloat();
  182. duk_context* ctx = vm_->GetJSContext();
  183. if (!started_)
  184. {
  185. started_ = true;
  186. if (methods_[JSMETHOD_START])
  187. {
  188. duk_push_heapptr(ctx, methods_[JSMETHOD_START]);
  189. if (duk_pcall(ctx, 0) != 0)
  190. {
  191. vm_->SendJSErrorEvent();
  192. }
  193. duk_pop(ctx);
  194. }
  195. }
  196. if (methods_[JSMETHOD_UPDATE])
  197. {
  198. duk_push_heapptr(ctx, methods_[JSMETHOD_UPDATE]);
  199. duk_push_number(ctx, timeStep);
  200. if ( duk_pcall(ctx, 1) != DUK_EXEC_SUCCESS)
  201. {
  202. if (duk_is_object(ctx, -1))
  203. {
  204. vm_->SendJSErrorEvent();
  205. }
  206. else
  207. {
  208. assert(0);
  209. }
  210. }
  211. duk_pop(ctx);
  212. }
  213. else
  214. {
  215. Scene* scene = GetScene();
  216. if (scene)
  217. UnsubscribeFromEvent(scene, E_SCENEUPDATE);
  218. subscribed_ = false;
  219. }
  220. }
  221. void JSComponent::UpdateEventSubscription()
  222. {
  223. Scene* scene = GetScene();
  224. if (!scene)
  225. {
  226. LOGWARNING("Node is detached from scene, can not subscribe script object to update events");
  227. return;
  228. }
  229. bool enabled = scriptObject_ && IsEnabledEffective();
  230. if (enabled)
  231. {
  232. // we get at least one scene update if not started
  233. if (!subscribed_ && (!started_ || (methods_[JSMETHOD_UPDATE] || methods_[JSMETHOD_DELAYEDSTART] )))
  234. {
  235. SubscribeToEvent(scene, E_SCENEUPDATE, HANDLER(JSComponent, HandleSceneUpdate));
  236. subscribed_ = true;
  237. }
  238. if (!subscribedPostFixed_)
  239. {
  240. if (methods_[JSMETHOD_POSTUPDATE])
  241. SubscribeToEvent(scene, E_SCENEPOSTUPDATE, HANDLER(JSComponent, HandleScenePostUpdate));
  242. #ifdef ATOMIC_PHYSICS
  243. if (methods_[JSMETHOD_FIXEDUPDATE] || methods_[JSMETHOD_FIXEDPOSTUPDATE])
  244. {
  245. PhysicsWorld* world = scene->GetOrCreateComponent<PhysicsWorld>();
  246. if (world)
  247. {
  248. if (methods_[JSMETHOD_FIXEDUPDATE])
  249. SubscribeToEvent(world, E_PHYSICSPRESTEP, HANDLER(JSComponent, HandlePhysicsPreStep));
  250. if (methods_[JSMETHOD_FIXEDPOSTUPDATE])
  251. SubscribeToEvent(world, E_PHYSICSPOSTSTEP, HANDLER(JSComponent, HandlePhysicsPostStep));
  252. }
  253. else
  254. LOGERROR("No physics world, can not subscribe script object to fixed update events");
  255. }
  256. #endif
  257. subscribedPostFixed_ = true;
  258. }
  259. if (methods_[JSMETHOD_TRANSFORMCHANGED])
  260. node_->AddListener(this);
  261. }
  262. else
  263. {
  264. if (subscribed_)
  265. {
  266. UnsubscribeFromEvent(scene, E_SCENEUPDATE);
  267. subscribed_ = false;
  268. }
  269. if (subscribedPostFixed_)
  270. {
  271. UnsubscribeFromEvent(scene, E_SCENEPOSTUPDATE);
  272. #ifdef ATOMIC_PHYSICS
  273. PhysicsWorld* world = scene->GetComponent<PhysicsWorld>();
  274. if (world)
  275. {
  276. UnsubscribeFromEvent(world, E_PHYSICSPRESTEP);
  277. UnsubscribeFromEvent(world, E_PHYSICSPOSTSTEP);
  278. }
  279. #endif
  280. subscribedPostFixed_ = false;
  281. }
  282. if (methods_[JSMETHOD_TRANSFORMCHANGED])
  283. node_->RemoveListener(this);
  284. }
  285. }
  286. void JSComponent::HandleScenePostUpdate(StringHash eventType, VariantMap& eventData)
  287. {
  288. if (!scriptObject_)
  289. return;
  290. assert(!destroyed_);
  291. using namespace ScenePostUpdate;
  292. if (methods_[JSMETHOD_POSTUPDATE])
  293. {
  294. duk_context* ctx = vm_->GetJSContext();
  295. duk_push_heapptr(ctx, methods_[JSMETHOD_POSTUPDATE]);
  296. duk_push_number(ctx, eventData[P_TIMESTEP].GetFloat());
  297. duk_pcall(ctx, 1);
  298. duk_pop(ctx);
  299. }
  300. }
  301. #ifdef ATOMIC_PHYSICS
  302. void JSComponent::HandlePhysicsPreStep(StringHash eventType, VariantMap& eventData)
  303. {
  304. if (!scriptObject_)
  305. return;
  306. assert(!destroyed_);
  307. using namespace PhysicsPreStep;
  308. float timeStep = eventData[P_TIMESTEP].GetFloat();
  309. if (methods_[JSMETHOD_FIXEDUPDATE])
  310. {
  311. duk_context* ctx = vm_->GetJSContext();
  312. duk_push_heapptr(ctx, methods_[JSMETHOD_FIXEDUPDATE]);
  313. duk_push_number(ctx, timeStep);
  314. duk_pcall(ctx, 1);
  315. duk_pop(ctx);
  316. }
  317. }
  318. void JSComponent::HandlePhysicsPostStep(StringHash eventType, VariantMap& eventData)
  319. {
  320. if (!scriptObject_)
  321. return;
  322. assert(!destroyed_);
  323. using namespace PhysicsPostStep;
  324. VariantVector parameters;
  325. parameters.Push(eventData[P_TIMESTEP]);
  326. }
  327. #endif
  328. void JSComponent::HandleScriptEvent(StringHash eventType, VariantMap& eventData)
  329. {
  330. if (!IsEnabledEffective() || !scriptObject_)
  331. return;
  332. assert(!destroyed_);
  333. if (scriptEventFunctions_.Contains(eventType))
  334. {
  335. duk_context* ctx = vm_->GetJSContext();
  336. JS_HEAP_PTR function = scriptEventFunctions_[eventType];
  337. if (eventType == E_UIMOUSECLICK)
  338. {
  339. UIElement* clicked = static_cast<UIElement*>(eventData[UIMouseClick::P_ELEMENT].GetPtr());
  340. if (clicked)
  341. {
  342. duk_push_heapptr(ctx, function);
  343. js_push_class_object_instance(ctx, clicked);
  344. if (duk_pcall(ctx, 1) != 0)
  345. {
  346. vm_->SendJSErrorEvent();
  347. }
  348. duk_pop(ctx);
  349. }
  350. }
  351. else if (eventType == E_PHYSICSBEGINCONTACT2D || E_PHYSICSENDCONTACT2D)
  352. {
  353. using namespace PhysicsBeginContact2D;
  354. PhysicsWorld2D* world = static_cast<PhysicsWorld2D*>(eventData[P_WORLD].GetPtr());
  355. RigidBody2D* bodyA = static_cast<RigidBody2D*>(eventData[P_BODYA].GetPtr());
  356. RigidBody2D* bodyB = static_cast<RigidBody2D*>(eventData[P_BODYB].GetPtr());
  357. Node* nodeA = static_cast<Node*>(eventData[P_NODEA].GetPtr());
  358. Node* nodeB = static_cast<Node*>(eventData[P_NODEB].GetPtr());
  359. duk_push_heapptr(ctx, function);
  360. js_push_class_object_instance(ctx, world);
  361. js_push_class_object_instance(ctx, bodyA);
  362. js_push_class_object_instance(ctx, bodyB);
  363. js_push_class_object_instance(ctx, nodeA);
  364. js_push_class_object_instance(ctx, nodeB);
  365. if (duk_pcall(ctx, 5) != 0)
  366. {
  367. vm_->SendJSErrorEvent();
  368. }
  369. duk_pop(ctx);
  370. }
  371. else if (eventType == E_NODECOLLISION)
  372. {
  373. // Check collision contacts and see if character is standing on ground (look for a contact that has near vertical normal)
  374. using namespace NodeCollision;
  375. MemoryBuffer contacts(eventData[P_CONTACTS].GetBuffer());
  376. while (!contacts.IsEof())
  377. {
  378. Vector3 contactPosition = contacts.ReadVector3();
  379. Vector3 contactNormal = contacts.ReadVector3();
  380. float contactDistance = contacts.ReadFloat();
  381. float contactImpulse = contacts.ReadFloat();
  382. duk_push_heapptr(ctx, function);
  383. duk_push_array(ctx);
  384. duk_push_number(ctx, contactPosition.x_);
  385. duk_put_prop_index(ctx, -2, 0);
  386. duk_push_number(ctx, contactPosition.y_);
  387. duk_put_prop_index(ctx, -2, 1);
  388. duk_push_number(ctx, contactPosition.z_);
  389. duk_put_prop_index(ctx, -2, 2);
  390. duk_push_array(ctx);
  391. duk_push_number(ctx, contactNormal.x_);
  392. duk_put_prop_index(ctx, -2, 0);
  393. duk_push_number(ctx, contactNormal.y_);
  394. duk_put_prop_index(ctx, -2, 1);
  395. duk_push_number(ctx, contactNormal.z_);
  396. duk_put_prop_index(ctx, -2, 2);
  397. duk_call(ctx, 2);
  398. duk_pop(ctx);
  399. }
  400. }
  401. else
  402. {
  403. duk_push_heapptr(ctx, function);
  404. if (duk_pcall(ctx, 0) != 0)
  405. {
  406. vm_->SendJSErrorEvent();
  407. }
  408. duk_pop(ctx);
  409. }
  410. }
  411. }
  412. void JSComponent::GetScriptMethods()
  413. {
  414. if (!scriptObject_)
  415. return;
  416. duk_context* ctx = vm_->GetJSContext();
  417. duk_push_heapptr(ctx, scriptObject_);
  418. for (unsigned i = 0; i < MAX_JSSCRIPT_METHODS; ++i)
  419. {
  420. duk_get_prop_string(ctx, -1, methodDeclarations[i]);
  421. if (duk_is_function(ctx, -1))
  422. {
  423. methods_[i] = duk_get_heapptr(ctx, -1);
  424. }
  425. duk_pop(ctx);
  426. }
  427. duk_pop(ctx);
  428. }
  429. }