-
-
Notifications
You must be signed in to change notification settings - Fork 224
Safely integrate Godot cross-thread calls (e.g. AudioStreamPlayback::mix
)
#1131
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
Comments
Thanks for raising this! Input on thread safety is definitely appreciated. 👍 A few things we need to clarify:
There is an entire rabbit hole of thread safety discussed in #18. No need to read the whole thread, but there are some ideas towards the end that we might end up integrating, such as Planning-wise, we're currently finalizing v0.3, and this being a bigger endeavour means it would like aim for v0.4, given it probably introduces breaking changes. But I think it should be fine, it would likely also take some time to settle on an approach, and maybe we can start with v0.4 sooner than with past minor releases. |
AudioStreamPlayback::mix
)
I must be completely honest here: I have exactly 11 days of experience with Godot so far. But based on what little I've seen, I think it's safe to say that it's definitely necessary to consider thread safety down to the method level, at least for some classes. Just considering For some of the subclasses of My working assumption at the moment is that if a function can be overridden in GDScript, then it is always called from the main thread. But if it can only be overridden in native code then there is a good chance it is called from some other thread. This makes sense because GDScript would surely blow up horribly if called from a non-main thread. For that to work it'd be necessary to do something like Web Workers where an individual GDScript runs in its own isolated container, and I'm not aware of anything like that in Godot. I suppose it's possible that there's some function that Godot calls in the main thread if its overridden in GDScript but calls in some other thread otherwise, but that seems unlikely.
Yeah, agreed. I was getting my thoughts down quickly and made a silly choice here.
Yeah, I weighed up the same debate in my own mind. There are good arguments on both sides to be sure. There are a couple of use cases I can think of for abstracting the type. Firstly, what if having one lock around the whole state object doesn't make sense for a particular use case? You might want to do something like: #[derive(Clone)]
struct State {
pub messages: std::sync::mpsc::Receiver<Message>,
pub a: RwLock<Something>,
pub b: RwLock<SomethingElse>,
} Requiring the above to be wrapped in a A middle ground might be to say that the State is always an But then the second use case is if the threaded function doesn't actually need to modify any state. This is a weak argument because I can't give a specific example, but I can imagine a case where a threaded function just needs some data that is initialized once per class instance, in which case a plain struct that implements Clone or even a scalar type might be sufficient for the 'state'. On balance I am sure I would be happy with the middle ground of
Yeah... this is where my proposal is least fleshed out. I think it'd be necessary to experiment and implement some ideas to see if this works or not. Speaking specifically about Apart from that, there's no sensible reason to call any Godot API from I really hope it is generally the case that any functions Godot calls from a non-main thread are going to be well-isolated in this way because if not that is going to be a horrendous can of worms. I am hoping for something approaching a one-way data flow type of solution (my proposal does not achieve that on its own, but it facilitates that approach). Whether that's feasible absolutely depends the design of bits of Godot that I haven't dug into yet. I will have a look through the other proposals and think on this further. Thanks! |
Just to clarify, this is not quite true. All implementations, with and without the |
@TitanNano Thank you! I wasn't aware of the I like to state "obvious" assumptions like this precisely because if they're incorrect stating them gives people a chance to correct my understanding :-). |
So the change that is proposed here could be an optimization, but is not strictly necessary to make such classes usable. As far as I know, all rust code should be thread-safe and sound when then |
This is explicitly not the case, I even renamed the feature from
|
@Bromeon hmm yea want I was thinking of is calling into rust code. There is a risk of unsoundness, but this relates to the interaction with the engine types. Godot classes that have been defined in rust should be safe to be accessed from multiple threads at the same time. |
This is a rough proposal for how to safely implement functions that currently require
experimental-threads
. It's not completely fleshed out but I wanted to see if there's any interest before I expand on it further.If you're not taking proposals on how to solve this problem at the moment, or you don't find this proposal useful, I won't be offended if you close the issue.
Take
AudioStreamPlayback
as an example:Godot calls
mix
from a non-main thread, which means that access toself
in this function is unsound. (Withoutexperimental-threads
this condition is checked at runtime by godot-rust, which will panic beforemix
is even called).My proposal is to remove the
self
parameter from functions that are called from a non-main thread, and replace it with a user-definedstate
parameter, which must implClone + Send + 'static
and can therefore be passed between threads safely. For example:A sensible choice for
AudioState
might be for exampleArc<RwLock<S>>
, whereS
is some arbitrary struct. TheAudioState
could then be safely read and mutated from multiple threads. But the idea is that the actual type ofAudioState
should be left up to the programmer as long as it implementsClone + Send + 'static
.The state object should be attached to the class struct, for example:
godot-rust would then have some glue code that takes care of cloning
state
and passing it through toIAudioStreamPlayback::mix
whenmix
is called from Godot.It would be illegal to mark a field
#[export]
if it is also marked#[audio_state]
.This approach is roughly what I'm using in my own project, although I of course have to implement the glue manually each time I extend
AudioStreamPlayback
.I think this general approach should be logically extensible to other cases where Godot calls functions from non-main threads.
If you're interested in something like this approach I would be interested in having a go at implementing it.
The text was updated successfully, but these errors were encountered: