From f8f0fcc49a60630ba583ae7666785dc200dee2d6 Mon Sep 17 00:00:00 2001 From: dave Date: Mon, 10 Dec 2018 17:25:49 -0700 Subject: [PATCH 1/4] initial POC implementation --- .../Private/PythonFunction.cpp | 96 ++- .../UnrealEnginePython/Private/UEPyModule.cpp | 573 ++++++++---------- 2 files changed, 332 insertions(+), 337 deletions(-) diff --git a/Source/UnrealEnginePython/Private/PythonFunction.cpp b/Source/UnrealEnginePython/Private/PythonFunction.cpp index efeb5b059..9393f4d78 100644 --- a/Source/UnrealEnginePython/Private/PythonFunction.cpp +++ b/Source/UnrealEnginePython/Private/PythonFunction.cpp @@ -31,7 +31,7 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) // 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) { + for (; IArgs && ((IArgs->PropertyFlags & (CPF_Parm | CPF_OutParm)) == CPF_Parm); ++IArgs) { argn++; } #if defined(UEPY_MEMORY_DEBUG) @@ -55,10 +55,15 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) uint8 *frame = Stack.Locals; // is it a blueprint call ? + UProperty *first_out_prop = nullptr; if (*Stack.Code == EX_EndFunctionParms) { for (UProperty *prop = (UProperty *)function->Children; prop; prop = (UProperty *)prop->Next) { - if (prop->PropertyFlags & CPF_ReturnParm) + if (prop->PropertyFlags & CPF_OutParm) + { + if (!first_out_prop) + first_out_prop = prop; continue; + } if (!on_error) { PyObject *arg = ue_py_convert_property(prop, (uint8 *)Stack.Locals, 0); if (!arg) { @@ -77,8 +82,12 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) FMemory::Memzero(frame, function->PropertiesSize); 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->PropertyFlags & CPF_OutParm) + { + if (!first_out_prop) + first_out_prop = prop; continue; + } if (!on_error) { PyObject *arg = ue_py_convert_property(prop, frame, 0); if (!arg) { @@ -106,19 +115,74 @@ 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 + if (PyTuple_Check(ret)) + { // function has multiple output params or a return value and one or more output params + int nret = PyTuple_Size(ret); + int tuple_index = 0; + for( TFieldIterator It(function); It && (It->PropertyFlags & CPF_Parm); ++It ) + { + if( It->PropertyFlags & CPF_OutParm ) + { + if (tuple_index >= nret) + { + UE_LOG(LogPython, Error, TEXT("Python function %s didn't return enough values"), *function->GetFName().ToString()); + } + else + { + UProperty *prop = *It; + PyObject *py_obj = PyTuple_GetItem(ret, tuple_index); + uint8 *out_frame = frame; + for (FOutParmRec *rec = Stack.OutParms; rec != nullptr; rec = rec->NextOutParm) + { + if (rec->Property == prop) + { + out_frame = rec->PropAddr - prop->GetOffset_ForUFunction(); + break; + } + } + if (ue_py_convert_pyobject(py_obj, prop, out_frame, 0)) + { + if (prop->PropertyFlags & CPF_ReturnParm) + { + // copy value to stack result value + //FMemory::Memcpy(RESULT_PARAM, frame + function->ReturnValueOffset, prop->ArrayDim * prop->ElementSize); + } + } + else { + UE_LOG(LogPython, Error, TEXT("Invalid return value type for function %s"), *function->GetFName().ToString()); + } + tuple_index++; + } + } + } + + } + else + { // no output params, but maybe a return value + if (first_out_prop) + { + uint8 *out_frame = frame; + for (FOutParmRec *rec = Stack.OutParms; rec != nullptr; rec = rec->NextOutParm) + { + if (rec->Property == first_out_prop) + { + out_frame = rec->PropAddr - first_out_prop->GetOffset_ForUFunction(); + break; + } + } + if (ue_py_convert_pyobject(ret, first_out_prop, out_frame, 0)) + { + if (function->ReturnValueOffset != MAX_uint16) + { + // copy value to stack result value + //FMemory::Memcpy(RESULT_PARAM, frame + function->ReturnValueOffset, first_out_prop->ArrayDim * first_out_prop->ElementSize); + } + } + else { + UE_LOG(LogPython, Error, TEXT("Invalid return value type for function %s"), *function->GetFName().ToString()); + } + } } Py_DECREF(ret); } diff --git a/Source/UnrealEnginePython/Private/UEPyModule.cpp b/Source/UnrealEnginePython/Private/UEPyModule.cpp index 542b5a42f..463e1a293 100644 --- a/Source/UnrealEnginePython/Private/UEPyModule.cpp +++ b/Source/UnrealEnginePython/Private/UEPyModule.cpp @@ -2915,42 +2915,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->PropertyFlags & CPF_OutParm) + { + 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++; } @@ -2964,56 +2968,37 @@ 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 Python function return behavior: 'return x' produces a single object, while 'return x,y' produces 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)) { - // 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 @@ -3071,6 +3056,132 @@ PyObject *ue_bind_pyevent(ue_PyUObject *u_obj, FString event_name, PyObject *py_ Py_RETURN_NONE; } +// 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) + { + prop = NewObject(owner, UTF8_TO_TCHAR(prop_name), RF_Public); + } + 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_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 *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(*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 (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) { @@ -3086,6 +3197,26 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ } } + // note the index of the return param, if any + 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 (p->PropertyFlags & CPF_OutParm) + cur_index++; + ++It; + } + } + UPythonFunction *function = NewObject(u_class, UTF8_TO_TCHAR(name), RF_Public | RF_Transient | RF_MarkAsNative); function->SetPyCallable(py_callable); @@ -3147,126 +3278,7 @@ 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_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); @@ -3288,140 +3300,54 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ 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_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)); - } + 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)); + } + } } } @@ -3440,7 +3366,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) { @@ -3456,7 +3382,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) { @@ -3475,11 +3401,14 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ // allocate properties storage (ignore super) TFieldIterator props(function, EFieldIteratorFlags::ExcludeSuper); + bool has_out_params = false; for (; props; ++props) { UProperty *p = *props; if (p->HasAnyPropertyFlags(CPF_Parm)) { + if (p->HasAnyPropertyFlags(CPF_OutParm)) + has_out_params = true; function->NumParms++; function->ParmsSize = p->GetOffset_ForUFunction() + p->GetSize(); if (p->HasAnyPropertyFlags(CPF_ReturnParm)) @@ -3488,6 +3417,8 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ } } } + if (has_out_params) + function_flags |= FUNC_HasOutParms; if (parent_function) { From 798c082219790368a99d1d2f892aad1c48efb3ef Mon Sep 17 00:00:00 2001 From: dave Date: Tue, 11 Dec 2018 21:25:05 -0700 Subject: [PATCH 2/4] all unit tests pass --- .../Private/PythonFunction.cpp | 111 ++++++++---------- .../UnrealEnginePython/Private/UEPyModule.cpp | 11 +- 2 files changed, 55 insertions(+), 67 deletions(-) diff --git a/Source/UnrealEnginePython/Private/PythonFunction.cpp b/Source/UnrealEnginePython/Private/PythonFunction.cpp index 9393f4d78..6a3e3c34b 100644 --- a/Source/UnrealEnginePython/Private/PythonFunction.cpp +++ b/Source/UnrealEnginePython/Private/PythonFunction.cpp @@ -55,15 +55,10 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) uint8 *frame = Stack.Locals; // is it a blueprint call ? - UProperty *first_out_prop = nullptr; if (*Stack.Code == EX_EndFunctionParms) { for (UProperty *prop = (UProperty *)function->Children; prop; prop = (UProperty *)prop->Next) { if (prop->PropertyFlags & CPF_OutParm) - { - if (!first_out_prop) - first_out_prop = prop; continue; - } if (!on_error) { PyObject *arg = ue_py_convert_property(prop, (uint8 *)Stack.Locals, 0); if (!arg) { @@ -76,16 +71,24 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) } } } - else { + else + { //UE_LOG(LogPython, Warning, TEXT("BLUEPRINT CALL")); + // largely copied from ScriptCore.cpp::CallFunction + // for BP calls, we need to set up the FOutParmRec stuff ourselves + Stack.OutParms = NULL; 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_OutParm) { - if (!first_out_prop) - first_out_prop = prop; + FOutParmRec *rec = (FOutParmRec*)FMemory_Alloca(sizeof(FOutParmRec)); + rec->Property = prop; + rec->PropAddr = Stack.MostRecentPropertyAddress; + rec->NextOutParm = Stack.OutParms; + Stack.OutParms = rec; continue; } if (!on_error) { @@ -115,75 +118,55 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) return; } - // get return value and/or any out params - if (PyTuple_Check(ret)) - { // function has multiple output params or a return value and one or more output params - int nret = PyTuple_Size(ret); - int tuple_index = 0; - for( TFieldIterator It(function); It && (It->PropertyFlags & CPF_Parm); ++It ) + // 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 (!(It->PropertyFlags & CPF_OutParm)) + continue; + if (tuple_index >= nret) { - if( It->PropertyFlags & CPF_OutParm ) + 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 have it write directly to the stack + if (!ue_py_convert_pyobject(py_obj, prop, (uint8*)RESULT_PARAM - prop->GetOffset_ForUFunction(), 0)) { - if (tuple_index >= nret) - { - UE_LOG(LogPython, Error, TEXT("Python function %s didn't return enough values"), *function->GetFName().ToString()); - } - else - { - UProperty *prop = *It; - PyObject *py_obj = PyTuple_GetItem(ret, tuple_index); - uint8 *out_frame = frame; - for (FOutParmRec *rec = Stack.OutParms; rec != nullptr; rec = rec->NextOutParm) - { - if (rec->Property == prop) - { - out_frame = rec->PropAddr - prop->GetOffset_ForUFunction(); - break; - } - } - if (ue_py_convert_pyobject(py_obj, prop, out_frame, 0)) - { - if (prop->PropertyFlags & CPF_ReturnParm) - { - // copy value to stack result value - //FMemory::Memcpy(RESULT_PARAM, frame + function->ReturnValueOffset, prop->ArrayDim * prop->ElementSize); - } - } - else { - UE_LOG(LogPython, Error, TEXT("Invalid return value type for function %s"), *function->GetFName().ToString()); - } - tuple_index++; - } + UE_LOG(LogPython, Error, TEXT("Invalid return value type for function %s"), *function->GetFName().ToString()); } } - - } - else - { // no output params, but maybe a return value - if (first_out_prop) + else { - uint8 *out_frame = frame; + uint8 *out_frame = frame; for (FOutParmRec *rec = Stack.OutParms; rec != nullptr; rec = rec->NextOutParm) { - if (rec->Property == first_out_prop) + if (rec->Property == prop) { - out_frame = rec->PropAddr - first_out_prop->GetOffset_ForUFunction(); + out_frame = rec->PropAddr - prop->GetOffset_ForUFunction(); break; } } - if (ue_py_convert_pyobject(ret, first_out_prop, out_frame, 0)) + if (!ue_py_convert_pyobject(py_obj, prop, out_frame, 0)) { - if (function->ReturnValueOffset != MAX_uint16) - { - // copy value to stack result value - //FMemory::Memcpy(RESULT_PARAM, frame + function->ReturnValueOffset, first_out_prop->ArrayDim * first_out_prop->ElementSize); - } - } - else { - UE_LOG(LogPython, Error, TEXT("Invalid return value type for function %s"), *function->GetFName().ToString()); + 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 463e1a293..78aed655f 100644 --- a/Source/UnrealEnginePython/Private/UEPyModule.cpp +++ b/Source/UnrealEnginePython/Private/UEPyModule.cpp @@ -3401,23 +3401,28 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ // allocate properties storage (ignore super) TFieldIterator props(function, EFieldIteratorFlags::ExcludeSuper); - bool has_out_params = false; + int num_out_params = 0; + int num_return_params = 0; for (; props; ++props) { UProperty *p = *props; if (p->HasAnyPropertyFlags(CPF_Parm)) { if (p->HasAnyPropertyFlags(CPF_OutParm)) - has_out_params = true; + num_out_params++; function->NumParms++; function->ParmsSize = p->GetOffset_ForUFunction() + p->GetSize(); if (p->HasAnyPropertyFlags(CPF_ReturnParm)) { function->ReturnValueOffset = p->GetOffset_ForUFunction(); + num_return_params++; } } } - if (has_out_params) + + // UProps have both out + return flags set on the property that is the return value, but a function with a return value but no other + // out properties does not get HasOutParms set. + if (num_out_params > 0) //num_return_params) function_flags |= FUNC_HasOutParms; if (parent_function) From f53a8e51c3d5c4aac9c341bf3dbbd5071c9d13f4 Mon Sep 17 00:00:00 2001 From: dave Date: Wed, 12 Dec 2018 14:09:10 -0700 Subject: [PATCH 3/4] misc cleanup --- .../Private/PythonFunction.cpp | 16 +++-- .../UnrealEnginePython/Private/UEPyModule.cpp | 62 ++++++++----------- 2 files changed, 33 insertions(+), 45 deletions(-) diff --git a/Source/UnrealEnginePython/Private/PythonFunction.cpp b/Source/UnrealEnginePython/Private/PythonFunction.cpp index 6a3e3c34b..a52a792a0 100644 --- a/Source/UnrealEnginePython/Private/PythonFunction.cpp +++ b/Source/UnrealEnginePython/Private/PythonFunction.cpp @@ -54,8 +54,8 @@ 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_OutParm) continue; @@ -72,10 +72,8 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) } } else - { - //UE_LOG(LogPython, Warning, TEXT("BLUEPRINT CALL")); - // largely copied from ScriptCore.cpp::CallFunction - // for BP calls, we need to set up the FOutParmRec stuff ourselves + { // blueprint call + // largely copied from ScriptCore.cpp::CallFunction - for BP calls, we need to set up the FOutParmRec stuff ourselves Stack.OutParms = NULL; frame = (uint8 *)FMemory_Alloca(function->PropertiesSize); FMemory::Memzero(frame, function->PropertiesSize); @@ -118,7 +116,7 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) return; } - // 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 + // 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)) @@ -143,14 +141,14 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) UProperty *prop = *It; PyObject *py_obj = PyTuple_GetItem(ret, tuple_index); if (prop->PropertyFlags & CPF_ReturnParm) - { // handle the return value specially by have it write directly to the stack + { // 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 uint8 *out_frame = frame; for (FOutParmRec *rec = Stack.OutParms; rec != nullptr; rec = rec->NextOutParm) { diff --git a/Source/UnrealEnginePython/Private/UEPyModule.cpp b/Source/UnrealEnginePython/Private/UEPyModule.cpp index 78aed655f..893b5581b 100644 --- a/Source/UnrealEnginePython/Private/UEPyModule.cpp +++ b/Source/UnrealEnginePython/Private/UEPyModule.cpp @@ -2970,7 +2970,8 @@ PyObject *py_ue_ufunction_call(UFunction *u_function, UObject *u_obj, PyObject * if (num_out_params > 0) { - // mirror Python function return behavior: 'return x' produces a single object, while 'return x,y' produces a tuple + // 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); @@ -3184,7 +3185,6 @@ UProperty *new_property_from_pyobject(UObject *owner, const char *prop_name, PyO 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 @@ -3197,26 +3197,6 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ } } - // note the index of the return param, if any - 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 (p->PropertyFlags & CPF_OutParm) - cur_index++; - ++It; - } - } - UPythonFunction *function = NewObject(u_class, UTF8_TO_TCHAR(name), RF_Public | RF_Transient | RF_MarkAsNative); function->SetPyCallable(py_callable); @@ -3294,15 +3274,35 @@ 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) { + // 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 (p->PropertyFlags & CPF_OutParm) + 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")); + //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); @@ -3329,7 +3329,7 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ } else { // either a single output param or a single return value - UE_LOG(LogPython, Warning, TEXT("Return value or single output value found")); + //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) @@ -3401,30 +3401,20 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ // allocate properties storage (ignore super) TFieldIterator props(function, EFieldIteratorFlags::ExcludeSuper); - int num_out_params = 0; - int num_return_params = 0; for (; props; ++props) { UProperty *p = *props; if (p->HasAnyPropertyFlags(CPF_Parm)) { if (p->HasAnyPropertyFlags(CPF_OutParm)) - num_out_params++; + function_flags |= FUNC_HasOutParms; function->NumParms++; function->ParmsSize = p->GetOffset_ForUFunction() + p->GetSize(); if (p->HasAnyPropertyFlags(CPF_ReturnParm)) - { function->ReturnValueOffset = p->GetOffset_ForUFunction(); - num_return_params++; - } } } - // UProps have both out + return flags set on the property that is the return value, but a function with a return value but no other - // out properties does not get HasOutParms set. - if (num_out_params > 0) //num_return_params) - function_flags |= FUNC_HasOutParms; - if (parent_function) { UE_LOG(LogPython, Warning, TEXT("OVERRIDDEN FUNCTION %s WITH %d PARAMS (size %d) %d"), *function->GetFName().ToString(), function->NumParms, function->ParmsSize, function->PropertiesSize); From 9b7663c786b509deea48ad950b0ac05ab841dc40 Mon Sep 17 00:00:00 2001 From: dave Date: Thu, 27 Dec 2018 15:06:56 -0700 Subject: [PATCH 4/4] fix incorrect stomping of caller's stack OutParms --- .../Private/PythonFunction.cpp | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/Source/UnrealEnginePython/Private/PythonFunction.cpp b/Source/UnrealEnginePython/Private/PythonFunction.cpp index a52a792a0..d303be725 100644 --- a/Source/UnrealEnginePython/Private/PythonFunction.cpp +++ b/Source/UnrealEnginePython/Private/PythonFunction.cpp @@ -27,6 +27,7 @@ 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; @@ -73,8 +74,7 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) } else { // blueprint call - // largely copied from ScriptCore.cpp::CallFunction - for BP calls, we need to set up the FOutParmRec stuff ourselves - Stack.OutParms = NULL; + // 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) @@ -85,8 +85,8 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) FOutParmRec *rec = (FOutParmRec*)FMemory_Alloca(sizeof(FOutParmRec)); rec->Property = prop; rec->PropAddr = Stack.MostRecentPropertyAddress; - rec->NextOutParm = Stack.OutParms; - Stack.OutParms = rec; + rec->NextOutParm = OutParms; + OutParms = rec; continue; } if (!on_error) { @@ -148,8 +148,8 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) } } else - { // Find the given FOutParmRec for this property - uint8 *out_frame = frame; + { // 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) @@ -158,6 +158,22 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) 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());