DEV Community

Gary Kramlich
Gary Kramlich

Posted on

From Sub-Systems to Managers

This article was originally posted on Patreon and has been brought over here to get all of the Pidgin Development/History posts into one single place.

In Purple 2 and earlier we had this concept of "sub-systems" which were responsible for object type signals and basic collection APIs. For example, the accounts sub-system in purple 2 keeps track of all accounts. It was also responsible for registering signals for accounts being enabled and disabled among others. While these sub-systems worked, they didn't come without issues.

The collection aspects of the sub-systems worked on file level global variables which made unit testing basically impossible as there was no easy way to reset everything to a known state. Also the "objects" in the collections were just pointers to allocated C structures, so there was no reference counting, and well you had to be very careful.

In Purple 3 we're moving all of these objects to GObjects. That means they have reference counting, properties, can possibly be sub-classed, among other things. But this actually leads to a problem with the old sub-systems and all the code that uses it.

As mentioned earlier, the managers keep a collection of objects. In Purple 2 things were wired up in a way so that when an account was created the subsystem already knew about it. This is very different in Purple 3 with the managers. It is now the responsibility of the creator of the object, to add the object to the manager.

All managers have add/remove or register/unregister functions to add the object to the manager. The naming difference is still up for debate and we may make them all consistent in the future. The managers will then also emit signals when an object is added/registered or removed/unregistered. For iteration, we originally went with a foreach function for each manager, but those are slow being replaced as we're implementing the GListModel interface in the managers instead.

With GObjects, signals are instance based, which means you connect to the signal for each instance. In Purple 2 it was type/class based because we want to know when any account is enabled, not just this one specific account. That's where the managers come in. Aside from the collection API of the managers, they also propagate signals from each item in their collection. This allows us to get the Purple 2 behavior while using GObjects.

Let's look at an example in both Purple 2 and Purple 3. This example is stripped down, but in the load function for a plugin we're going to connect to the account-enabled signal and output the account username via purple_debug_misc

Below is the Purple 2 example. In the plugin_load function, we get a handle to the accounts subsystem and connect to the account-enabled and account-disabled signals. In the callback handler, we log the user name and whether or not the account is enabled.

static void
enabled_cb(PurpleAccount *account, gpointer data) {
    purple_debug_misc("example", "account %s enabled:%d\n",
                      purple_account_get_username(account),
                      purple_account_get_enabled(account));
}

static gboolean
plugin_load(PurplePlugin *plugin) {
    gpointer handle= purple_accounts_get_handle();
    purple_signal_connect(handle, "account-enabled", plugin,
                          PURPLE_CALLBACK(enabled_cb),  NULL); 
    purple_signal_connect(handle, "account-disabled", plugin,
                          PURPLE_CALLBACK(enabled_cb),  NULL);

    return TRUE;
}
Enter fullscreen mode Exit fullscreen mode

In Purple 3 this looks a bit different because of GObjects and PurpleAccountManager, but the idea is fundamentally the same. In the plugin_load function we use the account-changed signal with a detail of enabled. The account-changed signal propagates the notify signal from the contained PurpleAccount objects, and since the enabled property is a registered property on a PurpleAccount, that means the notify signal will be emitted when the property is changed, which PurpleAccountManager will propagate for every PurpleAccount that it knows about!

static void
enabled_cb(GObject *obj, PurpleAccount *account,
           GParamSpec *pspec, gpointer data)
{
    PurpleContactInfo *info = PURPLE_CONTACT_INFO(account);

    purple_debug_misc("example", "account %s enabled:%d",
                      purple_contact_info_get_username(info),
                      purple_account_get_enabled(account));
}

static gboolean
plugin_load(GPluginPlugin *plugin, GError **error) {
    PurpleAccountManager *manager = NULL;
    manager = purple_account_manager_get_default();
    g_signal_connect(manager, "account-changed::enabled",
                     G_CALLBACK(enabled_cb), NULL);
    return TRUE;
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the Purple 3 approach is going to be a lot more familiar to other GLib/GObject developers and it also completely eliminates our need for our own signaling system.

The other benefit of the manager instances is that we can now create a brand new instance of them in each unit test which means we can actually start writing unit test for all of these objects! And that's great news for everyone!!

I hope you're enjoying these posts! Remember they go live for patron's at 9AM CST on Mondays and go public at 12AM CST on Thursdays! If there's something specific you'd like to see me cover here, please comment below!

Top comments (0)