JSComponent.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  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 "Javascript.h"
  21. #include "JSEvents.h"
  22. #include "JSComponent.h"
  23. #include "JSAPI.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. int top = duk_get_top(ctx);
  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, (void*) node);
  71. js_push_class_object_instance(ctx, node);
  72. duk_put_prop(ctx, -3);
  73. duk_pop_2(ctx);
  74. assert(duk_get_top(ctx) == top);
  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. assert(className.Length());
  119. if (className == className_ && scriptObject_)
  120. return;
  121. ReleaseObject();
  122. className_ = className;
  123. CreateObject();
  124. MarkNetworkUpdate();
  125. }
  126. void JSComponent::ReleaseObject()
  127. {
  128. if (scriptObject_)
  129. {
  130. //if (methods_[JSMETHOD_STOP])
  131. // scriptFile_->Execute(scriptObject_, methods_[JSMETHOD_STOP]);
  132. PODVector<StringHash> exceptions;
  133. exceptions.Push(E_RELOADSTARTED);
  134. exceptions.Push(E_RELOADFINISHED);
  135. UnsubscribeFromAllEventsExcept(exceptions, false);
  136. if (node_)
  137. node_->RemoveListener(this);
  138. subscribed_ = false;
  139. subscribedPostFixed_ = false;
  140. ClearScriptMethods();
  141. scriptObject_ = 0;
  142. }
  143. }
  144. void JSComponent::SetScriptFile(JSFile* scriptFile)
  145. {
  146. ReleaseObject();
  147. CreateObject();
  148. MarkNetworkUpdate();
  149. }
  150. void JSComponent::CreateObject()
  151. {
  152. if (className_.Empty())
  153. return;
  154. PROFILE(CreateScriptObject);
  155. duk_context* ctx = vm_->GetJSContext();
  156. duk_push_global_stash(ctx);
  157. duk_get_prop_index(ctx, -1, JS_GLOBALSTASH_INDEX_COMPONENTS);
  158. duk_get_prop_string(ctx, -1, className_.CString());
  159. assert(duk_is_function(ctx, -1));
  160. js_push_class_object_instance(ctx, this);
  161. if (duk_pcall(ctx, 1) != 0)
  162. {
  163. vm_->SendJSErrorEvent();
  164. }
  165. else
  166. {
  167. scriptObject_ = this->JSGetHeapPtr();
  168. }
  169. if (scriptObject_)
  170. {
  171. GetScriptMethods();
  172. UpdateEventSubscription();
  173. }
  174. duk_pop_n(ctx, 2);
  175. }
  176. void JSComponent::HandleSceneUpdate(StringHash eventType, VariantMap& eventData)
  177. {
  178. if (!scriptObject_)
  179. return;
  180. assert(!destroyed_);
  181. assert(JSGetHeapPtr());
  182. using namespace SceneUpdate;
  183. float timeStep = eventData[P_TIMESTEP].GetFloat();
  184. duk_context* ctx = vm_->GetJSContext();
  185. if (!started_)
  186. {
  187. started_ = true;
  188. if (methods_[JSMETHOD_START])
  189. {
  190. duk_push_heapptr(ctx, methods_[JSMETHOD_START]);
  191. if (duk_pcall(ctx, 0) != 0)
  192. {
  193. vm_->SendJSErrorEvent();
  194. }
  195. duk_pop(ctx);
  196. }
  197. }
  198. if (methods_[JSMETHOD_UPDATE])
  199. {
  200. duk_push_heapptr(ctx, methods_[JSMETHOD_UPDATE]);
  201. duk_push_number(ctx, timeStep);
  202. if ( duk_pcall(ctx, 1) != DUK_EXEC_SUCCESS)
  203. {
  204. if (duk_is_object(ctx, -1))
  205. {
  206. vm_->SendJSErrorEvent();
  207. }
  208. else
  209. {
  210. assert(0);
  211. }
  212. }
  213. duk_pop(ctx);
  214. }
  215. else
  216. {
  217. Scene* scene = GetScene();
  218. if (scene)
  219. UnsubscribeFromEvent(scene, E_SCENEUPDATE);
  220. subscribed_ = false;
  221. }
  222. }
  223. void JSComponent::UpdateEventSubscription()
  224. {
  225. Scene* scene = GetScene();
  226. if (!scene)
  227. {
  228. LOGWARNING("Node is detached from scene, can not subscribe script object to update events");
  229. return;
  230. }
  231. bool enabled = scriptObject_ && IsEnabledEffective();
  232. if (enabled)
  233. {
  234. // we get at least one scene update if not started
  235. if (!subscribed_ && (!started_ || (methods_[JSMETHOD_UPDATE] || methods_[JSMETHOD_DELAYEDSTART] )))
  236. {
  237. SubscribeToEvent(scene, E_SCENEUPDATE, HANDLER(JSComponent, HandleSceneUpdate));
  238. subscribed_ = true;
  239. }
  240. if (!subscribedPostFixed_)
  241. {
  242. if (methods_[JSMETHOD_POSTUPDATE])
  243. SubscribeToEvent(scene, E_SCENEPOSTUPDATE, HANDLER(JSComponent, HandleScenePostUpdate));
  244. #ifdef ATOMIC_PHYSICS
  245. if (methods_[JSMETHOD_FIXEDUPDATE] || methods_[JSMETHOD_FIXEDPOSTUPDATE])
  246. {
  247. PhysicsWorld* world = scene->GetOrCreateComponent<PhysicsWorld>();
  248. if (world)
  249. {
  250. if (methods_[JSMETHOD_FIXEDUPDATE])
  251. SubscribeToEvent(world, E_PHYSICSPRESTEP, HANDLER(JSComponent, HandlePhysicsPreStep));
  252. if (methods_[JSMETHOD_FIXEDPOSTUPDATE])
  253. SubscribeToEvent(world, E_PHYSICSPOSTSTEP, HANDLER(JSComponent, HandlePhysicsPostStep));
  254. }
  255. else
  256. LOGERROR("No physics world, can not subscribe script object to fixed update events");
  257. }
  258. #endif
  259. subscribedPostFixed_ = true;
  260. }
  261. if (methods_[JSMETHOD_TRANSFORMCHANGED])
  262. node_->AddListener(this);
  263. }
  264. else
  265. {
  266. if (subscribed_)
  267. {
  268. UnsubscribeFromEvent(scene, E_SCENEUPDATE);
  269. subscribed_ = false;
  270. }
  271. if (subscribedPostFixed_)
  272. {
  273. UnsubscribeFromEvent(scene, E_SCENEPOSTUPDATE);
  274. #ifdef ATOMIC_PHYSICS
  275. PhysicsWorld* world = scene->GetComponent<PhysicsWorld>();
  276. if (world)
  277. {
  278. UnsubscribeFromEvent(world, E_PHYSICSPRESTEP);
  279. UnsubscribeFromEvent(world, E_PHYSICSPOSTSTEP);
  280. }
  281. #endif
  282. subscribedPostFixed_ = false;
  283. }
  284. if (methods_[JSMETHOD_TRANSFORMCHANGED])
  285. node_->RemoveListener(this);
  286. }
  287. }
  288. void JSComponent::HandleScenePostUpdate(StringHash eventType, VariantMap& eventData)
  289. {
  290. if (!scriptObject_)
  291. return;
  292. assert(!destroyed_);
  293. using namespace ScenePostUpdate;
  294. if (methods_[JSMETHOD_POSTUPDATE])
  295. {
  296. duk_context* ctx = vm_->GetJSContext();
  297. duk_push_heapptr(ctx, methods_[JSMETHOD_POSTUPDATE]);
  298. duk_push_number(ctx, eventData[P_TIMESTEP].GetFloat());
  299. duk_pcall(ctx, 1);
  300. duk_pop(ctx);
  301. }
  302. }
  303. #ifdef ATOMIC_PHYSICS
  304. void JSComponent::HandlePhysicsPreStep(StringHash eventType, VariantMap& eventData)
  305. {
  306. if (!scriptObject_)
  307. return;
  308. assert(!destroyed_);
  309. using namespace PhysicsPreStep;
  310. float timeStep = eventData[P_TIMESTEP].GetFloat();
  311. if (methods_[JSMETHOD_FIXEDUPDATE])
  312. {
  313. duk_context* ctx = vm_->GetJSContext();
  314. duk_push_heapptr(ctx, methods_[JSMETHOD_FIXEDUPDATE]);
  315. duk_push_number(ctx, timeStep);
  316. duk_pcall(ctx, 1);
  317. duk_pop(ctx);
  318. }
  319. }
  320. void JSComponent::HandlePhysicsPostStep(StringHash eventType, VariantMap& eventData)
  321. {
  322. if (!scriptObject_)
  323. return;
  324. assert(!destroyed_);
  325. using namespace PhysicsPostStep;
  326. VariantVector parameters;
  327. parameters.Push(eventData[P_TIMESTEP]);
  328. }
  329. #endif
  330. void JSComponent::HandleScriptEvent(StringHash eventType, VariantMap& eventData)
  331. {
  332. if (!IsEnabledEffective() || !scriptObject_)
  333. return;
  334. assert(!destroyed_);
  335. if (scriptEventFunctions_.Contains(eventType))
  336. {
  337. duk_context* ctx = vm_->GetJSContext();
  338. JS_HEAP_PTR function = scriptEventFunctions_[eventType];
  339. if (eventType == E_PHYSICSBEGINCONTACT2D || E_PHYSICSENDCONTACT2D)
  340. {
  341. using namespace PhysicsBeginContact2D;
  342. PhysicsWorld2D* world = static_cast<PhysicsWorld2D*>(eventData[P_WORLD].GetPtr());
  343. RigidBody2D* bodyA = static_cast<RigidBody2D*>(eventData[P_BODYA].GetPtr());
  344. RigidBody2D* bodyB = static_cast<RigidBody2D*>(eventData[P_BODYB].GetPtr());
  345. Node* nodeA = static_cast<Node*>(eventData[P_NODEA].GetPtr());
  346. Node* nodeB = static_cast<Node*>(eventData[P_NODEB].GetPtr());
  347. duk_push_heapptr(ctx, function);
  348. js_push_class_object_instance(ctx, world);
  349. js_push_class_object_instance(ctx, bodyA);
  350. js_push_class_object_instance(ctx, bodyB);
  351. js_push_class_object_instance(ctx, nodeA);
  352. js_push_class_object_instance(ctx, nodeB);
  353. if (duk_pcall(ctx, 5) != 0)
  354. {
  355. vm_->SendJSErrorEvent();
  356. }
  357. duk_pop(ctx);
  358. }
  359. #ifdef ATOMIC_PHYSICS
  360. else if (eventType == E_NODECOLLISION)
  361. {
  362. // Check collision contacts and see if character is standing on ground (look for a contact that has near vertical normal)
  363. using namespace NodeCollision;
  364. MemoryBuffer contacts(eventData[P_CONTACTS].GetBuffer());
  365. while (!contacts.IsEof())
  366. {
  367. Vector3 contactPosition = contacts.ReadVector3();
  368. Vector3 contactNormal = contacts.ReadVector3();
  369. float contactDistance = contacts.ReadFloat();
  370. float contactImpulse = contacts.ReadFloat();
  371. duk_push_heapptr(ctx, function);
  372. duk_push_array(ctx);
  373. duk_push_number(ctx, contactPosition.x_);
  374. duk_put_prop_index(ctx, -2, 0);
  375. duk_push_number(ctx, contactPosition.y_);
  376. duk_put_prop_index(ctx, -2, 1);
  377. duk_push_number(ctx, contactPosition.z_);
  378. duk_put_prop_index(ctx, -2, 2);
  379. duk_push_array(ctx);
  380. duk_push_number(ctx, contactNormal.x_);
  381. duk_put_prop_index(ctx, -2, 0);
  382. duk_push_number(ctx, contactNormal.y_);
  383. duk_put_prop_index(ctx, -2, 1);
  384. duk_push_number(ctx, contactNormal.z_);
  385. duk_put_prop_index(ctx, -2, 2);
  386. duk_call(ctx, 2);
  387. duk_pop(ctx);
  388. }
  389. }
  390. #endif
  391. else
  392. {
  393. duk_push_heapptr(ctx, function);
  394. if (duk_pcall(ctx, 0) != 0)
  395. {
  396. vm_->SendJSErrorEvent();
  397. }
  398. duk_pop(ctx);
  399. }
  400. }
  401. }
  402. void JSComponent::GetScriptMethods()
  403. {
  404. if (!scriptObject_)
  405. return;
  406. duk_context* ctx = vm_->GetJSContext();
  407. duk_push_heapptr(ctx, scriptObject_);
  408. for (unsigned i = 0; i < MAX_JSSCRIPT_METHODS; ++i)
  409. {
  410. duk_get_prop_string(ctx, -1, methodDeclarations[i]);
  411. if (duk_is_function(ctx, -1))
  412. {
  413. methods_[i] = duk_get_heapptr(ctx, -1);
  414. }
  415. duk_pop(ctx);
  416. }
  417. duk_pop(ctx);
  418. }
  419. }