GraphObjectProxy.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  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/std/smart_ptr/make_shared.h>
  12. #include <AzFramework/StringFunc/StringFunc.h>
  13. #include <AzToolsFramework/API/EditorPythonConsoleBus.h>
  14. namespace AZ
  15. {
  16. namespace Python
  17. {
  18. static const char* const None = "None";
  19. class PythonBehaviorInfo final
  20. {
  21. public:
  22. AZ_RTTI(PythonBehaviorInfo, "{8055BD03-5B3B-490D-AEC5-1B1E2616D529}");
  23. AZ_CLASS_ALLOCATOR(PythonBehaviorInfo, AZ::SystemAllocator, 0);
  24. static void Reflect(AZ::ReflectContext* context);
  25. PythonBehaviorInfo(const AZ::BehaviorClass* behaviorClass);
  26. PythonBehaviorInfo() = delete;
  27. protected:
  28. bool IsMemberLike(const AZ::BehaviorMethod& method, const AZ::TypeId& typeId) const;
  29. AZStd::string FetchPythonType(const AZ::BehaviorParameter& param) const;
  30. void WriteMethod(AZStd::string_view methodName, const AZ::BehaviorMethod& behaviorMethod);
  31. private:
  32. const AZ::BehaviorClass* m_behaviorClass = nullptr;
  33. AZStd::vector<AZStd::string> m_methodList;
  34. };
  35. PythonBehaviorInfo::PythonBehaviorInfo(const AZ::BehaviorClass* behaviorClass)
  36. : m_behaviorClass(behaviorClass)
  37. {
  38. AZ_Assert(m_behaviorClass, "PythonBehaviorInfo requires a valid behaviorClass pointer");
  39. for (const auto& entry : behaviorClass->m_methods)
  40. {
  41. WriteMethod(entry.first, *entry.second);
  42. }
  43. }
  44. void PythonBehaviorInfo::Reflect(AZ::ReflectContext* context)
  45. {
  46. AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
  47. if (behaviorContext)
  48. {
  49. behaviorContext->Class<PythonBehaviorInfo>()
  50. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
  51. ->Attribute(AZ::Script::Attributes::Module, "scene.graph")
  52. ->Property("className", [](const PythonBehaviorInfo& self)
  53. { return self.m_behaviorClass->m_name; }, nullptr)
  54. ->Property("classUuid", [](const PythonBehaviorInfo& self)
  55. { return self.m_behaviorClass->m_typeId.ToString<AZStd::string>(); }, nullptr)
  56. ->Property("methodList", BehaviorValueGetter(&PythonBehaviorInfo::m_methodList), nullptr);
  57. }
  58. }
  59. bool PythonBehaviorInfo::IsMemberLike(const AZ::BehaviorMethod& method, const AZ::TypeId& typeId) const
  60. {
  61. return method.IsMember() || (method.GetNumArguments() > 0 && method.GetArgument(0)->m_typeId == typeId);
  62. }
  63. AZStd::string PythonBehaviorInfo::FetchPythonType(const AZ::BehaviorParameter& param) const
  64. {
  65. using namespace AzToolsFramework;
  66. EditorPythonConsoleInterface* editorPythonConsoleInterface = AZ::Interface<EditorPythonConsoleInterface>::Get();
  67. if (editorPythonConsoleInterface)
  68. {
  69. return editorPythonConsoleInterface->FetchPythonTypeName(param);
  70. }
  71. return None;
  72. }
  73. void PythonBehaviorInfo::WriteMethod(AZStd::string_view methodName, const AZ::BehaviorMethod& behaviorMethod)
  74. {
  75. // if the method is a static method then it is not a part of the abstract class
  76. const bool isMemberLike = IsMemberLike(behaviorMethod, m_behaviorClass->m_typeId);
  77. if (isMemberLike == false)
  78. {
  79. return;
  80. }
  81. AZStd::string buffer;
  82. AZStd::vector<AZStd::string> pythonArgs;
  83. AzFramework::StringFunc::Append(buffer, "def ");
  84. AzFramework::StringFunc::Append(buffer, methodName.data());
  85. AzFramework::StringFunc::Append(buffer, "(");
  86. pythonArgs.emplace_back("self");
  87. AZStd::string bufferArg;
  88. for (size_t argIndex = 1; argIndex < behaviorMethod.GetNumArguments(); ++argIndex)
  89. {
  90. const AZStd::string* name = behaviorMethod.GetArgumentName(argIndex);
  91. if (!name || name->empty())
  92. {
  93. bufferArg = AZStd::string::format(" arg%zu", argIndex);
  94. }
  95. else
  96. {
  97. bufferArg = *name;
  98. }
  99. AZStd::string_view type = FetchPythonType(*behaviorMethod.GetArgument(argIndex));
  100. if (!type.empty())
  101. {
  102. AzFramework::StringFunc::Append(bufferArg, ": ");
  103. AzFramework::StringFunc::Append(bufferArg, type.data());
  104. }
  105. pythonArgs.push_back(bufferArg);
  106. bufferArg.clear();
  107. }
  108. AZStd::string resultValue{ None };
  109. if (behaviorMethod.HasResult() && behaviorMethod.GetResult())
  110. {
  111. resultValue = FetchPythonType(*behaviorMethod.GetResult());
  112. }
  113. AZStd::string argsList;
  114. AzFramework::StringFunc::Join(buffer, pythonArgs.begin(), pythonArgs.end(), ",");
  115. AzFramework::StringFunc::Append(buffer, ") -> ");
  116. AzFramework::StringFunc::Append(buffer, resultValue.c_str());
  117. m_methodList.emplace_back(buffer);
  118. }
  119. }
  120. namespace SceneAPI
  121. {
  122. namespace Containers
  123. {
  124. void GraphObjectProxy::Reflect(AZ::ReflectContext* context)
  125. {
  126. AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
  127. if (behaviorContext)
  128. {
  129. Python::PythonBehaviorInfo::Reflect(context);
  130. behaviorContext->Class<DataTypes::IGraphObject>();
  131. behaviorContext->Class<GraphObjectProxy>()
  132. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
  133. ->Attribute(AZ::Script::Attributes::Module, "scene.graph")
  134. ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
  135. ->Method("CastWithTypeName", &GraphObjectProxy::CastWithTypeName)
  136. ->Method("Invoke", &GraphObjectProxy::Invoke)
  137. ->Method("GetClassInfo", [](GraphObjectProxy& self) -> Python::PythonBehaviorInfo*
  138. {
  139. if (self.m_pythonBehaviorInfo)
  140. {
  141. return self.m_pythonBehaviorInfo.get();
  142. }
  143. if (self.m_behaviorClass)
  144. {
  145. self.m_pythonBehaviorInfo = AZStd::make_shared<Python::PythonBehaviorInfo>(self.m_behaviorClass);
  146. return self.m_pythonBehaviorInfo.get();
  147. }
  148. return nullptr;
  149. })
  150. ;
  151. }
  152. }
  153. GraphObjectProxy::GraphObjectProxy(AZStd::shared_ptr<const DataTypes::IGraphObject> graphObject)
  154. {
  155. m_graphObject = graphObject;
  156. }
  157. GraphObjectProxy::~GraphObjectProxy()
  158. {
  159. m_graphObject.reset();
  160. m_behaviorClass = nullptr;
  161. }
  162. bool GraphObjectProxy::CastWithTypeName(const AZStd::string& classTypeName)
  163. {
  164. const AZ::BehaviorClass* behaviorClass = BehaviorContextHelper::GetClass(classTypeName);
  165. if (behaviorClass)
  166. {
  167. const void* baseClass = behaviorClass->m_azRtti->Cast(m_graphObject.get(), behaviorClass->m_azRtti->GetTypeId());
  168. if (baseClass)
  169. {
  170. m_behaviorClass = behaviorClass;
  171. return true;
  172. }
  173. }
  174. return false;
  175. }
  176. AZStd::any GraphObjectProxy::Invoke(AZStd::string_view method, AZStd::vector<AZStd::any> argList)
  177. {
  178. AZ::SerializeContext* serializeContext = nullptr;
  179. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
  180. if (!serializeContext)
  181. {
  182. AZ_Error("SceneAPI", false, "AZ::SerializeContext should be prepared.");
  183. return AZStd::any(false);
  184. }
  185. if (!m_behaviorClass)
  186. {
  187. AZ_Warning("SceneAPI", false, "Use the CastWithTypeName() to assign the concrete type of IGraphObject to Invoke().");
  188. return AZStd::any(false);
  189. }
  190. auto entry = m_behaviorClass->m_methods.find(method);
  191. if (m_behaviorClass->m_methods.end() == entry)
  192. {
  193. AZ_Warning("SceneAPI", false, "Missing method %.*s from class %s",
  194. aznumeric_cast<int>(method.size()),
  195. method.data(),
  196. m_behaviorClass->m_name.c_str());
  197. return AZStd::any(false);
  198. }
  199. AZ::BehaviorMethod* behaviorMethod = entry->second;
  200. constexpr size_t behaviorParamListSize = 8;
  201. if (behaviorMethod->GetNumArguments() > behaviorParamListSize)
  202. {
  203. AZ_Error("SceneAPI", false, "Unsupported behavior method; supports max %zu but %s has %zu argument slots",
  204. behaviorParamListSize,
  205. behaviorMethod->m_name.c_str(),
  206. behaviorMethod->GetNumArguments());
  207. return AZStd::any(false);
  208. }
  209. AZ::BehaviorValueParameter behaviorParamList[behaviorParamListSize];
  210. // detect if the method passes in a "this" pointer which can be true if the method is a member function
  211. // or if the first argument is the same as the behavior class such as from a lambda function
  212. bool hasSelfPointer = behaviorMethod->IsMember();
  213. if (!hasSelfPointer)
  214. {
  215. hasSelfPointer = behaviorMethod->GetArgument(0)->m_typeId == m_behaviorClass->m_typeId;
  216. }
  217. // record the "this" pointer's metadata like its RTTI so that it can be
  218. // down casted to a parent class type if needed to invoke a parent method
  219. if (const AZ::BehaviorParameter* thisInfo = behaviorMethod->GetArgument(0); hasSelfPointer)
  220. {
  221. // avoiding the "Special handling for the generic object holder." since it assumes
  222. // the BehaviorObject.m_value is a pointer; the reference version is already dereferenced
  223. AZ::BehaviorValueParameter theThisPointer;
  224. const void* self = reinterpret_cast<const void*>(m_graphObject.get());
  225. if ((thisInfo->m_traits & AZ::BehaviorParameter::TR_POINTER) == AZ::BehaviorParameter::TR_POINTER)
  226. {
  227. theThisPointer.m_value = &self;
  228. }
  229. else
  230. {
  231. theThisPointer.m_value = const_cast<void*>(self);
  232. }
  233. theThisPointer.Set(*thisInfo);
  234. behaviorParamList[0].Set(theThisPointer);
  235. }
  236. int paramCount = 0;
  237. for (; paramCount < argList.size() && paramCount < behaviorMethod->GetNumArguments(); ++paramCount)
  238. {
  239. size_t behaviorArgIndex = hasSelfPointer ? paramCount + 1 : paramCount;
  240. const AZ::BehaviorParameter* argBehaviorInfo = behaviorMethod->GetArgument(behaviorArgIndex);
  241. if (!Convert(argList[paramCount], argBehaviorInfo, behaviorParamList[behaviorArgIndex]))
  242. {
  243. AZ_Error("SceneAPI", false, "Could not convert from %s to %s at index %zu",
  244. argBehaviorInfo->m_typeId.ToString<AZStd::string>().c_str(),
  245. behaviorParamList[behaviorArgIndex].m_typeId.ToString<AZStd::string>().c_str(),
  246. paramCount);
  247. return AZStd::any(false);
  248. }
  249. }
  250. if (hasSelfPointer)
  251. {
  252. ++paramCount;
  253. }
  254. AZ::BehaviorValueParameter returnBehaviorValue;
  255. if (behaviorMethod->HasResult())
  256. {
  257. returnBehaviorValue.Set(*behaviorMethod->GetResult());
  258. returnBehaviorValue.m_value =
  259. returnBehaviorValue.m_tempData.allocate(returnBehaviorValue.m_azRtti->GetTypeSize(), 16);
  260. }
  261. if (!entry->second->Call(behaviorParamList, paramCount, &returnBehaviorValue))
  262. {
  263. return AZStd::any(false);
  264. }
  265. if (!behaviorMethod->HasResult())
  266. {
  267. return AZStd::any(true);
  268. }
  269. // Create temporary any to get its type info to construct a new AZStd::any with new data
  270. AZStd::any tempAny = serializeContext->CreateAny(returnBehaviorValue.m_typeId);
  271. return AZStd::move(AZStd::any(returnBehaviorValue.m_value, tempAny.get_type_info()));
  272. }
  273. template <typename FROM, typename TO>
  274. bool ConvertFromTo(AZStd::any& input, const AZ::BehaviorParameter* argBehaviorInfo, AZ::BehaviorValueParameter& behaviorParam)
  275. {
  276. if (input.get_type_info().m_id != azrtti_typeid<FROM>())
  277. {
  278. return false;
  279. }
  280. if (argBehaviorInfo->m_typeId != azrtti_typeid<TO>())
  281. {
  282. return false;
  283. }
  284. TO* storage = reinterpret_cast<TO*>(behaviorParam.m_tempData.allocate(argBehaviorInfo->m_azRtti->GetTypeSize(), 16));
  285. *storage = aznumeric_cast<TO>(*AZStd::any_cast<FROM>(&input));
  286. behaviorParam.m_typeId = azrtti_typeid<TO>();
  287. behaviorParam.m_value = storage;
  288. return true;
  289. }
  290. bool GraphObjectProxy::Convert(AZStd::any& input, const AZ::BehaviorParameter* argBehaviorInfo, AZ::BehaviorValueParameter& behaviorParam)
  291. {
  292. if (input.get_type_info().m_id == argBehaviorInfo->m_typeId)
  293. {
  294. behaviorParam.m_typeId = input.get_type_info().m_id;
  295. behaviorParam.m_value = AZStd::any_cast<void>(&input);
  296. return true;
  297. }
  298. #define CONVERT_ANY_NUMERIC(TYPE) ( \
  299. ConvertFromTo<TYPE, double>(input, argBehaviorInfo, behaviorParam) || \
  300. ConvertFromTo<TYPE, float>(input, argBehaviorInfo, behaviorParam) || \
  301. ConvertFromTo<TYPE, AZ::s8>(input, argBehaviorInfo, behaviorParam) || \
  302. ConvertFromTo<TYPE, AZ::u8>(input, argBehaviorInfo, behaviorParam) || \
  303. ConvertFromTo<TYPE, AZ::s16>(input, argBehaviorInfo, behaviorParam) || \
  304. ConvertFromTo<TYPE, AZ::u16>(input, argBehaviorInfo, behaviorParam) || \
  305. ConvertFromTo<TYPE, AZ::s32>(input, argBehaviorInfo, behaviorParam) || \
  306. ConvertFromTo<TYPE, AZ::u32>(input, argBehaviorInfo, behaviorParam) || \
  307. ConvertFromTo<TYPE, AZ::s64>(input, argBehaviorInfo, behaviorParam) || \
  308. ConvertFromTo<TYPE, AZ::u64>(input, argBehaviorInfo, behaviorParam) )
  309. if (CONVERT_ANY_NUMERIC(double) || CONVERT_ANY_NUMERIC(float) ||
  310. CONVERT_ANY_NUMERIC(AZ::s8) || CONVERT_ANY_NUMERIC(AZ::u8) ||
  311. CONVERT_ANY_NUMERIC(AZ::s16) || CONVERT_ANY_NUMERIC(AZ::u16) ||
  312. CONVERT_ANY_NUMERIC(AZ::s32) || CONVERT_ANY_NUMERIC(AZ::u32) ||
  313. CONVERT_ANY_NUMERIC(AZ::s64) || CONVERT_ANY_NUMERIC(AZ::u64) )
  314. {
  315. return true;
  316. }
  317. #undef CONVERT_ANY_NUMERIC
  318. return false;
  319. }
  320. } // Containers
  321. } // SceneAPI
  322. } // AZ