JSComponent.cpp 15 KB

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