GraphObjectProxy.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  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_view 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_view 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_view 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. if (const AZ::BehaviorParameter* thisInfo = behaviorMethod->GetArgument(0); hasSelfPointer)
  245. {
  246. // avoiding the "Special handling for the generic object holder." since it assumes
  247. // the BehaviorObject.m_value is a pointer; the reference version is already dereferenced
  248. AZ::BehaviorArgument theThisPointer;
  249. const void* self = reinterpret_cast<const void*>(m_graphObject.get());
  250. if ((thisInfo->m_traits & AZ::BehaviorParameter::TR_POINTER) == AZ::BehaviorParameter::TR_POINTER)
  251. {
  252. theThisPointer.m_value = &self;
  253. }
  254. else
  255. {
  256. theThisPointer.m_value = const_cast<void*>(self);
  257. }
  258. theThisPointer.Set(*thisInfo);
  259. behaviorParamList[0].Set(theThisPointer);
  260. }
  261. int paramCount = 0;
  262. for (; paramCount < argList.size() && paramCount < behaviorMethod->GetNumArguments(); ++paramCount)
  263. {
  264. size_t behaviorArgIndex = hasSelfPointer ? paramCount + 1 : paramCount;
  265. const AZ::BehaviorParameter* argBehaviorInfo = behaviorMethod->GetArgument(behaviorArgIndex);
  266. if (!Convert(argList[paramCount], argBehaviorInfo, behaviorParamList[behaviorArgIndex]))
  267. {
  268. AZ_Error("SceneAPI", false, "Could not convert from %s to %s at index %zu",
  269. argBehaviorInfo->m_typeId.ToString<AZStd::string>().c_str(),
  270. behaviorParamList[behaviorArgIndex].m_typeId.ToString<AZStd::string>().c_str(),
  271. paramCount);
  272. return AZStd::any(false);
  273. }
  274. }
  275. if (hasSelfPointer)
  276. {
  277. ++paramCount;
  278. }
  279. AZ::BehaviorArgument returnBehaviorValue;
  280. if (behaviorMethod->HasResult())
  281. {
  282. returnBehaviorValue.Set(*behaviorMethod->GetResult());
  283. const size_t typeSize = returnBehaviorValue.m_azRtti->GetTypeSize();
  284. if (returnBehaviorValue.m_traits & BehaviorParameter::TR_POINTER)
  285. {
  286. // Used to allocate storage to store a copy of a pointer and the allocated memory address in one block.
  287. constexpr size_t PointerAllocationStorage = 2 * sizeof(void*);
  288. void* valueAddress = returnBehaviorValue.m_tempData.allocate(PointerAllocationStorage, 16, 0);
  289. void* valueAddressPtr = reinterpret_cast<AZ::u8*>(valueAddress) + sizeof(void*);
  290. ::memset(valueAddress, 0, sizeof(void*));
  291. *reinterpret_cast<void**>(valueAddressPtr) = valueAddress;
  292. returnBehaviorValue.m_value = valueAddressPtr;
  293. }
  294. else if (returnBehaviorValue.m_traits & BehaviorParameter::TR_REFERENCE)
  295. {
  296. // the reference value will just be assigned
  297. returnBehaviorValue.m_value = nullptr;
  298. }
  299. else if (typeSize < returnBehaviorValue.m_tempData.max_size())
  300. {
  301. returnBehaviorValue.m_value = returnBehaviorValue.m_tempData.allocate(typeSize, 16);
  302. }
  303. else
  304. {
  305. AZ_Warning("SceneAPI", false, "Can't invoke method since the return value is too big; %d bytes", typeSize);
  306. return AZStd::any(false);
  307. }
  308. }
  309. if (!behaviorMethod->Call(behaviorParamList, paramCount, &returnBehaviorValue))
  310. {
  311. return AZStd::any(false);
  312. }
  313. if (!behaviorMethod->HasResult())
  314. {
  315. return AZStd::any(true);
  316. }
  317. AZ::SerializeContext* serializeContext = nullptr;
  318. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
  319. if (!serializeContext)
  320. {
  321. AZ_Error("SceneAPI", false, "AZ::SerializeContext should be prepared.");
  322. return AZStd::any(false);
  323. }
  324. // Create temporary any to get its type info to construct a new AZStd::any with new data
  325. AZStd::any tempAny = serializeContext->CreateAny(returnBehaviorValue.m_typeId);
  326. return AZStd::move(AZStd::any(returnBehaviorValue.m_value, tempAny.get_type_info()));
  327. }
  328. AZStd::any GraphObjectProxy::Invoke(AZStd::string_view method, AZStd::vector<AZStd::any> argList)
  329. {
  330. if (!m_behaviorClass)
  331. {
  332. AZ_Warning("SceneAPI", false, "Use the CastWithTypeName() to assign the concrete type of IGraphObject to Invoke().");
  333. return AZStd::any(false);
  334. }
  335. auto entry = m_behaviorClass->m_methods.find(method);
  336. if (m_behaviorClass->m_methods.end() == entry)
  337. {
  338. AZ_Warning("SceneAPI", false, "Missing method %.*s from class %s",
  339. aznumeric_cast<int>(method.size()),
  340. method.data(),
  341. m_behaviorClass->m_name.c_str());
  342. return AZStd::any(false);
  343. }
  344. return InvokeBehaviorMethod(entry->second, argList);
  345. }
  346. template <typename FROM, typename TO>
  347. bool ConvertFromTo(AZStd::any& input, const AZ::BehaviorParameter* argBehaviorInfo, AZ::BehaviorArgument& behaviorParam)
  348. {
  349. if (input.get_type_info().m_id != azrtti_typeid<FROM>())
  350. {
  351. return false;
  352. }
  353. if (argBehaviorInfo->m_typeId != azrtti_typeid<TO>())
  354. {
  355. return false;
  356. }
  357. TO* storage = reinterpret_cast<TO*>(behaviorParam.m_tempData.allocate(argBehaviorInfo->m_azRtti->GetTypeSize(), 16));
  358. *storage = aznumeric_cast<TO>(*AZStd::any_cast<FROM>(&input));
  359. behaviorParam.m_typeId = azrtti_typeid<TO>();
  360. behaviorParam.m_value = storage;
  361. return true;
  362. }
  363. bool GraphObjectProxy::Convert(AZStd::any& input, const AZ::BehaviorParameter* argBehaviorInfo, AZ::BehaviorArgument& behaviorParam)
  364. {
  365. if (input.get_type_info().m_id == argBehaviorInfo->m_typeId)
  366. {
  367. behaviorParam.m_typeId = input.get_type_info().m_id;
  368. behaviorParam.m_value = AZStd::any_cast<void>(&input);
  369. return true;
  370. }
  371. #define CONVERT_ANY_NUMERIC(TYPE) ( \
  372. ConvertFromTo<TYPE, double>(input, argBehaviorInfo, behaviorParam) || \
  373. ConvertFromTo<TYPE, float>(input, argBehaviorInfo, behaviorParam) || \
  374. ConvertFromTo<TYPE, AZ::s8>(input, argBehaviorInfo, behaviorParam) || \
  375. ConvertFromTo<TYPE, AZ::u8>(input, argBehaviorInfo, behaviorParam) || \
  376. ConvertFromTo<TYPE, AZ::s16>(input, argBehaviorInfo, behaviorParam) || \
  377. ConvertFromTo<TYPE, AZ::u16>(input, argBehaviorInfo, behaviorParam) || \
  378. ConvertFromTo<TYPE, AZ::s32>(input, argBehaviorInfo, behaviorParam) || \
  379. ConvertFromTo<TYPE, AZ::u32>(input, argBehaviorInfo, behaviorParam) || \
  380. ConvertFromTo<TYPE, AZ::s64>(input, argBehaviorInfo, behaviorParam) || \
  381. ConvertFromTo<TYPE, AZ::u64>(input, argBehaviorInfo, behaviorParam) )
  382. if (CONVERT_ANY_NUMERIC(double) || CONVERT_ANY_NUMERIC(float) ||
  383. CONVERT_ANY_NUMERIC(AZ::s8) || CONVERT_ANY_NUMERIC(AZ::u8) ||
  384. CONVERT_ANY_NUMERIC(AZ::s16) || CONVERT_ANY_NUMERIC(AZ::u16) ||
  385. CONVERT_ANY_NUMERIC(AZ::s32) || CONVERT_ANY_NUMERIC(AZ::u32) ||
  386. CONVERT_ANY_NUMERIC(AZ::s64) || CONVERT_ANY_NUMERIC(AZ::u64) )
  387. {
  388. return true;
  389. }
  390. #undef CONVERT_ANY_NUMERIC
  391. return false;
  392. }
  393. } // Containers
  394. } // SceneAPI
  395. } // AZ