PythonProxyBus.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <PythonProxyBus.h>
  9. #include <Source/PythonUtility.h>
  10. #include <Source/PythonTypeCasters.h>
  11. #include <Source/PythonCommon.h>
  12. #include <Source/PythonSymbolsBus.h>
  13. #include <pybind11/embed.h>
  14. #include <AzCore/PlatformDef.h>
  15. #include <AzCore/RTTI/BehaviorContext.h>
  16. #include <AzCore/RTTI/AttributeReader.h>
  17. #include <AzCore/std/optional.h>
  18. #include <AzFramework/StringFunc/StringFunc.h>
  19. namespace EditorPythonBindings
  20. {
  21. namespace Internal
  22. {
  23. enum class EventType
  24. {
  25. Broadcast,
  26. Event,
  27. QueueBroadcast,
  28. QueueEvent
  29. };
  30. pybind11::object InvokeEbus(AZ::BehaviorEBus& behaviorEBus, EventType eventType, AZStd::string_view eventName, pybind11::args pythonArgs)
  31. {
  32. auto eventIterator = behaviorEBus.m_events.find(eventName);
  33. AZ_Warning("python", eventIterator != behaviorEBus.m_events.end(), "Event %.*s does not exist in EBus %s", aznumeric_cast<int>(eventName.size()), eventName.data(), behaviorEBus.m_name.c_str());
  34. if (eventIterator == behaviorEBus.m_events.end())
  35. {
  36. return pybind11::cast<pybind11::none>(Py_None);
  37. }
  38. auto& behaviorEBusEventSender = eventIterator->second;
  39. switch (eventType)
  40. {
  41. case EventType::Broadcast:
  42. {
  43. AZ_Warning("python", behaviorEBusEventSender.m_broadcast, "EventSender: function %.*s in EBus %s does not support the bus.Broadcast event type.", static_cast<int>(eventName.size()), eventName.data(), behaviorEBus.m_name.c_str());
  44. if (behaviorEBusEventSender.m_broadcast)
  45. {
  46. return Call::StaticMethod(behaviorEBusEventSender.m_broadcast, pythonArgs);
  47. }
  48. break;
  49. }
  50. case EventType::Event:
  51. {
  52. AZ_Warning("python", behaviorEBusEventSender.m_event, "EventSender: function %.*s in EBus %s does not support the bus.Event event type.", static_cast<int>(eventName.size()), eventName.data(), behaviorEBus.m_name.c_str());
  53. if (behaviorEBusEventSender.m_event)
  54. {
  55. return Call::StaticMethod(behaviorEBusEventSender.m_event, pythonArgs);
  56. }
  57. break;
  58. }
  59. case EventType::QueueBroadcast:
  60. {
  61. AZ_Warning("python", behaviorEBusEventSender.m_queueBroadcast, "EventSender: function %.*s in EBus %s does not support the bus.QueueBroadcast event type.", static_cast<int>(eventName.size()), eventName.data(), behaviorEBus.m_name.c_str());
  62. if (behaviorEBusEventSender.m_queueBroadcast)
  63. {
  64. return Call::StaticMethod(behaviorEBusEventSender.m_queueBroadcast, pythonArgs);
  65. }
  66. break;
  67. }
  68. case EventType::QueueEvent:
  69. {
  70. AZ_Warning("python", behaviorEBusEventSender.m_queueEvent, "EventSender: function %.*s in EBus %s does not support the bus.QueueEvent event type.", static_cast<int>(eventName.size()), eventName.data(), behaviorEBus.m_name.c_str());
  71. if (behaviorEBusEventSender.m_queueEvent)
  72. {
  73. return Call::StaticMethod(behaviorEBusEventSender.m_queueEvent, pythonArgs);
  74. }
  75. break;
  76. }
  77. default:
  78. AZ_Error("python", false, "Unknown EBus call type %d", eventType);
  79. break;
  80. }
  81. return pybind11::cast<pybind11::none>(Py_None);
  82. }
  83. class PythonProxyNotificationHandler final
  84. {
  85. public:
  86. AZ_CLASS_ALLOCATOR(PythonProxyNotificationHandler, AZ::SystemAllocator, 0);
  87. PythonProxyNotificationHandler(AZStd::string_view busName)
  88. {
  89. AZ::BehaviorContext* behaviorContext(nullptr);
  90. AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
  91. if (!behaviorContext)
  92. {
  93. AZ_Error("python", false, "A behavior context is required to bind the buses!");
  94. return;
  95. }
  96. auto behaviorEBusEntry = behaviorContext->m_ebuses.find(busName);
  97. if (behaviorEBusEntry == behaviorContext->m_ebuses.end())
  98. {
  99. AZ_Error("python", false, "There is no EBus by the name of %.*s", static_cast<int>(busName.size()), busName.data());
  100. return;
  101. }
  102. AZ_Assert(behaviorEBusEntry->second, "A null EBus:%s is in the Behavior Context!", behaviorEBusEntry->first.c_str());
  103. m_ebus = behaviorEBusEntry->second;
  104. }
  105. ~PythonProxyNotificationHandler()
  106. {
  107. Disconnect();
  108. }
  109. bool IsConnected() const
  110. {
  111. if (m_handler)
  112. {
  113. return m_handler->IsConnected();
  114. }
  115. return false;
  116. }
  117. bool Connect(pybind11::object busId)
  118. {
  119. if (!m_ebus)
  120. {
  121. AZ_Error("python", false, "EBus not set.");
  122. return false;
  123. }
  124. if (!CreateHandler(*m_ebus))
  125. {
  126. AZ_Error("python", false, "Could not create a handler for ebus");
  127. return false;
  128. }
  129. // does the EBus require an address to connect?
  130. if (m_ebus->m_idParam.m_typeId.IsNull())
  131. {
  132. AZ_Warning("python", busId.is_none(), "Connecting to an singleton EBus but was given a non-None busId(%s)", pybind11::cast<AZStd::string>(busId).c_str());
  133. return m_handler->Connect();
  134. }
  135. else if (busId.is_none())
  136. {
  137. AZ_Warning("python", busId.is_none(), "Connecting to an EBus that requires an address but was given a None busId");
  138. return false;
  139. }
  140. Convert::StackVariableAllocator stackVariableAllocator;
  141. AZ::BehaviorValueParameter busAddress;
  142. if (!Convert::PythonToBehaviorValueParameter(m_ebus->m_idParam, busId, busAddress, stackVariableAllocator))
  143. {
  144. AZ_Warning("python", busId.is_none(), "Could not convert busId(%s) to address type (%s)",
  145. pybind11::cast<AZStd::string>(busId).c_str(), m_ebus->m_idParam.m_typeId.ToString<AZStd::string>().c_str());
  146. return false;
  147. }
  148. return m_handler->Connect(&busAddress);
  149. }
  150. bool Disconnect()
  151. {
  152. if (!m_handler)
  153. {
  154. return false;
  155. }
  156. m_handler->Disconnect();
  157. if (m_ebus)
  158. {
  159. DestroyHandler(*m_ebus);
  160. }
  161. return true;
  162. }
  163. bool AddCallback(AZStd::string_view eventName, pybind11::function callback)
  164. {
  165. if (!PyCallable_Check(callback.ptr()))
  166. {
  167. AZ_Error("python", false, "The callback needs to be a callable python function.");
  168. return false;
  169. }
  170. if (!m_handler)
  171. {
  172. AZ_Error("python", false, "No EBus connection deteced; missing call or failed call to connect()?");
  173. return false;
  174. }
  175. const AZ::BehaviorEBusHandler::EventArray& events = m_handler->GetEvents();
  176. for (int iEvent = 0; iEvent < static_cast<int>(events.size()); ++iEvent)
  177. {
  178. const AZ::BehaviorEBusHandler::BusForwarderEvent& e = events[iEvent];
  179. if (eventName == e.m_name)
  180. {
  181. AZStd::string eventNameValue{ eventName };
  182. #if defined(AZ_ENABLE_TRACING)
  183. const auto& callbackIt = m_callbackMap.find(eventNameValue);
  184. #endif
  185. AZ_Warning("python", m_callbackMap.end() == callbackIt, "Replacing callback for eventName:%s", eventNameValue.c_str());
  186. m_callbackMap[eventNameValue] = callback;
  187. return true;
  188. }
  189. }
  190. return false;
  191. }
  192. protected:
  193. void DestroyHandler(const AZ::BehaviorEBus& ebus)
  194. {
  195. if (m_handler)
  196. {
  197. AZ_Warning("python", ebus.m_destroyHandler, "Ebus (%s) does not have a handler destroyer.", ebus.m_name.c_str());
  198. if (ebus.m_destroyHandler)
  199. {
  200. ebus.m_destroyHandler->Invoke(m_handler);
  201. }
  202. }
  203. m_handler = nullptr;
  204. m_callbackMap.clear();
  205. }
  206. bool CreateHandler(const AZ::BehaviorEBus& ebus)
  207. {
  208. DestroyHandler(ebus);
  209. AZ_Warning("python", ebus.m_createHandler, "Ebus (%s) does not have a handler creator.", ebus.m_name.c_str());
  210. if (!ebus.m_createHandler)
  211. {
  212. return false;
  213. }
  214. if (!ebus.m_createHandler->InvokeResult(m_handler))
  215. {
  216. AZ_Warning("python", ebus.m_createHandler, "Ebus (%s) failed to create a handler.", ebus.m_name.c_str());
  217. return false;
  218. }
  219. if (m_handler)
  220. {
  221. const AZ::BehaviorEBusHandler::EventArray& events = m_handler->GetEvents();
  222. for (int iEvent = 0; iEvent < static_cast<int>(events.size()); ++iEvent)
  223. {
  224. m_handler->InstallGenericHook(iEvent, &PythonProxyNotificationHandler::OnEventGenericHook, this);
  225. }
  226. }
  227. return true;
  228. }
  229. static void OnEventGenericHook(void* userData, const char* eventName, int eventIndex, AZ::BehaviorValueParameter* result, int numParameters, AZ::BehaviorValueParameter* parameters)
  230. {
  231. reinterpret_cast<PythonProxyNotificationHandler*>(userData)->OnEventGenericHook(eventName, eventIndex, result, numParameters, parameters);
  232. }
  233. void OnEventGenericHook(const char* eventName, [[maybe_unused]] int eventIndex, AZ::BehaviorValueParameter* result, int numParameters, AZ::BehaviorValueParameter* parameters)
  234. {
  235. // find the callback for the event
  236. const auto& callbackEntry = m_callbackMap.find(eventName);
  237. if (callbackEntry == m_callbackMap.end())
  238. {
  239. return;
  240. }
  241. pybind11::function callback = callbackEntry->second;
  242. // build the parameters to send to callback
  243. Convert::StackVariableAllocator stackVariableAllocator;
  244. pybind11::tuple pythonParamters(numParameters);
  245. for (int index = 0; index < numParameters; ++index)
  246. {
  247. AZ::BehaviorValueParameter& behaviorValueParameter{ *(parameters + index) };
  248. pythonParamters[index] = Convert::BehaviorValueParameterToPython(behaviorValueParameter, stackVariableAllocator);
  249. if (pythonParamters[index].is_none())
  250. {
  251. AZ_Warning("python", false, "Ebus(%s) event(%s) failed to convert parameter at index(%d)", m_ebus->m_name.c_str(), eventName, index);
  252. return;
  253. }
  254. }
  255. try
  256. {
  257. pybind11::object pyResult = callback(pythonParamters);
  258. // store the result
  259. if (result && pyResult.is_none() == false)
  260. {
  261. // reset/prepare the stack allocator
  262. m_stackVariableAllocator = {};
  263. // Reset the result parameter
  264. m_resultParam = {};
  265. const AZ::u32 traits = result->m_traits;
  266. if (Convert::PythonToBehaviorValueParameter(*result, pyResult, m_resultParam, m_stackVariableAllocator))
  267. {
  268. // Setting result parameter into the output parameter will not fix its pointers
  269. // to use output parameter's internal memory, because of this, result parameter
  270. // needs to be a member so its memory is still valid when accessed in BehaviorEBusHandler::CallResult.
  271. result->Set(m_resultParam);
  272. result->m_value = m_resultParam.GetValueAddress();
  273. if ((traits & AZ::BehaviorParameter::TR_POINTER) == AZ::BehaviorParameter::TR_POINTER)
  274. {
  275. result->m_value = &result->m_value;
  276. }
  277. }
  278. }
  279. }
  280. catch ([[maybe_unused]] const std::exception& e)
  281. {
  282. AZ_Error("python", false, "Python callback threw an exception %s", e.what());
  283. }
  284. }
  285. private:
  286. const AZ::BehaviorEBus* m_ebus = nullptr;
  287. AZ::BehaviorEBusHandler* m_handler = nullptr;
  288. AZStd::unordered_map<AZStd::string, pybind11::function> m_callbackMap;
  289. Convert::StackVariableAllocator m_stackVariableAllocator;
  290. AZ::BehaviorValueParameter m_resultParam;
  291. };
  292. }
  293. namespace PythonProxyBusManagement
  294. {
  295. void CreateSubmodule(pybind11::module baseModule)
  296. {
  297. AZ::BehaviorContext* behaviorContext(nullptr);
  298. AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
  299. if (!behaviorContext)
  300. {
  301. AZ_Error("python", false, "A behavior context is required to bind the buses!");
  302. return;
  303. }
  304. auto busModule = baseModule.def_submodule("bus");
  305. Module::PackageMapType modulePackageMap;
  306. // export possible ways an EBus can be invoked
  307. pybind11::enum_<Internal::EventType>(busModule, "EventType")
  308. .value("Event", Internal::EventType::Event)
  309. .value("Broadcast", Internal::EventType::Broadcast)
  310. .value("QueueEvent", Internal::EventType::QueueEvent)
  311. .value("QueueBroadcast", Internal::EventType::QueueBroadcast)
  312. .export_values();
  313. // export the EBuses flagged for Automation or Common scope
  314. for (auto&& busEntry : behaviorContext->m_ebuses)
  315. {
  316. AZStd::string& ebusName = busEntry.first;
  317. AZ::BehaviorEBus* behaviorEBus = busEntry.second;
  318. if (Scope::IsBehaviorFlaggedForEditor(behaviorEBus->m_attributes))
  319. {
  320. auto busCaller = pybind11::cpp_function([behaviorEBus](Internal::EventType eventType, AZStd::string_view eventName, pybind11::args pythonArgs)
  321. {
  322. return Internal::InvokeEbus(*behaviorEBus, eventType, eventName, pythonArgs);
  323. });
  324. auto createPythonProxyNotificationHandler = pybind11::cpp_function([behaviorEBus]()
  325. {
  326. return aznew Internal::PythonProxyNotificationHandler(behaviorEBus->m_name.c_str());
  327. });
  328. pybind11::module thisBusModule = busModule;
  329. auto moduleName = Module::GetName(behaviorEBus->m_attributes);
  330. if (moduleName)
  331. {
  332. // this will place the bus into either:
  333. // 1) if the module is valid, then azlmbr.<module name>.<ebus name>
  334. // 2) or, then azlmbr.bus.<ebus name>
  335. thisBusModule = Module::DeterminePackageModule(modulePackageMap, *moduleName, baseModule, busModule, true);
  336. }
  337. // for each notification handler type, make a convenient Python type to make the script more Python-ic
  338. if (behaviorEBus->m_createHandler && behaviorEBus->m_destroyHandler)
  339. {
  340. AZStd::string ebusNotificationName{ AZStd::string::format("%sHandler", ebusName.c_str()) };
  341. thisBusModule.attr(ebusNotificationName.c_str()) = createPythonProxyNotificationHandler;
  342. }
  343. // is a request EBus
  344. thisBusModule.attr(ebusName.c_str()) = busCaller;
  345. // log the bus symbol
  346. AZStd::string subModuleName = pybind11::cast<AZStd::string>(thisBusModule.attr("__name__"));
  347. PythonSymbolEventBus::QueueBroadcast(&PythonSymbolEventBus::Events::LogBus, subModuleName, ebusName, behaviorEBus);
  348. }
  349. }
  350. // export possible ways an EBus can be invoked
  351. pybind11::class_<Internal::PythonProxyNotificationHandler>(busModule, "NotificationHandler")
  352. .def(pybind11::init<AZStd::string_view>())
  353. .def("is_connected", &Internal::PythonProxyNotificationHandler::IsConnected)
  354. .def("connect", &Internal::PythonProxyNotificationHandler::Connect, pybind11::arg("busId") = pybind11::none())
  355. .def("disconnect", &Internal::PythonProxyNotificationHandler::Disconnect)
  356. .def("add_callback", &Internal::PythonProxyNotificationHandler::AddCallback)
  357. ;
  358. }
  359. }
  360. }