-
Notifications
You must be signed in to change notification settings - Fork 757
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multithreading #181
Comments
Hi, you can enable multithreading (albeit pretty experimental as we are basically mixing 3 threading models) https://github.com/20tab/UnrealEnginePython#threading-experimental or you can follow a 'coooperative' approach, where your python script periodically 'tick' the editor via unreal_engine.editor_tick() or uobject.world_tick() when you are in the game. |
I also read something about subinterpreters, and the thread about the guy trying to create an instanced VM in an object so that he could create his hardware engineering game using multiple components to engineer something with, dunno if you remember. Could subinterpreters be another option? And by the way, how would i access the output from the python script, once it analyzes the data? I did read about the experimental multithreading, but the reason i asked was precisely because i didn't know just how experimental it is xD Additionally, i was very interested in a reply to this question if possible: And, finally, could you elaborate on the second "cooperative" option you mentioned? I'm not quite sure i understand what exactly it means, how would i set it up, what could be some rough instructions to do it? Sorry for all the questions xD |
There are lot of users that work with multithreading enabled in the plugin, so maybe it could work for you out of the box. Regarding 'blocking tasks' it is a limit in basically any game engine. You should offload every heavy task or your game will block. Threads are a way to do it, in other cases multiprocessing is another solution (at least on unix system where the copy on write fork() semantics allow for pretty funny tricks). Multiple interpreters are pretty useless in this concept. The best thing to do is designing your script to work as 'tick' objects. So an actor (or a component) periodically update its status. To be a bit cleaner this would be a heavy approach: class HeavyActor:
def tick(self):
for i in range(0, 10000):
do_something_heavy() that could be refactored to be game-engine friendly: class HeavyActor:
def begin_play(self):
self.i = 0
def tick(self):
self.i += 1
if self.i < 10000:
do_something_heavy() If for some reason you cannot work in this way the cooperative approach works this way: import unreal_engine as ue
class HeavyActor:
def tick(self):
for i in range(0, 10000):
do_something_heavy()
self.uobject.get_world().world_tick() |
Oh yes now i understand what you mean by tick objects, so basically check every x amount of time instead of every millisec because of course that would be unnecessary and too heavy. About the last code example instead, does "self.uobject.get_world().world_tick()" make the world tick manually? So to sum it up, my two options are to either use multithreading or to try manually ticking the time in my script so that it doesn't stay frozen, correct? And how would i handle the multithreading once i enable it, is it automatically handled? |
Yes, "ticking" the world manually moves it forward of the specified time slice (_tick() takes a float argument). I do not have a strong opinion between threading or 'playing with time', threading would be (maybe) a bit easier to integrate in already existing scripts. Regarding the output i am not sure to follow, the stdout is mapped to the plugin console, but your scripts should return objects (like strings). If they are already developed scripts that blindly writes to stdout, you can use the FPythonOutputDevice class: from unreal_engine import FPythonOutputDevice
def logger(message, verbosity, category):
do_something_with_message()
output_device = FPythonOutputDevice(logger) The 'logger' function will be called whenever a new message is sent to the console |
So threading is handled automatically, right? Oh, so scripts return objects. I also noticed sys.exit() doesn't work to end a script. |
@TylerReynolds I've solved my multithreading use cases by adding two convenience functions to this plugin in a fork. ut.run_on_bt(<function name>,<callback>) #should have a third parameter for args... ue.run_on_gt(<function name>,<args>) This allows me to run a long training function on a background thread, e.g.: https://github.com/getnamo/tensorflow-ue4/blob/master/Content/Scripts/TensorFlowComponent.py#L92 and then give a game-thread callback when it has completed or weave multiple status updates while it's running without worrying about being on the wrong thread in UE4. https://github.com/getnamo/tensorflow-ue4/blob/master/Content/Scripts/TensorFlowComponent.py#L83 I usually then handle data conversion using JSON encoding since SIOJson supports easy conversion between BP types and structs<->JSON. The convenience functions are defined in: https://github.com/getnamo/UnrealEnginePython/blob/master/Source/UnrealEnginePython/Private/UEPyModule.cpp#L273 which calls the modified function: and background thread action using python Thread: (just realized it doesn't support calling bt with args in my use case, but that's an easy fix) If these would be useful I could open a pull request with the two functions on the master repo. |
@TylerReynolds i fear you are seeing it from an 'unclassical' point of view. You do not run python scripts like in a shell. The whole UE4 engine is a python vm that can execute code in a big context. You create classes that you attach to Actors, or you execute scripts in the UE4 python vm. Calling sys.exit() would mean destroying the editor itself, so it instead generates an exception so the result would be the ending of the script execution. Passing data between scripts is not an issue as if a script create the 'foobar' object, the other scripts will be able to see it. If it is not the intended behaviout you have the sandbox_exec() (or 'Execute in Sandbox' button in the editor) that generate a new python interpreter that is destroyed at the end of the script execution @getnamo i am not sure to understand why you did not use the create_and_dispatch_when_ready() function, am i missing some difference ? (except for passing args) |
@rdeioris I think i understand much more clearly now that you put it that way, being fairly new to both python and unreal engine i'm trying my best to follow along xD I'll leave this issue open for now, so that i can focus on experimenting with the new stuff and seeing how it is working for me, so i'll post once again when i'm done testing to let you know whether or not everything is working properly, thank you very much for your help! @getnamo Thank you for your reply and suggestion! Thank you once again for your contribution! |
@rdeioris You are correct, it is using the As for why I use a renamed version? It's mainly contextual coming from wanting to use similar concepts to android's MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
Log.d("UI thread", "I am the UI thread");
}
}); You may wonder why I do not use the Sadly python doesn't support proper lambdas, but by using inline defined python functions, the event driven style comes through with |
Okay, finally had couple minutes today to experiment with multi-threading, and looks like it worked like a charm right away! Only problem is, when i close the game the background thread still stays open, and keeps going even if i close the project itself. I know this is supposed to happen because i didn't really define when to close it, but since i need it running all the time whilst i'm playing i think i'll have to define something to close it when the game stops, or actually even better when the project is closed (it's even better not to have to reload the ram every time i stop playing, for example when testing) So my question now is, how do i make the thread stop when the game stops, or preferably when the project itself is closed? @getnamo I got a little confused about JSON conversion and stuff, but reading again your post i guess i have nothing to worry about, as i just need to call those two functions since you handled everything in your code? Also, does your fork solve the problem of stopping the threads when the game/editor is closed? |
Set the daemon flag as True: t = threading.Thread(target=foobar)
t.daemon = True
t.start() |
Will this close it when the editor is closed or the game? Also, what do you think about getnamo's fork, should i use it? |
Putting a python thread in daemon mode will means that when the main program (the editor in your case) exits, even the daemon thread will be destroyed |
Thank you! Also, what about getnamo's fork, do you think i should use it, is it safe to use? |
you could subscribe to the 'OnEndPlay' event of the actor (never tried it honestly, so feedback will be interesting). Regarding getnamo 's fork i will include its features in the next days |
That's fantastic, thank you, i will definitely provide some feedback and will also try getnamo's fork, so thank you @getnamo for your work! |
@rdeioris Since I mainly use python components, I just added an end_play() signature on the python component which intercepts the game exit and I can gracefully request threads to stop (usually ending training early in tensorflow). But seeing the documentation for events does your automagic event system work for python components too? if so would I just do def on_end_play(self, reason):
pass or @TylerReynolds Also forget the stuff about I mentioned about JSON, it's only useful for large/generic structured data in generalized used cases (e.g. I need this custom struct data I defined in BP in python). The cost of that is both some performance (JSON conversion) and in my case that another plugin dependency and you want to avoid adding dependencies as much as possible. So if the main repository can provide what you're looking for, definitely stick with it. Looking forward to @rdeioris take on MT feature implementation, one day I may not need to use a fork either :). |
@getnamo I see, but the whole daemon thing should take care automatically of memory leaks and so on, in fact i checked the ram and there was no leakage. @rdeioris Testing the whole virtual machine thing concept, i tried creating a variable in a script and accessing it from another script, but it doesn't seem to be working. Or does it work only for objects? Actually, i seemed to recall something about an exec function and reading your reply about the virtual machine more thoroughly and about executing the script in the vm i concluded that only scripts executed through the ue.exec('foobar') make their variables available globally. In fact, using exec the variables were available for reading, awesome. Is there another way, or is exec the only way to "load" variables and objects globally, so that they can be accessed by any other scripts/objects? |
@TylerReynolds consider wrapping both functions in a class and have the stopping variable be a member variable. So you'd then do def setShouldRun(isTrue):
self.ShouldRun = isTrue in my case I use the flip version (shouldstop) here https://github.com/getnamo/tensorflow-ue4/blob/master/Content/Scripts/TFPluginAPI.py#L44 If you need to access this from another module, make sure you call the function on the same instance to get the desired result. |
Thank you very much @getnamo, but i think the whole virtual machine concept should make things very straightforward since everything is accessible when running in a virtual machine, it's not just a bunch of separated scripts with no connection, so i was looking into this vm thing to take full advantage of it. But the solution you provided is very interesting when working in a different environment (when executing scripts from a shell like i'm used to), and i also just noticed your tensorflow plugin which i'll definitely have some fun with as soon as i have a more sound knowledge about unreal and this plugin xD @rdeioris So, i noticed the scripts executed have variables that can be accessed by the console, but not from within a script? Should i make the variable global or something? I don't understand why the console can access them whereas scripts cannot For the moment my workaround is to simply use a python script that acts as a launcher by calling ue.exec(), and it's working just fine, but i'd like to know what exactly is going on and why scripts cannot access global variables without using the .exec method, is this the only way the virtual machine works? |
Well, everything is working very nicely so far and i understand both uepython and unreal engine much better now, so things have been quite smooth. One problem i encountered now, though, is about making a thread return a result, so that i can then pass it along in the node "call python actor method string", which calls a python method from a blueprint and returns a string. How would i solve such a problem? I realized it's impossible to continue the blueprint execution if the thread is still going and needs time to elaborate the result, so i simply set up a custom event that fires when the thread is done. Do you think there is a more efficient or better approach? |
@TylerReynolds I handled this by expanding this plugin's taskgraph callback to take arguments (mentioned earlier), and since it uses UE4's threading system there is no need to use .join while still having the results return asynchronously on the game thread. Regarding the second point, I use the same method, make the ue taskgraph function with the result call a custom bp function, which then forwards the result to a event dispatcher inside my python blueprint component (which is the tensorflow component). That way any other blueprint using my tensorflow component can receive events by clicking the green + next to the OnResults event dispatch. |
@getnamo finally multithreading is enabled by default and with a solid api. I think the only step missing is the create_and_dispatch_when_ready() variant you made. Would you like to make a pull request ? |
Excellent I'll extract the method and make a pull request in the following week |
pull request made: #564 |
Dear all I am trying to use Threading with UE4 and Python, but whenever I start the thread UE4 crashes. Could you please help me in this matter?
I am using UE4 4.19 Mac |
(Incidentally, i am in fact multi-threading, as this is my second thread, sorry xD)
Hey, sorry for being here asking something once again, but since you programmed the plugin it should take you 0.9 secs to help me with something i would struggle with for quite a few hours xD
Sssoo... I'm currently trying to keep a python script open while playing, because it loads some vectors in the RAM and it takes a couple minutes to initialize.
I really can't wait 5 minutes every time i call the script before being able to actually use it xD
The issue here is that whenever i load the python script, of course the game freezes, and even when the script is done loading the game cannot resume because i need to keep it open, and apparently script open = frozen.
What is your suggestion, what options do i have here?
Does this mean that i cannot have multiple actors executing python scripts, or that python scripts that require some time to compute would freeze the game every time?
The best option would be to be able to keep it open in unreal while playing, so that i can load it once when first starting the game, then i can send the script all the data it needs to elaborate without closing it.
It really just has to stay open and idle, no activity whatsoever, until i send it the data to elaborate on.
So basically i think i would just need a way to unfreeze the game once it is done loading, without stopping the script but keeping it idle and ready to go when needed.
So i think there really is no other way other than multithreading.
Grazie tante ancora una volta :D
The text was updated successfully, but these errors were encountered: