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:
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);
}
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);
}
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);
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] );
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);
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;
... TBD ...
Top comments (0)