PythonReflectionComponent.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  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 <PythonReflectionComponent.h>
  9. #include <AzFramework/StringFunc/StringFunc.h>
  10. #include <Source/PythonCommon.h>
  11. #include <Source/PythonUtility.h>
  12. #include <Source/PythonTypeCasters.h>
  13. #include <Source/PythonProxyBus.h>
  14. #include <Source/PythonProxyObject.h>
  15. #include <Source/PythonSymbolsBus.h>
  16. #include <pybind11/embed.h>
  17. #include <AzCore/PlatformDef.h>
  18. #include <AzCore/RTTI/AttributeReader.h>
  19. #include <AzCore/RTTI/BehaviorContext.h>
  20. #include <AzCore/std/smart_ptr/make_shared.h>
  21. #include <AzCore/Serialization/EditContextConstants.inl>
  22. #include <AzCore/PlatformDef.h>
  23. #include <AzCore/IO/SystemFile.h>
  24. #include <AzCore/IO/SystemFile.h>
  25. #include <AzFramework/IO/LocalFileIO.h>
  26. namespace EditorPythonBindings
  27. {
  28. namespace Internal
  29. {
  30. static constexpr const char* s_azlmbr = "azlmbr";
  31. static constexpr const char* s_default = "default";
  32. static constexpr const char* s_globals = "globals";
  33. // a structure for pybind11 to bind to hold constants, properties, and enums from the Behavior Context
  34. struct StaticPropertyHolder final
  35. {
  36. AZ_CLASS_ALLOCATOR(StaticPropertyHolder, AZ::SystemAllocator, 0);
  37. StaticPropertyHolder() = default;
  38. ~StaticPropertyHolder() = default;
  39. bool AddToScope(pybind11::module scope)
  40. {
  41. m_behaviorContext = nullptr;
  42. AZ::ComponentApplicationBus::BroadcastResult(m_behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
  43. AZ_Error("python", m_behaviorContext, "Behavior context not available");
  44. if (m_behaviorContext == nullptr)
  45. {
  46. return false;
  47. }
  48. m_fullName = PyModule_GetName(scope.ptr());
  49. pybind11::setattr(scope, "__getattr__", pybind11::cpp_function([this](const char* attribute)
  50. {
  51. return this->GetPropertyValue(attribute);
  52. }));
  53. pybind11::setattr(scope, "__setattr__", pybind11::cpp_function([this](const char* attribute, pybind11::object value)
  54. {
  55. return this->SetPropertyValue(attribute, value);
  56. }));
  57. return true;
  58. }
  59. void AddProperty(AZStd::string_view name, AZ::BehaviorProperty* behaviorProperty)
  60. {
  61. AZStd::string baseName(name);
  62. Scope::FetchScriptName(behaviorProperty->m_attributes, baseName);
  63. AZ::Crc32 namedKey(baseName);
  64. if (m_properties.find(namedKey) == m_properties.end())
  65. {
  66. m_properties[namedKey] = behaviorProperty;
  67. }
  68. else
  69. {
  70. AZ_Warning("python", false, "Skipping duplicate property named %s\n", baseName.c_str());
  71. }
  72. }
  73. protected:
  74. void SetPropertyValue(const char* attributeName, pybind11::object value)
  75. {
  76. auto behaviorPropertyIter = m_properties.find(AZ::Crc32(attributeName));
  77. if (behaviorPropertyIter != m_properties.end())
  78. {
  79. AZ::BehaviorProperty* property = behaviorPropertyIter->second;
  80. AZ_Error("python", property->m_setter, "%s is not a writable property in %s.", attributeName, m_fullName.c_str());
  81. if (property->m_setter)
  82. {
  83. EditorPythonBindings::Call::StaticMethod(property->m_setter, pybind11::args(pybind11::make_tuple(value)));
  84. }
  85. }
  86. }
  87. pybind11::object GetPropertyValue(const char* attributeName)
  88. {
  89. AZ::Crc32 crcAttributeName(attributeName);
  90. auto behaviorPropertyIter = m_properties.find(crcAttributeName);
  91. if (behaviorPropertyIter != m_properties.end())
  92. {
  93. AZ::BehaviorProperty* property = behaviorPropertyIter->second;
  94. AZ_Error("python", property->m_getter, "%s is not a readable property in %s.", attributeName, m_fullName.c_str());
  95. if (property->m_getter)
  96. {
  97. return EditorPythonBindings::Call::StaticMethod(property->m_getter, pybind11::args());
  98. }
  99. }
  100. return pybind11::cast<pybind11::none>(Py_None);
  101. }
  102. AZ::BehaviorContext* m_behaviorContext = nullptr;
  103. AZStd::unordered_map<AZ::Crc32, AZ::BehaviorProperty*> m_properties;
  104. AZStd::string m_fullName;
  105. };
  106. using StaticPropertyHolderPointer = AZStd::unique_ptr<StaticPropertyHolder>;
  107. using StaticPropertyHolderMapEntry = AZStd::pair<pybind11::module, StaticPropertyHolderPointer>;
  108. struct StaticPropertyHolderMap final
  109. : public AZStd::unordered_map<AZStd::string, StaticPropertyHolderMapEntry>
  110. {
  111. Module::PackageMapType m_packageMap;
  112. void AddToScope()
  113. {
  114. for (auto&& element : *this)
  115. {
  116. StaticPropertyHolderMapEntry& entry = element.second;
  117. entry.second->AddToScope(entry.first);
  118. }
  119. }
  120. void AddProperty(pybind11::module scope, const AZStd::string& propertyName, AZ::BehaviorProperty* behaviorProperty)
  121. {
  122. AZStd::string scopeName = PyModule_GetName(scope.ptr());
  123. auto&& iter = find(scopeName);
  124. if (iter == end())
  125. {
  126. StaticPropertyHolder* holder = aznew StaticPropertyHolder();
  127. insert(AZStd::make_pair(scopeName, StaticPropertyHolderMapEntry{ scope, holder }));
  128. holder->AddProperty(propertyName, behaviorProperty);
  129. }
  130. else
  131. {
  132. StaticPropertyHolderMapEntry& entry = iter->second;
  133. entry.second->AddProperty(propertyName, behaviorProperty);
  134. }
  135. PythonSymbolEventBus::QueueBroadcast(&PythonSymbolEventBus::Events::LogGlobalProperty, scopeName, propertyName, behaviorProperty);
  136. }
  137. pybind11::module DetermineScope(pybind11::module scope, const AZStd::string& fullName)
  138. {
  139. return Module::DeterminePackageModule(m_packageMap, fullName, scope, scope, false);
  140. }
  141. };
  142. AZStd::string PyResolvePath(AZStd::string_view path)
  143. {
  144. char pyPath[AZ_MAX_PATH_LEN];
  145. AZ::IO::FileIOBase::GetInstance()->ResolvePath(path.data(), pyPath, AZ_MAX_PATH_LEN);
  146. return { pyPath };
  147. }
  148. void RegisterAliasIfExists(pybind11::module pathsModule, AZStd::string_view alias, AZStd::string_view attribute)
  149. {
  150. const char* aliasPath = AZ::IO::FileIOBase::GetInstance()->GetAlias(alias.data());
  151. if (aliasPath)
  152. {
  153. pathsModule.attr(attribute.data()) = aliasPath;
  154. }
  155. else
  156. {
  157. pathsModule.attr(attribute.data()) = "";
  158. }
  159. }
  160. void RegisterPaths(pybind11::module parentModule)
  161. {
  162. pybind11::module pathsModule = parentModule.def_submodule("paths");
  163. pathsModule.def("resolve_path", [](const char* path)
  164. {
  165. return PyResolvePath(path);
  166. });
  167. pathsModule.def("ensure_alias", [](const char* alias, const char* path)
  168. {
  169. const char* aliasPath = AZ::IO::FileIOBase::GetInstance()->GetAlias(alias);
  170. if (aliasPath == nullptr)
  171. {
  172. AZ::IO::FileIOBase::GetInstance()->SetAlias(alias, path);
  173. }
  174. });
  175. RegisterAliasIfExists(pathsModule, "@devroot@", "devroot");
  176. RegisterAliasIfExists(pathsModule, "@engroot@", "engroot");
  177. RegisterAliasIfExists(pathsModule, "@assets@", "assets");
  178. RegisterAliasIfExists(pathsModule, "@devassets@", "devassets");
  179. RegisterAliasIfExists(pathsModule, "@log@", "log");
  180. RegisterAliasIfExists(pathsModule, "@root@", "root");
  181. const char* executableFolder = nullptr;
  182. AZ::ComponentApplicationBus::BroadcastResult(executableFolder, &AZ::ComponentApplicationBus::Events::GetExecutableFolder);
  183. if (executableFolder)
  184. {
  185. pathsModule.attr("executableFolder") = executableFolder;
  186. }
  187. }
  188. }
  189. //////////////////////////////////////////////////////////////////////////
  190. // PythonReflectionComponent
  191. void PythonReflectionComponent::Reflect(AZ::ReflectContext* context)
  192. {
  193. if (auto&& serialize = azrtti_cast<AZ::SerializeContext*>(context))
  194. {
  195. serialize->Class<PythonReflectionComponent, AZ::Component>()
  196. ->Version(1)
  197. ->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector<AZ::Crc32>{AZ_CRC_CE("AssetBuilder")})
  198. ;
  199. }
  200. }
  201. void PythonReflectionComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  202. {
  203. provided.push_back(PythonReflectionService);
  204. }
  205. void PythonReflectionComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  206. {
  207. incompatible.push_back(PythonReflectionService);
  208. }
  209. void PythonReflectionComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  210. {
  211. required.push_back(PythonEmbeddedService);
  212. }
  213. void PythonReflectionComponent::Activate()
  214. {
  215. EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler::BusConnect();
  216. }
  217. void PythonReflectionComponent::Deactivate()
  218. {
  219. OnPreFinalize();
  220. }
  221. void PythonReflectionComponent::ExportGlobalsFromBehaviorContext(pybind11::module parentModule)
  222. {
  223. AZ::BehaviorContext* behaviorContext = nullptr;
  224. AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
  225. AZ_Error("Editor", behaviorContext, "Behavior context not available");
  226. if (!behaviorContext)
  227. {
  228. return;
  229. }
  230. // when a global method does not have a Module attribute put into the 'azlmbr.globals' module
  231. auto globalsModule = parentModule.def_submodule(Internal::s_globals);
  232. Module::PackageMapType modulePackageMap;
  233. // add global methods flagged for Automation as Python global functions
  234. for (const auto& methodEntry : behaviorContext->m_methods)
  235. {
  236. const AZStd::string& methodName = methodEntry.first;
  237. AZ::BehaviorMethod* behaviorMethod = methodEntry.second;
  238. if (Scope::IsBehaviorFlaggedForEditor(behaviorMethod->m_attributes))
  239. {
  240. pybind11::module targetModule;
  241. auto moduleNameResult = Module::GetName(behaviorMethod->m_attributes);
  242. if(moduleNameResult)
  243. {
  244. targetModule = Module::DeterminePackageModule(modulePackageMap, *moduleNameResult, parentModule, globalsModule, false);
  245. }
  246. else
  247. {
  248. targetModule = globalsModule;
  249. }
  250. if (behaviorMethod->HasResult())
  251. {
  252. targetModule.def(methodName.c_str(), [behaviorMethod](pybind11::args args)
  253. {
  254. return Call::StaticMethod(behaviorMethod, args);
  255. });
  256. }
  257. else
  258. {
  259. targetModule.def(methodName.c_str(), [behaviorMethod](pybind11::args args)
  260. {
  261. Call::StaticMethod(behaviorMethod, args);
  262. });
  263. }
  264. // log global method symbol
  265. AZStd::string subModuleName = pybind11::cast<AZStd::string>(targetModule.attr("__name__"));
  266. PythonSymbolEventBus::QueueBroadcast(&PythonSymbolEventBus::Events::LogGlobalMethod, subModuleName, methodName, behaviorMethod);
  267. }
  268. }
  269. // add global properties flagged for Automation as Python static class properties
  270. m_staticPropertyHolderMap = AZStd::make_shared<Internal::StaticPropertyHolderMap>();
  271. struct GlobalPropertyHolder {};
  272. pybind11::class_<GlobalPropertyHolder> staticPropertyHolder(globalsModule, "property");
  273. for (const auto& propertyEntry : behaviorContext->m_properties)
  274. {
  275. const AZStd::string& propertyName = propertyEntry.first;
  276. AZ::BehaviorProperty* behaviorProperty = propertyEntry.second;
  277. if (Scope::IsBehaviorFlaggedForEditor(behaviorProperty->m_attributes))
  278. {
  279. auto propertyScopeName = Module::GetName(behaviorProperty->m_attributes);
  280. if (propertyScopeName)
  281. {
  282. pybind11::module scope = m_staticPropertyHolderMap->DetermineScope(parentModule, *propertyScopeName);
  283. m_staticPropertyHolderMap->AddProperty(scope, propertyName, behaviorProperty);
  284. }
  285. // log global property symbol
  286. AZStd::string subModuleName = pybind11::cast<AZStd::string>(globalsModule.attr("__name__"));
  287. PythonSymbolEventBus::QueueBroadcast(&PythonSymbolEventBus::Events::LogGlobalProperty, subModuleName, propertyName, behaviorProperty);
  288. if (behaviorProperty->m_getter && behaviorProperty->m_setter)
  289. {
  290. staticPropertyHolder.def_property_static(
  291. propertyName.c_str(),
  292. [behaviorProperty](pybind11::object) { return Call::StaticMethod(behaviorProperty->m_getter, {}); },
  293. [behaviorProperty](pybind11::object, pybind11::args args) { return Call::StaticMethod(behaviorProperty->m_setter, args); }
  294. );
  295. }
  296. else if (behaviorProperty->m_getter)
  297. {
  298. staticPropertyHolder.def_property_static(
  299. propertyName.c_str(),
  300. [behaviorProperty](pybind11::object) { return Call::StaticMethod(behaviorProperty->m_getter, {}); },
  301. pybind11::cpp_function()
  302. );
  303. }
  304. else if (behaviorProperty->m_setter)
  305. {
  306. AZ_Warning("python", false, "Global property %s only has a m_setter; write only properties not supported", propertyName.c_str());
  307. }
  308. else
  309. {
  310. AZ_Error("python", false, "Global property %s has neither a m_getter or m_setter", propertyName.c_str());
  311. }
  312. }
  313. }
  314. m_staticPropertyHolderMap->AddToScope();
  315. }
  316. void PythonReflectionComponent::OnPreFinalize()
  317. {
  318. m_staticPropertyHolderMap.reset();
  319. EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler::BusDisconnect();
  320. }
  321. void PythonReflectionComponent::OnImportModule(PyObject* module)
  322. {
  323. pybind11::module parentModule = pybind11::cast<pybind11::module>(module);
  324. std::string pythonModuleName = pybind11::cast<std::string>(parentModule.attr("__name__"));
  325. if (AzFramework::StringFunc::Equal(pythonModuleName.c_str(), Internal::s_azlmbr))
  326. {
  327. // declare the default module to capture behavior that did not define a "Module" attribute
  328. pybind11::module defaultModule = parentModule.def_submodule(Internal::s_default);
  329. ExportGlobalsFromBehaviorContext(parentModule);
  330. PythonProxyObjectManagement::CreateSubmodule(parentModule, defaultModule);
  331. PythonProxyBusManagement::CreateSubmodule(parentModule);
  332. Internal::RegisterPaths(parentModule);
  333. PythonSymbolEventBus::QueueBroadcast(&PythonSymbolEventBus::Events::Finalize);
  334. }
  335. }
  336. }