diff --git a/Source/UnrealEnginePython/Private/PythonFunction.cpp b/Source/UnrealEnginePython/Private/PythonFunction.cpp index efeb5b059..ba57658c5 100644 --- a/Source/UnrealEnginePython/Private/PythonFunction.cpp +++ b/Source/UnrealEnginePython/Private/PythonFunction.cpp @@ -9,7 +9,6 @@ void UPythonFunction::SetPyCallable(PyObject *callable) Py_INCREF(py_callable); } - #if ENGINE_MINOR_VERSION > 18 void UPythonFunction::CallPythonCallable(UObject *Context, FFrame& Stack, RESULT_DECL) #else @@ -27,12 +26,14 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) bool on_error = false; bool is_static = function->HasAnyFunctionFlags(FUNC_Static); + FOutParmRec *OutParms = nullptr; // count the number of arguments Py_ssize_t argn = (Context && !is_static) ? 1 : 0; - TFieldIterator IArgs(function); - for (; IArgs && ((IArgs->PropertyFlags & (CPF_Parm | CPF_ReturnParm)) == CPF_Parm); ++IArgs) { - argn++; + for (TFieldIterator IArgs(function); IArgs; ++IArgs) + { + if (!PROP_IS_OUT_PARAM(*IArgs)) + argn++; } #if defined(UEPY_MEMORY_DEBUG) UE_LOG(LogPython, Warning, TEXT("Initializing %d parameters"), argn); @@ -54,11 +55,11 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) uint8 *frame = Stack.Locals; - // is it a blueprint call ? - if (*Stack.Code == EX_EndFunctionParms) { + if (*Stack.Code == EX_EndFunctionParms) + { // native call for (UProperty *prop = (UProperty *)function->Children; prop; prop = (UProperty *)prop->Next) { - if (prop->PropertyFlags & CPF_ReturnParm) - continue; + if (PROP_IS_OUT_PARAM(prop)) + continue; if (!on_error) { PyObject *arg = ue_py_convert_property(prop, (uint8 *)Stack.Locals, 0); if (!arg) { @@ -71,14 +72,23 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) } } } - else { - //UE_LOG(LogPython, Warning, TEXT("BLUEPRINT CALL")); + else + { // blueprint call + // largely copied from ScriptCore.cpp::CallFunction - for BP calls, we need to set up some of the FOutParmRec stuff ourselves frame = (uint8 *)FMemory_Alloca(function->PropertiesSize); FMemory::Memzero(frame, function->PropertiesSize); - for (UProperty *prop = (UProperty *)function->Children; *Stack.Code != EX_EndFunctionParms; prop = (UProperty *)prop->Next) { + for (UProperty *prop = (UProperty *)function->Children; *Stack.Code != EX_EndFunctionParms; prop = (UProperty *)prop->Next) + { Stack.Step(Stack.Object, prop->ContainerPtrToValuePtr(frame)); - if (prop->PropertyFlags & CPF_ReturnParm) + if (PROP_IS_OUT_PARAM(prop)) + { + FOutParmRec *rec = (FOutParmRec*)FMemory_Alloca(sizeof(FOutParmRec)); + rec->Property = prop; + rec->PropAddr = Stack.MostRecentPropertyAddress; + rec->NextOutParm = OutParms; + OutParms = rec; continue; + } if (!on_error) { PyObject *arg = ue_py_convert_property(prop, frame, 0); if (!arg) { @@ -106,20 +116,72 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) return; } - // get return value (if required) - UProperty *return_property = function->GetReturnProperty(); - if (return_property && function->ReturnValueOffset != MAX_uint16) { -#if defined(UEPY_MEMORY_DEBUG) - UE_LOG(LogPython, Warning, TEXT("FOUND RETURN VALUE")); -#endif - if (ue_py_convert_pyobject(ret, return_property, frame, 0)) { - // copy value to stack result value - FMemory::Memcpy(RESULT_PARAM, frame + function->ReturnValueOffset, return_property->ArrayDim * return_property->ElementSize); - } - else { - UE_LOG(LogPython, Error, TEXT("Invalid return value type for function %s"), *function->GetFName().ToString()); - } - } + // get return value and/or any out params - for convenience, if a single item is returned, wrap it in a tuple so that we can process + // multi-out params and single out params with one block of code + bool wrapped_ret = false; + if (!PyTuple_Check(ret)) + { + PyObject *wrapped = PyTuple_New(1); + PyTuple_SetItem(wrapped, 0, ret); + ret = wrapped; + } + + int nret = PyTuple_Size(ret); + int tuple_index = 0; + for (TFieldIterator It(function); It && (It->PropertyFlags & CPF_Parm); ++It) + { + if (!PROP_IS_OUT_PARAM(*It)) + continue; + if (tuple_index >= nret) + { + UE_LOG(LogPython, Error, TEXT("Python function %s didn't return enough values"), *function->GetFName().ToString()); + break; + } + + UProperty *prop = *It; + PyObject *py_obj = PyTuple_GetItem(ret, tuple_index); + if (prop->PropertyFlags & CPF_ReturnParm) + { // handle the return value specially by having it write directly to the stack + if (!ue_py_convert_pyobject(py_obj, prop, (uint8*)RESULT_PARAM - prop->GetOffset_ForUFunction(), 0)) + { + UE_LOG(LogPython, Error, TEXT("Invalid return value type for function %s"), *function->GetFName().ToString()); + } + } + else + { // Find the given FOutParmRec for this property - look in the stack first + uint8 *out_frame = nullptr; + for (FOutParmRec *rec = Stack.OutParms; rec != nullptr; rec = rec->NextOutParm) + { + if (rec->Property == prop) + { + out_frame = rec->PropAddr - prop->GetOffset_ForUFunction(); + break; + } + } + if (!out_frame) + { // look in our local out parms next + for (FOutParmRec *rec = OutParms; rec != nullptr; rec = rec->NextOutParm) + { + if (rec->Property == prop) + { + out_frame = rec->PropAddr - prop->GetOffset_ForUFunction(); + break; + } + } + } + if (!out_frame) + { // default to our current frame + out_frame = frame; + + } + + if (!ue_py_convert_pyobject(py_obj, prop, out_frame, 0)) + { + UE_LOG(LogPython, Error, TEXT("Failed to convert output property for function %s"), *function->GetFName().ToString()); + } + } + tuple_index++; + } Py_DECREF(ret); } diff --git a/Source/UnrealEnginePython/Private/UEPyModule.cpp b/Source/UnrealEnginePython/Private/UEPyModule.cpp index 704593c8d..9b243d05d 100644 --- a/Source/UnrealEnginePython/Private/UEPyModule.cpp +++ b/Source/UnrealEnginePython/Private/UEPyModule.cpp @@ -2143,7 +2143,7 @@ PyObject *ue_py_convert_property(UProperty *prop, uint8 *buffer, int32 index) FLinearColor color = *casted_prop->ContainerPtrToValuePtr(buffer, index); return py_ue_new_flinearcolor(color); } - return py_ue_new_uscriptstruct(casted_struct, casted_prop->ContainerPtrToValuePtr(buffer, index)); + return py_ue_new_owned_uscriptstruct(casted_struct, casted_prop->ContainerPtrToValuePtr(buffer, index)); } return PyErr_Format(PyExc_TypeError, "unsupported UStruct type"); } @@ -2934,42 +2934,46 @@ PyObject *py_ue_ufunction_call(UFunction *u_function, UObject *u_obj, PyObject * Py_ssize_t tuple_len = PyTuple_Size(args); - int has_out_params = 0; + int num_out_params = 0; TFieldIterator PArgs(u_function); - for (; PArgs && ((PArgs->PropertyFlags & (CPF_Parm | CPF_ReturnParm)) == CPF_Parm); ++PArgs) + for (; PArgs && ((PArgs->PropertyFlags & CPF_Parm) == CPF_Parm); ++PArgs) { UProperty *prop = *PArgs; - if (argn < tuple_len) - { - PyObject *py_arg = PyTuple_GetItem(args, argn); - if (!py_arg) - { - py_ue_destroy_params(u_function, buffer); - return PyErr_Format(PyExc_TypeError, "unable to get pyobject for property %s", TCHAR_TO_UTF8(*prop->GetName())); - } - if (!ue_py_convert_pyobject(py_arg, prop, buffer, 0)) - { - py_ue_destroy_params(u_function, buffer); - return PyErr_Format(PyExc_TypeError, "unable to convert pyobject to property %s (%s)", TCHAR_TO_UTF8(*prop->GetName()), TCHAR_TO_UTF8(*prop->GetClass()->GetName())); - } - } - else if (kwargs) - { - char *prop_name = TCHAR_TO_UTF8(*prop->GetName()); - PyObject *dict_value = PyDict_GetItemString(kwargs, prop_name); - if (dict_value) - { - if (!ue_py_convert_pyobject(dict_value, prop, buffer, 0)) - { - py_ue_destroy_params(u_function, buffer); - return PyErr_Format(PyExc_TypeError, "unable to convert pyobject to property %s (%s)", TCHAR_TO_UTF8(*prop->GetName()), TCHAR_TO_UTF8(*prop->GetClass()->GetName())); - } - } - } - if (prop->HasAnyPropertyFlags(CPF_OutParm) && (prop->IsA() || prop->HasAnyPropertyFlags(CPF_ConstParm) == false)) - { - has_out_params++; + if (PROP_IS_OUT_PARAM(prop)) + { + if (prop->IsA() || prop->HasAnyPropertyFlags(CPF_ConstParm) == false) + num_out_params++; + } + else + { + if (argn < tuple_len) + { + PyObject *py_arg = PyTuple_GetItem(args, argn); + if (!py_arg) + { + py_ue_destroy_params(u_function, buffer); + return PyErr_Format(PyExc_TypeError, "unable to get pyobject for property %s", TCHAR_TO_UTF8(*prop->GetName())); + } + if (!ue_py_convert_pyobject(py_arg, prop, buffer, 0)) + { + py_ue_destroy_params(u_function, buffer); + return PyErr_Format(PyExc_TypeError, "unable to convert pyobject to property %s (%s)", TCHAR_TO_UTF8(*prop->GetName()), TCHAR_TO_UTF8(*prop->GetClass()->GetName())); + } + } + else if (kwargs) + { + char *prop_name = TCHAR_TO_UTF8(*prop->GetName()); + PyObject *dict_value = PyDict_GetItemString(kwargs, prop_name); + if (dict_value) + { + if (!ue_py_convert_pyobject(dict_value, prop, buffer, 0)) + { + py_ue_destroy_params(u_function, buffer); + return PyErr_Format(PyExc_TypeError, "unable to convert pyobject to property %s (%s)", TCHAR_TO_UTF8(*prop->GetName()), TCHAR_TO_UTF8(*prop->GetClass()->GetName())); + } + } + } } argn++; } @@ -2983,56 +2987,38 @@ PyObject *py_ue_ufunction_call(UFunction *u_function, UObject *u_obj, PyObject * PyObject *ret = nullptr; - int has_ret_param = 0; - TFieldIterator Props(u_function); - for (; Props; ++Props) + if (num_out_params > 0) { - UProperty *prop = *Props; - if (prop->GetPropertyFlags() & CPF_ReturnParm) - { - ret = ue_py_convert_property(prop, buffer, 0); - if (!ret) - { - // destroy params - py_ue_destroy_params(u_function, buffer); - return NULL; - } - has_ret_param = 1; - break; - } - } + // mirror normal Python function return behavior in that there is ever only a single return value, and returning multiple items is + // actually achieved by returning a tuple + if (num_out_params > 1) + ret = PyTuple_New(num_out_params); - if (has_out_params > 0) - { - PyObject *multi_ret = PyTuple_New(has_out_params + has_ret_param); - if (ret) - { - PyTuple_SetItem(multi_ret, 0, ret); - } TFieldIterator OProps(u_function); + int cur_out_param = 0; for (; OProps; ++OProps) { UProperty *prop = *OProps; - if (prop->HasAnyPropertyFlags(CPF_OutParm) && (prop->IsA() || prop->HasAnyPropertyFlags(CPF_ConstParm) == false)) + if (PROP_IS_OUT_PARAM(prop)) { - // skip return param as it must be always the first - if (prop->GetPropertyFlags() & CPF_ReturnParm) - continue; PyObject *py_out = ue_py_convert_property(prop, buffer, 0); if (!py_out) { - Py_DECREF(multi_ret); + if (ret) + Py_DECREF(ret); // destroy params py_ue_destroy_params(u_function, buffer); return NULL; } - PyTuple_SetItem(multi_ret, has_ret_param, py_out); - has_ret_param++; + if (num_out_params > 1) + PyTuple_SetItem(ret, cur_out_param++, py_out); + else + { // there's just one return/output param and this is it + ret = py_out; + break; + } } } - // destroy params - py_ue_destroy_params(u_function, buffer); - return multi_ret; } // destroy params @@ -3090,9 +3076,187 @@ PyObject *ue_bind_pyevent(ue_PyUObject *u_obj, FString event_name, PyObject *py_ Py_RETURN_NONE; } -UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_callable, uint32 function_flags) +UProperty *new_property_from_pyobject(UObject *owner, const char *prop_name, PyObject *value); + +// used by new_property_from_pyobject to create an array property of the given name and type, or nullptr if it couldn't +// be created for some reason. Array properties can be created using type annotations in either of the following +// forms: arg:[arrayType] or arg:typing.List[arrayType] +// where arrayType is a type that new_property_from_pyobject knows how to handle +UProperty *new_array_property(UObject *owner, const char *prop_name, PyObject *py_array_type) +{ + UArrayProperty *array_prop = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + UProperty *inner = new_property_from_pyobject(array_prop, "Inner", py_array_type); + if (!inner) + { + UE_LOG(LogPython, Error, TEXT("Unsupported type for list property %s"), UTF8_TO_TCHAR(*prop_name)); + return nullptr; + } + array_prop->Inner = inner; + return array_prop; +} + +// Creates and configures a UProperty on the given owner using info from a PyObject (typically a type +// object) representing function parameter or return value type info, or nullptr if the given type is unsupported. +UProperty *new_property_from_pyobject(UObject *owner, const char *prop_name, PyObject *value) { + UProperty *prop = nullptr; + if (PyType_Check(value)) + { + if ((PyTypeObject *)value == &PyFloat_Type) + { + prop = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + } + else if ((PyTypeObject *)value == &PyUnicode_Type) + { + prop = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + } + else if ((PyTypeObject *)value == &PyBool_Type) + { + UBoolProperty *prop_bool = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + prop_bool->SetBoolSize(1, true); + prop = prop_bool; + } + else if ((PyTypeObject *)value == &PyLong_Type) + { + prop = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + } + else if ((PyTypeObject *)value == &ue_PyFVectorType) + { + UStructProperty *prop_struct = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } + else if ((PyTypeObject *)value == &ue_PyFVector2DType) + { + UStructProperty *prop_struct = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } + else if ((PyTypeObject *)value == &ue_PyFRotatorType) + { + UStructProperty *prop_struct = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } + else if ((PyTypeObject *)value == &ue_PyFLinearColorType) + { + UStructProperty *prop_struct = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } + else if ((PyTypeObject *)value == &ue_PyFColorType) + { + UStructProperty *prop_struct = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } + else if ((PyTypeObject *)value == &ue_PyFTransformType) + { + UStructProperty *prop_struct = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } +#if ENGINE_MINOR_VERSION > 18 + else if ((PyTypeObject *)value == &ue_PyFQuatType) + { + UStructProperty *prop_struct = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } +#endif + else if (PyObject_IsInstance(value, (PyObject *)&PyType_Type)) + { + // Method annotation like foo:typing.Type[Pawn] produces annotations like typing.Type[Pawn], with .__args__ = (Pawn,) + PyObject *type_args = PyObject_GetAttrString(value, "__args__"); + if (!type_args) + { + UE_LOG(LogPython, Error, TEXT("missing type info on %s"), UTF8_TO_TCHAR(*owner->GetName())); + return nullptr; + } + if (PyTuple_Size(type_args) != 1) + { + Py_DECREF(type_args); + UE_LOG(LogPython, Error, TEXT("exactly one class is allowed in type info for %s"), UTF8_TO_TCHAR(*owner->GetName())); + return nullptr; + } + PyObject *type_name = PyObject_GetAttrString(value, "__name__"); + if (!type_name) + { + UE_LOG(LogPython, Error, TEXT("failed to get type object name from %s"), UTF8_TO_TCHAR(prop_name)); + return nullptr; + } + + // If the annotation was like "foo:typing.List[int]" (i.e. __name__ == 'List') then it's an array property. Anything + // else (like foo:typing.List[Pawn]) will be treated as a class property + PyObject *py_class = PyTuple_GetItem(type_args, 0); + bool is_array = !strcmp(PyUnicode_AsUTF8(type_name), "List"); + Py_DECREF(type_name); + + if (is_array) + return new_array_property(owner, prop_name, py_class); + + ue_PyUObject *py_obj = ue_is_pyuobject(py_class); + if (!py_obj) + { + Py_DECREF(type_args); + UE_LOG(LogPython, Error, TEXT("type for %s must be a ue_PyUObject"), UTF8_TO_TCHAR(*owner->GetName())); + return nullptr; + } + if (!py_obj->ue_object->IsA()) + { + Py_DECREF(type_args); + UE_LOG(LogPython, Error, TEXT("type for %s must be a UClass"), UTF8_TO_TCHAR(*owner->GetName())); + return nullptr; + } + UClassProperty *prop_class = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + prop_class->SetMetaClass((UClass*)py_obj->ue_object); + prop_class->PropertyClass = UClass::StaticClass(); + prop = prop_class; + Py_DECREF(type_args); + } + } + else if (PyList_Check(value)) + { + // has to be a single item list containing something that can be used as a property + if (PyList_Size(value) != 1) + { + UE_LOG(LogPython, Error, TEXT("List property %s must have a single item providing the array type"), UTF8_TO_TCHAR(prop_name)); + return nullptr; + } + + return new_array_property(owner, prop_name, PyList_GetItem(value, 0)); + } + else if (ue_PyUObject *py_obj = ue_is_pyuobject(value)) + { + if (py_obj->ue_object->IsA()) + { + UClass *p_u_class = (UClass *)py_obj->ue_object; + UObjectProperty *prop_base = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + prop_base->SetPropertyClass(p_u_class); + prop = prop_base; + } +#if ENGINE_MINOR_VERSION > 17 + else if (py_obj->ue_object->IsA()) + { + UEnumProperty *prop_enum = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + UNumericProperty *prop_underlying = NewObject(prop_enum, TEXT("UnderlyingType"), RF_Public); + prop_enum->SetEnum((UEnum*)py_obj->ue_object); + prop_enum->AddCppProperty(prop_underlying); + prop = prop_enum; + } +#endif + else if (py_obj->ue_object->IsA()) + { + UStructProperty *prop_struct = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + prop_struct->Struct = (UScriptStruct*)py_obj->ue_object; + prop = prop_struct; + } + } + return prop; +} +UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_callable, uint32 function_flags) +{ UFunction *parent_function = u_class->GetSuperClass()->FindFunctionByName(UTF8_TO_TCHAR(name)); // if the function is not available in the parent // check for name collision @@ -3166,135 +3330,18 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ if (!value) continue; - UProperty *prop = nullptr; - if (PyType_Check(value)) - { - if ((PyTypeObject *)value == &PyFloat_Type) - { - prop = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - } - else if ((PyTypeObject *)value == &PyUnicode_Type) - { - prop = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - } - else if ((PyTypeObject *)value == &PyBool_Type) - { - prop = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - } - else if ((PyTypeObject *)value == &PyLong_Type) - { - prop = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - } - else if ((PyTypeObject *)value == &ue_PyFVectorType) - { - UStructProperty *prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_struct->Struct = TBaseStructure::Get(); - prop = prop_struct; - } - else if ((PyTypeObject *)value == &ue_PyFVector2DType) - { - UStructProperty *prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_struct->Struct = TBaseStructure::Get(); - prop = prop_struct; - } - else if ((PyTypeObject *)value == &ue_PyFRotatorType) - { - UStructProperty *prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_struct->Struct = TBaseStructure::Get(); - prop = prop_struct; - } - else if ((PyTypeObject *)value == &ue_PyFLinearColorType) - { - UStructProperty *prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_struct->Struct = TBaseStructure::Get(); - prop = prop_struct; - } - else if ((PyTypeObject *)value == &ue_PyFColorType) - { - UStructProperty *prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_struct->Struct = TBaseStructure::Get(); - prop = prop_struct; - } - else if ((PyTypeObject *)value == &ue_PyFTransformType) - { - UStructProperty *prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_struct->Struct = TBaseStructure::Get(); - prop = prop_struct; - } -#if ENGINE_MINOR_VERSION > 18 - else if ((PyTypeObject *)value == &ue_PyFQuatType) - { - UStructProperty *prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_struct->Struct = TBaseStructure::Get(); - prop = prop_struct; - } -#endif - else if (PyObject_IsInstance(value, (PyObject *)&PyType_Type)) - { - // Method annotation like foo:typing.Type[Pawn] produces annotations like typing.Type[Pawn], with .__args__ = (Pawn,) - PyObject *type_args = PyObject_GetAttrString(value, "__args__"); - if (!type_args) - { - UE_LOG(LogPython, Error, TEXT("missing type info on %s"), UTF8_TO_TCHAR(name)); - return nullptr; - } - if (PyTuple_Size(type_args) != 1) - { - Py_DECREF(type_args); - UE_LOG(LogPython, Error, TEXT("exactly one class is allowed in type info for %s"), UTF8_TO_TCHAR(name)); - return nullptr; - } - PyObject *py_class = PyTuple_GetItem(type_args, 0); - ue_PyUObject *py_obj = ue_is_pyuobject(py_class); - if (!py_obj) - { - Py_DECREF(type_args); - UE_LOG(LogPython, Error, TEXT("type for %s must be a ue_PyUObject"), UTF8_TO_TCHAR(name)); - return nullptr; - } - if (!py_obj->ue_object->IsA()) - { - Py_DECREF(type_args); - UE_LOG(LogPython, Error, TEXT("type for %s must be a UClass"), UTF8_TO_TCHAR(name)); - return nullptr; - } - UClassProperty *prop_class = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_class->SetMetaClass((UClass*)py_obj->ue_object); - prop_class->PropertyClass = UClass::StaticClass(); - prop = prop_class; - Py_DECREF(type_args); - } - } - else if (ue_PyUObject *py_obj = ue_is_pyuobject(value)) - { - if (py_obj->ue_object->IsA()) - { - UClass *p_u_class = (UClass *)py_obj->ue_object; - UObjectProperty *prop_base = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_base->SetPropertyClass(p_u_class); - prop = prop_base; - } -#if ENGINE_MINOR_VERSION > 17 - else if (py_obj->ue_object->IsA()) - { - UEnumProperty *prop_enum = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - UNumericProperty *prop_underlying = NewObject(prop_enum, TEXT("UnderlyingType"), RF_Public); - prop_enum->SetEnum((UEnum*)py_obj->ue_object); - prop_enum->AddCppProperty(prop_underlying); - prop = prop_enum; - } -#endif - else if (py_obj->ue_object->IsA()) - { - UStructProperty *prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_struct->Struct = (UScriptStruct*)py_obj->ue_object; - prop = prop_struct; - } - } - + UProperty *prop = new_property_from_pyobject(function, p_name, value); if (prop) { - prop->SetPropertyFlags(CPF_Parm); + uint64 flags = CPF_Parm; + if (prop->IsA()) + { // some weirdness to mimic what the engine does: arrays are always passed by reference, so we need to mark this as + // a reference parameter. But the definition for CPF_ReferenceParm says that CPF_OutParm should be set too. So we + // mark it as an out param even though it isn't, flag it as const to indicate that it's gotta be an input param, + // and then on the calling side skip it. + flags |= CPF_ReferenceParm | CPF_OutParm | CPF_ConstParm; + } + prop->SetPropertyFlags(flags); *next_property = prop; next_property = &prop->Next; *next_property_link = prop; @@ -3307,152 +3354,80 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ } } - // check for return value + // check for return value (including out params) if (annotations) { PyObject *py_return_value = PyDict_GetItemString(annotations, "return"); if (py_return_value) { - UE_LOG(LogPython, Warning, TEXT("Return Value found")); - UProperty *prop = nullptr; - char *p_name = (char *) "ReturnValue"; - if (PyType_Check(py_return_value)) - { - if ((PyTypeObject *)py_return_value == &PyFloat_Type) - { - prop = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - } - else if ((PyTypeObject *)py_return_value == &PyUnicode_Type) - { - prop = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - } - else if ((PyTypeObject *)py_return_value == &PyBool_Type) - { - prop = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - } - else if ((PyTypeObject *)py_return_value == &PyLong_Type) - { - prop = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - } - else if ((PyTypeObject *)py_return_value == &ue_PyFVectorType) - { - UStructProperty *prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_struct->Struct = TBaseStructure::Get(); - prop = prop_struct; - } - else if ((PyTypeObject *)py_return_value == &ue_PyFVector2DType) - { - UStructProperty *prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_struct->Struct = TBaseStructure::Get(); - prop = prop_struct; - } - else if ((PyTypeObject *)py_return_value == &ue_PyFRotatorType) - { - UStructProperty *prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_struct->Struct = TBaseStructure::Get(); - prop = prop_struct; - } - else if ((PyTypeObject *)py_return_value == &ue_PyFLinearColorType) - { - UStructProperty *prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_struct->Struct = TBaseStructure::Get(); - prop = prop_struct; - } - else if ((PyTypeObject *)py_return_value == &ue_PyFColorType) - { - UStructProperty *prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_struct->Struct = TBaseStructure::Get(); - prop = prop_struct; - } - else if ((PyTypeObject *)py_return_value == &ue_PyFTransformType) - { - UStructProperty *prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_struct->Struct = TBaseStructure::Get(); - prop = prop_struct; - } -#if ENGINE_MINOR_VERSION > 18 - else if ((PyTypeObject *)py_return_value == &ue_PyFQuatType) - { - UStructProperty *prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_struct->Struct = TBaseStructure::Get(); - prop = prop_struct; - } -#endif - else if (PyObject_IsInstance(py_return_value, (PyObject *)&PyType_Type)) - { - // Method annotation like foo:typing.Type[Pawn] produces annotations like typing.Type[Pawn], with .__args__ = (Pawn,) - PyObject *type_args = PyObject_GetAttrString(py_return_value, "__args__"); - if (!type_args) - { - UE_LOG(LogPython, Error, TEXT("missing type info on %s"), UTF8_TO_TCHAR(name)); - return nullptr; - } - if (PyTuple_Size(type_args) != 1) - { - Py_DECREF(type_args); - UE_LOG(LogPython, Error, TEXT("exactly one class is allowed in type info for %s"), UTF8_TO_TCHAR(name)); - return nullptr; - } - PyObject *py_class = PyTuple_GetItem(type_args, 0); - ue_PyUObject *py_obj = ue_is_pyuobject(py_class); - if (!py_obj) - { - Py_DECREF(type_args); - UE_LOG(LogPython, Error, TEXT("type for %s must be a ue_PyUObject"), UTF8_TO_TCHAR(name)); - return nullptr; - } - if (!py_obj->ue_object->IsA()) - { - Py_DECREF(type_args); - UE_LOG(LogPython, Error, TEXT("type for %s must be a UClass"), UTF8_TO_TCHAR(name)); - return nullptr; - } - UClassProperty *prop_class = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_class->SetMetaClass((UClass*)py_obj->ue_object); - prop_class->PropertyClass = UClass::StaticClass(); - prop = prop_class; - Py_DECREF(type_args); - } - } - else if (ue_PyUObject *py_obj = ue_is_pyuobject(py_return_value)) - { - if (py_obj->ue_object->IsA()) - { - UClass *p_u_class = (UClass *)py_obj->ue_object; - UObjectProperty *prop_base = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_base->SetPropertyClass(p_u_class); - prop = prop_base; - } -#if ENGINE_MINOR_VERSION > 17 - else if (py_obj->ue_object->IsA()) - { - UEnumProperty *prop_enum = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - UNumericProperty *prop_underlying = NewObject(prop_enum, TEXT("UnderlyingType"), RF_Public); - prop_enum->SetEnum((UEnum*)py_obj->ue_object); - prop_enum->AddCppProperty(prop_underlying); - prop = prop_enum; - } -#endif - else if (py_obj->ue_object->IsA()) - { - UStructProperty *prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_struct->Struct = (UScriptStruct*)py_obj->ue_object; - prop = prop_struct; - } - } - - if (prop) - { - prop->SetPropertyFlags(CPF_Parm | CPF_OutParm | CPF_ReturnParm); - *next_property = prop; - next_property = &prop->Next; - *next_property_link = prop; - next_property_link = &prop->PropertyLinkNext; - } - else - { - UE_LOG(LogPython, Warning, TEXT("Unable to map return value to function %s"), UTF8_TO_TCHAR(name)); - } + // in the parent, note the index of the return param, if any, among all out params + int return_param_index = -1; + if (parent_function) + { + TFieldIterator It(parent_function); + int cur_index = 0; + while (It) + { + UProperty *p = *It; + if (p->PropertyFlags & CPF_ReturnParm) + { + return_param_index = cur_index; + break; + } + if (PROP_IS_OUT_PARAM(p)) + cur_index++; + ++It; + } + } + + if (PyTuple_Check(py_return_value)) + { // some combination of a return value and output params + //UE_LOG(LogPython, Warning, TEXT("Multiple return values found")); + for (auto i=0; i < PyTuple_Size(py_return_value); i++) + { + PyObject *item = PyTuple_GetItem(py_return_value, i); + FString out_param_name(_T("ReturnValue")); + if (i != return_param_index) + out_param_name = FString::Printf(_T("OutParam%d"), i); + UProperty *prop = new_property_from_pyobject(function, TCHAR_TO_UTF8(*out_param_name), item); + if (prop) + { + uint64 flags = CPF_Parm | CPF_OutParm; + if (i == return_param_index) + flags |= CPF_ReturnParm; + prop->SetPropertyFlags(flags); + *next_property = prop; + next_property = &prop->Next; + *next_property_link = prop; + next_property_link = &prop->PropertyLinkNext; + } + else + { + UE_LOG(LogPython, Warning, TEXT("Unable to map return value %d to function %s"), i, UTF8_TO_TCHAR(name)); + } + } + } + else + { // either a single output param or a single return value + //UE_LOG(LogPython, Warning, TEXT("Return value or single output value found")); + FString param_name(return_param_index == -1 ? _T("OutParam0") : _T("ReturnValue")); + UProperty *prop = new_property_from_pyobject(function, TCHAR_TO_UTF8(*param_name), py_return_value); + if (prop) + { + uint64 flags = CPF_Parm | CPF_OutParm; + if (return_param_index != -1) + flags |= CPF_ReturnParm; + prop->SetPropertyFlags(flags); + *next_property = prop; + next_property = &prop->Next; + *next_property_link = prop; + next_property_link = &prop->PropertyLinkNext; + } + else + { + UE_LOG(LogPython, Warning, TEXT("Unable to map return value to function %s"), UTF8_TO_TCHAR(name)); + } + } } } @@ -3471,7 +3446,7 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ UProperty *p = *It; if (p->PropertyFlags & CPF_Parm) { - UE_LOG(LogPython, Warning, TEXT("Parent PROP: %s %d/%d %d %d %d %s %p"), *p->GetName(), (int)p->PropertyFlags, (int)UFunction::GetDefaultIgnoredSignatureCompatibilityFlags(), (int)(p->PropertyFlags & ~UFunction::GetDefaultIgnoredSignatureCompatibilityFlags()), p->GetSize(), p->GetOffset_ForGC(), *p->GetClass()->GetName(), p->GetClass()); + UE_LOG(LogPython, Warning, TEXT("Parent PROP: %s %X/%X %d %d %d %s %p"), *p->GetName(), (int)p->PropertyFlags, (int)UFunction::GetDefaultIgnoredSignatureCompatibilityFlags(), (int)(p->PropertyFlags & ~UFunction::GetDefaultIgnoredSignatureCompatibilityFlags()), p->GetSize(), p->GetOffset_ForGC(), *p->GetClass()->GetName(), p->GetClass()); UClassProperty *ucp = Cast(p); if (ucp) { @@ -3487,7 +3462,7 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ UProperty *p = *It2; if (p->PropertyFlags & CPF_Parm) { - UE_LOG(LogPython, Warning, TEXT("Function PROP: %s %d/%d %d %d %d %s %p"), *p->GetName(), (int)p->PropertyFlags, (int)UFunction::GetDefaultIgnoredSignatureCompatibilityFlags(), (int)(p->PropertyFlags & ~UFunction::GetDefaultIgnoredSignatureCompatibilityFlags()), p->GetSize(), p->GetOffset_ForGC(), *p->GetClass()->GetName(), p->GetClass()); + UE_LOG(LogPython, Warning, TEXT("Function PROP: %s %X/%X %d %d %d %s %p"), *p->GetName(), (int)p->PropertyFlags, (int)UFunction::GetDefaultIgnoredSignatureCompatibilityFlags(), (int)(p->PropertyFlags & ~UFunction::GetDefaultIgnoredSignatureCompatibilityFlags()), p->GetSize(), p->GetOffset_ForGC(), *p->GetClass()->GetName(), p->GetClass()); UClassProperty *ucp = Cast(p); if (ucp) { @@ -3511,12 +3486,12 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ UProperty *p = *props; if (p->HasAnyPropertyFlags(CPF_Parm)) { + if (p->HasAnyPropertyFlags(CPF_OutParm)) + function_flags |= FUNC_HasOutParms; function->NumParms++; function->ParmsSize = p->GetOffset_ForUFunction() + p->GetSize(); if (p->HasAnyPropertyFlags(CPF_ReturnParm)) - { function->ReturnValueOffset = p->GetOffset_ForUFunction(); - } } } diff --git a/Source/UnrealEnginePython/Private/UEPyModule.h b/Source/UnrealEnginePython/Private/UEPyModule.h index 08e599d52..461a9b9da 100644 --- a/Source/UnrealEnginePython/Private/UEPyModule.h +++ b/Source/UnrealEnginePython/Private/UEPyModule.h @@ -14,16 +14,9 @@ #include "Wrappers/UEPyFColor.h" #include "Wrappers/UEPyFLinearColor.h" -// backward compatibility for UE4.20 TCHAR_TO_WCHAR -#ifndef TCHAR_TO_WCHAR - // SIZEOF_WCHAR_T is provided by pyconfig.h - #if SIZEOF_WCHAR_T == (PLATFORM_TCHAR_IS_4_BYTES ? 4 : 2) - #define TCHAR_TO_WCHAR(str) str - #else - #define TCHAR_TO_WCHAR(str) (wchar_t*)StringCast(static_cast(str)).Get() - #endif -#endif - +// returns true if the given UProperty is a function output parameter (some function parameters are incorrectly +// marked as output parameters, but if they are also marked as const refs, then they are actually input parameters) +#define PROP_IS_OUT_PARAM(prop) ((prop)->HasAllPropertyFlags(CPF_Parm|CPF_OutParm) && !(prop)->HasAllPropertyFlags(CPF_ReferenceParm|CPF_ConstParm)) UWorld *ue_get_uworld(ue_PyUObject *); AActor *ue_get_actor(ue_PyUObject *);