Skip to content
Open
Prev Previous commit
Next Next commit
initial fixes for array params
  • Loading branch information
dfb committed Dec 18, 2018
commit 731f7f68e66665ebde848928f230a7f477d3d83b
16 changes: 8 additions & 8 deletions Source/UnrealEnginePython/Private/PythonFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -30,9 +29,10 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL)

// count the number of arguments
Py_ssize_t argn = (Context && !is_static) ? 1 : 0;
TFieldIterator<UProperty> IArgs(function);
for (; IArgs && ((IArgs->PropertyFlags & (CPF_Parm | CPF_OutParm)) == CPF_Parm); ++IArgs) {
argn++;
for (TFieldIterator<UProperty> 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);
Expand All @@ -57,8 +57,8 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL)
if (*Stack.Code == EX_EndFunctionParms)
{ // native call
for (UProperty *prop = (UProperty *)function->Children; prop; prop = (UProperty *)prop->Next) {
if (prop->PropertyFlags & CPF_OutParm)
continue;
if (PROP_IS_OUT_PARAM(prop))
continue;
if (!on_error) {
PyObject *arg = ue_py_convert_property(prop, (uint8 *)Stack.Locals, 0);
if (!arg) {
Expand All @@ -80,7 +80,7 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL)
for (UProperty *prop = (UProperty *)function->Children; *Stack.Code != EX_EndFunctionParms; prop = (UProperty *)prop->Next)
{
Stack.Step(Stack.Object, prop->ContainerPtrToValuePtr<uint8>(frame));
if (prop->PropertyFlags & CPF_OutParm)
if (PROP_IS_OUT_PARAM(prop))
{
FOutParmRec *rec = (FOutParmRec*)FMemory_Alloca(sizeof(FOutParmRec));
rec->Property = prop;
Expand Down Expand Up @@ -130,7 +130,7 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL)
int tuple_index = 0;
for (TFieldIterator<UProperty> It(function); It && (It->PropertyFlags & CPF_Parm); ++It)
{
if (!(It->PropertyFlags & CPF_OutParm))
if (!PROP_IS_OUT_PARAM(*It))
continue;
if (tuple_index >= nret)
{
Expand Down
61 changes: 57 additions & 4 deletions Source/UnrealEnginePython/Private/UEPyModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2940,7 +2940,7 @@ PyObject *py_ue_ufunction_call(UFunction *u_function, UObject *u_obj, PyObject *
for (; PArgs && ((PArgs->PropertyFlags & CPF_Parm) == CPF_Parm); ++PArgs)
{
UProperty *prop = *PArgs;
if (prop->PropertyFlags & CPF_OutParm)
if (PROP_IS_OUT_PARAM(prop))
{
if (prop->IsA<UArrayProperty>() || prop->HasAnyPropertyFlags(CPF_ConstParm) == false)
num_out_params++;
Expand Down Expand Up @@ -2999,7 +2999,7 @@ PyObject *py_ue_ufunction_call(UFunction *u_function, UObject *u_obj, PyObject *
for (; OProps; ++OProps)
{
UProperty *prop = *OProps;
if (prop->HasAnyPropertyFlags(CPF_OutParm) && (prop->IsA<UArrayProperty>() || prop->HasAnyPropertyFlags(CPF_ConstParm) == false))
if (PROP_IS_OUT_PARAM(prop))
{
PyObject *py_out = ue_py_convert_property(prop, buffer, 0);
if (!py_out)
Expand Down Expand Up @@ -3076,6 +3076,25 @@ PyObject *ue_bind_pyevent(ue_PyUObject *u_obj, FString event_name, PyObject *py_
Py_RETURN_NONE;
}

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<UArrayProperty>(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)
Expand Down Expand Up @@ -3158,7 +3177,22 @@ UProperty *new_property_from_pyobject(UObject *owner, const char *prop_name, PyO
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)
{
Expand All @@ -3179,6 +3213,17 @@ UProperty *new_property_from_pyobject(UObject *owner, const char *prop_name, PyO
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>())
Expand Down Expand Up @@ -3286,7 +3331,15 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_
UProperty *prop = new_property_from_pyobject(function, p_name, value);
if (prop)
{
prop->SetPropertyFlags(CPF_Parm);
uint64 flags = CPF_Parm;
if (prop->IsA<UArrayProperty>())
{ // 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;
Expand Down Expand Up @@ -3319,7 +3372,7 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_
return_param_index = cur_index;
break;
}
if (p->PropertyFlags & CPF_OutParm)
if (PROP_IS_OUT_PARAM(p))
cur_index++;
++It;
}
Expand Down