Skip to content

Commit f950e27

Browse files
author
Roberto De Ioris
committed
preliminary support for subclassing, 20tab#8
1 parent 4efba12 commit f950e27

5 files changed

Lines changed: 261 additions & 195 deletions

File tree

‎Source/UnrealEnginePython/Private/UEPyEngine.cpp‎

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -374,8 +374,7 @@ PyObject *py_unreal_engine_new_class(PyObject * self, PyObject * args) {
374374
return NULL;
375375
}
376376

377-
UObject *outer = GetTransientPackage();
378-
UClass *parent = UObject::StaticClass();
377+
UClass *parent = nullptr;
379378

380379
if (py_parent != Py_None) {
381380
if (!ue_is_pyuobject(py_parent)) {
@@ -385,31 +384,12 @@ PyObject *py_unreal_engine_new_class(PyObject * self, PyObject * args) {
385384
if (!py_obj->ue_object->IsA<UClass>())
386385
return PyErr_Format(PyExc_Exception, "uobject is not a UClass");
387386
parent = (UClass *)py_obj->ue_object;
388-
outer = parent->GetOuter();
389387
}
390388

391-
392-
UClass *new_object = NewObject<UClass>(outer, UTF8_TO_TCHAR(name), RF_Public | RF_Transient | RF_MarkAsNative);
389+
UClass *new_object = unreal_engine_new_uclass(name, parent);
393390
if (!new_object)
394391
return PyErr_Format(PyExc_Exception, "unable to create UClass");
395392

396-
new_object->ClassConstructor = parent->ClassConstructor;
397-
new_object->SetSuperStruct(parent);
398-
399-
new_object->PropertyLink = parent->PropertyLink;
400-
new_object->ClassWithin = parent->ClassWithin;
401-
new_object->ClassConfigName = parent->ClassConfigName;
402-
403-
new_object->ClassFlags |= (parent->ClassFlags & (CLASS_Inherit | CLASS_ScriptInherit));
404-
new_object->ClassFlags |= CLASS_Native;
405-
406-
new_object->ClassCastFlags = parent->ClassCastFlags;
407-
408-
new_object->Bind();
409-
new_object->StaticLink(true);
410-
411-
new_object->GetDefaultObject();
412-
413393
ue_PyUObject *ret = ue_get_python_wrapper(new_object);
414394
if (!ret)
415395
return PyErr_Format(PyExc_Exception, "uobject is in invalid state");

‎Source/UnrealEnginePython/Private/UEPyModule.cpp‎

Lines changed: 250 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727

2828
#include "PythonDelegate.h"
29+
#include "PythonFunction.h"
2930

3031

3132
DEFINE_LOG_CATEGORY(LogPython);
@@ -68,7 +69,7 @@ static PyObject *py_unreal_engine_py_gc(PyObject * self, PyObject * args) {
6869
ue_PyUObject *py_obj = ue_python_gc.at(u_obj);
6970
Py_DECREF(py_obj);
7071
}
71-
72+
7273
return PyLong_FromLong(broken_list.size());
7374

7475
}
@@ -370,8 +371,8 @@ static PyMethodDef ue_PyUObject_methods[] = {
370371
{ "sequencer_possessables", (PyCFunction)py_ue_sequencer_possessables, METH_VARARGS, "" },
371372
{ "sequencer_find_possessable", (PyCFunction)py_ue_sequencer_find_possessable, METH_VARARGS, "" },
372373
{ "sequencer_add_master_track", (PyCFunction)py_ue_sequencer_add_master_track, METH_VARARGS, "" },
373-
374-
374+
375+
375376
#if PY_MAJOR_VERSION >= 3
376377
{ "add_function", (PyCFunction)py_ue_add_function, METH_VARARGS, "" },
377378
#endif
@@ -540,8 +541,84 @@ static PyTypeObject ue_PyUObjectType = {
540541
ue_PyUObject_methods, /* tp_methods */
541542
};
542543

544+
UClass *unreal_engine_new_uclass(char *name, UClass *outer_parent) {
545+
UObject *outer = GetTransientPackage();
546+
UClass *parent = UObject::StaticClass();
547+
548+
if (outer_parent) {
549+
parent = outer_parent;
550+
outer = parent->GetOuter();
551+
}
552+
UClass *new_object = NewObject<UClass>(outer, UTF8_TO_TCHAR(name), RF_Public | RF_Transient | RF_MarkAsNative);
553+
if (!new_object)
554+
return nullptr;
555+
556+
new_object->ClassConstructor = parent->ClassConstructor;
557+
new_object->SetSuperStruct(parent);
558+
559+
new_object->PropertyLink = parent->PropertyLink;
560+
new_object->ClassWithin = parent->ClassWithin;
561+
new_object->ClassConfigName = parent->ClassConfigName;
562+
563+
new_object->ClassFlags |= (parent->ClassFlags & (CLASS_Inherit | CLASS_ScriptInherit));
564+
new_object->ClassFlags |= CLASS_Native;
543565

566+
new_object->ClassCastFlags = parent->ClassCastFlags;
567+
568+
new_object->Bind();
569+
new_object->StaticLink(true);
570+
571+
new_object->GetDefaultObject();
572+
573+
return new_object;
574+
}
575+
576+
static int unreal_engine_py_init(ue_PyUObject *self, PyObject *args, PyObject *kwds) {
577+
// is it subclassing ?
578+
if (PyTuple_Size(args) == 3) {
579+
UE_LOG(LogPython, Warning, TEXT("%s"), UTF8_TO_TCHAR(PyUnicode_AsUTF8(PyObject_Str(PyTuple_GetItem(args, 0)))));
580+
UE_LOG(LogPython, Warning, TEXT("%s"), UTF8_TO_TCHAR(PyUnicode_AsUTF8(PyObject_Str(PyTuple_GetItem(args, 1)))));
581+
UE_LOG(LogPython, Warning, TEXT("%s"), UTF8_TO_TCHAR(PyUnicode_AsUTF8(PyObject_Str(PyTuple_GetItem(args, 2)))));
582+
583+
PyObject *parents = PyTuple_GetItem(args, 1);
584+
ue_PyUObject *parent = (ue_PyUObject *)PyTuple_GetItem(parents, 0);
585+
586+
PyObject *class_attributes = PyTuple_GetItem(args, 2);
587+
588+
PyObject *class_name = PyDict_GetItemString(class_attributes, (char *)"__qualname__");
589+
char *name = PyUnicode_AsUTF8(class_name);
590+
// check if parent is a uclass
591+
UClass *new_class = unreal_engine_new_uclass(name, (UClass *)parent->ue_object);
592+
if (!new_class)
593+
return -1;
594+
UE_LOG(LogPython, Warning, TEXT("%s"), *parent->ue_object->GetName());
595+
596+
PyObject *class_attributes_keys = PyObject_GetIter(class_attributes);
597+
for (;;) {
598+
PyObject *key = PyIter_Next(class_attributes_keys);
599+
if (!key) {
600+
if (PyErr_Occurred())
601+
return -1;
602+
break;
603+
}
604+
if (!PyUnicode_Check(key))
605+
continue;
606+
char *class_key = PyUnicode_AsUTF8(key);
607+
if (strlen(class_key) > 2 && class_key[0] == '_' && class_key[1] == '_')
608+
continue;
609+
PyObject *value = PyDict_GetItem(class_attributes, key);
610+
if (PyCallable_Check(value)) {
611+
if (!unreal_engine_add_function(new_class, class_key, value, FUNC_Native | FUNC_BlueprintCallable | FUNC_Public)) {
612+
UE_LOG(LogPython, Error, TEXT("unable to add function %s"), UTF8_TO_TCHAR(class_key));
613+
return -1;
614+
}
615+
}
616+
}
544617

618+
self->ue_object = new_class;
619+
}
620+
return 0;
621+
}
545622

546623
void unreal_engine_init_py_module() {
547624
#if PY_MAJOR_VERSION >= 3
@@ -563,6 +640,7 @@ void unreal_engine_init_py_module() {
563640

564641

565642
ue_PyUObjectType.tp_new = PyType_GenericNew;
643+
ue_PyUObjectType.tp_init = (initproc)unreal_engine_py_init;
566644
if (PyType_Ready(&ue_PyUObjectType) < 0)
567645
return;
568646

@@ -735,7 +813,7 @@ void unreal_engine_py_log_error() {
735813
}
736814

737815
PyErr_Clear();
738-
}
816+
}
739817

740818
// retrieve a UWorld from a generic UObject (if possible)
741819
UWorld *ue_get_uworld(ue_PyUObject *py_obj) {
@@ -1299,3 +1377,171 @@ PyObject *ue_bind_pyevent(ue_PyUObject *u_obj, FString event_name, PyObject *py_
12991377
Py_INCREF(Py_None);
13001378
return Py_None;
13011379
}
1380+
1381+
UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_callable, uint32 function_flags) {
1382+
1383+
if (u_class->FindFunctionByName(UTF8_TO_TCHAR(name))) {
1384+
UE_LOG(LogPython, Error, TEXT("function %s is already registered"), UTF8_TO_TCHAR(name));
1385+
return nullptr;
1386+
}
1387+
1388+
UFunction *parent_function = u_class->GetSuperClass()->FindFunctionByName(UTF8_TO_TCHAR(name));
1389+
1390+
UPythonFunction *function = NewObject<UPythonFunction>(u_class, UTF8_TO_TCHAR(name), RF_Public | RF_Transient | RF_MarkAsNative);
1391+
function->SetPyCallable(py_callable);
1392+
1393+
function->RepOffset = MAX_uint16;
1394+
function->ReturnValueOffset = MAX_uint16;
1395+
function->FirstPropertyToInit = NULL;
1396+
function->Script.Add(EX_EndFunctionParms);
1397+
1398+
1399+
if (parent_function) {
1400+
function->SetSuperStruct(parent_function);
1401+
/*
1402+
TODO: duplicate properties of parent
1403+
*/
1404+
}
1405+
else {
1406+
1407+
// iterate all arguments using inspect.signature()
1408+
// this is required to maintaining args order
1409+
PyObject *inspect = PyImport_ImportModule("inspect");
1410+
if (!inspect) {
1411+
return NULL;
1412+
}
1413+
PyObject *signature = PyObject_CallMethod(inspect, "signature", "O", py_callable);
1414+
if (!signature) {
1415+
return NULL;
1416+
}
1417+
1418+
PyObject *parameters = PyObject_GetAttrString(signature, "parameters");
1419+
if (!parameters) {
1420+
return NULL;
1421+
}
1422+
1423+
PyObject *annotations = PyObject_GetAttrString(py_callable, "__annotations__");
1424+
1425+
UField **next_property = &function->Children;
1426+
UProperty **next_property_link = &function->PropertyLink;
1427+
1428+
PyObject *parameters_keys = PyObject_GetIter(parameters);
1429+
// do not process args if no annotations are available
1430+
while (annotations) {
1431+
PyObject *key = PyIter_Next(parameters_keys);
1432+
if (!key) {
1433+
if (PyErr_Occurred())
1434+
return NULL;
1435+
break;
1436+
}
1437+
if (!PyUnicode_Check(key))
1438+
continue;
1439+
1440+
char *p_name = PyUnicode_AsUTF8(key);
1441+
1442+
PyObject *value = PyDict_GetItem(annotations, key);
1443+
if (!value)
1444+
continue;
1445+
UProperty *prop = nullptr;
1446+
if (PyType_Check(value)) {
1447+
if ((PyTypeObject *)value == &PyFloat_Type) {
1448+
prop = NewObject<UFloatProperty>(function, UTF8_TO_TCHAR(p_name), RF_Public);
1449+
}
1450+
else if ((PyTypeObject *)value == &PyUnicode_Type) {
1451+
prop = NewObject<UStrProperty>(function, UTF8_TO_TCHAR(p_name), RF_Public);
1452+
}
1453+
else if ((PyTypeObject *)value == &PyBool_Type) {
1454+
prop = NewObject<UBoolProperty>(function, UTF8_TO_TCHAR(p_name), RF_Public);
1455+
}
1456+
else if ((PyTypeObject *)value == &PyLong_Type) {
1457+
prop = NewObject<UInt64Property>(function, UTF8_TO_TCHAR(p_name), RF_Public);
1458+
}
1459+
// TODO add native types (like vectors, rotators...)
1460+
}
1461+
if (prop) {
1462+
prop->SetPropertyFlags(CPF_Parm);
1463+
*next_property = prop;
1464+
next_property = &prop->Next;
1465+
*next_property_link = prop;
1466+
next_property_link = &prop->PropertyLinkNext;
1467+
}
1468+
else {
1469+
UE_LOG(LogPython, Warning, TEXT("Unable to map argument %s to function %s"), UTF8_TO_TCHAR(p_name), UTF8_TO_TCHAR(name));
1470+
}
1471+
}
1472+
1473+
// check for return value
1474+
if (annotations) {
1475+
PyObject *py_return_value = PyDict_GetItemString(annotations, "return");
1476+
if (py_return_value) {
1477+
UProperty *prop = nullptr;
1478+
if (PyType_Check(py_return_value)) {
1479+
char *p_name = (char *) "ReturnValue";
1480+
if ((PyTypeObject *)py_return_value == &PyFloat_Type) {
1481+
prop = NewObject<UFloatProperty>(function, UTF8_TO_TCHAR(p_name), RF_Public);
1482+
}
1483+
else if ((PyTypeObject *)py_return_value == &PyUnicode_Type) {
1484+
prop = NewObject<UStrProperty>(function, UTF8_TO_TCHAR(p_name), RF_Public);
1485+
}
1486+
else if ((PyTypeObject *)py_return_value == &PyBool_Type) {
1487+
prop = NewObject<UBoolProperty>(function, UTF8_TO_TCHAR(p_name), RF_Public);
1488+
}
1489+
else if ((PyTypeObject *)py_return_value == &PyLong_Type) {
1490+
prop = NewObject<UInt64Property>(function, UTF8_TO_TCHAR(p_name), RF_Public);
1491+
}
1492+
// TODO add native types (like vectors, rotators...)
1493+
}
1494+
if (prop) {
1495+
prop->SetPropertyFlags(CPF_Parm | CPF_ReturnParm);
1496+
*next_property = prop;
1497+
next_property = &prop->Next;
1498+
*next_property_link = prop;
1499+
next_property_link = &prop->PropertyLinkNext;
1500+
}
1501+
else {
1502+
UE_LOG(LogPython, Warning, TEXT("Unable to map return value to function %s"), UTF8_TO_TCHAR(name));
1503+
}
1504+
}
1505+
}
1506+
}
1507+
1508+
function->Bind();
1509+
function->StaticLink(true);
1510+
1511+
function->ParmsSize = 0;
1512+
function->NumParms = 0;
1513+
1514+
// allocate properties storage (ignore super)
1515+
TFieldIterator<UProperty> props(function, EFieldIteratorFlags::ExcludeSuper);
1516+
for (; props; ++props) {
1517+
UProperty *p = *props;
1518+
if (p->HasAnyPropertyFlags(CPF_Parm)) {
1519+
function->NumParms++;
1520+
function->ParmsSize = p->GetOffset_ForUFunction() + p->GetSize();
1521+
if (p->HasAnyPropertyFlags(CPF_ReturnParm)) {
1522+
function->ReturnValueOffset = p->GetOffset_ForUFunction();
1523+
}
1524+
}
1525+
}
1526+
1527+
UE_LOG(LogPython, Warning, TEXT("REGISTERED FUNCTION %s WITH %d PARAMS (size %d) %d"), *function->GetFName().ToString(), function->NumParms, function->ParmsSize, function->PropertiesSize);
1528+
1529+
function->FunctionFlags = function_flags;
1530+
//function->FunctionFlags |= FUNC_Native | FUNC_Event | FUNC_BlueprintEvent;
1531+
1532+
function->SetNativeFunc((Native)&UPythonFunction::CallPythonCallable);
1533+
1534+
function->Next = u_class->Children;
1535+
u_class->Children = function;
1536+
u_class->AddFunctionToFunctionMap(function);
1537+
1538+
u_class->StaticLink(true);
1539+
1540+
// regenerate CDO
1541+
u_class->GetDefaultObject()->RemoveFromRoot();
1542+
u_class->GetDefaultObject()->MarkPendingKill();
1543+
u_class->ClassDefaultObject = nullptr;
1544+
u_class->GetDefaultObject();
1545+
1546+
return function;
1547+
}

‎Source/UnrealEnginePython/Private/UEPyModule.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ PyObject *ue_bind_pyevent(ue_PyUObject *, FString, PyObject *, bool);
2929
PyObject *py_ue_ufunction_call(UFunction *, UObject *, PyObject *, int, PyObject *);
3030

3131
void ue_pydelegates_cleanup(ue_PyUObject *);
32+
33+
UClass *unreal_engine_new_uclass(char *, UClass *);
34+
UFunction *unreal_engine_add_function(UClass *, char *, PyObject *, uint32);

0 commit comments

Comments
 (0)