Skip to content

Commit 731f7f6

Browse files
committed
initial fixes for array params
1 parent f3cfaaf commit 731f7f6

File tree

2 files changed

+65
-12
lines changed

2 files changed

+65
-12
lines changed

Source/UnrealEnginePython/Private/PythonFunction.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ void UPythonFunction::SetPyCallable(PyObject *callable)
99
Py_INCREF(py_callable);
1010
}
1111

12-
1312
#if ENGINE_MINOR_VERSION > 18
1413
void UPythonFunction::CallPythonCallable(UObject *Context, FFrame& Stack, RESULT_DECL)
1514
#else
@@ -30,9 +29,10 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL)
3029

3130
// count the number of arguments
3231
Py_ssize_t argn = (Context && !is_static) ? 1 : 0;
33-
TFieldIterator<UProperty> IArgs(function);
34-
for (; IArgs && ((IArgs->PropertyFlags & (CPF_Parm | CPF_OutParm)) == CPF_Parm); ++IArgs) {
35-
argn++;
32+
for (TFieldIterator<UProperty> IArgs(function); IArgs; ++IArgs)
33+
{
34+
if (!PROP_IS_OUT_PARAM(*IArgs))
35+
argn++;
3636
}
3737
#if defined(UEPY_MEMORY_DEBUG)
3838
UE_LOG(LogPython, Warning, TEXT("Initializing %d parameters"), argn);
@@ -57,8 +57,8 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL)
5757
if (*Stack.Code == EX_EndFunctionParms)
5858
{ // native call
5959
for (UProperty *prop = (UProperty *)function->Children; prop; prop = (UProperty *)prop->Next) {
60-
if (prop->PropertyFlags & CPF_OutParm)
61-
continue;
60+
if (PROP_IS_OUT_PARAM(prop))
61+
continue;
6262
if (!on_error) {
6363
PyObject *arg = ue_py_convert_property(prop, (uint8 *)Stack.Locals, 0);
6464
if (!arg) {
@@ -80,7 +80,7 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL)
8080
for (UProperty *prop = (UProperty *)function->Children; *Stack.Code != EX_EndFunctionParms; prop = (UProperty *)prop->Next)
8181
{
8282
Stack.Step(Stack.Object, prop->ContainerPtrToValuePtr<uint8>(frame));
83-
if (prop->PropertyFlags & CPF_OutParm)
83+
if (PROP_IS_OUT_PARAM(prop))
8484
{
8585
FOutParmRec *rec = (FOutParmRec*)FMemory_Alloca(sizeof(FOutParmRec));
8686
rec->Property = prop;
@@ -130,7 +130,7 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL)
130130
int tuple_index = 0;
131131
for (TFieldIterator<UProperty> It(function); It && (It->PropertyFlags & CPF_Parm); ++It)
132132
{
133-
if (!(It->PropertyFlags & CPF_OutParm))
133+
if (!PROP_IS_OUT_PARAM(*It))
134134
continue;
135135
if (tuple_index >= nret)
136136
{

Source/UnrealEnginePython/Private/UEPyModule.cpp

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2940,7 +2940,7 @@ PyObject *py_ue_ufunction_call(UFunction *u_function, UObject *u_obj, PyObject *
29402940
for (; PArgs && ((PArgs->PropertyFlags & CPF_Parm) == CPF_Parm); ++PArgs)
29412941
{
29422942
UProperty *prop = *PArgs;
2943-
if (prop->PropertyFlags & CPF_OutParm)
2943+
if (PROP_IS_OUT_PARAM(prop))
29442944
{
29452945
if (prop->IsA<UArrayProperty>() || prop->HasAnyPropertyFlags(CPF_ConstParm) == false)
29462946
num_out_params++;
@@ -2999,7 +2999,7 @@ PyObject *py_ue_ufunction_call(UFunction *u_function, UObject *u_obj, PyObject *
29992999
for (; OProps; ++OProps)
30003000
{
30013001
UProperty *prop = *OProps;
3002-
if (prop->HasAnyPropertyFlags(CPF_OutParm) && (prop->IsA<UArrayProperty>() || prop->HasAnyPropertyFlags(CPF_ConstParm) == false))
3002+
if (PROP_IS_OUT_PARAM(prop))
30033003
{
30043004
PyObject *py_out = ue_py_convert_property(prop, buffer, 0);
30053005
if (!py_out)
@@ -3076,6 +3076,25 @@ PyObject *ue_bind_pyevent(ue_PyUObject *u_obj, FString event_name, PyObject *py_
30763076
Py_RETURN_NONE;
30773077
}
30783078

3079+
UProperty *new_property_from_pyobject(UObject *owner, const char *prop_name, PyObject *value);
3080+
3081+
// used by new_property_from_pyobject to create an array property of the given name and type, or nullptr if it couldn't
3082+
// be created for some reason. Array properties can be created using type annotations in either of the following
3083+
// forms: arg:[arrayType] or arg:typing.List[arrayType]
3084+
// where arrayType is a type that new_property_from_pyobject knows how to handle
3085+
UProperty *new_array_property(UObject *owner, const char *prop_name, PyObject *py_array_type)
3086+
{
3087+
UArrayProperty *array_prop = NewObject<UArrayProperty>(owner, UTF8_TO_TCHAR(prop_name), RF_Public);
3088+
UProperty *inner = new_property_from_pyobject(array_prop, "Inner", py_array_type);
3089+
if (!inner)
3090+
{
3091+
UE_LOG(LogPython, Error, TEXT("Unsupported type for list property %s"), UTF8_TO_TCHAR(*prop_name));
3092+
return nullptr;
3093+
}
3094+
array_prop->Inner = inner;
3095+
return array_prop;
3096+
}
3097+
30793098
// Creates and configures a UProperty on the given owner using info from a PyObject (typically a type
30803099
// object) representing function parameter or return value type info, or nullptr if the given type is unsupported.
30813100
UProperty *new_property_from_pyobject(UObject *owner, const char *prop_name, PyObject *value)
@@ -3158,7 +3177,22 @@ UProperty *new_property_from_pyobject(UObject *owner, const char *prop_name, PyO
31583177
UE_LOG(LogPython, Error, TEXT("exactly one class is allowed in type info for %s"), UTF8_TO_TCHAR(*owner->GetName()));
31593178
return nullptr;
31603179
}
3180+
PyObject *type_name = PyObject_GetAttrString(value, "__name__");
3181+
if (!type_name)
3182+
{
3183+
UE_LOG(LogPython, Error, TEXT("failed to get type object name from %s"), UTF8_TO_TCHAR(prop_name));
3184+
return nullptr;
3185+
}
3186+
3187+
// If the annotation was like "foo:typing.List[int]" (i.e. __name__ == 'List') then it's an array property. Anything
3188+
// else (like foo:typing.List[Pawn]) will be treated as a class property
31613189
PyObject *py_class = PyTuple_GetItem(type_args, 0);
3190+
bool is_array = !strcmp(PyUnicode_AsUTF8(type_name), "List");
3191+
Py_DECREF(type_name);
3192+
3193+
if (is_array)
3194+
return new_array_property(owner, prop_name, py_class);
3195+
31623196
ue_PyUObject *py_obj = ue_is_pyuobject(py_class);
31633197
if (!py_obj)
31643198
{
@@ -3179,6 +3213,17 @@ UProperty *new_property_from_pyobject(UObject *owner, const char *prop_name, PyO
31793213
Py_DECREF(type_args);
31803214
}
31813215
}
3216+
else if (PyList_Check(value))
3217+
{
3218+
// has to be a single item list containing something that can be used as a property
3219+
if (PyList_Size(value) != 1)
3220+
{
3221+
UE_LOG(LogPython, Error, TEXT("List property %s must have a single item providing the array type"), UTF8_TO_TCHAR(prop_name));
3222+
return nullptr;
3223+
}
3224+
3225+
return new_array_property(owner, prop_name, PyList_GetItem(value, 0));
3226+
}
31823227
else if (ue_PyUObject *py_obj = ue_is_pyuobject(value))
31833228
{
31843229
if (py_obj->ue_object->IsA<UClass>())
@@ -3286,7 +3331,15 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_
32863331
UProperty *prop = new_property_from_pyobject(function, p_name, value);
32873332
if (prop)
32883333
{
3289-
prop->SetPropertyFlags(CPF_Parm);
3334+
uint64 flags = CPF_Parm;
3335+
if (prop->IsA<UArrayProperty>())
3336+
{ // some weirdness to mimic what the engine does: arrays are always passed by reference, so we need to mark this as
3337+
// a reference parameter. But the definition for CPF_ReferenceParm says that CPF_OutParm should be set too. So we
3338+
// 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,
3339+
// and then on the calling side skip it.
3340+
flags |= CPF_ReferenceParm | CPF_OutParm | CPF_ConstParm;
3341+
}
3342+
prop->SetPropertyFlags(flags);
32903343
*next_property = prop;
32913344
next_property = &prop->Next;
32923345
*next_property_link = prop;
@@ -3319,7 +3372,7 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_
33193372
return_param_index = cur_index;
33203373
break;
33213374
}
3322-
if (p->PropertyFlags & CPF_OutParm)
3375+
if (PROP_IS_OUT_PARAM(p))
33233376
cur_index++;
33243377
++It;
33253378
}

0 commit comments

Comments
 (0)