From 38d3fd01c43c39784665f3f43281c69e9b372c2d Mon Sep 17 00:00:00 2001 From: akashin Date: Thu, 1 Dec 2016 22:15:38 +0300 Subject: [PATCH 1/2] Implement PyController class --- .../Public/PyController.cpp | 196 ++++++++++++++++++ .../UnrealEnginePython/Public/PyController.h | 52 +++++ 2 files changed, 248 insertions(+) create mode 100644 Source/UnrealEnginePython/Public/PyController.cpp create mode 100644 Source/UnrealEnginePython/Public/PyController.h diff --git a/Source/UnrealEnginePython/Public/PyController.cpp b/Source/UnrealEnginePython/Public/PyController.cpp new file mode 100644 index 000000000..78c3e0836 --- /dev/null +++ b/Source/UnrealEnginePython/Public/PyController.cpp @@ -0,0 +1,196 @@ +#include "UnrealEnginePythonPrivatePCH.h" +#include "PyController.h" + + +APyController::APyController() +{ + PrimaryActorTick.bCanEverTick = true; + + PythonTickForceDisabled = false; + PythonDisableAutoBinding = false; + +} + + +// Called when the game starts +void APyController::BeginPlay() +{ + Super::BeginPlay(); + + // ... + + if (PythonModule.IsEmpty()) + return; + + FScopePythonGIL gil; + + py_uobject = ue_get_python_wrapper(this); + if (!py_uobject) { + unreal_engine_py_log_error(); + return; + } + + PyObject *py_controller_module = PyImport_ImportModule(TCHAR_TO_UTF8(*PythonModule)); + if (!py_controller_module) { + unreal_engine_py_log_error(); + return; + } + +#if WITH_EDITOR + // todo implement autoreload with a dictionary of module timestamps + py_controller_module = PyImport_ReloadModule(py_controller_module); + if (!py_controller_module) { + unreal_engine_py_log_error(); + return; + } +#endif + + if (PythonClass.IsEmpty()) + return; + + PyObject *py_controller_module_dict = PyModule_GetDict(py_controller_module); + PyObject *py_controller_class = PyDict_GetItemString(py_controller_module_dict, TCHAR_TO_UTF8(*PythonClass)); + + if (!py_controller_class) { + UE_LOG(LogPython, Error, TEXT("Unable to find class %s in module %s"), *PythonClass, *PythonModule); + return; + } + + py_controller_instance = PyObject_CallObject(py_controller_class, NULL); + if (!py_controller_instance) { + unreal_engine_py_log_error(); + return; + } + + py_uobject->py_proxy = py_controller_instance; + + PyObject_SetAttrString(py_controller_instance, (char *)"uobject", (PyObject *)py_uobject); + + + // disable ticking if not required + if (!PyObject_HasAttrString(py_controller_instance, (char *)"tick") || PythonTickForceDisabled) { + SetActorTickEnabled(false); + } + + if (!PythonDisableAutoBinding) + ue_autobind_events_for_pyclass(py_uobject, py_controller_instance); + + if (!PyObject_HasAttrString(py_controller_instance, (char *)"begin_play")) + return; + + PyObject *bp_ret = PyObject_CallMethod(py_controller_instance, (char *)"begin_play", NULL); + if (!bp_ret) { + unreal_engine_py_log_error(); + return; + } + Py_DECREF(bp_ret); +} + + +// Called every frame +void APyController::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if (!py_controller_instance) + return; + + FScopePythonGIL gil; + + PyObject *ret = PyObject_CallMethod(py_controller_instance, (char *)"tick", (char *)"f", DeltaTime); + if (!ret) { + unreal_engine_py_log_error(); + return; + } + Py_DECREF(ret); + +} + + +void APyController::CallPythonControllerMethod(FString method_name) +{ + if (!py_controller_instance) + return; + + FScopePythonGIL gil; + + PyObject *ret = PyObject_CallMethod(py_controller_instance, TCHAR_TO_UTF8(*method_name), NULL); + if (!ret) { + unreal_engine_py_log_error(); + return; + } + Py_DECREF(ret); +} + +bool APyController::CallPythonControllerMethodBool(FString method_name) +{ + if (!py_controller_instance) + return false; + + FScopePythonGIL gil; + + PyObject *ret = PyObject_CallMethod(py_controller_instance, TCHAR_TO_UTF8(*method_name), NULL); + if (!ret) { + unreal_engine_py_log_error(); + return false; + } + + if (PyObject_IsTrue(ret)) { + Py_DECREF(ret); + return true; + } + + Py_DECREF(ret); + return false; +} + +FString APyController::CallPythonControllerMethodString(FString method_name) +{ + if (!py_controller_instance) + return FString(); + + FScopePythonGIL gil; + + PyObject *ret = PyObject_CallMethod(py_controller_instance, TCHAR_TO_UTF8(*method_name), NULL); + if (!ret) { + unreal_engine_py_log_error(); + return FString(); + } + + PyObject *py_str = PyObject_Str(ret); + if (!py_str) { + Py_DECREF(ret); + return FString(); + } + + char *str_ret = PyUnicode_AsUTF8(py_str); + + FString ret_fstring = FString(UTF8_TO_TCHAR(str_ret)); + + Py_DECREF(py_str); + + return ret_fstring; +} + + +APyController::~APyController() +{ + FScopePythonGIL gil; + + ue_pydelegates_cleanup(py_uobject); + +#if UEPY_MEMORY_DEBUG + if (py_controller_instance && py_controller_instance->ob_refcnt != 1) { + UE_LOG(LogPython, Error, TEXT("Inconsistent Python AController wrapper refcnt = %d"), py_controller_instance->ob_refcnt); + } +#endif + Py_XDECREF(py_controller_instance); + + +#if UEPY_MEMORY_DEBUG + UE_LOG(LogPython, Warning, TEXT("Python AController (mapped to %p) wrapper XDECREF'ed"), py_uobject ? py_uobject->ue_object : nullptr); +#endif + + // this could trigger the distruction of the python/uobject mapper + Py_XDECREF(py_uobject); +} diff --git a/Source/UnrealEnginePython/Public/PyController.h b/Source/UnrealEnginePython/Public/PyController.h new file mode 100644 index 000000000..cd9300fbf --- /dev/null +++ b/Source/UnrealEnginePython/Public/PyController.h @@ -0,0 +1,52 @@ +#pragma once + + +#include "GameFramework/Controller.h" + +#include "PyController.generated.h" + + + +UCLASS(BlueprintType, Blueprintable) +class APyController : public AController +{ + GENERATED_BODY() + +public: + // Sets default values for this component's properties + APyController(); + ~APyController(); + + // Called when the game starts + virtual void BeginPlay() override; + + // Called every frame + virtual void Tick(float DeltaSeconds) override; + + UPROPERTY(EditAnywhere , Category = "Python") + FString PythonModule; + + UPROPERTY(EditAnywhere, Category = "Python") + FString PythonClass; + + UPROPERTY(EditAnywhere, Category = "Python") + bool PythonTickForceDisabled; + + UPROPERTY(EditAnywhere, Category = "Python") + bool PythonDisableAutoBinding; + + UFUNCTION(BlueprintCallable, Category = "Python") + void CallPythonControllerMethod(FString method_name); + + UFUNCTION(BlueprintCallable, Category = "Python") + bool CallPythonControllerMethodBool(FString method_name); + + UFUNCTION(BlueprintCallable, Category = "Python") + FString CallPythonControllerMethodString(FString method_name); + +private: + PyObject *py_controller_instance; + // mapped uobject, required for debug and advanced reflection + ue_PyUObject *py_uobject; +}; + From 0ab388c5b2472e6ed92aee606ba36a689ee08832 Mon Sep 17 00:00:00 2001 From: akashin Date: Tue, 13 Dec 2016 13:08:29 +0300 Subject: [PATCH 2/2] Fix compilation errors on linux --- Source/UnrealEnginePython/Private/UEPyPlayer.cpp | 2 ++ Source/UnrealEnginePython/UnrealEnginePython.Build.cs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/UnrealEnginePython/Private/UEPyPlayer.cpp b/Source/UnrealEnginePython/Private/UEPyPlayer.cpp index ab3fc8a3a..e43edeb68 100644 --- a/Source/UnrealEnginePython/Private/UEPyPlayer.cpp +++ b/Source/UnrealEnginePython/Private/UEPyPlayer.cpp @@ -63,6 +63,7 @@ PyObject *py_ue_get_num_players(ue_PyUObject *self, PyObject * args) { #else AGameModeBase *game_mode = world->GetAuthGameMode(); #endif + if (!game_mode) return PyErr_Format(PyExc_Exception, "unable to retrieve GameMode from world"); #if ENGINE_MINOR_VERSION < 14 @@ -84,6 +85,7 @@ PyObject *py_ue_get_num_spectators(ue_PyUObject *self, PyObject * args) { #else AGameModeBase *game_mode = world->GetAuthGameMode(); #endif + if (!game_mode) return PyErr_Format(PyExc_Exception, "unable to retrieve GameMode from world"); #if ENGINE_MINOR_VERSION < 14 diff --git a/Source/UnrealEnginePython/UnrealEnginePython.Build.cs b/Source/UnrealEnginePython/UnrealEnginePython.Build.cs index ea198c026..2f8c9df7b 100644 --- a/Source/UnrealEnginePython/UnrealEnginePython.Build.cs +++ b/Source/UnrealEnginePython/UnrealEnginePython.Build.cs @@ -54,7 +54,8 @@ public UnrealEnginePython(TargetInfo Target) { "Core", "Sockets", - "Networking" + "Networking", + "LevelEditor", // ... add other public dependencies that you statically link with here ... } );