summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Fl_System_Driver.H14
-rw-r--r--src/Fl_lock.cxx302
-rw-r--r--src/Fl_win32.cxx2
-rw-r--r--src/drivers/Posix/Fl_Posix_System_Driver.cxx2
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);
}
}