PythonProxyBus.cpp 20 KB

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