diff options
| author | Matthias Melcher <github@matthiasm.com> | 2025-06-19 15:33:38 +0200 |
|---|---|---|
| committer | Matthias Melcher <github@matthiasm.com> | 2025-06-19 15:33:38 +0200 |
| commit | eadea6a992d4e105b3165a22d091c66be374ae99 (patch) | |
| tree | 6521e659aa52c5f477bfbf81d0f05675fbc641ef /src | |
| parent | 3d13dfefa9d53ef76d77fe57a4d6d51eddc33739 (diff) | |
Update Fl::await() and friends API and documentation
This creates the base for #1263, but does not fix it yet.
Diffstat (limited to 'src')
| -rw-r--r-- | src/Fl_System_Driver.H | 14 | ||||
| -rw-r--r-- | src/Fl_lock.cxx | 302 | ||||
| -rw-r--r-- | src/Fl_win32.cxx | 2 | ||||
| -rw-r--r-- | src/drivers/Posix/Fl_Posix_System_Driver.cxx | 2 |
4 files changed, 229 insertions, 91 deletions
diff --git a/src/Fl_System_Driver.H b/src/Fl_System_Driver.H index e306e7e74..2ec99745a 100644 --- a/src/Fl_System_Driver.H +++ b/src/Fl_System_Driver.H @@ -63,8 +63,20 @@ protected: // implement once for each platform static Fl_System_Driver *newSystemDriver(); Fl_System_Driver(); - static bool awake_ring_empty(); int filename_relative_(char *to, int tolen, const char *from, const char *base, bool case_sensitive); + + // -- Awake handler stuff -- +public: + static Fl_Awake_Handler *awake_ring_; + static void **awake_data_; + static int awake_ring_size_; + static int awake_ring_head_; + static int awake_ring_tail_; + static bool awake_pending_; + static int push_awake_handler(Fl_Awake_Handler, void*, bool once); + static int pop_awake_handler(Fl_Awake_Handler&, void*&); + static bool awake_ring_empty(); + public: virtual ~Fl_System_Driver(); static int command_key; diff --git a/src/Fl_lock.cxx b/src/Fl_lock.cxx index 117d047a5..41c050e94 100644 --- a/src/Fl_lock.cxx +++ b/src/Fl_lock.cxx @@ -1,7 +1,7 @@ // // Multi-threading support code for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2021 by Bill Spitzak and others. +// Copyright 1998-2025 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this @@ -34,7 +34,6 @@ Starting with 1.1.8, we now have a callback so that you can process awake() messages as they come in. - The API: Fl::lock() - recursive lock. You must call this before the @@ -54,23 +53,47 @@ Fl::awake() call, or returns NULL if none. WARNING: the current implementation only has a one-entry queue and only returns the most recent value! + + From Matt: + + 25 years later, we have blazing fast CPUs with 24 cores and more. + Fl::awake(void*) is no longer useful as "the last value" could have + been overwritten by another thread before the main thread gets to it. + Also, the ring buffer may potentially fill up much faster than expected, + so I introduced Fl::awake_once(void (*cb)(void *), void*) which removes + duplicate entries in the queue. */ #ifndef FL_DOXYGEN -Fl_Awake_Handler *Fl::awake_ring_; -void **Fl::awake_data_; -int Fl::awake_ring_size_; -int Fl::awake_ring_head_; -int Fl::awake_ring_tail_; + +static constexpr int AWAKE_RING_SIZE = 1024; +Fl_Awake_Handler *Fl_System_Driver::awake_ring_ = nullptr; +void **Fl_System_Driver::awake_data_ = nullptr; +int Fl_System_Driver::awake_ring_size_ = 0; +int Fl_System_Driver::awake_ring_head_ = 0; +int Fl_System_Driver::awake_ring_tail_ = 0; +bool Fl_System_Driverawake_pending_ = false; + #endif -static const int AWAKE_RING_SIZE = 1024; -/** Adds an awake handler for use in awake(). */ -int Fl::add_awake_handler_(Fl_Awake_Handler func, void *data) +/** + \brief Adds an awake handler for use in awake(). + + \internal Adds an awake handler for use in awake(). + + \param[in] func The function to call when the main thread is awake. + \param[in] data The user data to pass to the function. + \param[in] once If true, the handler will be added only once, removing any + existing handler with the same function pointer and data pointer. + \return 0 on success, -1 if the ring buffer is full. + */ +int Fl_System_Driver::push_awake_handler(Fl_Awake_Handler func, void *data, bool once) { int ret = 0; Fl::system_driver()->lock_ring(); + + // Allocate the ring buffers if we have not done so yet. if (!awake_ring_) { awake_ring_size_ = AWAKE_RING_SIZE; awake_ring_ = (Fl_Awake_Handler*)malloc(awake_ring_size_*sizeof(Fl_Awake_Handler)); @@ -78,6 +101,28 @@ int Fl::add_awake_handler_(Fl_Awake_Handler func, void *data) // explicitly initialize the head and tail indices awake_ring_head_= awake_ring_tail_ = 0; } + + // If we want to add the handler only once, go through the list of existing + // handlers and remove any handler with the same function pointer + // and data pointer. + if (once) { + int src = awake_ring_tail_; + int dst = awake_ring_tail_; + while (src != awake_ring_head_) { + if ((awake_ring_[src] != func) || (awake_data_[src] != data)) { + if (src != dst) { + awake_ring_[dst] = awake_ring_[src]; + awake_data_[dst] = awake_data_[src]; + } + dst++; + if (dst >= awake_ring_size_) dst = 0; // wrap around + } + src++; + if (src >= awake_ring_size_) src = 0; // wrap around + } + awake_ring_head_ = dst; + } + // The next head index we will want (not the current index): // We use this to check if the ring-buffer is full or not // (and to update awake_ring_head_ if we do use the current index.) @@ -85,21 +130,25 @@ int Fl::add_awake_handler_(Fl_Awake_Handler func, void *data) if (next_head >= awake_ring_size_) { next_head = 0; } - // check that the ring buffer is not full, and that it exists - if ((!awake_ring_) || (next_head == awake_ring_tail_)) { - // ring is non-existent or full. Return -1 as an error indicator. + // check that the ring buffer is not full + if (next_head == awake_ring_tail_) { + // ring is full. Return -1 as an error indicator. ret = -1; } else { awake_ring_[awake_ring_head_] = func; awake_data_[awake_ring_head_] = data; awake_ring_head_ = next_head; } + Fl::system_driver()->unlock_ring(); return ret; } -/** Gets the last stored awake handler for use in awake(). */ -int Fl::get_awake_handler_(Fl_Awake_Handler &func, void *&data) +/** + \brief Gets the last stored awake handler for use in awake(). + \internal Used in the main event loop when an Awake message is received. + */ +int Fl_System_Driver::pop_awake_handler(Fl_Awake_Handler &func, void *&data) { int ret = 0; Fl::system_driver()->lock_ring(); @@ -118,100 +167,177 @@ int Fl::get_awake_handler_(Fl_Awake_Handler &func, void *&data) } /** - Let the main thread know an update is pending and have it call a specific function. - Registers a function that will be - called by the main thread during the next message handling cycle. - Returns 0 if the callback function was registered, - and -1 if registration failed. Over a thousand awake callbacks can be - registered simultaneously. - - \see Fl::awake(void* message=0) -*/ -int Fl::awake(Fl_Awake_Handler func, void *data) { - int ret = add_awake_handler_(func, data); - Fl::awake(); - return ret; + \brief Checks if the awake ring buffer is empty. + \internal Used in the main event loop when an Awake message is received. + */ +bool Fl_System_Driver::awake_ring_empty() { + Fl::system_driver()->lock_ring(); + bool retval = (awake_ring_head_ == awake_ring_tail_); + Fl::system_driver()->unlock_ring(); + return retval; } -/** \fn int Fl::lock() - The lock() method blocks the current thread until it - can safely access FLTK widgets and data. Child threads should - call this method prior to updating any widgets or accessing - data. The main thread must call lock() to initialize - the threading support in FLTK. lock() will return non-zero - if threading is not available on the platform. +/** + \brief Notifies the main GUI thread from a worker thread. - Child threads must call unlock() when they are done - accessing FLTK. + In FLTK, worker threads can update the UI, but all UI changes must be wrapped + between Fl::lock() and Fl::unlock(). After calling Fl::unlock(), the worker + thread should call Fl::awake() to signal the main thread that + updates are pending. - When the wait() method is waiting - for input or timeouts, child threads are given access to FLTK. - Similarly, when the main thread needs to do processing, it will - wait until all child threads have called unlock() before processing - additional data. + \note Worker threads must not create, show, or hide windows. - \return 0 if threading is available on the platform; non-zero - otherwise. + \see \ref advanced_multithreading + \see Fl::awake(Fl_Awake_Handler, void*) + \see Fl::awake_once(Fl_Awake_Handler, void*) + */ +void Fl::awake() { + Fl::system_driver()->awake(nullptr); +} - See also: \ref advanced_multithreading -*/ -/** \fn void Fl::unlock() - The unlock() method releases the lock that was set - using the lock() method. Child - threads should call this method as soon as they are finished - accessing FLTK. +/** + \brief Awake the main GUI thread and leave a message pointer. - See also: \ref advanced_multithreading -*/ -/** \fn void Fl::awake(void* msg) - Sends a message pointer to the main thread, - causing any pending Fl::wait() call to - terminate so that the main thread can retrieve the message and any pending - redraws can be processed. - - Multiple calls to Fl::awake() will queue multiple pointers - for the main thread to process, up to a system-defined (typically several - thousand) depth. The default message handler saves the last message which - can be accessed using the - Fl::thread_message() function. - - In the context of a threaded application, a call to Fl::awake() with no - argument will trigger event loop handling in the main thread. Since - it is not possible to call Fl::flush() from a subsidiary thread, - Fl::awake() is the best (and only, really) substitute. - - It's \e not necessary to wrap calls to any form of Fl::awake() by Fl::lock() and Fl::unlock(). - Nevertheless, the early, single call to Fl::lock() used to initialize threading support is necessary. - - Function Fl::awake() in all its forms is typically called by worker threads, but it can be used safely - by the main thread too, as a means to break the event loop. - - \see \ref advanced_multithreading -*/ + \deprecated Use Fl::awake() or Fl::awake(Fl_Awake_Handler, void*) instead. + + This method is deprecated. The API can not ensure that Fl::thread_message() + returns the messages sent by Fl::awake(void *v) complete and in the correct + order. + Use Fl::awake() instead if you do not need to send a specific message. + Use Fl::awake(Fl_Awake_Handler, void*) or Fl::awake_once(Fl_Awake_Handler, void*) + if you need to send a message to the main thread and ensure that all messages + are processed in the order they were sent. + + \see \ref advanced_multithreading + \see Fl::awake() + \see Fl::awake(Fl_Awake_Handler, void*) + \see Fl::awake_once(Fl_Awake_Handler, void*) +*/ void Fl::awake(void *v) { Fl::system_driver()->awake(v); } +/** + \brief Schedules a callback to be executed by the main thread, then wakes up the main thread. + + This function lets a worker thread request that a specific callback function + be run by the main thread, passing optional user data. The callback will be + executed during the main thread's next event handling cycle. + + The queue holding the list of handlers is limited to 1024 entries. + If the queue is full, the function will return -1 and the callback will not be + scheduled. However the main thread will still be woken up to process any + other pending events. + + \note If user_data points to dynamically allocated memory, it is the + responsibility of the caller to ensure that the memory is valid until the + callback is executed. The callback will be executed during the main thread's + next event handling cycle, but depending on the sytems load, this may take + several seconds. + + \return 0 if the callback was successfully scheduled + \return -1 if the queue is full. + + \see Fl::awake() + \see Fl::awake_once(Fl_Awake_Handler, void*) + \see \ref advanced_multithreading +*/ +int Fl::awake(Fl_Awake_Handler handler, void *user_data) { + int ret = Fl_System_Driver::push_awake_handler(handler, user_data, false); + Fl::awake(); + return ret; +} + +/** + \brief Schedules a callback to be executed once by the main thread, then wakes up the main thread. + + This function lets a worker thread request that a specific callback function + be run by the main thread, passing optional user data. If a callback with the + same user_data is already scheduled, the previous entry will be removed and + the new entry will be appended to the list. + + \return 0 if the callback was successfully scheduled + \return -1 if the queue is full. + + \see Fl::awake() + \see Fl::awake(Fl_Awake_Handler, void*) + \see \ref advanced_multithreading +*/ +int Fl::awake_once(Fl_Awake_Handler handler, void *user_data) { + // TODO: remove any previous entry with the same handler and user_data + int ret = Fl_System_Driver::push_awake_handler(handler, user_data, true); + Fl::awake(); + return ret; +} + +/** + \brief Returns the last message sent by a child thread. + + \deprecated Use Fl::awake(Fl_Awake_Handler, void*) or + Fl::awake_once(Fl_Awake_Handler, void*) instead. + + The thread_message() method returns the last message + that was sent from a child by the Fl::awake(void*) method. + + This method is deprecated. The API can not ensure that Fl::thread_message() + returns the messages sent by Fl::awake(void *v) complete and in the correct + order. + + \see \ref advanced_multithreading + \see Fl::awake() + \see Fl::awake(Fl_Awake_Handler, void*) + \see Fl::awake_once(Fl_Awake_Handler, void*) +*/ void* Fl::thread_message() { return Fl::system_driver()->thread_message(); } + +/** + \brief Acquire the global UI lock for FLTK. + + The lock() method blocks the current thread until it + can safely access FLTK widgets and data. Child threads should + call this method prior to updating any widgets or accessing + data. The main thread must call Fl::lock() once before any windows are shown + to initialize the threading support in FLTK. The initial Fl::lock() call + will return non-zero if threading is not available on the platform. + + Child threads enclose calls to FLTK functions between Fl::lock() and + Fl::unlock() accessing FLTK. When a child thread has finshed accessing FLTK + and wants the main thread to update the UI, it should call Fl::awake(). + + Child threads can never create, show, or hide windows. + + When the wait() method is waiting + for input or timeouts, child threads are given access to FLTK. + Similarly, when the main thread needs to do processing, it will + wait until all child threads have called unlock() before processing + additional data. + + \return 0 if threading is available on the platform; non-zero + otherwise. + + \see \ref advanced_multithreading + \see Fl::lock() + \see Fl::awake() +*/ int Fl::lock() { return Fl::system_driver()->lock(); } -void Fl::unlock() { - Fl::system_driver()->unlock(); -} +/** + \brief Release the global UI lock set by Fl::lock(). -#ifndef FL_DOXYGEN + The unlock() method releases the lock that was set using the lock() method. + Child threads should call this method as soon as they are finished + accessing FLTK. -bool Fl_System_Driver::awake_ring_empty() { - Fl::system_driver()->lock_ring(); - bool retval = (Fl::awake_ring_head_ == Fl::awake_ring_tail_); - Fl::system_driver()->unlock_ring(); - return retval; + \see \ref advanced_multithreading + \see Fl::lock() +*/ +void Fl::unlock() { + Fl::system_driver()->unlock(); } -#endif // FL_DOXYGEN diff --git a/src/Fl_win32.cxx b/src/Fl_win32.cxx index 84cb6649d..a1a9ae39c 100644 --- a/src/Fl_win32.cxx +++ b/src/Fl_win32.cxx @@ -350,7 +350,7 @@ MSG fl_msg; static void process_awake_handler_requests(void) { Fl_Awake_Handler func; void *data; - while (Fl::get_awake_handler_(func, data) == 0) { + while (Fl::pop_awake_handler(func, data) == 0) { func(data); } } diff --git a/src/drivers/Posix/Fl_Posix_System_Driver.cxx b/src/drivers/Posix/Fl_Posix_System_Driver.cxx index c4f2dc63c..4b8a14d79 100644 --- a/src/drivers/Posix/Fl_Posix_System_Driver.cxx +++ b/src/drivers/Posix/Fl_Posix_System_Driver.cxx @@ -387,7 +387,7 @@ static void thread_awake_cb(int fd, void*) { } Fl_Awake_Handler func; void *data; - while (Fl::get_awake_handler_(func, data)==0) { + while (Fl_System_Driver::pop_awake_handler(func, data)==0) { (*func)(data); } } |
