JSComponent.cpp 14 KB

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