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
Enregistrer un commentaire