2
0

GraphObjectProxy.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  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 <SceneAPI/SceneCore/Containers/GraphObjectProxy.h>
  9. #include <AzCore/Casting/numeric_cast.h>
  10. #include <AzCore/RTTI/BehaviorContext.h>
  11. #include <AzCore/Serialization/SerializeContext.h>
  12. #include <AzCore/std/smart_ptr/make_shared.h>
  13. #include <AzFramework/StringFunc/StringFunc.h>
  14. #include <AzToolsFramework/API/EditorPythonConsoleBus.h>
  15. namespace AZ
  16. {
  17. namespace Python
  18. {
  19. static const char* const None = "None";
  20. PythonBehaviorInfo::PythonBehaviorInfo(const AZ::BehaviorClass* behaviorClass)
  21. : m_behaviorClass(behaviorClass)
  22. {
  23. AZ_Assert(m_behaviorClass, "PythonBehaviorInfo requires a valid behaviorClass pointer");
  24. for (const auto& method : behaviorClass->m_methods)
  25. {
  26. PrepareMethod(method.first, *method.second);
  27. }
  28. for (const auto& property : behaviorClass->m_properties)
  29. {
  30. PrepareProperty(property.first, *property.second);
  31. }
  32. }
  33. void PythonBehaviorInfo::Reflect(AZ::ReflectContext* context)
  34. {
  35. AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
  36. if (behaviorContext)
  37. {
  38. behaviorContext->Class<PythonBehaviorInfo>()
  39. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  40. ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::RuntimeOwn)
  41. ->Attribute(AZ::Script::Attributes::Module, "scene.graph")
  42. ->Property("className", [](const PythonBehaviorInfo& self)
  43. { return self.m_behaviorClass->m_name; }, nullptr)
  44. ->Property("classUuid", [](const PythonBehaviorInfo& self)
  45. { return self.m_behaviorClass->m_typeId.ToString<AZStd::string>(); }, nullptr)
  46. ->Property("methodList", BehaviorValueGetter(&PythonBehaviorInfo::m_methodList), nullptr)
  47. ->Property("propertyList", BehaviorValueGetter(&PythonBehaviorInfo::m_propertyList), nullptr)
  48. ;
  49. }
  50. }
  51. bool PythonBehaviorInfo::IsMemberLike(const AZ::BehaviorMethod& method, const AZ::TypeId& typeId) const
  52. {
  53. return method.IsMember() || (method.GetNumArguments() > 0 && method.GetArgument(0)->m_typeId == typeId);
  54. }
  55. AZStd::string PythonBehaviorInfo::FetchPythonType(const AZ::BehaviorParameter& param) const
  56. {
  57. using namespace AzToolsFramework;
  58. EditorPythonConsoleInterface* editorPythonConsoleInterface = AZ::Interface<EditorPythonConsoleInterface>::Get();
  59. if (editorPythonConsoleInterface)
  60. {
  61. return editorPythonConsoleInterface->FetchPythonTypeName(param);
  62. }
  63. return None;
  64. }
  65. void PythonBehaviorInfo::PrepareMethod(AZStd::string_view methodName, const AZ::BehaviorMethod& behaviorMethod)
  66. {
  67. // if the method is a static method then it is not a part of the abstract class
  68. const bool isMemberLike = IsMemberLike(behaviorMethod, m_behaviorClass->m_typeId);
  69. if (isMemberLike == false)
  70. {
  71. return;
  72. }
  73. AZStd::string buffer;
  74. AZStd::vector<AZStd::string> pythonArgs;
  75. AzFramework::StringFunc::Append(buffer, "def ");
  76. AzFramework::StringFunc::Append(buffer, methodName.data());
  77. AzFramework::StringFunc::Append(buffer, "(");
  78. pythonArgs.emplace_back("self");
  79. AZStd::string bufferArg;
  80. for (size_t argIndex = 1; argIndex < behaviorMethod.GetNumArguments(); ++argIndex)
  81. {
  82. const AZStd::string* name = behaviorMethod.GetArgumentName(argIndex);
  83. if (!name || name->empty())
  84. {
  85. bufferArg = AZStd::string::format(" arg%zu", argIndex);
  86. }
  87. else
  88. {
  89. bufferArg = *name;
  90. }
  91. AZStd::string type = FetchPythonType(*behaviorMethod.GetArgument(argIndex));
  92. if (!type.empty())
  93. {
  94. AzFramework::StringFunc::Append(bufferArg, ": ");
  95. AzFramework::StringFunc::Append(bufferArg, type.data());
  96. }
  97. pythonArgs.push_back(bufferArg);
  98. bufferArg.clear();
  99. }
  100. AZStd::string resultValue{ None };
  101. if (behaviorMethod.HasResult() && behaviorMethod.GetResult())
  102. {
  103. resultValue = FetchPythonType(*behaviorMethod.GetResult());
  104. }
  105. AZStd::string argsList;
  106. AzFramework::StringFunc::Join(buffer, pythonArgs.begin(), pythonArgs.end(), ",");
  107. AzFramework::StringFunc::Append(buffer, ") -> ");
  108. AzFramework::StringFunc::Append(buffer, resultValue.c_str());
  109. m_methodList.emplace_back(buffer);
  110. }
  111. void PythonBehaviorInfo::PrepareProperty(AZStd::string_view propertyName, const AZ::BehaviorProperty& behaviorProperty)
  112. {
  113. AZStd::string buffer;
  114. AZStd::vector<AZStd::string> pythonArgs;
  115. AzFramework::StringFunc::Append(buffer, propertyName.data());
  116. AzFramework::StringFunc::Append(buffer, "(");
  117. if (behaviorProperty.m_setter)
  118. {
  119. AZStd::string type = FetchPythonType(*behaviorProperty.m_setter->GetArgument(1));
  120. AzFramework::StringFunc::Append(buffer, type.data());
  121. }
  122. AzFramework::StringFunc::Append(buffer, ")");
  123. if (behaviorProperty.m_getter)
  124. {
  125. AZStd::string type = FetchPythonType(*behaviorProperty.m_getter->GetResult());
  126. AzFramework::StringFunc::Append(buffer, "->");
  127. AzFramework::StringFunc::Append(buffer, type.data());
  128. }
  129. m_propertyList.emplace_back(buffer);
  130. }
  131. }
  132. namespace SceneAPI
  133. {
  134. namespace Containers
  135. {
  136. void GraphObjectProxy::Reflect(AZ::ReflectContext* context)
  137. {
  138. AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
  139. if (behaviorContext)
  140. {
  141. Python::PythonBehaviorInfo::Reflect(context);
  142. behaviorContext->Class<DataTypes::IGraphObject>();
  143. behaviorContext->Class<GraphObjectProxy>()
  144. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  145. ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
  146. ->Attribute(AZ::Script::Attributes::Module, "scene.graph")
  147. ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
  148. ->Method("CastWithTypeName", &GraphObjectProxy::CastWithTypeName)
  149. ->Method("Invoke", &GraphObjectProxy::Invoke)
  150. ->Method("Fetch", &GraphObjectProxy::Fetch)
  151. ->Method("GetClassInfo", [](GraphObjectProxy& self) -> Python::PythonBehaviorInfo*
  152. {
  153. if (self.m_pythonBehaviorInfo)
  154. {
  155. return self.m_pythonBehaviorInfo.get();
  156. }
  157. else if (self.m_behaviorClass)
  158. {
  159. self.m_pythonBehaviorInfo = AZStd::make_shared<Python::PythonBehaviorInfo>(self.m_behaviorClass);
  160. return self.m_pythonBehaviorInfo.get();
  161. }
  162. return nullptr;
  163. })
  164. ;
  165. }
  166. }
  167. GraphObjectProxy::GraphObjectProxy(AZStd::shared_ptr<const DataTypes::IGraphObject> graphObject)
  168. {
  169. m_graphObject = graphObject;
  170. }
  171. GraphObjectProxy::GraphObjectProxy(const GraphObjectProxy& other)
  172. {
  173. m_graphObject = other.m_graphObject;
  174. m_behaviorClass = other.m_behaviorClass;
  175. m_pythonBehaviorInfo = other.m_pythonBehaviorInfo;
  176. }
  177. GraphObjectProxy::~GraphObjectProxy()
  178. {
  179. m_pythonBehaviorInfo.reset();
  180. m_graphObject.reset();
  181. m_behaviorClass = nullptr;
  182. }
  183. bool GraphObjectProxy::CastWithTypeName(const AZStd::string& classTypeName)
  184. {
  185. const AZ::BehaviorClass* behaviorClass = BehaviorContextHelper::GetClass(classTypeName);
  186. if (behaviorClass)
  187. {
  188. const void* baseClass = behaviorClass->m_azRtti->Cast(m_graphObject.get(), behaviorClass->m_azRtti->GetTypeId());
  189. if (baseClass)
  190. {
  191. m_behaviorClass = behaviorClass;
  192. return true;
  193. }
  194. }
  195. return false;
  196. }
  197. AZStd::any GraphObjectProxy::Fetch(AZStd::string_view property)
  198. {
  199. if (!m_behaviorClass)
  200. {
  201. AZ_Warning("SceneAPI", false, "Empty behavior class. Use the CastWithTypeName() to assign the concrete type of IGraphObject to Invoke().");
  202. return AZStd::any(false);
  203. }
  204. auto entry = m_behaviorClass->m_properties.find(property);
  205. if (m_behaviorClass->m_properties.end() == entry)
  206. {
  207. AZ_Warning("SceneAPI", false, "Missing property %.*s from class %s",
  208. aznumeric_cast<int>(property.size()),
  209. property.data(),
  210. m_behaviorClass->m_name.c_str());
  211. return AZStd::any(false);
  212. }
  213. if (!entry->second->m_getter)
  214. {
  215. AZ_Warning("SceneAPI", false, "Property %.*s from class %s has a NULL getter",
  216. aznumeric_cast<int>(property.size()),
  217. property.data(),
  218. m_behaviorClass->m_name.c_str());
  219. return AZStd::any(false);
  220. }
  221. return InvokeBehaviorMethod(entry->second->m_getter, {});
  222. }
  223. AZStd::any GraphObjectProxy::InvokeBehaviorMethod(AZ::BehaviorMethod* behaviorMethod, AZStd::vector<AZStd::any> argList)
  224. {
  225. constexpr size_t behaviorParamListSize = 8;
  226. if (behaviorMethod->GetNumArguments() > behaviorParamListSize)
  227. {
  228. AZ_Error("SceneAPI", false, "Unsupported behavior method; supports max %zu but %s has %zu argument slots",
  229. behaviorParamListSize,
  230. behaviorMethod->m_name.c_str(),
  231. behaviorMethod->GetNumArguments());
  232. return AZStd::any(false);
  233. }
  234. AZ::BehaviorArgument behaviorParamList[behaviorParamListSize];
  235. // detect if the method passes in a "this" pointer which can be true if the method is a member function
  236. // or if the first argument is the same as the behavior class such as from a lambda function
  237. bool hasSelfPointer = behaviorMethod->IsMember();
  238. if (!hasSelfPointer)
  239. {
  240. hasSelfPointer = behaviorMethod->GetArgument(0)->m_typeId == m_behaviorClass->m_typeId;
  241. }
  242. // record the "this" pointer's meta data like its RTTI so that it can be
  243. // down casted to a parent class type if needed to invoke a parent method
  244. // When storing a parameter, if the behavior parameter indicates that it is a pointer, (TR_POINTER flag is set)
  245. // it is expected that what is stored in that parameter is a pointer to the value, more specifically
  246. // it will decode it on the other end by dereferencing it twice, like this:
  247. // if (m_traits & BehaviorParameter::TR_POINTER)
  248. // {
  249. // valueAddress = *reinterpret_cast<void**>(valueAddress); // pointer to a pointer
  250. // }
  251. // Notice the it expects it to be a void** (double reference) and dereferences it to get the address of the object.
  252. //
  253. // That means that when TR_POINTER is set, not only does the object pointed to have to survive until used
  254. // but the memory storing that pointer also has to survive until used.
  255. // for example, this would be a scope use-after-free bug:
  256. // {
  257. // AZ::Entity entity;
  258. // AZ::BehaviorArgument arg;
  259. // arg.m_traits = BehaviorParameter::TR_POINTER;
  260. //
  261. // {
  262. // const void* objectPtr = &entity; // this is a 64-bit object living in the stack, holding the addr of entity.
  263. // arg.m_value = const_cast<void*>(&objectPtr); // notice, we are referencing objectPtr again with & since its TR_POINTER
  264. // }
  265. //
  266. // arg.GetValueAddress(); // double dereference causes memory error.
  267. //
  268. // The above is a memory error because the objectPtr is a stack variable, and the ADDRESS of it is what's stored in m_value.
  269. // to avoid this, we cache the self pointer to the graph object ahead of time.
  270. const void* self = reinterpret_cast<const void*>(m_graphObject.get());
  271. if (const AZ::BehaviorParameter* thisInfo = behaviorMethod->GetArgument(0); hasSelfPointer)
  272. {
  273. // avoiding the "Special handling for the generic object holder." since it assumes
  274. // the BehaviorObject.m_value is a pointer; the reference version is already dereferenced
  275. AZ::BehaviorArgument theThisPointer;
  276. if ((thisInfo->m_traits & AZ::BehaviorParameter::TR_POINTER) == AZ::BehaviorParameter::TR_POINTER)
  277. {
  278. theThisPointer.m_value = &self;
  279. }
  280. else
  281. {
  282. theThisPointer.m_value = const_cast<void*>(self);
  283. }
  284. theThisPointer.Set(*thisInfo);
  285. behaviorParamList[0].Set(theThisPointer);
  286. }
  287. int paramCount = 0;
  288. for (; paramCount < argList.size() && paramCount < behaviorMethod->GetNumArguments(); ++paramCount)
  289. {
  290. size_t behaviorArgIndex = hasSelfPointer ? paramCount + 1 : paramCount;
  291. const AZ::BehaviorParameter* argBehaviorInfo = behaviorMethod->GetArgument(behaviorArgIndex);
  292. if (!Convert(argList[paramCount], argBehaviorInfo, behaviorParamList[behaviorArgIndex]))
  293. {
  294. AZ_Error("SceneAPI", false, "Could not convert from %s to %s at index %zu",
  295. argBehaviorInfo->m_typeId.ToString<AZStd::string>().c_str(),
  296. behaviorParamList[behaviorArgIndex].m_typeId.ToString<AZStd::string>().c_str(),
  297. paramCount);
  298. return AZStd::any(false);
  299. }
  300. }
  301. if (hasSelfPointer)
  302. {
  303. ++paramCount;
  304. }
  305. AZ::BehaviorArgument returnBehaviorValue;
  306. if (behaviorMethod->HasResult())
  307. {
  308. returnBehaviorValue.Set(*behaviorMethod->GetResult());
  309. const size_t typeSize = returnBehaviorValue.m_azRtti->GetTypeSize();
  310. if (returnBehaviorValue.m_traits & BehaviorParameter::TR_POINTER)
  311. {
  312. // Used to allocate storage to store a copy of a pointer and the allocated memory address in one block.
  313. constexpr size_t PointerAllocationStorage = 2 * sizeof(void*);
  314. void* valueAddress = returnBehaviorValue.m_tempData.allocate(PointerAllocationStorage, 16, 0);
  315. void* valueAddressPtr = reinterpret_cast<AZ::u8*>(valueAddress) + sizeof(void*);
  316. ::memset(valueAddress, 0, sizeof(void*));
  317. *reinterpret_cast<void**>(valueAddressPtr) = valueAddress;
  318. returnBehaviorValue.m_value = valueAddressPtr;
  319. }
  320. else if (returnBehaviorValue.m_traits & BehaviorParameter::TR_REFERENCE)
  321. {
  322. // the reference value will just be assigned
  323. returnBehaviorValue.m_value = nullptr;
  324. }
  325. else if (typeSize < returnBehaviorValue.m_tempData.max_size())
  326. {
  327. returnBehaviorValue.m_value = returnBehaviorValue.m_tempData.allocate(typeSize, 16);
  328. }
  329. else
  330. {
  331. AZ_Warning("SceneAPI", false, "Can't invoke method since the return value is too big; %d bytes", typeSize);
  332. return AZStd::any(false);
  333. }
  334. }
  335. if (!behaviorMethod->Call(behaviorParamList, paramCount, &returnBehaviorValue))
  336. {
  337. return AZStd::any(false);
  338. }
  339. if (!behaviorMethod->HasResult())
  340. {
  341. return AZStd::any(true);
  342. }
  343. AZ::SerializeContext* serializeContext = nullptr;
  344. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
  345. if (!serializeContext)
  346. {
  347. AZ_Error("SceneAPI", false, "AZ::SerializeContext should be prepared.");
  348. return AZStd::any(false);
  349. }
  350. // Create temporary any to get its type info to construct a new AZStd::any with new data
  351. AZStd::any tempAny = serializeContext->CreateAny(returnBehaviorValue.m_typeId);
  352. return AZStd::move(AZStd::any(returnBehaviorValue.m_value, tempAny.get_type_info()));
  353. }
  354. AZStd::any GraphObjectProxy::Invoke(AZStd::string_view method, AZStd::vector<AZStd::any> argList)
  355. {
  356. if (!m_behaviorClass)
  357. {
  358. AZ_Warning("SceneAPI", false, "Use the CastWithTypeName() to assign the concrete type of IGraphObject to Invoke().");
  359. return AZStd::any(false);
  360. }
  361. auto entry = m_behaviorClass->m_methods.find(method);
  362. if (m_behaviorClass->m_methods.end() == entry)
  363. {
  364. AZ_Warning("SceneAPI", false, "Missing method %.*s from class %s",
  365. aznumeric_cast<int>(method.size()),
  366. method.data(),
  367. m_behaviorClass->m_name.c_str());
  368. return AZStd::any(false);
  369. }
  370. return InvokeBehaviorMethod(entry->second, argList);
  371. }
  372. template <typename FROM, typename TO>
  373. bool ConvertFromTo(AZStd::any& input, const AZ::BehaviorParameter* argBehaviorInfo, AZ::BehaviorArgument& behaviorParam)
  374. {
  375. if (input.get_type_info().m_id != azrtti_typeid<FROM>())
  376. {
  377. return false;
  378. }
  379. if (argBehaviorInfo->m_typeId != azrtti_typeid<TO>())
  380. {
  381. return false;
  382. }
  383. TO* storage = reinterpret_cast<TO*>(behaviorParam.m_tempData.allocate(argBehaviorInfo->m_azRtti->GetTypeSize(), 16));
  384. *storage = aznumeric_cast<TO>(*AZStd::any_cast<FROM>(&input));
  385. behaviorParam.m_typeId = azrtti_typeid<TO>();
  386. behaviorParam.m_value = storage;
  387. return true;
  388. }
  389. bool GraphObjectProxy::Convert(AZStd::any& input, const AZ::BehaviorParameter* argBehaviorInfo, AZ::BehaviorArgument& behaviorParam)
  390. {
  391. if (input.get_type_info().m_id == argBehaviorInfo->m_typeId)
  392. {
  393. behaviorParam.m_typeId = input.get_type_info().m_id;
  394. behaviorParam.m_value = AZStd::any_cast<void>(&input);
  395. return true;
  396. }
  397. #define CONVERT_ANY_NUMERIC(TYPE) ( \
  398. ConvertFromTo<TYPE, double>(input, argBehaviorInfo, behaviorParam) || \
  399. ConvertFromTo<TYPE, float>(input, argBehaviorInfo, behaviorParam) || \
  400. ConvertFromTo<TYPE, AZ::s8>(input, argBehaviorInfo, behaviorParam) || \
  401. ConvertFromTo<TYPE, AZ::u8>(input, argBehaviorInfo, behaviorParam) || \
  402. ConvertFromTo<TYPE, AZ::s16>(input, argBehaviorInfo, behaviorParam) || \
  403. ConvertFromTo<TYPE, AZ::u16>(input, argBehaviorInfo, behaviorParam) || \
  404. ConvertFromTo<TYPE, AZ::s32>(input, argBehaviorInfo, behaviorParam) || \
  405. ConvertFromTo<TYPE, AZ::u32>(input, argBehaviorInfo, behaviorParam) || \
  406. ConvertFromTo<TYPE, AZ::s64>(input, argBehaviorInfo, behaviorParam) || \
  407. ConvertFromTo<TYPE, AZ::u64>(input, argBehaviorInfo, behaviorParam) )
  408. if (CONVERT_ANY_NUMERIC(double) || CONVERT_ANY_NUMERIC(float) ||
  409. CONVERT_ANY_NUMERIC(AZ::s8) || CONVERT_ANY_NUMERIC(AZ::u8) ||
  410. CONVERT_ANY_NUMERIC(AZ::s16) || CONVERT_ANY_NUMERIC(AZ::u16) ||
  411. CONVERT_ANY_NUMERIC(AZ::s32) || CONVERT_ANY_NUMERIC(AZ::u32) ||
  412. CONVERT_ANY_NUMERIC(AZ::s64) || CONVERT_ANY_NUMERIC(AZ::u64) )
  413. {
  414. return true;
  415. }
  416. #undef CONVERT_ANY_NUMERIC
  417. return false;
  418. }
  419. } // Containers
  420. } // SceneAPI
  421. } // AZ