From Sub-Systems to Managers
Tags: blog, modernization, pidgin3, repost
Published: 2023-04-23 14:00:00

> This article was originally posted on [Patreon](https://www.patreon.com/posts/from-sub-systems-75584736) 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 `GObject`s. 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 `GObject`s, 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 `GObject`s.

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.

```c
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;
}
```

In Purple 3 this looks a bit different because of `GObject`s 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!

```c
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;
}
```

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!