2
0

PythonMarshalTuple.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  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 <Source/PythonMarshalTuple.h>
  9. #include <Source/PythonProxyObject.h>
  10. #include <AzCore/Serialization/Utils.h>
  11. #include <AzCore/std/smart_ptr/make_shared.h>
  12. #include <pybind11/embed.h>
  13. #include <pybind11/pytypes.h>
  14. namespace EditorPythonBindings
  15. {
  16. // Check to see if the input object is a valid Python list.
  17. bool TypeConverterTuple::IsValidList(pybind11::object pyObj) const
  18. {
  19. return PyList_Check(pyObj.ptr()) != false;
  20. }
  21. // Check to see if the input object is a valid Python tuple.
  22. bool TypeConverterTuple::IsValidTuple(pybind11::object pyObj) const
  23. {
  24. return PyTuple_Check(pyObj.ptr()) != false;
  25. }
  26. // Check to see if the input object is a valid Python proxy object of a C++ tuple.
  27. bool TypeConverterTuple::IsCompatibleProxy(pybind11::object pyObj) const
  28. {
  29. if (pybind11::isinstance<EditorPythonBindings::PythonProxyObject>(pyObj))
  30. {
  31. auto behaviorObject = pybind11::cast<EditorPythonBindings::PythonProxyObject*>(pyObj)->GetBehaviorObject();
  32. AZ::Uuid typeId = behaviorObject.value()->m_typeId;
  33. return AZ::Utils::IsTupleContainerType(typeId);
  34. }
  35. return false;
  36. }
  37. // If the input object is either a Python list, Python tuple, or Proxy object of a C++ tuple, it can be converted
  38. // (or at least attempted to be converted) to a C++ tuple type.
  39. bool TypeConverterTuple::CanConvertPythonToBehaviorValue(
  40. [[maybe_unused]] PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const
  41. {
  42. return IsValidList(pyObj) || IsValidTuple(pyObj) || IsCompatibleProxy(pyObj);
  43. }
  44. // Given a Python object, clone it into a specific element in the tuple.
  45. bool TypeConverterTuple::LoadPythonToTupleElement(
  46. PyObject* pyItem,
  47. PythonMarshalTypeRequests::BehaviorTraits traits,
  48. const AZ::SerializeContext::ClassElement* itemElement,
  49. AZ::SerializeContext::IDataContainer* tupleContainer,
  50. size_t index,
  51. AZ::SerializeContext* serializeContext,
  52. void* newTuple)
  53. {
  54. pybind11::object pyObj{ pybind11::reinterpret_borrow<pybind11::object>(pyItem) };
  55. AZ::BehaviorArgument behaviorItem;
  56. auto behaviorResult = Container::ProcessPythonObject(traits, pyObj, itemElement->m_typeId, behaviorItem);
  57. if (behaviorResult && behaviorResult.value().first)
  58. {
  59. void* itemAddress = tupleContainer->GetElementByIndex(newTuple, itemElement, index);
  60. if (!itemAddress)
  61. {
  62. AZ_Error(
  63. "python", itemAddress,
  64. "Element reserved for associative container's tuple, but unable to retrieve address of the item:%d", index);
  65. return false;
  66. }
  67. serializeContext->CloneObjectInplace(itemAddress, behaviorItem.m_value, itemElement->m_typeId);
  68. }
  69. else
  70. {
  71. AZ_Warning(
  72. "python", false, "Could not convert to tuple element type %s for the tuple<>; failed to marshal Python input %s",
  73. itemElement->m_name, Convert::GetPythonTypeName(pyObj).c_str());
  74. return false;
  75. }
  76. return true;
  77. }
  78. // Convert a Python list / Python tuple / ProxyObject tuple to a C++ tuple.
  79. AZStd::optional<PythonMarshalTypeRequests::BehaviorValueResult> TypeConverterTuple::PythonToBehaviorValueParameter(
  80. PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorArgument& outValue)
  81. {
  82. if (!CanConvertPythonToBehaviorValue(traits, pyObj))
  83. {
  84. AZ_Warning("python", false, "Cannot convert tuple container for %s", m_classData->m_name);
  85. return AZStd::nullopt;
  86. }
  87. const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(m_typeId);
  88. if (!behaviorClass)
  89. {
  90. AZ_Warning("python", false, "Missing tuple behavior class for %s", m_typeId.ToString<AZStd::string>().c_str());
  91. return AZStd::nullopt;
  92. }
  93. AZ::SerializeContext* serializeContext = nullptr;
  94. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
  95. if (!serializeContext)
  96. {
  97. return AZStd::nullopt;
  98. }
  99. // prepare the AZStd::tuple<> container
  100. AZ::BehaviorObject tupleInstance = behaviorClass->Create();
  101. AZ::SerializeContext::IDataContainer* tupleDataContainer = m_classData->m_container;
  102. // get the element types
  103. AZStd::vector<const AZ::SerializeContext::ClassElement*> elements;
  104. bool allTypesValid = true;
  105. auto elementTypeEnumCallback =
  106. [&elements, &allTypesValid](const AZ::Uuid&, const AZ::SerializeContext::ClassElement* genericClassElement)
  107. {
  108. if (genericClassElement->m_flags & AZ::SerializeContext::ClassElement::Flags::FLG_POINTER)
  109. {
  110. AZ_Error("python", false, "Python marshalling does not handle naked pointers; not converting the tuple");
  111. allTypesValid = false;
  112. return false;
  113. }
  114. // Empty tuples are created with one element entry with an invalid type, so we need to check for and skip that.
  115. // Everything with a valid type gets added.
  116. if (genericClassElement->m_typeId != AZ::TypeId::CreateNull())
  117. {
  118. elements.push_back(genericClassElement);
  119. }
  120. return true;
  121. };
  122. tupleDataContainer->EnumTypes(elementTypeEnumCallback);
  123. if (!allTypesValid)
  124. {
  125. AZ_Error("python", false, "Could not convert tuple elements.");
  126. return AZStd::nullopt;
  127. }
  128. // load python items into tuple elements. If the input is a PythonProxyObject, keep a copy of the object returned
  129. // for each tuple value, not just its pointer, so that it doesn't get deallocated while we're converting the data.
  130. AZStd::vector<pybind11::object> proxyItems;
  131. AZStd::vector<PyObject*> items;
  132. if (IsValidList(pyObj))
  133. {
  134. // Python list, just grab raw pointers to each object.
  135. pybind11::list pyList(pyObj);
  136. for (size_t listIdx = 0; listIdx < pyList.size(); listIdx++)
  137. {
  138. items.push_back(pyList[listIdx].ptr());
  139. }
  140. }
  141. else if (IsValidTuple(pyObj))
  142. {
  143. // Python tuple, just grab raw pointers to each object.
  144. pybind11::tuple pyTuple(pyObj);
  145. for (size_t tupleIdx = 0; tupleIdx < pyTuple.size(); tupleIdx++)
  146. {
  147. items.push_back(pyTuple[tupleIdx].ptr());
  148. }
  149. }
  150. else if (IsCompatibleProxy(pyObj))
  151. {
  152. // Python Proxy Object that's a C++ tuple. This is a bit more complicated, because there's no easy way to detect
  153. // the number of properties and get them in the right order.
  154. // OnDemandReflection<AZStd::tuple<T...>> exposes "GetN" in the proxy object, so we'll keep calling that with increasing
  155. // numbers until it stops working.
  156. EditorPythonBindings::PythonProxyObject* proxy = pybind11::cast<EditorPythonBindings::PythonProxyObject*>(pyObj);
  157. bool propertyFound = true;
  158. do
  159. {
  160. // Generate method names like Get0(), Get1(), Get2(), etc. and call them.
  161. constexpr AZStd::size_t MaxPropertyNameSize = 32;
  162. auto propertyName = AZStd::fixed_string<MaxPropertyNameSize>::format("Get%zu", items.size());
  163. auto item = proxy->Invoke(propertyName.c_str(), {});
  164. // For each item that's returned, save both a copy of the object to keep it from deallocating, and the raw pointer
  165. // that we'll use for the conversion step.
  166. if (!item.is_none())
  167. {
  168. proxyItems.push_back(item);
  169. items.push_back(item.ptr());
  170. }
  171. else
  172. {
  173. propertyFound = false;
  174. }
  175. } while (propertyFound);
  176. }
  177. if (elements.size() != items.size())
  178. {
  179. AZ_Error("python", false, "Tuple requires %zu elements but received %zu elements.", elements.size(), items.size());
  180. return AZStd::nullopt;
  181. }
  182. // For each object found, create a copy of the value as the correct element in the C++ tuple.
  183. // Also, save the pointers for each allocation that we do so that we can free them in the case of an error during conversion.
  184. AZStd::vector<void*> reservedElements;
  185. for (size_t itemIdx = 0; itemIdx < elements.size(); itemIdx++)
  186. {
  187. bool successfulConversion = true;
  188. // Allocate space for each element.
  189. void* reserved = tupleDataContainer->ReserveElement(tupleInstance.m_address, elements[itemIdx]);
  190. if (reserved)
  191. {
  192. // Track the allocated space.
  193. reservedElements.push_back(reserved);
  194. }
  195. else
  196. {
  197. AZ_Error("python", reserved, "Could not allocate tuple's element %zu via ReserveElement()", itemIdx);
  198. successfulConversion = false;
  199. }
  200. // Attempt to convert the value. If it fails to convert, free everything we've allocated so far and return.
  201. if (items[itemIdx] &&
  202. !LoadPythonToTupleElement(
  203. items[itemIdx], traits, elements[itemIdx], tupleDataContainer, itemIdx, serializeContext, tupleInstance.m_address))
  204. {
  205. successfulConversion = false;
  206. }
  207. if (!successfulConversion)
  208. {
  209. for (auto& reservedElement : reservedElements)
  210. {
  211. tupleDataContainer->FreeReservedElement(tupleInstance.m_address, reservedElement, serializeContext);
  212. }
  213. return AZStd::nullopt;
  214. }
  215. }
  216. outValue.m_value = tupleInstance.m_address;
  217. outValue.m_typeId = tupleInstance.m_typeId;
  218. outValue.m_traits = traits;
  219. auto tupleInstanceDeleter = [behaviorClass, tupleInstance]()
  220. {
  221. behaviorClass->Destroy(tupleInstance);
  222. };
  223. return PythonMarshalTypeRequests::BehaviorValueResult{ true, tupleInstanceDeleter };
  224. }
  225. // Convert a C++ tuple into a Python list.
  226. AZStd::optional<PythonMarshalTypeRequests::PythonValueResult> TypeConverterTuple::BehaviorValueParameterToPython(
  227. AZ::BehaviorArgument& behaviorValue)
  228. {
  229. // the class data must have a container interface
  230. AZ::SerializeContext::IDataContainer* containerInterface = m_classData->m_container;
  231. if (!containerInterface)
  232. {
  233. AZ_Warning("python", false, "Container interface is missing from class %s.", m_classData->m_name);
  234. return AZStd::nullopt;
  235. }
  236. if (!behaviorValue.ConvertTo(m_typeId))
  237. {
  238. AZ_Warning("python", false, "Cannot convert behavior value %s.", behaviorValue.m_name);
  239. return AZStd::nullopt;
  240. }
  241. auto cleanUpList = AZStd::make_shared<AZStd::vector<PythonMarshalTypeRequests::DeallocateFunction>>();
  242. // return tuple as python tuple - if conversion fails for an element it will remain as 'none'
  243. size_t tupleSize = containerInterface->Size(behaviorValue.m_value);
  244. pybind11::tuple pythonTuple(tupleSize);
  245. size_t tupleElementIndex = 0;
  246. auto tupleElementCallback = [cleanUpList, tupleSize, &pythonTuple, &tupleElementIndex]
  247. (void* instancePair, const AZ::Uuid& elementClassId,
  248. [[maybe_unused]] const AZ::SerializeContext::ClassData* elementGenericClassData,
  249. [[maybe_unused]] const AZ::SerializeContext::ClassElement* genericClassElement)
  250. {
  251. AZ::BehaviorObject behaviorObjectValue(instancePair, elementClassId);
  252. auto result = Container::ProcessBehaviorObject(behaviorObjectValue);
  253. if (tupleElementIndex >= tupleSize)
  254. {
  255. // We've ended up with too many elements in the tuple somehow.
  256. AZ_Error("python", false, "Tuple contains more than the expected number of elements (%zu).", tupleSize);
  257. return false;
  258. }
  259. if (result.has_value())
  260. {
  261. // If the element was converted, we'll put the converted value into the output tuple.
  262. PythonMarshalTypeRequests::DeallocateFunction deallocateFunction = result.value().second;
  263. if (result.value().second)
  264. {
  265. // If it has a deallocate function, we'll add that to our list of deallocators to get called on cleanup.
  266. cleanUpList->emplace_back(AZStd::move(result.value().second));
  267. }
  268. pybind11::object pythonResult = result.value().first;
  269. pythonTuple[tupleElementIndex] = pythonResult;
  270. }
  271. else
  272. {
  273. // The element couldn't be converted, so we'll add 'none' as a placeholder in the tuple.
  274. AZ_Warning("python", false, "BehaviorObject was not processed, python item will remain 'none'.");
  275. pythonTuple[tupleElementIndex] = pybind11::none();
  276. }
  277. tupleElementIndex++;
  278. return true;
  279. };
  280. containerInterface->EnumElements(behaviorValue.m_value, tupleElementCallback);
  281. PythonMarshalTypeRequests::PythonValueResult result;
  282. result.first = pythonTuple;
  283. if (!cleanUpList->empty())
  284. {
  285. AZStd::weak_ptr<AZStd::vector<PythonMarshalTypeRequests::DeallocateFunction>> cleanUp(cleanUpList);
  286. result.second = [cleanUp]()
  287. {
  288. auto cleanupList = cleanUp.lock();
  289. if (cleanupList)
  290. {
  291. AZStd::for_each(cleanupList->begin(), cleanupList->end(), [](auto& deleteMe) { deleteMe(); });
  292. }
  293. };
  294. }
  295. return result;
  296. }
  297. }