Let's continue the discussion in a separate issue, #8 is already taking long to load on my phone.
Quite the opposite. The situation is the data is sent sometimes without a command. For example, there are commands that you send and the UI thread waits for a response. Then, depending on the response you send more data or just do nothing.
But why wouldn't that work with two enums? Call the enums command, response, message or whatever, the receiver can match the result and send whatever it wants in reponse (or nothing at all).
There are more responses that commands. There are currently 55 commands, and between 50-100 data types that are send one way or another.
That is a lot indeed, but not too much imo. Which commands have different responses?
Also, there's another problem related to merging the channels. When I started the 0.9 version and the Qt port, I set a goal: the UI MUST NOT HANG. To do it, RPFM currently makes the UI thread to enter on a loop that keeps processing events (like resizing the window) until it gets a response from the background thread.
That is completely reasonable! A few cents from what I noticed while browsing your channel code:
- shouldn't you phase out
check_message_validity_recv
for the non-blocking variant?
- why don't you loop within
check_message_validity_tryrecv
? Right now rpfm contains the same loop 22 times
check_message_validity_tryrecv
and check_message_validity_recv_background
do exactly the same (?), can't the former be a wrapper around the latter?
- you might want to move the ui and bg code into separate files. It helps matching IDE search results to functionality (at the moment I have to check whether a usage of foo in main.rs is in the bg thread or ui thread), and main.rs is almost 6k lines of code long - that is really a lot
That means you can potentially send a command while there is another one executing itself, making the entire program crash.
Yes and no. Yes, at the moment you can, but you could change that at any time by not sending a new command when there is a pending one on the "wire".
In earlier 0.9 versions, you could crash the program by keeping pressed the "Del" button because the deletion process got messed by a checking process, and message were received in the wrong place. To "fix it", I left a channel for commands only (so commands can be chained and don't interfere with one another) and another for data, and every time data received, RPFM checks that's the type it needs using generics and crashes if it's not .
Merging them will mean the commands can potentially crash in many more places that before.
Are you sure that was what fixed the issue? If process_events
allows your event handlers to execute, even with a dedicated data channel you should not send more than one commands at once. Imagine we have the commands A0, A1 and B. All three return data, A1 will only be sent after A0 finishes, and A0 and B are sent by user interaction.
Now
- the user initiates A0
- the A0 handler is in a tryrecv-loop, so the user can trigger new commands
- the user initiates B
The command channel contains: B -> A0
The bg->ui data channel contains: nothing
Now
- the bg thread handles A0, puts a0-data in the bg->ui channel
- the ui thread handles a0-data, and puts A1 into the ui->bg command channel
The command channel contains: A1 -> B
The bg->ui data channel contains: nothing
Now
- the bg thread handles B and puts b-data into the bg->ui channel
- the ui thread expects a1-data but gets b-data 💣 💥
Mmmm.... I'm not sure about the second and third one. The way I see it, the same problem with wrong messages being sent can also happen with enums, because you can receive the wrong variant and the program will have to crash, to avoid weird behavior.
You surely still can send the wrong variant, but it does stop you from putting a wrong data type into the correct variant! Makes reviewing code much easier, since you can be sure that a code change which does not touch the variant handling/sending panics because the channel is disrupted.
And the fifth one.... if you want data to be shared in an enum without deleting the original data (because the threads are isolated), you have to copy it anyways isn't?
Yes and no! Threads are not isolated, rust encourages and enables you to share data in completely safe ways. You don't have (at least without unsafe
) the freedom of other programming languages, but if you prove the compiler that what you are doing is safe it lets you do it.
So one the one hand yes, to send or share a type between threads it must implement Send
or Send
and Sync
, and those types in the stdlib internally copy themselves when you move them into a closure.
On the other hand no, you do not have to copy the data. To share a Vec<u8>
between threads without copying the vector, you can move it into an Arc
and move a/multiple clone(s) to the/some other thread(s). An arc is just a few bytes in size which can be easily cloned, the contained type will exist only once in memory and be dropped as soon as all arcs which point to it are dropped.