Accéder au contenu principal

retro-gtk: Renaissance

This is the second article in a small series about retro-gtk, I recommend you to read the first one, retro-gtk: Postmortem, before this one.

In the previous article I listed some problems I encountered while developing and using retro-gtk; in this one I will present some solutions I implemented to fix them! ☺ All that is presented in this article is part of the newly-released retro-gtk 0.13.1, which is the first version of the 0.14 development cycle.

Changing the Scope

The Libretro API is tricky: lots of little details need to be handled properly and it isn't always very clear how to do so. By mimicking this API, retro-gtk inherited its complexity, making it way more complex than it should be as there aren't many different ways for a Libretro frontend to handle the cores correctly. retro-gtk was forwarding the complexity of the Libretro API to its users rather than abstracting it.

About a year ago I decided to slowly change the scope of the library. In the previous article, I described retro-gtk as "a GObject-based plugin system based on the Libretro plugin definition", and this still holds true, what changed is how its users will handle the cores. By taking inspiration from how it was used by the GamesRetroRunner class of Games, I slowly moved retro-gtk away from a Libretro reimplementation for frontends and turned it into a Libretro frontend as a library, offering higher level building block and taking care of most of the complexity of the original API internally.

Do you remember the pseudo-code example from the first article, implementing a load_game() function? It was overly complicated compared to what we actually wanted to do, wasn't it? Well, here is how to implement it in C with the new simplified API.

void load_game (RetroCore           *core,
                const gchar * const *media_uris)
{
    GError *error = NULL;

    retro_core_set_medias (core, media_uris);
    retro_core_boot (core, &error);
    if (error != NULL) {
        g_debug ("Couldn't boot the Libretro core: %s", error->message);
        g_error_free (error);
    }
}

With the new API, even C code with GError handling is shorter than the previous pseudo-code example!

As you can see that's much simpler to use, most of the complexity is now handled internally by retro-gtk. Instead of having to use the many components inherited from the Libretro API, you now simply give the medias to the core prior to booting it, booting up the core will take care of its initialization and of loading the game. This also means that retro-gtk doesn't have to expose the game info or disk interface types, making the API smaller and hence simpler to understand.

Many other similar changes were implemented all around the API, way too many to list. Many features that were implemented as complex to use classes tied to RetroCore have been merged into it, removing lot's of the artificially introduced complexity in-between them.

A noteworthy improvement is the introduction of the RetroCoreView widget. Previously, the video output of the core was handled by RetroCairoDisplay, the audio output by RetroPaPlayer and widget specific input devices — forwarding keyboard and mouse inputs to the core or using the keyboard as a gamepad — were handled by input device objects taking a GtkWidget and listening to its events to implement a specific Libretro input device. It worked somewhat well but demanded lots of code to the user, and interaction between these objects was more complex than it should be.

RetroCoreView implement all these features in a single GTK+ widget with a simple API. There are two main functions to this widget. The first one is to allow you to set a core it should handle, it will display its video and play its audio without requiring you to take care of how to do so. The second one is to allow you to simply access the user inputs it receives by exposing it as controllers of the desired RetroControllerType.

Having all of this inside one widget avoid the user to deal with multiple layers of widgets and objects, rendering the video or capturing events. Handling the video output under the cover gives us more freedom on how to implement it. For example when a hardware accelerated renderer will be introduced, we should be able to change it without breaking the ABI and the users should automatically benefit from it, with no change to their codes or their binaries. This also allows to handle very easily interdependencies between the controllers and the outputs, for example a pointer controller is dependent on where the events are happening on the rendered video. All of this makes this widget simpler to use but also simpler to maintain as lot's of the code became way simpler in the transformation proccess.

For you curiosity, here is a slightly simplified version of RetroCoreView in the generated VAPI.

public class Retro.CoreView : Gtk.EventBox {
 public CoreView ();
 public Retro.Controller as_controller (Retro.ControllerType controller_type);
 public uint64 get_controller_capabilities ();
 public int16 get_input_state (Retro.ControllerType controller_type, uint index, uint id);
 public void set_core (Retro.Core? core);
 public void set_filter (Retro.VideoFilter filter);
 public bool can_grab_pointer { get; set; }
 public Gdk.Pixbuf pixbuf { get; set; }
 public bool snap_pointer_to_borders { get; set; }
}

There are a few things I'm not sure how to handle properly yet:

  • how to make it clear the keyboard can't be used at the same time as a keyboard and a gamepad;
  • how to make it clear the mouse can't be used at the same time as a mouse and a pointer, the can-grab-pointer property toggles it for the moment but I don't think the wording is right;
  • how to handle user-defined filters (GLSL shaders, etc.) once we will support them.

Porting retro-gtk to C

Porting retro-gtk to C comes with downsides:

  • the porting process is a lot of work, lots of working code will be rewritten in a lower level language which means that lots of code will introduce bugs and memory leaks;
  • C being harder to read and write than Vala, ported code will be harder to maintain and new code will be harder to write correctly;
  • the Vala API will need to be generated from C code instead of Vala code, making it harder to produce and less accurate.

But this port also comes with advantages:

  • C being the native language of the libraries used by retro-gtk, porting retro-gtk to C avoids to access them via language bindings;
  • C being the native language of the Libretro API, there is no need to abstract it in order to use it inside retro-gtk, no need to tweak the low-level memory access the API requires;
  • the complexity of binding Libretro to Vala is removed, which counterweights a bit the complexity of writing C;
  • it's easier to maintain a stable C API and ABI from C than from Vala;
  • it removes the compile-time warnings caused by the Vala-generated C code;
  • the code should be a tiny bit faster (so little I don't expect it to be noticeable) as variable and string duplications from the Vala-generated C code are removed;
  • the compilation is faster as the Vala to C step is removed;
  • the dependency on Vala is removed, even if the one on vapigen is maintained.

Now, retro-gtk is a C library, it uses GTK-Doc to support documentation and introspection, it is introspectable via GObject Introspection and is usable in Vala by compiling a VAPI from the GIR file.

Emergent Advantages

The combination of these two changes, offering a higher level API which doesn't expose too much of the inner working of the library and developing retro-gtk with the same language as its dependencies, allows more room for the devs to work inside the library and to move things around without hitting and breaking the API ceilling. To continue on the room analogy, writing the API directly instead of compiling it from Vala allows to perfectly control what is part of it and what isn't, there are less risks for unexpected cables to hang from the API ceilling, cables we could hit while working. All of this should allow us to have a more stable API.

With the ability to control the API, and now that I am somewhat happy about it I want to change the stability promise of retro-gtk a bit: we will keep refactoring some bits of the API during the 0.14 development cycle, but after that we will try to keep it somewhat stable. What it means is that if we break it, we will try to keep the break as small as possible and we will document and advertise this explicit API break. If this not totally unstable state doesn't frighten you, you can start using retro-gtk for your own software with 0.14!

To celebrate this important milestone in the life of retro-gtk, the library just got its first logo!

So… What Now‽ 😃

So far I explained what was retro-gtk and what were its major problems in the first article, how I solved some of them but more importantly how I prepared the terrain to solve bigger ones in this article… but what's coming next? Now that the library can be improved upon more freely, the next article will detail the plans for its evolution, introducing shiny new big features to make it rock-solid!

Commentaires

Posts les plus consultés de ce blog

GTK+ Apps on Phones

As some of you may already know, I recently joined Purism to help developing GTK+ apps for the upcoming Librem 5 phone . Purism and GNOME share a lot of ideas and values, so the GNOME HIG and GNOME apps are what we will focus on primarily: we will do all we can to not fork nor to reinvent the wheel but to help allowing existing GTK+ applications to work on phones. How Fit are Existing GTK+ Apps? Phones are very different from laptops and even tablets: their screen is very small and their main input method is a single thumb on a touchscreen. Luckily, many GNOME applications are touch-friendly and are fit for small screens. Many applications present you a tree of information you can browse and I see two main layouts used by for GNOME applications to let you navigate it. A first kind of layout is found in applications like Documents, I'll call it stack UI : it uses all the available space to display the collection of information sources (in that case, documents), clicking a...

libhandy 0.0.10

libhandy 0.0.10 just got released, and it comes with a few new adaptive widgets for your GTK app. You can get this new version here . The View Switcher GNOME applications typically use a GtkStackSwitcher to switch between their views. This design works fine on a desktop, but not so well on really narrow devices like mobile phones, so Tobias Bernard designed a more modern and adaptive replacement — now available in libhandy as the HdyViewSwitcher . In many ways, the HdyViewSwitcher functions very similarly to a GtkStackSwitcher : you assign it a GtkStack containing your application's pages, and it will display a row of side-by-side, homogeneously-sized buttons, each representing a page. It differs in that it can display both the title and the icon of your pages, and that the layout of the buttons automatically adapts to a narrower version, depending on the available width. We have also added a view switcher bar, designed to be used at he bottom of the window: HdyView...

My Name is Handy, Lib Handy

Libhandy 0.0.7 just got released! I didn't blog about this mobile and adaptive oriented GTK widget library since the release of its 0.0.4 version three months ago , so let's catch up on what has been added since. List Rows A common pattern in GNOME applications is lists , which are typically implemented via GtkListBox . More specific patterns arose, where rows have a title at the start, an optional subtitle below it, actions at the end and an icon or some other widget like a radio button as a prefix. These rows can also be expanded to reveal nested rows or anything else that fits the need. So far every application using these patterns implemented the rows by hand for each and every row. It made using these a bit cumbersome and it led to inconsistencies in sizing, even inside a single application. To make these patterns easier to use, we implemented HdyActionRow , HdyComboRow and HdyExpanderRow . HdyActionRow The action row is a simple and flexible row, it lets...