PythonReflectionComponent.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  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 <AzCore/PlatformDef.h>
  9. #include <PythonReflectionComponent.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/IO/SystemFile.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/Serialization/SerializeContext.h>
  23. #include <AzCore/StringFunc/StringFunc.h>
  24. #include <AzCore/Utils/Utils.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);
  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. void RegisterPaths(pybind11::module parentModule)
  143. {
  144. pybind11::module pathsModule = parentModule.def_submodule("paths");
  145. pathsModule.def("resolve_path", [](const char* path) -> AZStd::string
  146. {
  147. AZStd::optional<AZ::IO::FixedMaxPath> pyPath = AZ::IO::FileIOBase::GetInstance()->ResolvePath(path);
  148. return pyPath ? pyPath->String() : AZStd::string{};
  149. });
  150. pathsModule.def("ensure_alias", [](const char* alias, const char* path)
  151. {
  152. const char* aliasPath = AZ::IO::FileIOBase::GetInstance()->GetAlias(alias);
  153. if (aliasPath == nullptr)
  154. {
  155. AZ::IO::FileIOBase::GetInstance()->SetAlias(alias, path);
  156. }
  157. });
  158. pathsModule.attr("engroot") = AZ::Utils::GetEnginePath().c_str();
  159. pathsModule.attr("products") = AZ::Utils::GetProjectProductPathForPlatform().c_str();
  160. pathsModule.attr("projectroot") = AZ::Utils::GetProjectPath().c_str();
  161. pathsModule.attr("log") = AZ::Utils::GetProjectLogPath().c_str();
  162. // Add a gemroot method for querying gem paths
  163. pathsModule.def("gemroot", [](const char* gemName) -> AZStd::string
  164. {
  165. return AZStd::string(AZ::Utils::GetGemPath(gemName));
  166. });
  167. pathsModule.attr("executableFolder") = AZ::Utils::GetExecutableDirectory().c_str();
  168. }
  169. }
  170. //////////////////////////////////////////////////////////////////////////
  171. // PythonReflectionComponent
  172. void PythonReflectionComponent::Reflect(AZ::ReflectContext* context)
  173. {
  174. if (auto&& serialize = azrtti_cast<AZ::SerializeContext*>(context))
  175. {
  176. serialize->Class<PythonReflectionComponent, AZ::Component>()
  177. ->Version(1)
  178. ->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector<AZ::Crc32>{AZ_CRC_CE("AssetBuilder")})
  179. ;
  180. }
  181. }
  182. void PythonReflectionComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  183. {
  184. provided.push_back(PythonReflectionService);
  185. }
  186. void PythonReflectionComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  187. {
  188. incompatible.push_back(PythonReflectionService);
  189. }
  190. void PythonReflectionComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  191. {
  192. required.push_back(PythonEmbeddedService);
  193. }
  194. void PythonReflectionComponent::Activate()
  195. {
  196. EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler::BusConnect();
  197. }
  198. void PythonReflectionComponent::Deactivate()
  199. {
  200. OnPreFinalize();
  201. }
  202. void PythonReflectionComponent::ExportGlobalsFromBehaviorContext(pybind11::module parentModule)
  203. {
  204. AZ::BehaviorContext* behaviorContext = nullptr;
  205. AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
  206. AZ_Error("Editor", behaviorContext, "Behavior context not available");
  207. if (!behaviorContext)
  208. {
  209. return;
  210. }
  211. // when a global method does not have a Module attribute put into the 'azlmbr.globals' module
  212. auto globalsModule = parentModule.def_submodule(Internal::s_globals);
  213. Module::PackageMapType modulePackageMap;
  214. // add global methods flagged for Automation as Python global functions
  215. for (const auto& methodEntry : behaviorContext->m_methods)
  216. {
  217. const AZStd::string& methodName = methodEntry.first;
  218. AZ::BehaviorMethod* behaviorMethod = methodEntry.second;
  219. if (Scope::IsBehaviorFlaggedForEditor(behaviorMethod->m_attributes))
  220. {
  221. pybind11::module targetModule;
  222. auto moduleNameResult = Module::GetName(behaviorMethod->m_attributes);
  223. if(moduleNameResult)
  224. {
  225. targetModule = Module::DeterminePackageModule(modulePackageMap, *moduleNameResult, parentModule, globalsModule, false);
  226. }
  227. else
  228. {
  229. targetModule = globalsModule;
  230. }
  231. if (behaviorMethod->HasResult())
  232. {
  233. targetModule.def(methodName.c_str(), [behaviorMethod](pybind11::args args)
  234. {
  235. return Call::StaticMethod(behaviorMethod, args);
  236. });
  237. }
  238. else
  239. {
  240. targetModule.def(methodName.c_str(), [behaviorMethod](pybind11::args args)
  241. {
  242. Call::StaticMethod(behaviorMethod, args);
  243. });
  244. }
  245. // log global method symbol
  246. AZStd::string subModuleName = pybind11::cast<AZStd::string>(targetModule.attr("__name__"));
  247. PythonSymbolEventBus::QueueBroadcast(&PythonSymbolEventBus::Events::LogGlobalMethod, subModuleName, methodName, behaviorMethod);
  248. }
  249. }
  250. // add global properties flagged for Automation as Python static class properties
  251. m_staticPropertyHolderMap = AZStd::make_shared<Internal::StaticPropertyHolderMap>();
  252. struct GlobalPropertyHolder {};
  253. pybind11::class_<GlobalPropertyHolder> staticPropertyHolder(globalsModule, "property");
  254. for (const auto& propertyEntry : behaviorContext->m_properties)
  255. {
  256. const AZStd::string& propertyName = propertyEntry.first;
  257. AZ::BehaviorProperty* behaviorProperty = propertyEntry.second;
  258. if (Scope::IsBehaviorFlaggedForEditor(behaviorProperty->m_attributes))
  259. {
  260. auto propertyScopeName = Module::GetName(behaviorProperty->m_attributes);
  261. if (propertyScopeName)
  262. {
  263. pybind11::module scope = m_staticPropertyHolderMap->DetermineScope(parentModule, *propertyScopeName);
  264. m_staticPropertyHolderMap->AddProperty(scope, propertyName, behaviorProperty);
  265. }
  266. // log global property symbol
  267. AZStd::string subModuleName = pybind11::cast<AZStd::string>(globalsModule.attr("__name__"));
  268. PythonSymbolEventBus::QueueBroadcast(&PythonSymbolEventBus::Events::LogGlobalProperty, subModuleName, propertyName, behaviorProperty);
  269. if (behaviorProperty->m_getter && behaviorProperty->m_setter)
  270. {
  271. staticPropertyHolder.def_property_static(
  272. propertyName.c_str(),
  273. [behaviorProperty](pybind11::object) { return Call::StaticMethod(behaviorProperty->m_getter, {}); },
  274. [behaviorProperty](pybind11::object, pybind11::args args) { return Call::StaticMethod(behaviorProperty->m_setter, args); }
  275. );
  276. }
  277. else if (behaviorProperty->m_getter)
  278. {
  279. staticPropertyHolder.def_property_static(
  280. propertyName.c_str(),
  281. [behaviorProperty](pybind11::object) { return Call::StaticMethod(behaviorProperty->m_getter, {}); },
  282. pybind11::cpp_function()
  283. );
  284. }
  285. else if (behaviorProperty->m_setter)
  286. {
  287. AZ_Warning("python", false, "Global property %s only has a m_setter; write only properties not supported", propertyName.c_str());
  288. }
  289. else
  290. {
  291. AZ_Error("python", false, "Global property %s has neither a m_getter or m_setter", propertyName.c_str());
  292. }
  293. }
  294. }
  295. m_staticPropertyHolderMap->AddToScope();
  296. }
  297. void PythonReflectionComponent::OnPreFinalize()
  298. {
  299. m_staticPropertyHolderMap.reset();
  300. EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler::BusDisconnect();
  301. }
  302. void PythonReflectionComponent::OnImportModule(PyObject* module)
  303. {
  304. pybind11::module parentModule = pybind11::cast<pybind11::module>(module);
  305. std::string pythonModuleName = pybind11::cast<std::string>(parentModule.attr("__name__"));
  306. if (AZ::StringFunc::Equal(pythonModuleName.c_str(), Internal::s_azlmbr))
  307. {
  308. // declare the default module to capture behavior that did not define a "Module" attribute
  309. pybind11::module defaultModule = parentModule.def_submodule(Internal::s_default);
  310. ExportGlobalsFromBehaviorContext(parentModule);
  311. PythonProxyObjectManagement::CreateSubmodule(parentModule, defaultModule);
  312. PythonProxyBusManagement::CreateSubmodule(parentModule);
  313. Internal::RegisterPaths(parentModule);
  314. PythonSymbolEventBus::QueueBroadcast(&PythonSymbolEventBus::Events::Finalize);
  315. }
  316. }
  317. }