PythonProxyObject.cpp 42 KB

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