DEV Community

Unpublished Post. This URL is public but secret, so share at your own discretion.

Working with GLib and Raku

With the release of the GLib library, I felt it time to write a blog post describing just a small aspect of how it can be used.

First off, a bit of a description. GLib is the library at the base of ALL Gnome software, this includes GTK and all of the things built on top of that as well.

When you get into GLib programming, it's not to long before you become aware that GLib programming is somewhat different than most languages. Anything beyond the most basic of programs relies on a Source, or a Context, which is an asynchronous run loop that might interact with other sources or contexts. A basic GLib program might, logically, look like the following flowchart:

Image description

So the asynchronous nature of GLib may take a minute for some to get used to, but it's not that difficult. After all, we're going to eventually move into GUI programming, and all modern GUI programming is event-based.

However, I digress. ince we have to start somewhere, let's look at how GLib implements hash tables, something Raku users take for granted.

Consider the following program:

#include <stdio.h>
#include <glib.h>
#include <string.h>

GHashTable *mainHash;

char *names[] = {
    "Abigail",
    "Aaron",
    "Byron",
    "Brenda",
    "Chiffon",
    "Charles",
    "Willow",
    "Winston",
    "Xavier",
    "Xia",
    "Yvette",
    "Yves",
    "Zacary",
    "Zochelle"
};

char NAME[]  = "name";
char ORDER[] = "order";
char PROD[]  = "prod";

guint     iter  = 10;
GMainLoop *loop = NULL;

gboolean add_another_order (gpointer data)
{
  static char *producedby[] = { "C" };

  gint64     current_time = g_get_monotonic_time ();
  gchar      *dtkey       = g_strdup_printf("%li", current_time);
  GHashTable *new_order   = g_hash_table_new( g_str_hash, g_str_equal );
  int        nameIdx      = g_random_int_range( 0, G_N_ELEMENTS(names) );
  char       *name        = names[nameIdx];
  double     *val         = malloc(sizeof(gdouble *));

  *val = g_random_double_range(0, 10.0);

  g_hash_table_insert(new_order, NAME,  (void *)name  );
  g_hash_table_insert(new_order, ORDER, (void *)val   );
  g_hash_table_insert(new_order, PROD,  producedby[0] );

  g_hash_table_insert(mainHash, dtkey, (void *)new_order);

  printf("new order added: %s, %.2f, %s as %s\n",
        name, *val, producedby[0], dtkey);

  if (--iter) {
    return G_SOURCE_CONTINUE;
  } else {
    // cw: Print all of the items in the hash table and exit;
    int l = 0;
    int i;

    char **keys = (char **)g_hash_table_get_keys_as_array(mainHash, &l);

    for (i = 0; i < l; i++) {
        GHashTable *sh   = (GHashTable *)g_hash_table_lookup( mainHash, keys[i] );
        char       *name = (char *)g_hash_table_lookup(sh, NAME);
        gdouble    *o    = (double *)g_hash_table_lookup(sh, ORDER);
        char       *prod = (char *)g_hash_table_lookup(sh, PROD);

        printf("%s: name = %s, order = %.2f, produced by = %s\n",
            keys[i],
            name,
            o[0],
            prod
        );
    }

    // cw: Ends the program.
    g_main_loop_quit(loop);
    return G_SOURCE_REMOVE;
  }
}

void main (int argc, char **argv) {
  loop     = g_main_loop_new(NULL, FALSE);
  mainHash = g_hash_table_new( g_str_hash, g_str_equal );

  // cw: This is to remind new users of the asynchronous nature of
    //     GLib code
  g_timeout_add_seconds (2, add_another_order, NULL);

  g_main_loop_run (loop);
  g_main_loop_unref (loop);
}
Enter fullscreen mode Exit fullscreen mode

It's a bit long, so let's break it down into pieces. First, let's look at main():

#include <stdio.h>
#include <glib.h>
#include <string.h>

GHashTable *mainHash;

/* ... */

void main (int argc, char **argv) {
  loop     = g_main_loop_new(NULL, FALSE);
  mainHash = g_hash_table_new( g_str_hash, g_str_equal );

    // cw: This is to remind new users of the asynchronous nature of
    //     GLib code
  g_timeout_add_seconds (2, add_another_order, NULL);

  g_main_loop_run (loop);
  g_main_loop_unref (loop);
}
Enter fullscreen mode Exit fullscreen mode

This is equivalend to the left part of our diagram: the box that says "Create Main Context". Here we are creating the main context with the call to g_main_loop_new[1].

We then create our global GHash table with the call to g_hash_table_new.[2] The tricky part here are the parameters to that call. The g_str_hash and the g_str_equal tell the GHash table how to hash its keys. Long story short: we're creating a pointer-based hash table with strings as the keys. There are more helper functions for diffent types in the library, but let's start witth these, for now.

The main portion of the program is handled by a periodic event, which occurs every w seconds: add_another_order.[3] We then start the program with g_main_run and we use our context value as the parameter[4]. The program will then hold here. The call to g_main_loop_unref will not occur until the program is officially told to exit.

We can now move on to the main loop handler, or add_another_order. This function is equivalent to the right part of our diagram: the box that says "Context Does More Stuff".

The whole purpose of this routine is to add a Hash table containing a random name, value and production string, to our main hash table.

These lines produce our random values:

  GHashTable *new_order   = g_hash_table_new( g_str_hash, g_str_equal );
  int        nameIdx      = g_random_int_range( 0, G_N_ELEMENTS(names) );
  char       *name        = names[nameIdx];
  double     *val         = malloc(sizeof(gdouble *));

    *val = g_random_double_range(0, 10.0);
Enter fullscreen mode Exit fullscreen mode

And these lines, store the random data into our inner hash:

g_hash_table_insert(new_order, NAME,  (void *)name  );
g_hash_table_insert(new_order, ORDER, (void *)val   );
g_hash_table_insert(new_order, PROD,  producedby[0] );
Enter fullscreen mode Exit fullscreen mode

The new_order hash is now inserted into our mainHash using the stringified posix value of the current date time as the key:

  gint64     current_time = g_get_monotonic_time ();
  gchar      *dtkey       = g_strdup_printf("%li", current_time);

/* ... */
g_hash_table_insert(mainHash, dtkey, (void *)new_order);
Enter fullscreen mode Exit fullscreen mode

This loop is repeated 10 times, and then the output of the entire hash is displayed.

There are a number of use cases that can be realized by this patter, and they are left as an excercise for the user starting with the main one I had in mind when I wrote this code: that of a data producer storing its transaction *iyr inner hash) into a database (the outer one), using GHashTables as the storage mediums.

This is just a sample of the power of GHashTables, and they are used throughout the entirety of GNOME's libraries.

Now let's see what this code would look like in Raku using the GLib module to attain the same functionality.

use v6.c;

use GLib::HashTable;
use GLib::MainLoop;
use GLib::Timeout;

my @names = <
    Abigail Aaron   Byron  Brenda Chiffon Charles
    Willow  Winston Xavier Xia    Yvette  Yves
    Zacary  Zochell
>;

my $loop      = GLib::MainLoop.new;
my $mainTable = GLib::HashTable.new;
my $iter      = 10;

GLib::Timeout.add(2, -> *@ {
  unless ($iter--) {
    $mainTable.table.say;
    $loop.quit;
  }

  my $order = GLib::HashTable.new( dict => {
    name  => G_TYPE_STRING,
    order => G_TYPE_DOUBLE.
    prod  => G_TYPE_STRING
  );

  $order<name order prod> = ( @names.pick, 10.0.rand, 'C' );

  $mainTable{ DateTime.now.posix.Str } = $order;
});

$loop.run;
Enter fullscreen mode Exit fullscreen mode

... TBD ...

Top comments (0)