I have to display data, received from a USB device, every 5ms.
I use a LevelBar and a Label widget to display the received value.
Everything seems to be running perfectly fine, but after 10 minutes or so, I realize that there is buffering meaning that the data displayed is not realtime, even if I plug-out the USB, some data keeps being displayed.
I’m supposed to make it real-time. So I was wondering if there’s a way to increase the main loop iteration speed.
If you need real-time, then don’t do the acquisition in the mainloop, but from a separate thread.
Just keep in mind that Gtk is not thread-safe. You can use g_idle_add from the acquisition thread to notify the mainloop about new received data then update the display.
I actually do the data acquisition in another thread and notify the mainloop from there. Not a GTK expert but from what I understand, notifications are queueing up and even if no data is currently being received, because of the un-processed notifications in the queue, widget value keeps being updated.
Instead of a mutex and direct update, you probably want to use an asynchronous queue: the worker thread pushes data on the queue, and the UI thread pops it.
GLib has GAsyncQueue for this task; the C++ bindings should expose something similar.
I didn’t understand how asynchronous queue increases performance. When you’re popping from the queue, isn’t it the same thing as reading the variable once you have the mutex.
While typing the above sentence I realized that the GUI thread would be waiting the worker thread to write the data to variable, and release the mutex. With an async queue, they both would keep working without stopping each other from what I understand.
Everything seems to be running perfectly fine, but after 10 minutes or so, I realize that there is buffering meaning that the data displayed is not realtime, even if I plug-out the USB, some data keeps being displayed.
If you have to display real-time data, you can avoid queuing data altogether. Just enable a VSync-locked animation on the widget and show the very latest data retrieved by the USB acquisition thread.
That can be done with Gtk.Widget.add_tick_callback. Note: if you have multiple widgets that should update in real time, simply add the tick callback to one of them. Then update all the widgets from your tick callback.
/* This data structure contains the latest retrieved data
* from the USB device. It's written from the USB thread
* and read from UI thread. Concurrent access is protected
* by shared_data_mutex.
*/
struct {
int foo;
int bar;
} shared_data;
GMutex shared_data_mutex;
/* The GTK widget showing real time USB data */
GtkWidget *usb_widget;
guint usb_widget_tick_id;
/* thread: UI
*
* Runs at each VSync
*/
static gboolean
usb_widget_tick_callback (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer user_data)
{
/* Update the UI with whatever you have in shared_data.
* In this case USB widget is a simple label */
g_mutex_lock (&shared_data_mutex);
char *text = g_strdup_printf ("foo: %d, bar: %d",
shared_data.foo,
shared_data.bar);
gtk_label_set_text (GTK_LABEL (usb_widget), text);
g_ free (text);
g_mutex_unlock (&shared_data_mutex);
return G_SOURCE_CONTINUE;
}
/* thread: UI */
static void
usb_widget_tick_callback_start (gpointer user_data)
{
usb_widget_tick_id = gtk_widget_add_tick_callback (usb_widget, usb_widget_tick_callback, NULL, NULL);
}
/* thread: UI */
static void
usb_widget_tick_callback_stop (gpointer user_data)
{
gtk_widget_remove_tick_callback (usb_widget, usb_widget_tick_id);
usb_widget_tick_id = 0;
}
/* thread: USB */
static void
usb_acquisition_starting (void)
{
/* Start an animation on the USB widget */
g_idle_add_once (usb_widget_tick_callback_start, NULL);
}
/* thread: USB */
static void
usb_acquisition_have_new_data (const USBData *data)
{
/* Update latest data for UI thread */
g_mutex_lock (&shared_data_mutex);
shared_data.foo = data->foo;
shared_data.bar = data->bar;
g_mutex_unlock (&shared_data_mutex);
}
/* thread: USB */
static void
usb_acquisition_ending (void)
{
/* Stop the animation on the USB widget */
g_idle_add_once (usb_widget_tick_callback_stop, NULL);
}
static void
app_activate (GtkApplication *app)
{
GtkWidget *window = gtk_application_window_new (app);
usb_widget = gtk_label_new (NULL);
gtk_window_set_child (GTK_WINDOW (window), usb_widget);
gtk_window_present (GTK_WINDOW (window));
/* start USB thread with callbacks for notification
* when data acquisition starts, ends, new data has
* arrived */
start_usb_thread (...);
}
int main (int argc, char *argv[]) {
GtkApplication *app = gtk_application_new ("com.example.GtkApplication",
G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
return g_application_run (G_APPLICATION (app), argc, argv);
}
I think this is the most suitable answer in my case because I’m trying to get as real-time as possible, and my 5ms period can possibly go lower in the future.
Thank you so much for your informative post and the sample code!