-
-
Notifications
You must be signed in to change notification settings - Fork 4
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
Fix yield bug #2
Conversation
That's an interesting approach, but running the test scene, the tasks that don't even use |
Hmm interesting, Ok I'll take another look at it! |
Fix sometimes yield bug Show error on invalid state Fix bug where task never finishes
Thanks for finding the bug! It should work now Checking about the freeze issue now. |
Hmm, it doesn't seem to be working yet. Do you get the same behavior as I do when running the sample scene? Not related, but why are you force-pushing commits into your main branch? Adding new commits is better for reviewing only the latest changes, to see the history of incremental fixes one by one. |
Ooooh, I didn't pay attention enough to this part, I'm really sorry =X |
I'm using this updated test scene: The functions now seem to complete properly and without crashing, but there is a weird bug with the As we can see from here I am actually checking that it's a valid function state, but the call to resume immediately after prints an error. I think it's more a Godot side of things, as the tasks are now working correctly without crashing and returning the right values 🤔 Maybe time to submit a bug report there.
I got too used to Godot github style 😅 |
Hmm, I think I know what's going on with this error. On I think we shouldn't resume coroutines manually, I don't see a use case of yielding without waiting for a signal on tasks running in background threads. func execute() -> void:
var result = object.callv(method, args)
if result is GDScriptFunctionState:
result = yield(result, "completed")
emit_signal("finished", result)
if group:
group.mark_task_finished(self, result) This works almost everytime, but is giving a
Oh, got it! I've never contributed to Godot, so I don't know how it goes. For this case, I prefer separate commits to see each change on top of the other. We can always see the diff between several commits easily as well. But feel free to work as you prefer ^^ |
Hmm that makes sense yea, I was trying that but I think then the godot-dispatch-queue/addons/dispatch_queue/dispatch_queue.gd Lines 239 to 248 in 99f5ecf
If I push that change perhaps we should also yield the Perhaps in the execute |
After some testing, putting a
|
The call to
Well, that's the thing with multithreading: it is quite unpredictable. You didn't get errors, but it might still crash. I think that the signal is being emitted at the same time that the task yields awaiting for the coroutine to complete, but in another thread. The same GDScriptFunctionState object is being edited by both threads (one is disconnecting the signal slot from So, that's a tough one. I don't know if there's any way to work around this concurrency issue without going to Godot's code and adding some synchronization code, which might not be worth it. To be honest, I don't think using |
I like to run code in the editor and also in game. In the game I will likely be using a yield for a small pause between loading cycles, without having to move everything to a process buffer. In the editor I don't use yield, as I would rather my development be really fast, with a few small delays. In this case we have a maybe yield state, which means I should use yield-idle-frame at the start of the function for consistency. Having the ability to spawn a thread to do the same task is really useful, because I can choose to do that when tasks are not urgent. An example would be saving an image to the hard drive. In game I want that to run in a thread, but when the game is exiting I want that to be as snappy as possible. Using process sync in this case means I have to duplicate a lot of logic, more bugs, less extensible. As I can see, right now this PR supports yield in a way that the previous version did not (threadlock and stackoverflow issues). I realise you might not want to start pushing yield into this repo and I agree with that, but stability wins in my book. |
So, if I understand correctly, you'd like to yield on the game, but not when running code in the editor. As a sidenote, for a "yield until idle frame", you can use
I don't think I follow. What would this "process sync" be exactly?
Supporting Said that, I still have my doubts if we should yield until the coroutines complete or just let the user deal with the GDScriptFunctionState in the tasks' |
I'd like to support as many crash-free ideas as possible without restriction on users of this lib
Syncing updates on heavy
Thankfully less than in the original example posted, which was actually a crash 3/5 times for me, in the new version I have not seen a crash yet even with fairly extensive testing.
Unfortunately it does not seem to mitigate the original crash bug.
Unfortunately I don't see a better solution to this issue as As far as I can see we've come quite a way to reducing crashes when using threaded yield. Passing the At this point I don't really know how to improve further on this, I haven't had the time to step through all of the code in the repository. If you know of a better solution it would be great, but I will be using the code above in my projects from now forward until a better more crash free solution is found. (Probably when I move to Godot 4.x) |
Well, in your test project, you could have handled the GDScriptFunctionState after dispatching the tasks, for example like: prints("RUNNING TASK 1")
for child in self.get_children():
var function_state = yield(Threads.dispatch(child, "task"), "finished")
yield(function_state, "completed") and the results are the same as having the yield inside the task runner. This is what I mean by "let the user deal with the GDScriptFunctionState in the tasks' finished signal". Again, in this case that the
Well, if you depend on the main thread, maybe you shouldn't be using threaded dispatch queues? At least make the main thread sync with the background task, not the other way around. But I agree with you that we're better off with the
I guess we can't zero out the crashes, due to the nature of using non thread-safe features in a multithreaded environment. And that's fine, we just have to warn the users about the risks. I'll add a note in the README about how we yield if the method returns a Thank you very much for pointing out this problem and for contributing to fixing it, I hadn't thought of using yield inside background tasks. |
Thank you! 🎉 I'm using this in production and so far it's crash free after the above changes. When I start seeing crashes again I will look deeper into this 👍 |
Just to note in that I'm still not seeing any crashes with this. Working really well for rendering in subviewports: func _render()
self.set_update_mode(Viewport.UPDATE_ALWAYS)
yield(VisualServer, "frame_post_draw")
self.set_update_mode(Viewport.UPDATE_DISABLED)
var to_return = self.get_texture().get_data()
to_return.lock()
return to_return Other file func _ready():
Threads.dispatch(some_node, "render") |
That's awesome! |
closes #1