You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/EditorPythonBindings/Code/Source/PythonMarshalComponent.cpp

1782 lines
85 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Source/PythonMarshalComponent.h>
#include <Source/PythonUtility.h>
#include <Source/PythonProxyObject.h>
#include <Source/PythonTypeCasters.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <AzCore/std/allocator.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <Source/PythonCommon.h>
#include <pybind11/embed.h>
#include <pybind11/pytypes.h>
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 <typename TInput, typename TPythonType>
pybind11::object MarshalBehaviorValueParameter(AZ::BehaviorValueParameter& result)
{
if (result.ConvertTo<TInput>())
{
TInput inputValue = static_cast<TInput>(*result.GetAsUnsafe<TInput>());
return TPythonType(inputValue);
}
return pybind11::cast<pybind11::none>(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<AZStd::string>().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<AZStd::string>().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<AZStd::string>().c_str());
}
else if (classData->m_container)
{
AZ_Warning("python", false, "Missing Serialize class container for UUID:%s Name:%s", typeId.ToString<AZStd::string>().c_str(), classData->m_name);
}
else
{
AZ_Warning("python", false, "Missing Serialize class for UUID:%s Name:%s", typeId.ToString<AZStd::string>().c_str(), classData->m_name);
}
}
class TypeConverterAny
: public PythonMarshalComponent::TypeConverter
{
public:
AZStd::optional<PythonMarshalTypeRequests::BehaviorValueResult> 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<EditorPythonBindings::PythonProxyObject>(pyObj))
{
EditorPythonBindings::PythonProxyObject* proxyObj = pybind11::cast<EditorPythonBindings::PythonProxyObject*>(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<AZStd::any>(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<pybind11::list>(pyObj), outValue);
}
else if (PyBool_Check(pyObj.ptr()))
{
return ReturnSimpleType<bool>(Py_True == pyObj.ptr(), outValue);
}
else if (PyFloat_Check(pyObj.ptr()))
{
return ReturnSimpleType<double>(PyFloat_AsDouble(pyObj.ptr()), outValue);
}
else if (PyLong_Check(pyObj.ptr()))
{
return ReturnSimpleType<AZ::s64>(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>(AZStd::string_view(value, size), outValue);
}
}
return AZStd::nullopt;
}
AZStd::optional<PythonMarshalTypeRequests::PythonValueResult> 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<AZStd::any>())
{
AZ_Warning("python", false, "Cannot convert the return value to a AZStd::any value.");
return AZStd::nullopt;
}
AZStd::any* anyValue = static_cast<AZStd::any*>(behaviorValue.GetAsUnsafe<AZStd::any>());
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<void>(anyValue);
AZStd::optional<PythonMarshalTypeRequests::PythonValueResult> 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<void>(anyValue));
return result;
}
}
return AZStd::nullopt;
}
bool CanConvertPythonToBehaviorValue([[maybe_unused]] 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<EditorPythonBindings::PythonProxyObject>(pyObj);
}
protected:
template <typename T>
AZStd::optional<PythonMarshalTypeRequests::BehaviorValueResult> ReturnSimpleType(T value, AZ::BehaviorValueParameter& outValue)
{
AZStd::any* anyValue = aznew AZStd::any(value);
outValue.Set<AZStd::any>(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<void**>(dest) = sourceClass->Allocate();
}
else if (action == AZStd::any::Action::Copy)
{
sourceClass->m_cloner(AZStd::any_cast<void>(dest), AZStd::any_cast<void>(source), sourceClass->m_userData);
}
else if (action == AZStd::any::Action::Move)
{
sourceClass->m_mover(AZStd::any_cast<void>(dest), AZStd::any_cast<void>(const_cast<AZStd::any*>(source)), sourceClass->m_userData);
}
else if (action == AZStd::any::Action::Destroy)
{
sourceClass->Destroy(AZ::BehaviorObject(AZStd::any_cast<void>(dest), sourceClass->m_typeId));
}
};
return aznew AZStd::any(address, valueInfo);
}
AZStd::optional<PythonMarshalTypeRequests::BehaviorValueResult> 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<AZStd::any>(anyValue);
return AZStd::make_optional(PythonMarshalTypeRequests::BehaviorValueResult{ true, deleteAny });
}
AZStd::optional<PythonMarshalTypeRequests::BehaviorValueResult> 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<AZStd::any>(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<EditorPythonBindings::PythonProxyObject>(pyListElement))
{
// making a AZ::TypeId for a 'AZStd::vector<Element Type Id, AZStd::allocator>'
// the vector TypeId equals "underlying element type" + "allocator type" + "vector type"
auto* proxy = pybind11::cast<PythonProxyObject*>(pyListElement);
if (!proxy || !proxy->GetWrappedType())
{
return AZStd::nullopt;
}
constexpr const char AZStdVectorTypeId[] = "{A60E3E61-1FF6-4982-B6B8-9E4350C4C679}";
vectorType = proxy->GetWrappedType().value();
vectorType += azrtti_typeid<AZStd::allocator>();
vectorType += AZ::TypeId(AZStdVectorTypeId);
}
else if (PyBool_Check(pyListElement.ptr()))
{
vectorType = azrtti_typeid<AZStd::vector<bool>>();
}
else if (PyFloat_Check(pyListElement.ptr()))
{
vectorType = azrtti_typeid<AZStd::vector<double>>();
}
else if (PyNumber_Check(pyListElement.ptr()))
{
vectorType = azrtti_typeid<AZStd::vector<AZ::s64>>();
}
else if (PyUnicode_Check(pyListElement.ptr()))
{
vectorType = azrtti_typeid<AZStd::vector<AZStd::string>>();
}
AZStd::optional<PythonMarshalTypeRequests::BehaviorValueResult> 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<AZStd::any>(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<PythonMarshalTypeRequests::BehaviorValueResult> PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override
{
if (CanConvertPythonToBehaviorValue(traits,pyObj))
{
outValue.StoreInTempData(pybind11::cast<bool>(pyObj));
return { { true, nullptr } };
}
return AZStd::nullopt;
}
AZStd::optional<PythonMarshalTypeRequests::PythonValueResult> BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override
{
PythonMarshalTypeRequests::PythonValueResult result;
result.first = MarshalBehaviorValueParameter<bool, pybind11::bool_>(behaviorValue);
return result;
}
bool CanConvertPythonToBehaviorValue([[maybe_unused]] PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override
{
return (PyBool_Check(pyObj.ptr()) != false);
}
};
template <typename T>
class TypeConverterInteger
: public PythonMarshalComponent::TypeConverter
{
public:
AZStd::optional<PythonMarshalTypeRequests::BehaviorValueResult> PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override
{
if (CanConvertPythonToBehaviorValue(traits, pyObj))
{
outValue.StoreInTempData(pybind11::cast<T>(pyObj));
return { { true, nullptr } };
}
return AZStd::nullopt;
}
AZStd::optional<PythonMarshalTypeRequests::PythonValueResult> BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override
{
PythonMarshalTypeRequests::PythonValueResult result;
result.first = MarshalBehaviorValueParameter<T, pybind11::int_>(behaviorValue);
return result;
}
bool CanConvertPythonToBehaviorValue([[maybe_unused]] PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override
{
return PyLong_Check(pyObj.ptr()) != false;
}
};
template <typename BehaviorType, typename NativeType, typename PythonType>
class TypeConverterReal
: public PythonMarshalComponent::TypeConverter
{
public:
AZStd::optional<PythonMarshalTypeRequests::BehaviorValueResult> PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override
{
if (CanConvertPythonToBehaviorValue(traits, pyObj))
{
NativeType nativeType = pybind11::cast<NativeType>(pyObj);
outValue.StoreInTempData(BehaviorType{ nativeType });
return { { true, nullptr } };
}
return AZStd::nullopt;
}
AZStd::optional<PythonMarshalTypeRequests::PythonValueResult> BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override
{
PythonMarshalTypeRequests::PythonValueResult result;
result.first = MarshalBehaviorValueParameter<BehaviorType, pybind11::float_>(behaviorValue);
return result;
}
bool CanConvertPythonToBehaviorValue([[maybe_unused]] PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override
{
return (PyFloat_Check(pyObj.ptr()) != false);
}
};
template <typename T>
class TypeConverterString
: public PythonMarshalComponent::TypeConverter
{
public:
AZStd::optional<PythonMarshalTypeRequests::BehaviorValueResult> PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override
{
if (!CanConvertPythonToBehaviorValue(traits, pyObj))
{
return AZStd::nullopt;
}
else if (AZ::AzTypeInfo<T>::Uuid() == AZ::AzTypeInfo<AZStd::string_view>::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::string_view>(AZStd::move(stringView));
return { { true, nullptr } };
}
}
else if (AZ::AzTypeInfo<T>::Uuid() == AZ::AzTypeInfo<AZStd::string>::Uuid())
{
AZStd::string* stringValue = new AZStd::string(pybind11::cast<AZStd::string>(pyObj));
outValue.Set<AZStd::string>(stringValue);
return { {true, [stringValue]() { delete stringValue; }} };
}
return AZStd::nullopt;
}
AZStd::optional<PythonMarshalTypeRequests::PythonValueResult> BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override
{
PythonMarshalTypeRequests::PythonValueResult result;
result.first = pybind11::cast(behaviorValue.GetAsUnsafe<T>());
return result;
}
bool CanConvertPythonToBehaviorValue([[maybe_unused]] 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<PythonMarshalTypeRequests::BehaviorValueResult> 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<PythonMarshalTypeRequests::PythonValueResult> BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override
{
if (IsPointerType(static_cast<PythonMarshalTypeRequests::BehaviorTraits>(behaviorValue.m_traits)))
{
if (behaviorValue.ConvertTo<const char*>())
{
PythonMarshalTypeRequests::PythonValueResult resultString;
resultString.first = pybind11::str(*behaviorValue.GetAsUnsafe<const char*>());
return resultString;
}
}
else
{
if (behaviorValue.ConvertTo<char>())
{
char characters[2];
characters[0] = *behaviorValue.GetAsUnsafe<char>();
characters[1] = 0;
PythonMarshalTypeRequests::PythonValueResult resultCharNumber;
resultCharNumber.first = pybind11::str(characters, 1);
return resultCharNumber;
}
}
return AZStd::nullopt;
}
bool CanConvertPythonToBehaviorValue([[maybe_unused]] PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override
{
return (PyUnicode_Check(pyObj.ptr()) != false);
}
};
namespace Container
{
class TypeConverterByteStream
: public PythonMarshalComponent::TypeConverter
{
public:
AZStd::optional<PythonMarshalTypeRequests::BehaviorValueResult> 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<AZ::u8>* newByteStream = new AZStd::vector<AZ::u8>();
pybind11::list pyList(pyObj);
for (auto pyItem = pyList.begin(); pyItem != pyList.end(); ++pyItem)
{
AZ::u8 byte = (*pyItem).cast<AZ::u8>();
newByteStream->push_back(byte);
}
outValue.m_name = "AZStd::vector<AZ::u8>";
outValue.m_value = newByteStream;
outValue.m_typeId = AZ::AzTypeInfo<AZStd::vector<AZ::u8>>::Uuid();
outValue.m_traits = traits;
auto deleteVector = [newByteStream]()
{
delete newByteStream;
};
return { { true, deleteVector } };
}
AZStd::optional<PythonMarshalTypeRequests::PythonValueResult> BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override
{
if (behaviorValue.ConvertTo(AZ::AzTypeInfo<AZStd::vector<AZ::u8>>::Uuid()))
{
pybind11::list pythonList;
AZStd::vector<AZ::u8>* byteStream = behaviorValue.GetAsUnsafe<AZStd::vector<AZ::u8>>();
for (AZ::u8 byte : *byteStream)
{
pythonList.append(pybind11::cast(byte));
}
PythonMarshalTypeRequests::PythonValueResult result;
result.first = pythonList;
return result;
}
return AZStd::nullopt;
}
bool CanConvertPythonToBehaviorValue([[maybe_unused]] PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override
{
return (PyList_Check(pyObj.ptr()) != false);
}
};
AZStd::optional<PythonMarshalTypeRequests::PythonValueResult> ProcessBehaviorObject(AZ::BehaviorObject& behaviorObject)
{
AZ::BehaviorValueParameter source;
source.m_value = behaviorObject.m_address;
source.m_typeId = behaviorObject.m_typeId;
AZStd::optional<PythonMarshalTypeRequests::PythonValueResult> 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<PythonMarshalTypeRequests::BehaviorValueResult> 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<PythonMarshalTypeRequests::BehaviorValueResult> result;
PythonMarshalTypeRequestBus::EventResult(result, elementTypeId, &PythonMarshalTypeRequestBus::Events::PythonToBehaviorValueParameter, traits, pythonObj, outValue);
if (result)
{
return result;
}
else if (pybind11::isinstance<EditorPythonBindings::PythonProxyObject>(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<pybind11::object>(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<PythonMarshalTypeRequests::BehaviorValueResult> 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<PythonMarshalTypeRequests::BehaviorValueResult> result;
PythonMarshalTypeRequestBus::EventResult(result, elementTypeId, &PythonMarshalTypeRequestBus::Events::PythonToBehaviorValueParameter, traits, pythonElement, outValue);
if (result)
{
return result;
}
else if (pybind11::isinstance<EditorPythonBindings::PythonProxyObject>(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<PythonMarshalTypeRequests::BehaviorValueResult> 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<AZStd::string>().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<PythonMarshalTypeRequests::PythonValueResult> 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<AZStd::vector<PythonMarshalTypeRequests::DeallocateFunction>>();
pybind11::dict pythonDictionary;
// visit each unordered_map<K,V> entry
auto elementCallback = [pythonDictionary, cleanUpList](void* instancePointer, [[maybe_unused]] const auto& elementClassId, const auto* elementGenericClassData, [[maybe_unused]] const auto* genericClassElement)
{
pybind11::object pythonKey { pybind11::none() };
pybind11::object pythonItem { pybind11::none() };
// visit the AZStd::pair<K,V> elements
auto pairCallback = [cleanUpList, &pythonKey, &pythonItem](void* instancePair, const auto& elementClassId, [[maybe_unused]] const auto* elementGenericClassData, [[maybe_unused]] 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<AZStd::string>(pythonKey);
AZStd::string pythonItemString = pybind11::cast<AZStd::string>(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<AZStd::vector<PythonMarshalTypeRequests::DeallocateFunction>> 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([[maybe_unused]] PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override
{
// the underlying types must have exactly two elements
AZStd::vector<AZ::Uuid> 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<PythonMarshalTypeRequests::BehaviorValueResult> 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<PythonMarshalTypeRequests::BehaviorValueResult> result;
PythonMarshalTypeRequestBus::EventResult(result, elementTypeId, &PythonMarshalTypeRequestBus::Events::PythonToBehaviorValueParameter, traits, pythonElement, outValue);
if (result)
{
return result;
}
else if (pybind11::isinstance<EditorPythonBindings::PythonProxyObject>(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<PythonMarshalTypeRequests::BehaviorValueResult> 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<pybind11::object>(*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<AZStd::string>().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<PythonMarshalTypeRequests::BehaviorValueResult> 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<void>(newVector);
size_t vectorCount = 0;
pybind11::list pyList(pyObj);
for (auto pyItem = pyList.begin(); pyItem != pyList.end(); ++pyItem)
{
auto pyObjItem = pybind11::cast<pybind11::object>(*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<AZStd::string>().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<PythonMarshalTypeRequests::BehaviorValueResult> PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override
{
AZStd::vector<AZ::Uuid> 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<PythonMarshalTypeRequests::DeallocateFunction>;
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<PythonMarshalTypeRequests::PythonValueResult> 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<PythonMarshalTypeRequests::PythonValueResult> BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override
{
auto* container = m_classData->m_container;
if (behaviorValue.ConvertTo(m_typeId) && container)
{
auto deleterList = AZStd::make_shared<AZStd::vector<PythonMarshalTypeRequests::DeallocateFunction>>();
pybind11::list pythonList;
auto elementCallback = [this, pythonList, deleterList](void* instancePointer, const auto& elementClassId, [[maybe_unused]] const auto* elementGenericClassData, [[maybe_unused]] 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<AZStd::vector<PythonMarshalTypeRequests::DeallocateFunction>> 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([[maybe_unused]] PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override
{
AZStd::vector<AZ::Uuid> 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<PythonMarshalTypeRequests::BehaviorValueResult> 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<pybind11::object>(*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<AZStd::string>().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<PythonMarshalTypeRequests::BehaviorValueResult> 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<void>(newVector);
size_t itemCount = 0;
pybind11::set pySet(pyObj);
for (auto pyItem = pySet.begin(); pyItem != pySet.end(); ++pyItem)
{
auto pyObjItem = pybind11::cast<pybind11::object>(*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<AZStd::string>().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<PythonMarshalTypeRequests::BehaviorValueResult> PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override
{
AZStd::vector<AZ::Uuid> 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<PythonMarshalTypeRequests::DeallocateFunction> HandleSetElement(AZ::BehaviorObject& behaviorObject, pybind11::set pythonSet)
{
AZ::BehaviorValueParameter source;
source.m_value = behaviorObject.m_address;
source.m_typeId = behaviorObject.m_typeId;
AZStd::optional<PythonMarshalTypeRequests::PythonValueResult> 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<PythonMarshalTypeRequests::PythonValueResult> 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<AZStd::vector<PythonMarshalTypeRequests::DeallocateFunction>>();
pybind11::set pythonSet;
auto elementCallback = [this, pythonSet, deleterList](void* instancePointer, const auto& elementClassId, const auto*, const auto*)
{
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<AZStd::vector<PythonMarshalTypeRequests::DeallocateFunction>> 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([[maybe_unused]] PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override
{
AZStd::vector<AZ::Uuid> 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<EditorPythonBindings::PythonProxyObject>(pyObj))
{
auto behaviorObject = pybind11::cast<EditorPythonBindings::PythonProxyObject*>(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<PythonMarshalTypeRequests::BehaviorValueResult> 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<AZStd::string>().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<AZStd::pair<T1, T2>> exposes "first" and "second" in the proxy object
EditorPythonBindings::PythonProxyObject* proxy = pybind11::cast<EditorPythonBindings::PythonProxyObject*>(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 && item1 && !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<PythonMarshalTypeRequests::PythonValueResult> 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<AZStd::vector<PythonMarshalTypeRequests::DeallocateFunction>>();
// 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, [[maybe_unused]] const AZ::SerializeContext::ClassData* elementGenericClassData, [[maybe_unused]] 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<AZStd::vector<PythonMarshalTypeRequests::DeallocateFunction>> 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([[maybe_unused]] PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj) const override
{
AZStd::vector<AZ::Uuid> 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(const AZ::TypeId& typeId, PythonMarshalComponent::TypeConverterPointer typeConverterPointer)>;
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<AZStd::vector<AZ::u8>>::Uuid())
{
// AZStd::vector<AZ::u8> is registered in the Serialization Context as a ByteStream, so it fails on IsVectorContainerType()
registrant(typeId, AZStd::make_unique<TypeConverterByteStream>());
}
else if (AZ::Utils::IsVectorContainerType(typeId))
{
registrant(typeId, AZStd::make_unique<TypeConverterVector>(serializeContext->FindGenericClassInfo(typeId), classData, typeId));
}
else if (AZ::Utils::IsMapContainerType(typeId))
{
registrant(typeId, AZStd::make_unique<TypeConverterDictionary>(serializeContext->FindGenericClassInfo(typeId), classData, typeId));
}
else if (AZ::Utils::IsPairContainerType(typeId))
{
registrant(typeId, AZStd::make_unique<TypeConverterPair>(serializeContext->FindGenericClassInfo(typeId), classData, typeId));
}
else if (AZ::Utils::IsSetContainerType(typeId))
{
registrant(typeId, AZStd::make_unique<TypeConverterSet>(serializeContext->FindGenericClassInfo(typeId), classData, typeId));
}
return true;
};
const bool includeGenerics = true;
serializeContext->EnumerateAll(handleTypeInfo, includeGenerics);
}
}
AZStd::optional<PythonMarshalComponent::BehaviorValueResult> 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::PythonValueResult> 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<AZ::SerializeContext*>(context))
{
serialize->Class<PythonMarshalComponent, AZ::Component>()
->Version(1)
->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector<AZ::Crc32>{AZ_CRC_CE("AssetBuilder")})
;
}
}
void PythonMarshalComponent::Activate()
{
RegisterTypeConverter(AZ::AzTypeInfo<bool>::Uuid(), AZStd::make_unique<TypeConverterBool>());
RegisterTypeConverter(AZ::AzTypeInfo<char>::Uuid(), AZStd::make_unique<TypeConverterChar>());
RegisterTypeConverter(AZ::AzTypeInfo<AZ::s8>::Uuid(), AZStd::make_unique<TypeConverterInteger<AZ::s8>>());
RegisterTypeConverter(AZ::AzTypeInfo<AZ::u8>::Uuid(), AZStd::make_unique<TypeConverterInteger<AZ::u8>>());
RegisterTypeConverter(AZ::AzTypeInfo<AZ::s16>::Uuid(), AZStd::make_unique<TypeConverterInteger<AZ::s16>>());
RegisterTypeConverter(AZ::AzTypeInfo<AZ::u16>::Uuid(), AZStd::make_unique<TypeConverterInteger<AZ::u16>>());
RegisterTypeConverter(AZ::AzTypeInfo<AZ::s32>::Uuid(), AZStd::make_unique<TypeConverterInteger<AZ::s32>>());
RegisterTypeConverter(AZ::AzTypeInfo<AZ::u32>::Uuid(), AZStd::make_unique<TypeConverterInteger<AZ::u32>>());
RegisterTypeConverter(AZ::AzTypeInfo<AZ::s64>::Uuid(), AZStd::make_unique<TypeConverterInteger<AZ::s64>>());
RegisterTypeConverter(AZ::AzTypeInfo<AZ::u64>::Uuid(), AZStd::make_unique<TypeConverterInteger<AZ::u64>>());
RegisterTypeConverter(AZ::AzTypeInfo<long>::Uuid(), AZStd::make_unique<TypeConverterInteger<long>>());
RegisterTypeConverter(AZ::AzTypeInfo<unsigned long>::Uuid(), AZStd::make_unique<TypeConverterInteger<unsigned long>>());
RegisterTypeConverter(AZ::AzTypeInfo<float>::Uuid(), AZStd::make_unique<TypeConverterReal<float, float, float>>());
RegisterTypeConverter(AZ::AzTypeInfo<double>::Uuid(), AZStd::make_unique<TypeConverterReal<double, double, double>>());
RegisterTypeConverter(AZ::AzTypeInfo<AZStd::string>::Uuid(), AZStd::make_unique<TypeConverterString<AZStd::string>>());
RegisterTypeConverter(AZ::AzTypeInfo<AZStd::string_view>::Uuid(), AZStd::make_unique<TypeConverterString<AZStd::string_view>>());
RegisterTypeConverter(AZ::AzTypeInfo<AZStd::any>::Uuid(), AZStd::make_unique<TypeConverterAny>());
Container::RegisterContainerTypes([this](const AZ::TypeId& typeId, auto containerConverter)
{
this->RegisterTypeConverter(typeId, AZStd::move(containerConverter));
});
}
void PythonMarshalComponent::Deactivate()
{
PythonMarshalTypeRequestBus::MultiHandler::BusDisconnect();
m_typeConverterMap.clear();
}
}