/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace EditorPythonBindings { bool IsPointerType(PythonMarshalTypeRequests::BehaviorTraits traits) { return (((traits & AZ::BehaviorParameter::TR_POINTER) == AZ::BehaviorParameter::TR_POINTER) || ((traits & AZ::BehaviorParameter::TR_REFERENCE) == AZ::BehaviorParameter::TR_REFERENCE)); } template pybind11::object MarshalBehaviorValueParameter(AZ::BehaviorValueParameter& result) { if (result.ConvertTo()) { TInput inputValue = static_cast(*result.GetAsUnsafe()); return TPythonType(inputValue); } return pybind11::cast(Py_None); } void ReportMissingTypeId(AZ::TypeId typeId) { const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(typeId); if (behaviorClass) { AZ_Warning("python", false, "Missing BehaviorClass for UUID:%s Name:%s", typeId.ToString().c_str(), behaviorClass->m_name.c_str()); return; } AZ::SerializeContext* serializeContext = nullptr; AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); AZ_Error("python", serializeContext, "SerializeContext is missing"); if (!serializeContext) { AZ_Warning("python", false, "Missing Serialize class for UUID:%s", typeId.ToString().c_str()); return; } const AZ::SerializeContext::ClassData* classData = serializeContext->FindClassData(typeId); if(!classData) { AZ_Warning("python", false, "Missing Serialize class for UUID:%s", typeId.ToString().c_str()); } else if (classData->m_container) { AZ_Warning("python", false, "Missing Serialize class container for UUID:%s Name:%s", typeId.ToString().c_str(), classData->m_name); } else { AZ_Warning("python", false, "Missing Serialize class for UUID:%s Name:%s", typeId.ToString().c_str(), classData->m_name); } } class TypeConverterAny : public PythonMarshalComponent::TypeConverter { public: AZStd::optional PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override { if (!CanConvertPythonToBehaviorValue(traits, pyObj)) { AZ_Warning("python", false, "AZStd::any<> handles Behavior Class types only."); return AZStd::nullopt; } if ((traits & AZ::BehaviorParameter::TR_POINTER) == AZ::BehaviorParameter::TR_POINTER) { AZ_Warning("python", false, "AZStd::any* pointer argument types are not supported; try 'AZStd::any' value or 'const AZStd::any&' instead"); return AZStd::nullopt; } if (pybind11::isinstance(pyObj)) { EditorPythonBindings::PythonProxyObject* proxyObj = pybind11::cast(pyObj); if (proxyObj) { return PythonToParameterWithProxy(proxyObj, pyObj, outValue); } AZ_Warning("python", false, "Passed in PythonProxyObject is empty."); return AZStd::nullopt; } else if (pyObj.is_none()) { AZStd::any* anyValue = aznew AZStd::any(); outValue.Set(anyValue); auto deleteAny = [anyValue]() { delete anyValue; }; return AZStd::make_optional(PythonMarshalTypeRequests::BehaviorValueResult{ true, deleteAny }); } else if (PyList_Check(pyObj.ptr())) { return ReturnVectorFromList(traits, pybind11::cast(pyObj), outValue); } else if (PyBool_Check(pyObj.ptr())) { return ReturnSimpleType(Py_True == pyObj.ptr(), outValue); } else if (PyFloat_Check(pyObj.ptr())) { return ReturnSimpleType(PyFloat_AsDouble(pyObj.ptr()), outValue); } else if (PyLong_Check(pyObj.ptr())) { return ReturnSimpleType(PyLong_AsLongLong(pyObj.ptr()), outValue); } else if (PyUnicode_Check(pyObj.ptr())) { // in the case of an error, NULL is returned with an exception set and no size is stored Py_ssize_t size = -1; const char* value = PyUnicode_AsUTF8AndSize(pyObj.ptr(), &size); if (value && size != -1) { return ReturnSimpleType(AZStd::string_view(value, size), outValue); } } return AZStd::nullopt; } AZStd::optional BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override { if ((behaviorValue.m_traits & AZ::BehaviorParameter::TR_POINTER) == AZ::BehaviorParameter::TR_POINTER) { AZ_Warning("python", false, "Return value 'AZStd::any*' pointer argument types are not supported; try returning 'const AZStd::any&' instead"); return AZStd::nullopt; } if (!behaviorValue.ConvertTo()) { AZ_Warning("python", false, "Cannot convert the return value to a AZStd::any value."); return AZStd::nullopt; } AZStd::any* anyValue = static_cast(behaviorValue.GetAsUnsafe()); const AZ::TypeId anyValueTypId { anyValue->get_type_info().m_id }; // is a registered convertible type? if (PythonMarshalTypeRequestBus::GetNumOfEventHandlers(anyValueTypId)) { AZ::BehaviorValueParameter tempBehaviorValue; tempBehaviorValue.m_typeId = anyValueTypId; tempBehaviorValue.m_value = AZStd::any_cast(anyValue); AZStd::optional result; PythonMarshalTypeRequestBus::EventResult(result, anyValueTypId, &PythonMarshalTypeRequestBus::Events::BehaviorValueParameterToPython, tempBehaviorValue); return result; } else { AZ::BehaviorContext* behaviorContext {nullptr}; AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext); if (!behaviorContext) { AZ_Error("python", false, "A behavior context is required!"); return AZStd::nullopt; } AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(behaviorContext, anyValueTypId); if (behaviorClass) { PythonMarshalTypeRequests::PythonValueResult result; result.first = PythonProxyObjectManagement::CreatePythonProxyObject(anyValueTypId, AZStd::any_cast(anyValue)); return result; } } return AZStd::nullopt; } bool CanConvertPythonToBehaviorValue(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override { // supports Python native types None, Float, Long, Bool, List, or String if (pyObj.is_none() || PyFloat_Check(pyObj.ptr()) || PyLong_Check(pyObj.ptr()) || PyBool_Check(pyObj.ptr()) || PyList_Check(pyObj.ptr()) || PyUnicode_Check(pyObj.ptr())) { return true; } return pybind11::isinstance(pyObj); } protected: template AZStd::optional ReturnSimpleType(T value, AZ::BehaviorValueParameter& outValue) { AZStd::any* anyValue = aznew AZStd::any(value); outValue.Set(anyValue); auto deleteAny = [anyValue]() { delete anyValue; }; return AZStd::make_optional(PythonMarshalTypeRequests::BehaviorValueResult{ true, deleteAny }); } AZStd::any* CreateAnyValue(AZ::TypeId typeId, void* address) const { const AZ::BehaviorClass* sourceClass = AZ::BehaviorContextHelper::GetClass(typeId); if (!sourceClass) { ReportMissingTypeId(typeId); return nullptr; } if (!sourceClass->m_allocate || !sourceClass->m_cloner || !sourceClass->m_mover || !sourceClass->m_destructor || !sourceClass->m_deallocate) { AZ_Warning("python", false, "BehaviorClass:%s must handle allocation", sourceClass->m_name.c_str()); return nullptr; } AZStd::any::type_info valueInfo; valueInfo.m_id = typeId; valueInfo.m_isPointer = false; valueInfo.m_useHeap = true; valueInfo.m_handler = [sourceClass](AZStd::any::Action action, AZStd::any* dest, const AZStd::any* source) { if (action == AZStd::any::Action::Reserve) { *reinterpret_cast(dest) = sourceClass->Allocate(); } else if (action == AZStd::any::Action::Copy) { sourceClass->m_cloner(AZStd::any_cast(dest), AZStd::any_cast(source), sourceClass->m_userData); } else if (action == AZStd::any::Action::Move) { sourceClass->m_mover(AZStd::any_cast(dest), AZStd::any_cast(const_cast(source)), sourceClass->m_userData); } else if (action == AZStd::any::Action::Destroy) { sourceClass->Destroy(AZ::BehaviorObject(AZStd::any_cast(dest), sourceClass->m_typeId)); } }; return aznew AZStd::any(address, valueInfo); } AZStd::optional PythonToParameterWithProxy(EditorPythonBindings::PythonProxyObject* proxyObj, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) { auto behaviorObject = proxyObj->GetBehaviorObject(); if (!behaviorObject) { AZ_Warning("python", false, "Empty behavior object sent in."); return AZStd::nullopt; } AZStd::any* anyValue = CreateAnyValue(behaviorObject.value()->m_typeId, behaviorObject.value()->m_address); if (!anyValue) { return AZStd::nullopt; } auto deleteAny = [anyValue]() { delete anyValue; }; outValue.Set(anyValue); return AZStd::make_optional(PythonMarshalTypeRequests::BehaviorValueResult{ true, deleteAny }); } AZStd::optional ReturnVectorFromList(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::list pyList, AZ::BehaviorValueParameter& outValue) { // empty lists are okay, sending as an empty AZStd::any() if (pyList.size() == 0) { AZStd::any* anyValue = aznew AZStd::any(); auto deleteAny = [anyValue]() { delete anyValue; }; outValue.Set(anyValue); return AZStd::make_optional(PythonMarshalTypeRequests::BehaviorValueResult{ true, deleteAny }); } // determine the type from the Python type pybind11::object pyListElement = pyList[0]; AZ::TypeId vectorType; if (pybind11::isinstance(pyListElement)) { // making a AZ::TypeId for a 'AZStd::vector' // the vector TypeId equals "underlying element type" + "allocator type" + "vector type" auto* proxy = pybind11::cast(pyListElement); if (!proxy || !proxy->GetWrappedType()) { return AZStd::nullopt; } constexpr const char AZStdVectorTypeId[] = "{A60E3E61-1FF6-4982-B6B8-9E4350C4C679}"; vectorType = proxy->GetWrappedType().value(); vectorType += azrtti_typeid(); vectorType += AZ::TypeId(AZStdVectorTypeId); } else if (PyBool_Check(pyListElement.ptr())) { vectorType = azrtti_typeid>(); } else if (PyFloat_Check(pyListElement.ptr())) { vectorType = azrtti_typeid>(); } else if (PyNumber_Check(pyListElement.ptr())) { vectorType = azrtti_typeid>(); } else if (PyUnicode_Check(pyListElement.ptr())) { vectorType = azrtti_typeid>(); } AZStd::optional vectorResult; PythonMarshalTypeRequestBus::EventResult(vectorResult, vectorType, &PythonMarshalTypeRequestBus::Events::PythonToBehaviorValueParameter, traits, pyList, outValue); if (vectorResult && vectorResult.value().first) { AZStd::any* anyValue = CreateAnyValue(vectorType, outValue.m_value); if (!anyValue) { return AZStd::nullopt; } outValue.Set(anyValue); auto deleteAny = [anyValue]() { delete anyValue; }; return AZStd::make_optional(PythonMarshalTypeRequests::BehaviorValueResult{ true, deleteAny }); } return AZStd::nullopt; } }; class TypeConverterBool : public PythonMarshalComponent::TypeConverter { public: AZStd::optional PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override { if (CanConvertPythonToBehaviorValue(traits,pyObj)) { outValue.StoreInTempData(pybind11::cast(pyObj)); return { { true, nullptr } }; } return AZStd::nullopt; } AZStd::optional BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override { PythonMarshalTypeRequests::PythonValueResult result; result.first = MarshalBehaviorValueParameter(behaviorValue); return result; } bool CanConvertPythonToBehaviorValue(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override { return (PyBool_Check(pyObj.ptr()) != false); } }; template class TypeConverterInteger : public PythonMarshalComponent::TypeConverter { public: AZStd::optional PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override { if (CanConvertPythonToBehaviorValue(traits, pyObj)) { outValue.StoreInTempData(pybind11::cast(pyObj)); return { { true, nullptr } }; } return AZStd::nullopt; } AZStd::optional BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override { PythonMarshalTypeRequests::PythonValueResult result; result.first = MarshalBehaviorValueParameter(behaviorValue); return result; } bool CanConvertPythonToBehaviorValue(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override { return PyLong_Check(pyObj.ptr()) != false; } }; template class TypeConverterReal : public PythonMarshalComponent::TypeConverter { public: AZStd::optional PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override { if (CanConvertPythonToBehaviorValue(traits, pyObj)) { NativeType nativeType = pybind11::cast(pyObj); outValue.StoreInTempData(BehaviorType{ nativeType }); return { { true, nullptr } }; } return AZStd::nullopt; } AZStd::optional BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override { PythonMarshalTypeRequests::PythonValueResult result; result.first = MarshalBehaviorValueParameter(behaviorValue); return result; } bool CanConvertPythonToBehaviorValue(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override { return (PyFloat_Check(pyObj.ptr()) != false); } }; template class TypeConverterString : public PythonMarshalComponent::TypeConverter { public: AZStd::optional PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override { if (!CanConvertPythonToBehaviorValue(traits, pyObj)) { return AZStd::nullopt; } else if (AZ::AzTypeInfo::Uuid() == AZ::AzTypeInfo::Uuid()) { // in the case of an error, NULL is returned with an exception set and no size is stored Py_ssize_t size = -1; const char* value = PyUnicode_AsUTF8AndSize(pyObj.ptr(), &size); if (value || size != -1) { AZStd::string_view stringView(value, size); outValue.StoreInTempData(AZStd::move(stringView)); return { { true, nullptr } }; } } else if (AZ::AzTypeInfo::Uuid() == AZ::AzTypeInfo::Uuid()) { AZStd::string* stringValue = new AZStd::string(pybind11::cast(pyObj)); outValue.Set(stringValue); return { {true, [stringValue]() { delete stringValue; }} }; } return AZStd::nullopt; } AZStd::optional BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override { PythonMarshalTypeRequests::PythonValueResult result; result.first = pybind11::cast(behaviorValue.GetAsUnsafe()); return result; } bool CanConvertPythonToBehaviorValue(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override { return(PyUnicode_Check(pyObj.ptr()) != false ); } }; // The 'char' type can come in with a variety of type traits: // class TypeConverterChar : public PythonMarshalComponent::TypeConverter { public: AZStd::optional PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override { if (!CanConvertPythonToBehaviorValue(traits, pyObj)) { return AZStd::nullopt; } // in the case of an error, NULL is returned with an exception set and no size is stored Py_ssize_t size = -1; const char* value = PyUnicode_AsUTF8AndSize(pyObj.ptr(), &size); if (!value || size == -1) { return AZStd::nullopt; } if (IsPointerType(traits)) { outValue.StoreInTempData(value); } else { outValue.StoreInTempData(value[0]); } return { { true, nullptr } }; } AZStd::optional BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override { if (IsPointerType(static_cast(behaviorValue.m_traits))) { if (behaviorValue.ConvertTo()) { PythonMarshalTypeRequests::PythonValueResult resultString; resultString.first = pybind11::str(*behaviorValue.GetAsUnsafe()); return resultString; } } else { if (behaviorValue.ConvertTo()) { char characters[2]; characters[0] = *behaviorValue.GetAsUnsafe(); characters[1] = 0; PythonMarshalTypeRequests::PythonValueResult resultCharNumber; resultCharNumber.first = pybind11::str(characters, 1); return resultCharNumber; } } return AZStd::nullopt; } bool CanConvertPythonToBehaviorValue(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override { return (PyUnicode_Check(pyObj.ptr()) != false); } }; namespace Container { class TypeConverterByteStream : public PythonMarshalComponent::TypeConverter { public: AZStd::optional PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override { if (!CanConvertPythonToBehaviorValue(traits, pyObj)) { AZ_Warning("python", false, "Expected a Python List as input"); return AZStd::nullopt; } AZStd::vector* newByteStream = new AZStd::vector(); pybind11::list pyList(pyObj); for (auto pyItem = pyList.begin(); pyItem != pyList.end(); ++pyItem) { AZ::u8 byte = (*pyItem).cast(); newByteStream->push_back(byte); } outValue.m_name = "AZStd::vector"; outValue.m_value = newByteStream; outValue.m_typeId = AZ::AzTypeInfo>::Uuid(); outValue.m_traits = traits; auto deleteVector = [newByteStream]() { delete newByteStream; }; return { { true, deleteVector } }; } AZStd::optional BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override { if (behaviorValue.ConvertTo(AZ::AzTypeInfo>::Uuid())) { pybind11::list pythonList; AZStd::vector* byteStream = behaviorValue.GetAsUnsafe>(); for (AZ::u8 byte : *byteStream) { pythonList.append(pybind11::cast(byte)); } PythonMarshalTypeRequests::PythonValueResult result; result.first = pythonList; return result; } return AZStd::nullopt; } bool CanConvertPythonToBehaviorValue(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override { return (PyList_Check(pyObj.ptr()) != false); } }; AZStd::optional ProcessBehaviorObject(AZ::BehaviorObject& behaviorObject) { AZ::BehaviorValueParameter source; source.m_value = behaviorObject.m_address; source.m_typeId = behaviorObject.m_typeId; AZStd::optional result; PythonMarshalTypeRequestBus::EventResult(result, source.m_typeId, &PythonMarshalTypeRequestBus::Events::BehaviorValueParameterToPython, source); if (result.has_value()) { return result; } // return an opaque Behavior Objects to the caller if not a 'simple' type pybind11::object objectValue = PythonProxyObjectManagement::CreatePythonProxyObject(behaviorObject.m_typeId, behaviorObject.m_address); if (!objectValue.is_none()) { return PythonMarshalTypeRequests::PythonValueResult( objectValue, {} ); } return AZStd::nullopt; } AZStd::optional ProcessPythonObject(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pythonObj, const AZ::TypeId& elementTypeId, AZ::BehaviorValueParameter& outValue) { // first try to convert using the element's type ID AZStd::optional result; PythonMarshalTypeRequestBus::EventResult(result, elementTypeId, &PythonMarshalTypeRequestBus::Events::PythonToBehaviorValueParameter, traits, pythonObj, outValue); if (result) { return result; } else if (pybind11::isinstance(pythonObj)) { AZ::BehaviorValueParameter behaviorArg; behaviorArg.m_traits = traits; behaviorArg.m_typeId = elementTypeId; if (Convert::PythonProxyObjectToBehaviorValueParameter(behaviorArg, pythonObj, outValue)) { return PythonMarshalTypeRequests::BehaviorValueResult( { true, nullptr } ); } } return AZStd::nullopt; } bool LoadPythonToPairElement(PyObject* pyItem, PythonMarshalTypeRequests::BehaviorTraits traits, const AZ::SerializeContext::ClassElement* itemElement, AZ::SerializeContext::IDataContainer* pairContainer, size_t index, AZ::SerializeContext* serializeContext, void* newPair) { pybind11::object pyObj{ pybind11::reinterpret_borrow(pyItem) }; AZ::BehaviorValueParameter behaviorItem; auto behaviorResult = ProcessPythonObject(traits, pyObj, itemElement->m_typeId, behaviorItem); if (behaviorResult && behaviorResult.value().first) { void* itemAddress = pairContainer->GetElementByIndex(newPair, itemElement, index); AZ_Assert(itemAddress, "Element reserved for associative container's pair, but unable to retrieve address of the item:%d", index); serializeContext->CloneObjectInplace(itemAddress, behaviorItem.m_value, itemElement->m_typeId); } else { AZ_Warning("python", false, "Could not convert to pair element type %s for the pair<>; failed to marshal Python input %s", itemElement->m_name, Convert::GetPythonTypeName(pyObj).c_str()); return false; } return true; } AZStd::optional ConvertPythonElement(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pythonElement, const AZ::TypeId& elementTypeId, AZ::BehaviorValueParameter& outValue) { // first try to convert using the element's type ID AZStd::optional result; PythonMarshalTypeRequestBus::EventResult(result, elementTypeId, &PythonMarshalTypeRequestBus::Events::PythonToBehaviorValueParameter, traits, pythonElement, outValue); if (result) { return result; } else if (pybind11::isinstance(pythonElement)) { AZ::BehaviorValueParameter behaviorArg; behaviorArg.m_traits = traits; behaviorArg.m_typeId = elementTypeId; if (Convert::PythonProxyObjectToBehaviorValueParameter(behaviorArg, pythonElement, outValue)) { return { { true, nullptr } }; } } return AZStd::nullopt; } class TypeConverterDictionary final : public PythonMarshalComponent::TypeConverter { AZ::GenericClassInfo* m_genericClassInfo = nullptr; const AZ::SerializeContext::ClassData* m_classData = nullptr; const AZ::TypeId m_typeId = {}; public: TypeConverterDictionary(AZ::GenericClassInfo* genericClassInfo, const AZ::SerializeContext::ClassData* classData, const AZ::TypeId& typeId) : m_genericClassInfo(genericClassInfo) , m_classData(classData) , m_typeId(typeId) { } AZStd::optional PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override { if (!CanConvertPythonToBehaviorValue(traits, pyObj)) { AZ_Warning("python", false, "The dictionary container type for %s", m_classData->m_name); return AZStd::nullopt; } const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(m_typeId); if (!behaviorClass) { AZ_Warning("python", false, "Missing dictionary behavior class for %s", m_typeId.ToString().c_str()); return AZStd::nullopt; } AZ::SerializeContext* serializeContext = nullptr; AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); if (!serializeContext) { return AZStd::nullopt; } // prepare the AZStd::unordered_map<> container AZ::BehaviorObject mapInstance = behaviorClass->Create(); AZ::SerializeContext::IDataContainer* mapDataContainer = m_classData->m_container; const AZ::SerializeContext::ClassElement* pairElement = m_classData->m_container->GetElement(m_classData->m_container->GetDefaultElementNameCrc()); const AZ::SerializeContext::ClassData* pairClass = serializeContext->FindClassData(pairElement->m_typeId); AZ_Assert(pairClass, "Associative container was registered but not the pair that's used for storage."); AZ::SerializeContext::IDataContainer* pairContainer = pairClass->m_container; AZ_Assert(pairContainer, "Associative container is missing the interface to the storage container."); // get the key/value element types const AZ::SerializeContext::ClassElement* keyElement = nullptr; const AZ::SerializeContext::ClassElement* valueElement = nullptr; auto keyValueTypeEnumCallback = [&keyElement, &valueElement](const AZ::Uuid&, const AZ::SerializeContext::ClassElement* genericClassElement) { if (genericClassElement->m_flags & AZ::SerializeContext::ClassElement::Flags::FLG_POINTER) { AZ_Error("python", false, "Python marshalling does not handle naked pointers; not converting dict's pair"); return false; } else if (!keyElement) { keyElement = genericClassElement; } else if (!valueElement) { valueElement = genericClassElement; } else { AZ_Error("python", !valueElement, "The pair element in a container can't have more than 2 elements."); return false; } return true; }; pairContainer->EnumTypes(keyValueTypeEnumCallback); if (!keyElement || !valueElement) { return AZStd::nullopt; } PyObject* key = nullptr; PyObject* value = nullptr; Py_ssize_t pos = 0; while (PyDict_Next(pyObj.ptr(), &pos, &key, &value)) { void* newPair = mapDataContainer->ReserveElement(mapInstance.m_address, pairElement); AZ_Assert(newPair, "Could not allocate pair entry for map via ReserveElement()"); if (newPair) { const bool didKey = LoadPythonToPairElement(key, traits, keyElement, pairContainer, 0, serializeContext, newPair); const bool didValue = LoadPythonToPairElement(value, traits, valueElement, pairContainer, 1, serializeContext, newPair); if (didKey && didValue) { // store the pair in the map mapDataContainer->StoreElement(mapInstance.m_address, newPair); } else { // release element, due to a failed pair conversion mapDataContainer->FreeReservedElement(mapInstance.m_address, newPair, serializeContext); } } } AZ_Warning("python", PyDict_Size(pyObj.ptr()) == mapDataContainer->Size(mapInstance.m_address), "Python Dict size:%d does not match the size of the unordered_map:%d", pos, mapDataContainer->Size(mapInstance.m_address)); outValue.m_value = mapInstance.m_address; outValue.m_typeId = mapInstance.m_typeId; outValue.m_traits = traits; auto deleteMapInstance = [behaviorClass, mapInstance]() { behaviorClass->Destroy(mapInstance); }; return PythonMarshalTypeRequests::BehaviorValueResult{ true, deleteMapInstance }; } AZStd::optional BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override { // the class data must have a container interface auto* containerInterface = m_classData->m_container; if (!containerInterface) { return AZStd::nullopt; } if (behaviorValue.ConvertTo(m_typeId)) { auto cleanUpList = AZStd::make_shared>(); pybind11::dict pythonDictionary; // visit each unordered_map entry auto elementCallback = [pythonDictionary, cleanUpList](void* instancePointer, const auto& elementClassId, const auto* elementGenericClassData, const auto* genericClassElement) { pybind11::object pythonKey { pybind11::none() }; pybind11::object pythonItem { pybind11::none() }; // visit the AZStd::pair elements auto pairCallback = [cleanUpList, &pythonKey, &pythonItem](void* instancePair, const auto& elementClassId, const auto* elementGenericClassData, const auto* genericClassElement) { AZ::BehaviorObject behaviorObjectValue(instancePair, elementClassId); auto result = ProcessBehaviorObject(behaviorObjectValue); if (result) { PythonMarshalTypeRequests::DeallocateFunction deallocateFunction = result.value().second; if (result.value().second) { cleanUpList->emplace_back(AZStd::move(result.value().second)); } pybind11::object pythonResult = result.value().first; if (pythonKey.is_none()) { pythonKey = pythonResult; } else if (pythonItem.is_none()) { pythonItem = pythonResult; } } return true; }; elementGenericClassData->m_container->EnumElements(instancePointer, pairCallback); // have a valid key and value? if (!pythonKey.is_none() && !pythonItem.is_none()) { // assign the key's value in the dictionary? if (PyDict_SetItem(pythonDictionary.ptr(), pythonKey.ptr(), pythonItem.ptr()) < 0) { AZStd::string pythonKeyString = pybind11::cast(pythonKey); AZStd::string pythonItemString = pybind11::cast(pythonItem); AZ_Warning("python", false, "Could not add key:%s with item value:%s", pythonKeyString.c_str(), pythonKeyString.c_str()); } } return true; }; containerInterface->EnumElements(behaviorValue.m_value, elementCallback); PythonMarshalTypeRequests::PythonValueResult result; result.first = pythonDictionary; if (!cleanUpList->empty()) { AZStd::weak_ptr> cleanUp(cleanUpList); result.second = [cleanUp]() { auto cleanupList = cleanUp.lock(); if (cleanupList) { AZStd::for_each(cleanupList->begin(), cleanupList->end(), [](auto& deleteMe) { deleteMe(); }); } }; } return result; } return AZStd::nullopt; } bool CanConvertPythonToBehaviorValue(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override { // the underlying types must have exactly two elements AZStd::vector typeList = AZ::Utils::GetContainedTypes(m_typeId); if (typeList.size() != 2) { return false; } return (PyDict_Check(pyObj.ptr()) != false); } }; class TypeConverterVector : public PythonMarshalComponent::TypeConverter { public: AZ::GenericClassInfo* m_genericClassInfo = nullptr; const AZ::SerializeContext::ClassData* m_classData = nullptr; const AZ::TypeId m_typeId = {}; TypeConverterVector(AZ::GenericClassInfo* genericClassInfo, const AZ::SerializeContext::ClassData* classData, const AZ::TypeId& typeId) : m_genericClassInfo(genericClassInfo) , m_classData(classData) , m_typeId(typeId) { } AZStd::optional HandlePythonElement(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pythonElement, const AZ::TypeId& elementTypeId, AZ::BehaviorValueParameter& outValue) { // first try to convert using the element's type ID AZStd::optional result; PythonMarshalTypeRequestBus::EventResult(result, elementTypeId, &PythonMarshalTypeRequestBus::Events::PythonToBehaviorValueParameter, traits, pythonElement, outValue); if (result) { return result; } else if (pybind11::isinstance(pythonElement)) { AZ::BehaviorValueParameter behaviorArg; behaviorArg.m_traits = traits; behaviorArg.m_typeId = elementTypeId; if (Convert::PythonProxyObjectToBehaviorValueParameter(behaviorArg, pythonElement, outValue)) { return { { true, nullptr } }; } } return AZStd::nullopt; } // handle a vector of Behavior Class values AZStd::optional PythonToBehaviorObjectList(const AZ::TypeId& elementType, const AZ::BehaviorClass* behaviorClass, PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) { auto iteratorToPushBackMethod = behaviorClass->m_methods.find("push_back"); if (iteratorToPushBackMethod == behaviorClass->m_methods.end()) { AZ_Warning("python", false, "BehaviorClass container missing push_back method"); return AZStd::nullopt; } // prepare the AZStd::vector container AZ::BehaviorObject instance = behaviorClass->Create(); AZ::BehaviorMethod* pushBackMethod = iteratorToPushBackMethod->second; size_t vectorCount = 0; pybind11::list pyList(pyObj); for (auto pyItem = pyList.begin(); pyItem != pyList.end(); ++pyItem) { auto pyObjItem = pybind11::cast(*pyItem); AZ::BehaviorValueParameter elementValue; auto result = HandlePythonElement(traits, pyObjItem, elementType, elementValue); if (result && result.value().first) { AZ::BehaviorValueParameter parameters[2]; parameters[0].Set(&instance); parameters[1].Set(elementValue); pushBackMethod->Call(parameters, 2); ++vectorCount; } else { AZ_Warning("python", false, "Could not convert to behavior element type %s for the vector<>; failed to marshal Python input %s", elementType.ToString().c_str(), Convert::GetPythonTypeName(pyObjItem).c_str()); return AZStd::nullopt; } } AZ_Warning("python", vectorCount == pyList.size(), "Python list size:%d does not match the size of the vector:%d", pyList.size(), vectorCount); outValue.m_value = instance.m_address; outValue.m_typeId = instance.m_typeId; outValue.m_traits = traits; auto deleteVector = [behaviorClass, instance]() { behaviorClass->Destroy(instance); }; return { { true, deleteVector } }; } // handle a vector of a data type not registered with the Behavior Context AZStd::optional PythonToBehaviorSerializedList(const AZ::TypeId& elementType, PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) { // fetch the container parts const AZ::SerializeContext::ClassData* classData = m_genericClassInfo->GetClassData(); const AZ::SerializeContext::ClassElement* classElement = classData->m_container->GetElement(classData->m_container->GetDefaultElementNameCrc()); // prepare the AZStd::vector container AZ::SerializeContext* serializeContext = nullptr; AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); AZStd::any* newVector = new AZStd::any(serializeContext->CreateAny(m_typeId)); void* instance = AZStd::any_cast(newVector); size_t vectorCount = 0; pybind11::list pyList(pyObj); for (auto pyItem = pyList.begin(); pyItem != pyList.end(); ++pyItem) { auto pyObjItem = pybind11::cast(*pyItem); AZ::BehaviorValueParameter elementValue; auto elementResult = HandlePythonElement(traits, pyObjItem, elementType, elementValue); if (elementResult && elementResult.value().first) { void* destination = classData->m_container->ReserveElement(instance, classElement); AZ_Error("python", destination, "Could not allocate via ReserveElement()"); if (destination) { serializeContext->CloneObjectInplace(destination, elementValue.m_value, elementType); ++vectorCount; } } else { AZ_Warning("python", false, "Could not convert to serialized element type %s for the vector<>; failed to marshal Python input %s", elementType.ToString().c_str(), Convert::GetPythonTypeName(pyObjItem).c_str()); return AZStd::nullopt; } } AZ_Warning("python", vectorCount == pyList.size(), "Python list size:%d does not match the size of the vector:%d", pyList.size(), vectorCount); outValue.m_name = classData->m_name; outValue.m_value = instance; outValue.m_typeId = m_typeId; outValue.m_traits = traits; auto deleteVector = [newVector]() { delete newVector; }; return { { true, deleteVector } }; } AZStd::optional PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override { AZStd::vector typeList = AZ::Utils::GetContainedTypes(m_typeId); if (typeList.empty()) { AZ_Warning("python", false, "The list container type for %s had no types; expected one type", m_classData->m_name); return AZStd::nullopt; } else if (PyList_Check(pyObj.ptr()) == false) { AZ_Warning("python", false, "Expected a Python List as input"); return AZStd::nullopt; } const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(m_typeId); if (behaviorClass) { return PythonToBehaviorObjectList(typeList[0], behaviorClass, traits, pyObj, outValue); } return PythonToBehaviorSerializedList(typeList[0], traits, pyObj, outValue); } using HandleResult = AZStd::optional; HandleResult HandleElement(AZ::BehaviorObject& behaviorObject, pybind11::list pythonList) { AZ::BehaviorValueParameter source; source.m_value = behaviorObject.m_address; source.m_typeId = behaviorObject.m_typeId; AZStd::optional result; PythonMarshalTypeRequestBus::EventResult(result, source.m_typeId, &PythonMarshalTypeRequestBus::Events::BehaviorValueParameterToPython, source); if (result.has_value()) { pythonList.append(result.value().first); return AZStd::move(result.value().second); } // return back a 'list of opaque Behavior Objects' back to the caller if not a 'simple' type pybind11::object value = PythonProxyObjectManagement::CreatePythonProxyObject(behaviorObject.m_typeId, behaviorObject.m_address); if (!value.is_none()) { pythonList.append(value); } return AZStd::nullopt; } AZStd::optional BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override { auto* container = m_classData->m_container; if (behaviorValue.ConvertTo(m_typeId) && container) { auto deleterList = AZStd::make_shared>(); pybind11::list pythonList; auto elementCallback = [this, pythonList, deleterList](void* instancePointer, const auto& elementClassId, const auto* elementGenericClassData, const auto* genericClassElement) { AZ::BehaviorObject behaviorObject(instancePointer, elementClassId); auto result = this->HandleElement(behaviorObject, pythonList); if (result) { if (result.value()) { deleterList->emplace_back(AZStd::move(result.value())); } } return true; }; container->EnumElements(behaviorValue.m_value, elementCallback); PythonMarshalTypeRequests::PythonValueResult result; result.first = pythonList; if (!deleterList->empty()) { AZStd::weak_ptr> cleanUp(deleterList); result.second = [cleanUp]() { auto cleanupList = cleanUp.lock(); if (cleanupList) { AZStd::for_each(cleanupList->begin(), cleanupList->end(), [](auto& deleteMe) { deleteMe(); }); } }; } return result; } return AZStd::nullopt; } bool CanConvertPythonToBehaviorValue(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override { AZStd::vector typeList = AZ::Utils::GetContainedTypes(m_typeId); if (typeList.empty()) { return false; } return (PyList_Check(pyObj.ptr()) != false); } }; class TypeConverterSet : public PythonMarshalComponent::TypeConverter { public: AZ::GenericClassInfo* m_genericClassInfo = nullptr; const AZ::SerializeContext::ClassData* m_classData = nullptr; const AZ::TypeId m_typeId = {}; TypeConverterSet(AZ::GenericClassInfo* genericClassInfo, const AZ::SerializeContext::ClassData* classData, const AZ::TypeId& typeId) : m_genericClassInfo(genericClassInfo) , m_classData(classData) , m_typeId(typeId) { } // handle a vector of Behavior Class values AZStd::optional PythonToBehaviorObjectSet(const AZ::TypeId& elementType, const AZ::BehaviorClass* behaviorClass, PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) { auto iteratorToInsertMethod = behaviorClass->m_methods.find("Insert"); if (iteratorToInsertMethod == behaviorClass->m_methods.end()) { AZ_Error("python", false, "The AZStd::unordered_set BehaviorClass reflection is missing the Insert method!"); return AZStd::nullopt; } // prepare the AZStd::unordered_set container AZ::BehaviorObject instance = behaviorClass->Create(); AZ::BehaviorMethod* insertMethod = iteratorToInsertMethod->second; size_t itemCount = 0; pybind11::set pySet(pyObj); for (auto pyItem = pySet.begin(); pyItem != pySet.end(); ++pyItem) { auto pyObjItem = pybind11::cast(*pyItem); AZ::BehaviorValueParameter elementValue; auto result = ConvertPythonElement(traits, pyObjItem, elementType, elementValue); if (result && result.value().first) { AZ::BehaviorValueParameter parameters[2]; // set the 'this' pointer parameters[0].m_value = instance.m_address; parameters[0].m_typeId = instance.m_typeId; // set the value element parameters[1].Set(elementValue); insertMethod->Call(parameters, 2); ++itemCount; } else { AZ_Warning("python", false, "Convert to behavior element type %s for the unordered_set<> failed to marshal Python input %s", elementType.ToString().c_str(), Convert::GetPythonTypeName(pyObjItem).c_str()); return AZStd::nullopt; } } AZ_Warning("python", itemCount == pySet.size(), "Python Set size:%d does not match the size of the unordered_set:%d", pySet.size(), itemCount); outValue.m_value = instance.m_address; outValue.m_typeId = instance.m_typeId; outValue.m_traits = traits; auto deleteVector = [behaviorClass, instance]() { behaviorClass->Destroy(instance); }; return { { true, deleteVector } }; } AZStd::optional PythonToBehaviorSerializedSet(const AZ::TypeId& elementType, PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) { // fetch the container parts const AZ::SerializeContext::ClassData* classData = m_genericClassInfo->GetClassData(); const AZ::SerializeContext::ClassElement* classElement = classData->m_container->GetElement(classData->m_container->GetDefaultElementNameCrc()); // prepare the AZStd::unordered_set container AZ::SerializeContext* serializeContext = nullptr; AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); AZStd::any* newVector = new AZStd::any(serializeContext->CreateAny(m_typeId)); void* instance = AZStd::any_cast(newVector); size_t itemCount = 0; pybind11::set pySet(pyObj); for (auto pyItem = pySet.begin(); pyItem != pySet.end(); ++pyItem) { auto pyObjItem = pybind11::cast(*pyItem); AZ::BehaviorValueParameter elementValue; auto elementResult = ConvertPythonElement(traits, pyObjItem, elementType, elementValue); if (elementResult && elementResult.value().first) { void* destination = classData->m_container->ReserveElement(instance, classElement); AZ_Error("python", destination, "Could not allocate via ReserveElement()"); if (destination) { serializeContext->CloneObjectInplace(destination, elementValue.m_value, elementType); ++itemCount; } } else { AZ_Warning("python", false, "Convert to serialized element type %s for the unordered_set<> failed to marshal Python input %s", elementType.ToString().c_str(), Convert::GetPythonTypeName(pyObjItem).c_str()); return AZStd::nullopt; } } AZ_Warning("python", itemCount == pySet.size(), "Python list size:%d does not match the size of the unordered_set:%d", pySet.size(), itemCount); outValue.m_name = classData->m_name; outValue.m_value = instance; outValue.m_typeId = m_typeId; outValue.m_traits = traits; auto deleteVector = [newVector]() { delete newVector; }; return { { true, deleteVector } }; } AZStd::optional PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override { AZStd::vector typeList = AZ::Utils::GetContainedTypes(m_typeId); if (typeList.empty()) { AZ_Warning("python", false, "The unordered_set container type for %s had no types; expected one type", m_classData->m_name); return AZStd::nullopt; } else if (PySet_Check(pyObj.ptr()) == false) { AZ_Warning("python", false, "Expected a Python Set as input"); return AZStd::nullopt; } const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(m_typeId); if (behaviorClass) { return PythonToBehaviorObjectSet(typeList[0], behaviorClass, traits, pyObj, outValue); } return PythonToBehaviorSerializedSet(typeList[0], traits, pyObj, outValue); } AZStd::optional HandleSetElement(AZ::BehaviorObject& behaviorObject, pybind11::set pythonSet) { AZ::BehaviorValueParameter source; source.m_value = behaviorObject.m_address; source.m_typeId = behaviorObject.m_typeId; AZStd::optional result; PythonMarshalTypeRequestBus::EventResult(result, source.m_typeId, &PythonMarshalTypeRequestBus::Events::BehaviorValueParameterToPython, source); if (result.has_value()) { pythonSet.add(result.value().first); return AZStd::move(result.value().second); } // return back a 'list of opaque Behavior Objects' back to the caller if not a 'simple' type pybind11::object value = PythonProxyObjectManagement::CreatePythonProxyObject(behaviorObject.m_typeId, behaviorObject.m_address); if (!value.is_none()) { pythonSet.add(value); } return AZStd::nullopt; } AZStd::optional BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override { auto* container = m_classData->m_container; AZ_Error("python", container, "Set container class data is missing"); if (container == nullptr) { return AZStd::nullopt; } if (behaviorValue.ConvertTo(m_typeId)) { auto deleterList = AZStd::make_shared>(); pybind11::set pythonSet; auto elementCallback = [this, pythonSet, deleterList](void* instancePointer, const auto& elementClassId, const auto* elementGenericClassData, const auto* genericClassElement) { AZ::BehaviorObject behaviorObject(instancePointer, elementClassId); auto result = this->HandleSetElement(behaviorObject, pythonSet); if (result) { if (result.value()) { deleterList->emplace_back(AZStd::move(result.value())); } } return true; }; container->EnumElements(behaviorValue.m_value, elementCallback); PythonMarshalTypeRequests::PythonValueResult result; result.first = pythonSet; if (!deleterList->empty()) { AZStd::weak_ptr> cleanUp(deleterList); result.second = [cleanUp]() { auto cleanupList = cleanUp.lock(); if (cleanupList) { AZStd::for_each(cleanupList->begin(), cleanupList->end(), [](auto& deleteMe) { deleteMe(); }); } }; } return result; } return AZStd::nullopt; } bool CanConvertPythonToBehaviorValue(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override { AZStd::vector typeList = AZ::Utils::GetContainedTypes(m_typeId); if (typeList.empty()) { return false; } return (PySet_Check(pyObj.ptr()) != false); } }; class TypeConverterPair final : public PythonMarshalComponent::TypeConverter { AZ::GenericClassInfo* m_genericClassInfo = nullptr; const AZ::SerializeContext::ClassData* m_classData = nullptr; const AZ::TypeId m_typeId = {}; bool IsValidList(pybind11::object pyObj) const { return PyList_Check(pyObj.ptr()) != false && PyList_Size(pyObj.ptr()) == 2; } bool IsValidTuple(pybind11::object pyObj) const { return PyTuple_Check(pyObj.ptr()) != false && PyTuple_Size(pyObj.ptr()) == 2; } bool IsCompatibleProxy(pybind11::object pyObj) const { if (pybind11::isinstance(pyObj)) { auto behaviorObject = pybind11::cast(pyObj)->GetBehaviorObject(); AZ::Uuid typeId = behaviorObject.value()->m_typeId; return AZ::Utils::IsPairContainerType(typeId); } return false; } public: TypeConverterPair(AZ::GenericClassInfo* genericClassInfo, const AZ::SerializeContext::ClassData* classData, const AZ::TypeId& typeId) : m_genericClassInfo(genericClassInfo) , m_classData(classData) , m_typeId(typeId) { } AZStd::optional PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override { if (!CanConvertPythonToBehaviorValue(traits, pyObj)) { AZ_Warning("python", false, "Cannot convert pair container for %s", m_classData->m_name); return AZStd::nullopt; } const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(m_typeId); if (!behaviorClass) { AZ_Warning("python", false, "Missing pair behavior class for %s", m_typeId.ToString().c_str()); return AZStd::nullopt; } AZ::SerializeContext* serializeContext = nullptr; AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); if (!serializeContext) { return AZStd::nullopt; } // prepare the AZStd::pair<> container AZ::BehaviorObject pairInstance = behaviorClass->Create(); AZ::SerializeContext::IDataContainer* pairDataContainer = m_classData->m_container; // get the element types const AZ::SerializeContext::ClassElement* element0 = nullptr; const AZ::SerializeContext::ClassElement* element1 = nullptr; auto elementTypeEnumCallback = [&element0, &element1](const AZ::Uuid&, const AZ::SerializeContext::ClassElement* genericClassElement) { if (genericClassElement->m_flags & AZ::SerializeContext::ClassElement::Flags::FLG_POINTER) { AZ_Error("python", false, "Python marshalling does not handle naked pointers; not converting the pair"); return false; } else if (!element0) { element0 = genericClassElement; } else if (!element1) { element1 = genericClassElement; } else { AZ_Error("python", false, "The pair container can't have more than 2 elements."); return false; } return true; }; pairDataContainer->EnumTypes(elementTypeEnumCallback); if (!element0 || !element1) { AZ_Error("python", false, "Could not retrieve pair elements."); return AZStd::nullopt; } // load python items into pair elements PyObject* item0 = nullptr; PyObject* item1 = nullptr; if (IsValidList(pyObj)) { pybind11::list pyList(pyObj); item0 = pyList[0].ptr(); item1 = pyList[1].ptr(); } else if (IsValidTuple(pyObj)) { pybind11::tuple pyTuple(pyObj); item0 = pyTuple[0].ptr(); item1 = pyTuple[1].ptr(); } else if (IsCompatibleProxy(pyObj)) { // OnDemandReflection> exposes "first" and "second" in the proxy object EditorPythonBindings::PythonProxyObject* proxy = pybind11::cast(pyObj); item0 = proxy->GetPropertyValue("first").ptr(); item1 = proxy->GetPropertyValue("second").ptr(); } void* reserved0 = pairDataContainer->ReserveElement(pairInstance.m_address, element0); AZ_Assert(reserved0, "Could not allocate pair's first item via ReserveElement()"); if (item0 && item0 && !LoadPythonToPairElement(item0, traits, element0, pairDataContainer, 0, serializeContext, pairInstance.m_address)) { pairDataContainer->FreeReservedElement(pairInstance.m_address, reserved0, serializeContext); return AZStd::nullopt; } void* reserved1 = pairDataContainer->ReserveElement(pairInstance.m_address, element1); AZ_Assert(reserved1, "Could not allocate pair's second item via ReserveElement()"); if (item1 && !LoadPythonToPairElement(item1, traits, element1, pairDataContainer, 1, serializeContext, pairInstance.m_address)) { pairDataContainer->FreeReservedElement(pairInstance.m_address, reserved0, serializeContext); pairDataContainer->FreeReservedElement(pairInstance.m_address, reserved1, serializeContext); return AZStd::nullopt; } outValue.m_value = pairInstance.m_address; outValue.m_typeId = pairInstance.m_typeId; outValue.m_traits = traits; auto pairInstanceDeleter = [behaviorClass, pairInstance]() { behaviorClass->Destroy(pairInstance); }; return PythonMarshalTypeRequests::BehaviorValueResult{ true, pairInstanceDeleter }; } AZStd::optional BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override { // the class data must have a container interface AZ::SerializeContext::IDataContainer* containerInterface = m_classData->m_container; if (!containerInterface) { AZ_Warning("python", false, "Container interface is missing from class %s.", m_classData->m_name); return AZStd::nullopt; } if (!behaviorValue.ConvertTo(m_typeId)) { AZ_Warning("python", false, "Cannot convert behavior value %s.", behaviorValue.m_name); return AZStd::nullopt; } auto cleanUpList = AZStd::make_shared>(); // return pair as list, if conversion failed for an item it will remain as 'none' pybind11::list pythonList; pybind11::object pythonItem0{ pybind11::none() }; pybind11::object pythonItem1{ pybind11::none() }; size_t itemCount = 0; auto pairElementCallback = [cleanUpList, &pythonItem0, &pythonItem1, &itemCount](void* instancePair, const AZ::Uuid& elementClassId, const AZ::SerializeContext::ClassData* elementGenericClassData, const AZ::SerializeContext::ClassElement* genericClassElement) { AZ::BehaviorObject behaviorObjectValue(instancePair, elementClassId); auto result = ProcessBehaviorObject(behaviorObjectValue); if (result.has_value()) { PythonMarshalTypeRequests::DeallocateFunction deallocateFunction = result.value().second; if (result.value().second) { cleanUpList->emplace_back(AZStd::move(result.value().second)); } pybind11::object pythonResult = result.value().first; if (itemCount == 0) { pythonItem0 = pythonResult; } else { pythonItem1 = pythonResult; } itemCount++; } else { AZ_Warning("python", false, "BehaviorObject was not processed, python item will remain 'none'."); } return true; }; containerInterface->EnumElements(behaviorValue.m_value, pairElementCallback); pythonList.append(pythonItem0); pythonList.append(pythonItem1); PythonMarshalTypeRequests::PythonValueResult result; result.first = pythonList; if (!cleanUpList->empty()) { AZStd::weak_ptr> cleanUp(cleanUpList); result.second = [cleanUp]() { auto cleanupList = cleanUp.lock(); if (cleanupList) { AZStd::for_each(cleanupList->begin(), cleanupList->end(), [](auto& deleteMe) { deleteMe(); }); } }; } return result; } bool CanConvertPythonToBehaviorValue(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override { AZStd::vector typeList = AZ::Utils::GetContainedTypes(m_typeId); bool isList = IsValidList(pyObj); bool isTuple = IsValidTuple(pyObj); bool isCompatibleProxy = IsCompatibleProxy(pyObj); if (typeList.empty() || typeList.size() != 2) { return false; } return isList || isTuple || isCompatibleProxy; } }; using TypeConverterRegistrant = AZStd::function; void RegisterContainerTypes(TypeConverterRegistrant registrant) { AZ::SerializeContext* serializeContext = nullptr; AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); if (!serializeContext) { return; } // handle the generic container types and create type converters for each found auto handleTypeInfo = [registrant, serializeContext](const AZ::SerializeContext::ClassData* classData, const AZ::TypeId& typeId) { if (typeId == AZ::AzTypeInfo>::Uuid()) { // AZStd::vector is registered in the Serialization Context as a ByteStream, so it fails on IsVectorContainerType() registrant(typeId, AZStd::make_unique()); } else if (AZ::Utils::IsVectorContainerType(typeId)) { registrant(typeId, AZStd::make_unique(serializeContext->FindGenericClassInfo(typeId), classData, typeId)); } else if (AZ::Utils::IsMapContainerType(typeId)) { registrant(typeId, AZStd::make_unique(serializeContext->FindGenericClassInfo(typeId), classData, typeId)); } else if (AZ::Utils::IsPairContainerType(typeId)) { registrant(typeId, AZStd::make_unique(serializeContext->FindGenericClassInfo(typeId), classData, typeId)); } else if (AZ::Utils::IsSetContainerType(typeId)) { registrant(typeId, AZStd::make_unique(serializeContext->FindGenericClassInfo(typeId), classData, typeId)); } return true; }; const bool includeGenerics = true; serializeContext->EnumerateAll(handleTypeInfo, includeGenerics); } } AZStd::optional PythonMarshalComponent::PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) { const auto* typeId = PythonMarshalTypeRequestBus::GetCurrentBusId(); AZ_Error("python", typeId, "Requires a valid non-null AZ::TypeId pointer"); if (!typeId) { return AZStd::nullopt; } auto converterEntry = m_typeConverterMap.find(*typeId); if (m_typeConverterMap.end() == converterEntry) { return AZStd::nullopt; } return converterEntry->second->PythonToBehaviorValueParameter(traits, pyObj, outValue); } AZStd::optional PythonMarshalComponent::BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) { const auto* typeId = PythonMarshalTypeRequestBus::GetCurrentBusId(); AZ_Error("python", typeId, "Requires a valid non-null AZ::TypeId pointer"); if (!typeId) { return AZStd::nullopt; } auto converterEntry = m_typeConverterMap.find(*typeId); if (m_typeConverterMap.end() == converterEntry) { return AZStd::nullopt; } return converterEntry->second->BehaviorValueParameterToPython(behaviorValue); } ////////////////////////////////////////////////////////////////////////// // PythonMarshalComponent void PythonMarshalComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(PythonMarshalingService); } void PythonMarshalComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) { incompatible.push_back(PythonMarshalingService); } void PythonMarshalComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) { required.push_back(PythonEmbeddedService); } bool PythonMarshalComponent::CanConvertPythonToBehaviorValue(BehaviorTraits traits, pybind11::object pyObj) const { const auto* typeId = PythonMarshalTypeRequestBus::GetCurrentBusId(); AZ_Error("python", typeId, "Requires a valid non-null AZ::TypeId pointer"); if (!typeId) { return false; } auto converterEntry = m_typeConverterMap.find(*typeId); if (converterEntry == m_typeConverterMap.end()) { return false; } return converterEntry->second->CanConvertPythonToBehaviorValue(traits, pyObj); } void PythonMarshalComponent::RegisterTypeConverter(const AZ::TypeId& typeId, TypeConverterPointer typeConverterPointer) { PythonMarshalTypeRequestBus::MultiHandler::BusConnect(typeId); m_typeConverterMap[typeId] = AZStd::move(typeConverterPointer); } void PythonMarshalComponent::Reflect(AZ::ReflectContext* context) { if (auto&& serialize = azrtti_cast(context)) { serialize->Class() ->Version(1) ->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector{AZ_CRC_CE("AssetBuilder")}) ; } } void PythonMarshalComponent::Activate() { RegisterTypeConverter(AZ::AzTypeInfo::Uuid(), AZStd::make_unique()); RegisterTypeConverter(AZ::AzTypeInfo::Uuid(), AZStd::make_unique()); RegisterTypeConverter(AZ::AzTypeInfo::Uuid(), AZStd::make_unique>()); RegisterTypeConverter(AZ::AzTypeInfo::Uuid(), AZStd::make_unique>()); RegisterTypeConverter(AZ::AzTypeInfo::Uuid(), AZStd::make_unique>()); RegisterTypeConverter(AZ::AzTypeInfo::Uuid(), AZStd::make_unique>()); RegisterTypeConverter(AZ::AzTypeInfo::Uuid(), AZStd::make_unique>()); RegisterTypeConverter(AZ::AzTypeInfo::Uuid(), AZStd::make_unique>()); RegisterTypeConverter(AZ::AzTypeInfo::Uuid(), AZStd::make_unique>()); RegisterTypeConverter(AZ::AzTypeInfo::Uuid(), AZStd::make_unique>()); RegisterTypeConverter(AZ::AzTypeInfo::Uuid(), AZStd::make_unique>()); RegisterTypeConverter(AZ::AzTypeInfo::Uuid(), AZStd::make_unique>()); RegisterTypeConverter(AZ::AzTypeInfo::Uuid(), AZStd::make_unique>()); RegisterTypeConverter(AZ::AzTypeInfo::Uuid(), AZStd::make_unique>()); RegisterTypeConverter(AZ::AzTypeInfo::Uuid(), AZStd::make_unique>()); RegisterTypeConverter(AZ::AzTypeInfo::Uuid(), AZStd::make_unique()); Container::RegisterContainerTypes([this](const AZ::TypeId& typeId, auto containerConverter) { this->RegisterTypeConverter(typeId, AZStd::move(containerConverter)); }); } void PythonMarshalComponent::Deactivate() { PythonMarshalTypeRequestBus::MultiHandler::BusDisconnect(); m_typeConverterMap.clear(); } }