PythonProxyObject.cpp 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952
  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 <PythonProxyObject.h>
  9. #include <AzFramework/StringFunc/StringFunc.h>
  10. #include <Source/PythonCommon.h>
  11. #include <Source/PythonUtility.h>
  12. #include <Source/PythonMarshalComponent.h>
  13. #include <Source/PythonTypeCasters.h>
  14. #include <Source/PythonSymbolsBus.h>
  15. #include <pybind11/embed.h>
  16. #include <AzCore/PlatformDef.h>
  17. #include <AzCore/RTTI/BehaviorContext.h>
  18. #include <AzCore/RTTI/AttributeReader.h>
  19. namespace EditorPythonBindings
  20. {
  21. namespace Operator
  22. {
  23. constexpr const char s_isEqual[] = "__eq__";
  24. constexpr const char s_notEqual[] = "__ne__";
  25. constexpr const char s_greaterThan[] = "__gt__";
  26. constexpr const char s_greaterThanOrEqual[] = "__ge__";
  27. constexpr const char s_lessThan[] = "__lt__";
  28. constexpr const char s_lessThanOrEqual[] = "__le__";
  29. }
  30. namespace Builtins
  31. {
  32. constexpr const char s_repr[] = "__repr__";
  33. constexpr const char s_str[] = "__str__";
  34. }
  35. namespace Naming
  36. {
  37. void StripReplace(AZStd::string& inout, AZStd::string_view prefix, char bracketIn, char bracketOut, AZStd::string_view replacement)
  38. {
  39. size_t pos = inout.find(prefix);
  40. while (pos != AZStd::string::npos)
  41. {
  42. const char* const start = &inout[pos];
  43. pos += prefix.size();
  44. const char* end = &inout[pos];
  45. int bracketCount = 1;
  46. do
  47. {
  48. if (pos == inout.size())
  49. {
  50. break;
  51. }
  52. else if (inout[pos] == bracketIn)
  53. {
  54. bracketCount++;
  55. }
  56. else if (inout[pos] == bracketOut)
  57. {
  58. bracketCount--;
  59. }
  60. end++;
  61. pos++;
  62. }
  63. while (bracketCount > 0);
  64. AZStd::string target{ start, end };
  65. AZ::StringFunc::Replace(inout, target.c_str(), replacement.data());
  66. pos = inout.find(prefix);
  67. }
  68. }
  69. AZStd::optional<AZStd::string> GetPythonSyntax(const AZ::BehaviorClass& behaviorClass)
  70. {
  71. constexpr const char* invalidCharacters = " :<>,*&";
  72. if (behaviorClass.m_name.find_first_of(invalidCharacters) == AZStd::string::npos)
  73. {
  74. // this class name is not using invalid characters
  75. return AZStd::nullopt;
  76. }
  77. AZStd::string syntaxName = behaviorClass.m_name;
  78. // replace common core template types and name spaces like AZStd
  79. StripReplace(syntaxName, "AZStd::basic_string<", '<', '>', "string");
  80. AZ::StringFunc::Replace(syntaxName, "AZStd", "");
  81. AZStd::vector<AZStd::string> tokens;
  82. AZ::StringFunc::Tokenize(syntaxName, tokens, invalidCharacters, false, false);
  83. syntaxName.clear();
  84. AZ::StringFunc::Join(syntaxName, tokens.begin(), tokens.end(), "_");
  85. return syntaxName;
  86. }
  87. }
  88. PythonProxyObject::PythonProxyObject(const AZ::TypeId& typeId)
  89. {
  90. const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(typeId);
  91. if (behaviorClass)
  92. {
  93. CreateDefault(behaviorClass);
  94. }
  95. }
  96. PythonProxyObject::PythonProxyObject(const char* typeName)
  97. {
  98. SetByTypeName(typeName);
  99. }
  100. pybind11::object PythonProxyObject::Construct(const AZ::BehaviorClass& behaviorClass, pybind11::args args)
  101. {
  102. // nothing to construct with ...
  103. if (args.size() == 0 || behaviorClass.m_constructors.empty())
  104. {
  105. if (!CreateDefault(&behaviorClass))
  106. {
  107. return pybind11::cast<pybind11::none>(Py_None);
  108. }
  109. return pybind11::cast(this);
  110. }
  111. // find the right constructor
  112. for (AZ::BehaviorMethod* constructor : behaviorClass.m_constructors)
  113. {
  114. const size_t numArgsPlusSelf = args.size() + 1;
  115. AZ_Error("python", constructor, "Missing constructor value in behavior class %s", behaviorClass.m_name.c_str());
  116. if (constructor && constructor->GetNumArguments() == numArgsPlusSelf)
  117. {
  118. bool match = true;
  119. for (size_t index = 0; index < args.size(); ++index)
  120. {
  121. const AZ::BehaviorParameter* behaviorArg = constructor->GetArgument(index + 1);
  122. pybind11::object pythonArg = args[index];
  123. if (!behaviorArg || !CanConvertPythonToBehaviorValue(*behaviorArg, pythonArg))
  124. {
  125. match = false;
  126. break;
  127. }
  128. }
  129. if (match)
  130. {
  131. // prepare wrapped object instance
  132. m_wrappedObject.m_address = behaviorClass.Allocate();
  133. m_wrappedObject.m_typeId = behaviorClass.m_typeId;
  134. PrepareWrappedObject(behaviorClass);
  135. // execute constructor
  136. Call::ClassMethod(constructor, m_wrappedObject, args);
  137. return pybind11::cast(this);
  138. }
  139. }
  140. }
  141. return pybind11::cast<pybind11::none>(Py_None);
  142. }
  143. bool PythonProxyObject::CanConvertPythonToBehaviorValue(const AZ::BehaviorParameter& behaviorArg, pybind11::object pythonArg) const
  144. {
  145. bool canConvert = false;
  146. PythonMarshalTypeRequestBus::EventResult(
  147. canConvert,
  148. behaviorArg.m_typeId,
  149. &PythonMarshalTypeRequestBus::Events::CanConvertPythonToBehaviorValue,
  150. static_cast<PythonMarshalTypeRequests::BehaviorTraits>(behaviorArg.m_traits),
  151. pythonArg);
  152. if (canConvert)
  153. {
  154. return true;
  155. }
  156. // is already a wrapped type?
  157. if (pybind11::isinstance<PythonProxyObject>(pythonArg))
  158. {
  159. auto&& proxyObj = pybind11::cast<PythonProxyObject*>(pythonArg);
  160. if (proxyObj)
  161. {
  162. return behaviorArg.m_azRtti->IsTypeOf(proxyObj->GetWrappedType().value());
  163. }
  164. }
  165. return false;
  166. }
  167. PythonProxyObject::PythonProxyObject(const AZ::BehaviorObject& object)
  168. {
  169. const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(object.m_typeId);
  170. if (behaviorClass)
  171. {
  172. m_wrappedObject = behaviorClass->Clone(object);
  173. PrepareWrappedObject(*behaviorClass);
  174. }
  175. }
  176. PythonProxyObject::~PythonProxyObject()
  177. {
  178. ReleaseWrappedObject();
  179. }
  180. const char* PythonProxyObject::GetWrappedTypeName() const
  181. {
  182. return m_wrappedObjectTypeName.c_str();
  183. }
  184. void PythonProxyObject::SetPropertyValue(const char* attributeName, pybind11::object value)
  185. {
  186. if (!m_wrappedObject.IsValid())
  187. {
  188. PyErr_SetString(PyExc_RuntimeError, "The wrapped Proxy Object has not been setup correctly; missing call set_type()?");
  189. AZ_Error("python", false, "The wrapped Proxy Object has not been setup correctly; missing call set_type()?");
  190. return;
  191. }
  192. auto behaviorPropertyIter = m_properties.find(AZ::Crc32(attributeName));
  193. if (behaviorPropertyIter != m_properties.end())
  194. {
  195. AZ::BehaviorProperty* property = behaviorPropertyIter->second;
  196. AZ_Error("python", property->m_setter, "%s is not a writable property in class %s.", attributeName, m_wrappedObjectTypeName.c_str());
  197. if (property->m_setter)
  198. {
  199. EditorPythonBindings::Call::ClassMethod(property->m_setter, m_wrappedObject, pybind11::args(pybind11::make_tuple(value)));
  200. }
  201. }
  202. }
  203. pybind11::object PythonProxyObject::GetPropertyValue(const char* attributeName)
  204. {
  205. if (!m_wrappedObject.IsValid())
  206. {
  207. PyErr_SetString(PyExc_RuntimeError, "The wrapped Proxy Object has not been setup correctly; missing call set_type()?");
  208. AZ_Error("python", false, "The wrapped Proxy Object has not been setup correctly; missing call set_type()?");
  209. return pybind11::cast<pybind11::none>(Py_None);
  210. }
  211. AZ::Crc32 crcAttributeName(attributeName);
  212. // the attribute could refer to a method
  213. auto methodEntry = m_methods.find(crcAttributeName);
  214. if (methodEntry != m_methods.end())
  215. {
  216. AZ::BehaviorMethod* method = methodEntry->second;
  217. return pybind11::cpp_function([this, method](pybind11::args pythonArgs)
  218. {
  219. return EditorPythonBindings::Call::ClassMethod(method, m_wrappedObject, pythonArgs);
  220. });
  221. }
  222. // the attribute could refer to a property
  223. auto behaviorPropertyIter = m_properties.find(crcAttributeName);
  224. if (behaviorPropertyIter != m_properties.end())
  225. {
  226. AZ::BehaviorProperty* property = behaviorPropertyIter->second;
  227. AZ_Error("python", property->m_getter, "%s is not a readable property in class %s.", attributeName, m_wrappedObjectTypeName.c_str());
  228. if (property->m_getter)
  229. {
  230. return EditorPythonBindings::Call::ClassMethod(property->m_getter, m_wrappedObject, pybind11::args());
  231. }
  232. }
  233. return pybind11::cast<pybind11::none>(Py_None);
  234. }
  235. bool PythonProxyObject::SetByTypeName(const char* typeName)
  236. {
  237. const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(AZStd::string(typeName));
  238. if (behaviorClass)
  239. {
  240. return CreateDefault(behaviorClass);
  241. }
  242. return false;
  243. }
  244. pybind11::object PythonProxyObject::Invoke(const char* methodName, pybind11::args pythonArgs)
  245. {
  246. const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(m_wrappedObject.m_typeId);
  247. if (behaviorClass)
  248. {
  249. auto behaviorMethodIter = behaviorClass->m_methods.find(methodName);
  250. if (behaviorMethodIter != behaviorClass->m_methods.end())
  251. {
  252. AZ::BehaviorMethod* method = behaviorMethodIter->second;
  253. AZ_Error("python", method, "%s is not a method in class %s!", methodName, m_wrappedObjectTypeName.c_str());
  254. if (method && PythonProxyObjectManagement::IsMemberLike(*method, m_wrappedObject.m_typeId))
  255. {
  256. return EditorPythonBindings::Call::ClassMethod(method, m_wrappedObject, pythonArgs);
  257. }
  258. }
  259. }
  260. return pybind11::cast<pybind11::none>(Py_None);
  261. }
  262. AZStd::optional<AZ::TypeId> PythonProxyObject::GetWrappedType() const
  263. {
  264. if (m_wrappedObject.IsValid())
  265. {
  266. return AZStd::make_optional(m_wrappedObject.m_typeId);
  267. }
  268. return AZStd::nullopt;
  269. }
  270. AZStd::optional<AZ::BehaviorObject*> PythonProxyObject::GetBehaviorObject()
  271. {
  272. if (m_wrappedObject.IsValid())
  273. {
  274. return AZStd::make_optional(&m_wrappedObject);
  275. }
  276. return AZStd::nullopt;
  277. }
  278. void PythonProxyObject::PrepareWrappedObject(const AZ::BehaviorClass& behaviorClass)
  279. {
  280. m_ownership = Ownership::Owned;
  281. m_wrappedObjectTypeName = behaviorClass.m_name;
  282. // is this Behavior Class flagged to usage for tool bindings?
  283. if (!Scope::IsBehaviorFlaggedForEditor(behaviorClass.m_attributes))
  284. {
  285. return;
  286. }
  287. PopulateComparisonOperators(behaviorClass);
  288. PopulateMethodsAndProperties(behaviorClass);
  289. for (auto&& baseClassId : behaviorClass.m_baseClasses)
  290. {
  291. const AZ::BehaviorClass* baseClass = AZ::BehaviorContextHelper::GetClass(baseClassId);
  292. if (baseClass)
  293. {
  294. PopulateMethodsAndProperties(*baseClass);
  295. }
  296. }
  297. }
  298. void PythonProxyObject::PopulateComparisonOperators(const AZ::BehaviorClass& behaviorClass)
  299. {
  300. using namespace AZ::Script;
  301. for (auto&& equalMethodCandidatePair : behaviorClass.m_methods)
  302. {
  303. const AZ::AttributeArray& attributes = equalMethodCandidatePair.second->m_attributes;
  304. AZ::Attribute* operatorAttribute = AZ::FindAttribute(Attributes::Operator, attributes);
  305. if (!operatorAttribute)
  306. {
  307. continue;
  308. }
  309. Attributes::OperatorType operatorType;
  310. AZ::AttributeReader scopeAttributeReader(nullptr, operatorAttribute);
  311. if (!scopeAttributeReader.Read<Attributes::OperatorType>(operatorType))
  312. {
  313. continue;
  314. }
  315. AZ::Crc32 namedKey;
  316. if (operatorType == Attributes::OperatorType::Equal)
  317. {
  318. namedKey = AZ::Crc32{ Operator::s_isEqual };
  319. }
  320. else if (operatorType == Attributes::OperatorType::LessThan)
  321. {
  322. namedKey = AZ::Crc32{ Operator::s_lessThan };
  323. }
  324. else if (operatorType == Attributes::OperatorType::LessEqualThan)
  325. {
  326. namedKey = AZ::Crc32{ Operator::s_lessThanOrEqual };
  327. }
  328. else
  329. {
  330. continue;
  331. }
  332. if (m_methods.find(namedKey) == m_methods.end())
  333. {
  334. m_methods[namedKey] = equalMethodCandidatePair.second;
  335. }
  336. }
  337. }
  338. void PythonProxyObject::PopulateMethodsAndProperties(const AZ::BehaviorClass& behaviorClass)
  339. {
  340. AZStd::string baseName;
  341. // cache all the methods for this behavior class
  342. for (const auto& methodEntry : behaviorClass.m_methods)
  343. {
  344. AZ::BehaviorMethod* method = methodEntry.second;
  345. AZ_Error("python", method, "Missing method entry:%s value in behavior class:%s", methodEntry.first.c_str(), m_wrappedObjectTypeName.c_str());
  346. if (method && PythonProxyObjectManagement::IsMemberLike(*method, m_wrappedObject.m_typeId))
  347. {
  348. baseName = methodEntry.first;
  349. Scope::FetchScriptName(method->m_attributes, baseName);
  350. AZ::Crc32 namedKey(baseName);
  351. if (m_methods.find(namedKey) == m_methods.end())
  352. {
  353. m_methods[namedKey] = method;
  354. }
  355. else
  356. {
  357. AZ_TracePrintf("python", "Skipping duplicate method named %s\n", baseName.c_str());
  358. }
  359. }
  360. }
  361. // cache all the properties for this behavior class
  362. for (const auto& behaviorProperty : behaviorClass.m_properties)
  363. {
  364. AZ::BehaviorProperty* property = behaviorProperty.second;
  365. AZ_Error("python", property, "Missing property %s in behavior class:%s", behaviorProperty.first.c_str(), m_wrappedObjectTypeName.c_str());
  366. if (property)
  367. {
  368. baseName = behaviorProperty.first;
  369. Scope::FetchScriptName(property->m_attributes, baseName);
  370. AZ::Crc32 namedKey(baseName);
  371. if (m_properties.find(namedKey) == m_properties.end())
  372. {
  373. m_properties[namedKey] = property;
  374. }
  375. else
  376. {
  377. AZ_TracePrintf("python", "Skipping duplicate property named %s\n", baseName.c_str());
  378. }
  379. }
  380. }
  381. }
  382. pybind11::object PythonProxyObject::GetWrappedObjectRepr()
  383. {
  384. const AZ::Crc32 reprNamedKey { Builtins::s_repr };
  385. // Attempt to call the object's __repr__ implementation first to get the most accurate representation.
  386. AZ::BehaviorMethod* reprMethod = nullptr;
  387. auto methodEntry = m_methods.find(reprNamedKey);
  388. if (methodEntry != m_methods.end())
  389. {
  390. reprMethod = methodEntry->second;
  391. pybind11::object result = Call::ClassMethod(reprMethod, m_wrappedObject, pybind11::args());
  392. if (!result.is_none())
  393. {
  394. return result;
  395. }
  396. else
  397. {
  398. AZ_Warning("python", false, "The %s method in type (%s) did not return a valid value.", Builtins::s_repr, m_wrappedObjectTypeName.c_str());
  399. }
  400. }
  401. // There's no __repr__ implementation in the object, so use a basic representation and cache it.
  402. AZ_Warning("python", false, "The type (%s) does not implement the %s method.", m_wrappedObjectTypeName.c_str(), Builtins::s_repr);
  403. if (m_wrappedObjectCachedRepr.empty())
  404. {
  405. pybind11::module builtinsModule = pybind11::module::import("builtins");
  406. auto idFunc = builtinsModule.attr("id");
  407. pybind11::object resId = idFunc(this);
  408. AZStd::string wrappedObjectId = pybind11::str(resId).operator std::string().c_str();
  409. m_wrappedObjectCachedRepr = AZStd::string::format("<%s via PythonProxyObject at %s>", m_wrappedObjectTypeName.c_str(), wrappedObjectId.c_str());
  410. }
  411. return pybind11::str(m_wrappedObjectCachedRepr.c_str());
  412. }
  413. pybind11::object PythonProxyObject::GetWrappedObjectStr()
  414. {
  415. // Inspect methods with attributes to find the ToString attribute
  416. AZ::BehaviorMethod* strMethod = nullptr;
  417. using namespace AZ::Script;
  418. for (auto&& strMethodCandidatePair : m_methods)
  419. {
  420. const AZ::AttributeArray& attributes = strMethodCandidatePair.second->m_attributes;
  421. AZ::Attribute* operatorAttribute = AZ::FindAttribute(Attributes::Operator, attributes);
  422. if (!operatorAttribute)
  423. {
  424. continue;
  425. }
  426. Attributes::OperatorType operatorType;
  427. AZ::AttributeReader scopeAttributeReader(nullptr, operatorAttribute);
  428. if (!scopeAttributeReader.Read<Attributes::OperatorType>(operatorType))
  429. {
  430. continue;
  431. }
  432. if (operatorType == Attributes::OperatorType::ToString)
  433. {
  434. if (strMethod == nullptr)
  435. {
  436. strMethod = strMethodCandidatePair.second;
  437. }
  438. else
  439. {
  440. AZ_Warning("python", false, "The type (%s) has more than one method with OperatorType::ToString, using the first found.", m_wrappedObjectTypeName.c_str());
  441. break;
  442. }
  443. }
  444. }
  445. if (strMethod != nullptr)
  446. {
  447. pybind11::object result = Call::ClassMethod(strMethod, m_wrappedObject, pybind11::args());
  448. if (!result.is_none())
  449. {
  450. return result;
  451. }
  452. else
  453. {
  454. AZ_Warning("python", false, "The %s method in type (%s) did not return a valid value.", Builtins::s_str, m_wrappedObjectTypeName.c_str());
  455. }
  456. }
  457. // Fallback to __repr__ because there's no __str__ implementation in the object,
  458. // so use a basic representation and cache it.
  459. AZ_TracePrintf("python", "The type (%s) does not implement the %s method or did not return a valid value, trying %s.", m_wrappedObjectTypeName.c_str(), Builtins::s_str, Builtins::s_repr);
  460. return GetWrappedObjectRepr();
  461. }
  462. void PythonProxyObject::ReleaseWrappedObject()
  463. {
  464. if (m_wrappedObject.IsValid() && m_ownership == Ownership::Owned)
  465. {
  466. const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(m_wrappedObject.m_typeId);
  467. if (behaviorClass)
  468. {
  469. behaviorClass->Destroy(m_wrappedObject);
  470. m_wrappedObject = {};
  471. m_wrappedObjectTypeName.clear();
  472. m_wrappedObjectCachedRepr.clear();
  473. m_methods.clear();
  474. m_properties.clear();
  475. }
  476. }
  477. }
  478. bool PythonProxyObject::CreateDefault(const AZ::BehaviorClass* behaviorClass)
  479. {
  480. AZ_Error("python", behaviorClass, "Expecting a non-null BehaviorClass");
  481. if (behaviorClass)
  482. {
  483. if (Scope::IsBehaviorFlaggedForEditor(behaviorClass->m_attributes))
  484. {
  485. m_wrappedObject = behaviorClass->Create();
  486. PrepareWrappedObject(*behaviorClass);
  487. return true;
  488. }
  489. AZ_Warning("python", false, "The behavior class (%s) is not flagged for Editor use.", behaviorClass->m_name.c_str());
  490. }
  491. return false;
  492. }
  493. bool PythonProxyObject::DoEqualityEvaluation(pybind11::object pythonOther)
  494. {
  495. constexpr AZ::Crc32 namedEqKey(Operator::s_isEqual);
  496. auto&& equalOperatorMethodEntry = m_methods.find(namedEqKey);
  497. if (equalOperatorMethodEntry != m_methods.end())
  498. {
  499. AZ::BehaviorMethod* method = equalOperatorMethodEntry->second;
  500. pybind11::object result = Call::ClassMethod(method, m_wrappedObject, pybind11::args(pybind11::make_tuple(pythonOther)));
  501. if (result.is_none())
  502. {
  503. return false;
  504. }
  505. return result.cast<bool>();
  506. }
  507. return false;
  508. }
  509. bool PythonProxyObject::DoComparisonEvaluation(pybind11::object pythonOther, Comparison comparison)
  510. {
  511. bool invertLogic = false;
  512. AZ::Crc32 namedKey;
  513. if (comparison == Comparison::LessThan)
  514. {
  515. namedKey = AZ::Crc32{ Operator::s_lessThan };
  516. }
  517. else if (comparison == Comparison::LessThanOrEquals)
  518. {
  519. namedKey = AZ::Crc32{ Operator::s_lessThanOrEqual };
  520. }
  521. else if (comparison == Comparison::GreaterThan)
  522. {
  523. namedKey = AZ::Crc32{ Operator::s_lessThan };
  524. invertLogic = true;
  525. }
  526. else if (comparison == Comparison::GreaterThanOrEquals)
  527. {
  528. namedKey = AZ::Crc32{ Operator::s_lessThan };
  529. invertLogic = true;
  530. }
  531. else
  532. {
  533. return false;
  534. }
  535. auto&& equalOperatorMethodEntry = m_methods.find(namedKey);
  536. if (equalOperatorMethodEntry != m_methods.end())
  537. {
  538. AZ::BehaviorMethod* method = equalOperatorMethodEntry->second;
  539. pybind11::object result = Call::ClassMethod(method, m_wrappedObject, pybind11::args(pybind11::make_tuple(pythonOther)));
  540. if (result.is_none())
  541. {
  542. return false;
  543. }
  544. else if (invertLogic)
  545. {
  546. const bool greaterThanResult = !result.cast<bool>();
  547. // an additional check for "GreaterThanOrEquals" if the result of "LessThan" failed since the invert
  548. // of '3 <= 3' would fail since the 'or equals' would return true and be inverted to false
  549. if (comparison == Comparison::GreaterThanOrEquals && greaterThanResult == false)
  550. {
  551. return DoEqualityEvaluation(pythonOther);
  552. }
  553. return greaterThanResult;
  554. }
  555. return result.cast<bool>();
  556. }
  557. return false;
  558. }
  559. namespace PythonProxyObjectManagement
  560. {
  561. bool IsMemberLike(const AZ::BehaviorMethod& method, const AZ::TypeId& typeId)
  562. {
  563. return method.IsMember() || (method.GetNumArguments() > 0 && method.GetArgument(0)->m_typeId == typeId);
  564. }
  565. bool IsClassConstant(const AZ::BehaviorProperty* property)
  566. {
  567. bool value = false;
  568. AZ::Attribute* classConstantAttribute = AZ::FindAttribute(AZ::Script::Attributes::ClassConstantValue, property->m_attributes);
  569. if (classConstantAttribute)
  570. {
  571. AZ::AttributeReader attributeReader(nullptr, classConstantAttribute);
  572. attributeReader.Read<bool>(value);
  573. }
  574. return value;
  575. }
  576. pybind11::object CreatePythonProxyObject(const AZ::TypeId& typeId, void* data)
  577. {
  578. PythonProxyObject* instance = nullptr;
  579. if (!data)
  580. {
  581. instance = aznew PythonProxyObject(typeId);
  582. }
  583. else
  584. {
  585. instance = aznew PythonProxyObject(AZ::BehaviorObject(data, typeId));
  586. }
  587. if (!instance->GetWrappedType())
  588. {
  589. delete instance;
  590. PyErr_SetString(PyExc_TypeError, "Failed to create proxy object by type name.");
  591. return pybind11::cast<pybind11::none>(Py_None);
  592. }
  593. return pybind11::cast(instance);
  594. }
  595. pybind11::object CreatePythonProxyObjectByTypename(const char* classTypename)
  596. {
  597. const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(AZStd::string(classTypename));
  598. AZ_Warning("python", behaviorClass, "Missing Behavior Class for typename:%s", classTypename);
  599. if (!behaviorClass)
  600. {
  601. return pybind11::cast<pybind11::none>(Py_None);
  602. }
  603. return CreatePythonProxyObject(behaviorClass->m_typeId, nullptr);
  604. }
  605. pybind11::object ConstructPythonProxyObjectByTypename(const char* classTypename, pybind11::args args)
  606. {
  607. const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(AZStd::string(classTypename));
  608. AZ_Warning("python", behaviorClass, "Missing Behavior Class for typename:%s", classTypename);
  609. if (!behaviorClass)
  610. {
  611. return pybind11::cast<pybind11::none>(Py_None);
  612. }
  613. PythonProxyObject* instance = aznew PythonProxyObject();
  614. pybind11::object pythonInstance = instance->Construct(*behaviorClass, args);
  615. if (pythonInstance.is_none())
  616. {
  617. delete instance;
  618. PyErr_SetString(PyExc_TypeError, "Failed to construct proxy object with provided args.");
  619. return pybind11::cast<pybind11::none>(Py_None);
  620. }
  621. return pybind11::cast(instance);
  622. }
  623. void ExportStaticBehaviorClassElements(pybind11::module parentModule, pybind11::module defaultModule)
  624. {
  625. AZ::BehaviorContext* behaviorContext = nullptr;
  626. AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
  627. AZ_Error("python", behaviorContext, "Behavior context not available");
  628. if (!behaviorContext)
  629. {
  630. return;
  631. }
  632. // this will make the base package modules for namespace "azlmbr.*" and "azlmbr.default" for behavior that does not specify a module name
  633. Module::PackageMapType modulePackageMap;
  634. for (const auto& classEntry : behaviorContext->m_classes)
  635. {
  636. AZ::BehaviorClass* behaviorClass = classEntry.second;
  637. // is this Behavior Class flagged to usage for Editor.exe bindings?
  638. if (!Scope::IsBehaviorFlaggedForEditor(behaviorClass->m_attributes))
  639. {
  640. continue; // skip this class
  641. }
  642. // find the target module of the behavior's static methods
  643. auto moduleName = Module::GetName(behaviorClass->m_attributes);
  644. pybind11::module subModule = Module::DeterminePackageModule(modulePackageMap, moduleName ? *moduleName : "", parentModule, defaultModule, false);
  645. // early detection of instance based elements like constructors or properties
  646. bool hasMemberMethods = behaviorClass->m_constructors.empty() == false;
  647. bool hasMemberProperties = behaviorClass->m_properties.empty() == false;
  648. // does this class define methods that may be reflected in a Python module?
  649. if (!behaviorClass->m_methods.empty())
  650. {
  651. // add the non-member methods as Python 'free' function
  652. for (const auto& methodEntry : behaviorClass->m_methods)
  653. {
  654. const AZStd::string& methodName = methodEntry.first;
  655. AZ::BehaviorMethod* behaviorMethod = methodEntry.second;
  656. if (!PythonProxyObjectManagement::IsMemberLike(*behaviorMethod, behaviorClass->m_typeId))
  657. {
  658. // the name of the static method will be "azlmbr.<sub_module>.<Behavior Class>_<Behavior Method>"
  659. AZStd::string globalMethodName = AZStd::string::format("%s_%s", behaviorClass->m_name.c_str(), methodName.c_str());
  660. if (behaviorMethod->HasResult())
  661. {
  662. subModule.def(globalMethodName.c_str(), [behaviorMethod](pybind11::args args)
  663. {
  664. return Call::StaticMethod(behaviorMethod, args);
  665. });
  666. }
  667. else
  668. {
  669. subModule.def(globalMethodName.c_str(), [behaviorMethod](pybind11::args args)
  670. {
  671. Call::StaticMethod(behaviorMethod, args);
  672. });
  673. }
  674. AZStd::string subModuleName = pybind11::cast<AZStd::string>(subModule.attr("__name__"));
  675. PythonSymbolEventBus::QueueBroadcast(&PythonSymbolEventBus::Events::LogClassMethod, subModuleName, globalMethodName, behaviorClass, behaviorMethod);
  676. }
  677. else
  678. {
  679. // any member method means the class should be exported to Python
  680. hasMemberMethods = true;
  681. }
  682. }
  683. }
  684. // expose all the constant class properties for Python to use
  685. for (const auto& propertyEntry : behaviorClass->m_properties)
  686. {
  687. const AZStd::string& propertyEntryName = propertyEntry.first;
  688. AZ::BehaviorProperty* behaviorProperty = propertyEntry.second;
  689. if (IsClassConstant(behaviorProperty))
  690. {
  691. // the name of the property will be "azlmbr.<Module>.<Behavior Class>_<Behavior Property>"
  692. AZStd::string constantPropertyName =
  693. AZStd::string::format("%s_%s", behaviorClass->m_name.c_str(), propertyEntryName.c_str());
  694. pybind11::object constantValue = Call::StaticMethod(behaviorProperty->m_getter, {});
  695. pybind11::setattr(subModule, constantPropertyName.c_str(), constantValue);
  696. AZStd::string subModuleName = pybind11::cast<AZStd::string>(subModule.attr("__name__"));
  697. PythonSymbolEventBus::QueueBroadcast(&PythonSymbolEventBus::Events::LogGlobalProperty, subModuleName, constantPropertyName, behaviorProperty);
  698. }
  699. }
  700. // if the Behavior Class has any properties, methods, or constructors then export it
  701. const bool exportBehaviorClass = (hasMemberMethods || hasMemberProperties);
  702. // register all Behavior Class types with a Python function to construct an instance
  703. if (exportBehaviorClass)
  704. {
  705. const char* behaviorClassName = behaviorClass->m_name.c_str();
  706. subModule.attr(behaviorClassName) = pybind11::cpp_function([behaviorClassName](pybind11::args pythonArgs)
  707. {
  708. return ConstructPythonProxyObjectByTypename(behaviorClassName, pythonArgs);
  709. });
  710. AZStd::string subModuleName = pybind11::cast<AZStd::string>(subModule.attr("__name__"));
  711. // register an alternative class name that passes the Python syntax
  712. auto syntaxName = Naming::GetPythonSyntax(*behaviorClass);
  713. if (syntaxName)
  714. {
  715. const char* properSyntax = syntaxName.value().c_str();
  716. subModule.attr(properSyntax) = pybind11::cpp_function([behaviorClassName](pybind11::args pythonArgs)
  717. {
  718. return ConstructPythonProxyObjectByTypename(behaviorClassName, pythonArgs);
  719. });
  720. PythonSymbolEventBus::QueueBroadcast(&PythonSymbolEventBus::Events::LogClassWithName, subModuleName, behaviorClass, properSyntax);
  721. }
  722. else
  723. {
  724. PythonSymbolEventBus::QueueBroadcast(&PythonSymbolEventBus::Events::LogClass, subModuleName, behaviorClass);
  725. }
  726. }
  727. }
  728. }
  729. pybind11::list ListBehaviorAttributes(const PythonProxyObject& pythonProxyObject)
  730. {
  731. pybind11::list items;
  732. AZStd::string baseName;
  733. auto typeId = pythonProxyObject.GetWrappedType();
  734. if (!typeId)
  735. {
  736. return items;
  737. }
  738. const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(typeId.value());
  739. if (!behaviorClass)
  740. {
  741. return items;
  742. }
  743. if (!Scope::IsBehaviorFlaggedForEditor(behaviorClass->m_attributes))
  744. {
  745. return items;
  746. }
  747. for (const auto& methodEntry : behaviorClass->m_methods)
  748. {
  749. AZ::BehaviorMethod* method = methodEntry.second;
  750. if (method && PythonProxyObjectManagement::IsMemberLike(*method, typeId.value()))
  751. {
  752. baseName = methodEntry.first;
  753. Scope::FetchScriptName(method->m_attributes, baseName);
  754. items.append(pybind11::str(baseName.c_str()));
  755. }
  756. }
  757. for (const auto& behaviorProperty : behaviorClass->m_properties)
  758. {
  759. AZ::BehaviorProperty* property = behaviorProperty.second;
  760. if (property)
  761. {
  762. baseName = behaviorProperty.first;
  763. Scope::FetchScriptName(property->m_attributes, baseName);
  764. items.append(pybind11::str(baseName.c_str()));
  765. }
  766. }
  767. return items;
  768. }
  769. pybind11::list ListBehaviorClasses(bool onlyIncludeScopedForAutomation)
  770. {
  771. pybind11::list items;
  772. AZ::BehaviorContext* behaviorContext(nullptr);
  773. AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
  774. if (!behaviorContext)
  775. {
  776. AZ_Error("python", false, "A behavior context is required!");
  777. return items;
  778. }
  779. for (auto&& classEntry : behaviorContext->m_classes)
  780. {
  781. auto&& behaviorClass = classEntry.second;
  782. if (onlyIncludeScopedForAutomation )
  783. {
  784. if (Scope::IsBehaviorFlaggedForEditor(behaviorClass->m_attributes))
  785. {
  786. items.append(pybind11::str(classEntry.first.c_str()));
  787. }
  788. }
  789. else
  790. {
  791. items.append(pybind11::str(classEntry.first.c_str()));
  792. }
  793. }
  794. return items;
  795. }
  796. void CreateSubmodule(pybind11::module parentModule, pybind11::module defaultModule)
  797. {
  798. ExportStaticBehaviorClassElements(parentModule, defaultModule);
  799. auto objectModule = parentModule.def_submodule("object");
  800. objectModule.def("create", &CreatePythonProxyObjectByTypename);
  801. objectModule.def("construct", &ConstructPythonProxyObjectByTypename);
  802. objectModule.def("dir", &ListBehaviorAttributes);
  803. objectModule.def("list_classes", &ListBehaviorClasses, pybind11::arg("onlyIncludeScopedForAutomation") = true);
  804. pybind11::class_<PythonProxyObject>(objectModule, "PythonProxyObject", pybind11::dynamic_attr())
  805. .def(pybind11::init<>())
  806. .def(pybind11::init<const char*>())
  807. .def_property_readonly("typename", &PythonProxyObject::GetWrappedTypeName)
  808. .def("set_type", &PythonProxyObject::SetByTypeName)
  809. .def("set_property", &PythonProxyObject::SetPropertyValue)
  810. .def("get_property", &PythonProxyObject::GetPropertyValue)
  811. .def("invoke", &PythonProxyObject::Invoke)
  812. .def(Operator::s_isEqual, [](PythonProxyObject& self, pybind11::object rhs)
  813. {
  814. return self.DoEqualityEvaluation(rhs);
  815. })
  816. .def(Operator::s_notEqual, [](PythonProxyObject& self, pybind11::object rhs)
  817. {
  818. return self.DoEqualityEvaluation(rhs) == false;
  819. })
  820. .def(Operator::s_greaterThan, [](PythonProxyObject& self, pybind11::object rhs)
  821. {
  822. return self.DoComparisonEvaluation(rhs, PythonProxyObject::Comparison::GreaterThan);
  823. })
  824. .def(Operator::s_greaterThanOrEqual, [](PythonProxyObject& self, pybind11::object rhs)
  825. {
  826. return self.DoComparisonEvaluation(rhs, PythonProxyObject::Comparison::GreaterThanOrEquals);
  827. })
  828. .def(Operator::s_lessThan, [](PythonProxyObject& self, pybind11::object rhs)
  829. {
  830. return self.DoComparisonEvaluation(rhs, PythonProxyObject::Comparison::LessThan);
  831. })
  832. .def(Operator::s_lessThanOrEqual, [](PythonProxyObject& self, pybind11::object rhs)
  833. {
  834. return self.DoComparisonEvaluation(rhs, PythonProxyObject::Comparison::LessThanOrEquals);
  835. })
  836. .def("__setattr__", &PythonProxyObject::SetPropertyValue)
  837. .def("__getattr__", &PythonProxyObject::GetPropertyValue)
  838. .def(Builtins::s_repr, [](PythonProxyObject& self)
  839. {
  840. return self.GetWrappedObjectRepr();
  841. })
  842. .def(Builtins::s_str, [](PythonProxyObject& self)
  843. {
  844. return self.GetWrappedObjectStr();
  845. })
  846. ;
  847. }
  848. }
  849. }