can-lehmann / owlkettle

A declarative user interface framework based on GTK 4
https://can-lehmann.github.io/owlkettle/README
MIT License
383 stars 12 forks source link

Improve Multithreading support #163

Open PhilippMDoerner opened 5 months ago

PhilippMDoerner commented 5 months ago

Owlkettle should provide a clearer strategy on what to do for multithreading. At the very least, one-off tasks should be supported.

There are 2 general paths we can go down for that road: 1) We make use of an existing implementation of a multithreading mechanism in nim and provide a way to integrate it better with owlkettle 2) We make use of GTKs existing multithreading mechanisms, such as Gio.Task and potentially build something on top of it that expands on its functionality if necessary

Currently I'm leaning towards 2), potentially while throwing in some kind of mailbox mechanism like here in order to allow tasks to send messages even if they haven't finished yet, but Gio.Tasks may provide a solution for even that, I haven't checked yet.

Even that kind of mechanism could be built on top of a myriad of different solutions for sending the message itself: 1) system.Channel - Current nim default, will copy any message they receive. Guarantees correctness, inefficient for large messages 2) threading/channels.Chan - Planned nim default in the future. Does not copy messages but "moves" memory-ownership to another thread by "isolating" the memory in the current thread. Wonky for usage because isolating doesn't work 3) disruptek has Loonyqueue which seems technically sound 4) GTK has a locked-queue called AsyncQueue. Rather simple setup, just a list with a lock attached.

The problem more arises from how to trigger a re-render on the main-thread if a Task sends a message, so that it reacts to said message. But that's a problem we can delay after we have some basic wrapping around Gio.Task at least.

PhilippMDoerner commented 5 months ago

Just had a chat with somebody on the GTK channel. For scenarios where you have a long-running task that wants to update, we can use the g_timeout_add function we have already wrapped to register a callback that regularly checks for new messages. That turns creating a "progress-task" into a still pretty simple 2 step process: Basically:

1) Start a Task and make sure it has access to a queue/mailbox 2) Add a g_timeout_add callback that regularly checks in the main-thread the mailbox for new messages and trigger updates based on that 3) Let the task send messages to the mailbox 4) The callback notices them when it gets called, triggers GUI updates based on that 5) You de-register the callback when either a certain type of message is sent or some condition is true. Like if a progress-level now is "1" you know its finished and the callback is unnecessary. Or tasks can send "Finished" Messages or sth.

Edit: We can do even better! Tasks are allowed to schedule timeouts on the main thread! Therefore, that effectively functions as a potential message-passing mechanism! So basically "We made 10% progress" can just be done via a timeout callback that you schedule on the main-thread that updates the state. Damn that is neat.

Link to the matrix conversation