Organizing code around multiple APIs

Started by
6 comments, last by ryt 5 years, 6 months ago

I created a simple project/game around Allegro. The project is tightly bound to the API, including rendering, transforms, audio and other. I would like to port it to SFML but without loosing all of the work I already have done. I thought to create some wrapper around them so the game doesn't even know which one I'm using and I also wanted to be able to switch between them with few lines of code.

Has anyone done something similar and maybe has some recommendations how to do it?

Advertisement

My Gincu game engine supports both SFML and Allegro, you may be inspired from it. The game application doesn't know the underlying engine (SFML or Allego), so it's quite what you want.

https://www.kbasm.com -- My personal website

https://github.com/wqking/eventpp  eventpp -- C++ library for event dispatcher and callback list

https://github.com/cpgf/cpgf  cpgf library -- free C++ open source library for reflection, serialization, script binding, callbacks, and meta data for OpenGL Box2D, SFML and Irrlicht.

2 hours ago, ryt said:

I created a simple project/game around Allegro. The project is tightly bound to the API, including rendering, transforms, audio and other. I would like to port it to SFML but without loosing all of the work I already have done. I thought to create some wrapper around them so the game doesn't even know which one I'm using and I also wanted to be able to switch between them with few lines of code.

Has anyone done something similar and maybe has some recommendations how to do it?

Assuming you're doing it in proper OO, there are design patterns such as Adaptor and Facade which you might want to read about.

In my experience, backfitting a decoupling layer is usually a significantly larger PITA than doing things with that in mind from the beginning.

You might want to start by analysing which functionaliy you use from the library and how (at it's most basic, just make a list of objects used and methods called), which is boring from the point of view of a coder but does end up paying itself by sparing you some back-and-forth time wasting that happens when one dives into coding too early and then suddenly discovers something that screws up one's approach.

At the same time, you might want to set up some kind of unit testing around the whole thing so that you can test the before and after refactoring results and make sure they're the same.

Also, COMMIT EVERYTHING into your source control before you start refactoring! (probably needs not be said, but hey: no harm done)

Beware of implicit and non-visible dependencies, such as things like how NULLs are handled in one library vs the other, expectations in your code of getting back empty strings or empty arrays signifying NOT FOUND and now getting back nulls (or vice-versa), how objects are supposed to be initialized or not and other such expectations in the code which are not visible from simply the method signatures.

Keep in mind that if your code is tightly coupled to the library, this might be a much vaster task than you expect. That analysis job at the beginning will give you a much better grasp on the size of the task and let you see if for some parts it's less work to redo them from scratch than refactor them.

As a general principle, creating a "decoupling layer" should not consist of adding new modules, but of tidying up old code, defining interfaces and enforcing boundaries between the part of your game that depends on Allegro or SFML and the part that doesn't. Different approaches can be appropriate for different needs, for example rendering graphics by decreasing order of library dependent code size:

  • A rather monolithic library dependent "renderer" module that uses Allegro or SFML to render a snapshot of game state. This game state representation is the interface between the core of your engine and the library dependent presentation layer.
  • With the same kind of library-independent game state, a somewhat library independent renderer module that does something important on its own (culling, sorting, interfacing with event handling, etc.) and calls lower level library dependent services (draw a sprite, draw a 3D mesh, draw a GUI widget...) with carefully defined abstraction over library APIs.
  • Using OpenGL, Vulkan or Direct3D: Allegro or SFML window creation yields a bare window handle from which you create the appropriate contexts and GPU resources, then you avoid calling Allegro or SFML drawing commands at all (possibly rewriting something to use the generic 3D API instead of more convenient library functions).

Omae Wa Mou Shindeiru

I tried using Adapter and Facade patterns. I built new classes in my code to support that so now I have initial code for Allegro and Sfml.
With Allegro I'm using SimpleMath:Vector2 as it has more functions than Allegro counter part. Actually I'm not really sure Allegro has such thing.
Now that I have Sfml too, I wanted to use it's sf::Vector2f but I don't want to create another facade around these two types of vectors. Also in a lot of places I use SimpleMath::Vector2 that I just don't want to find and replace with sf::Vector2f.

What would be your advice on this? Generally I would like more to use sf::Vector2f as it's more integrated to Sfml than SimpleMath::Vector2 is in Allegro. Also Sfml has Thor math library that adds additional functionality.

1 hour ago, ryt said:

With Allegro I'm using SimpleMath:Vector2 as it has more functions than Allegro counter part. Actually I'm not really sure Allegro has such thing.
Now that I have Sfml too, I wanted to use it's sf::Vector2f but I don't want to create another facade around these two types of vectors. Also in a lot of places I use SimpleMath::Vector2 that I just don't want to find and replace with sf::Vector2f.

Remove these classes from the interface between the Allegro-dependent or SFML-dependent layer and the library-independent core of your game engine.

What library independent data structures (such as std::pair or std::array of the appropriate numerical type, or two loose numbers) can you initialize the Allegro and SFML 2D vectors from? Can you solve the problem more radically by completely purging these classes?

Taking a step back, why do you need to support both Allegro and SFML? There seems to be significant overlap in features between the two libraries, you should probably use only one of them. If you want to port your game engine because you think SFML is better or more suitable than Allegro, just leave the Allegro-based game engine alone and write a SFML-based proof of concept: if the experiment proves to be the way forward you won't use Allegro any more, and if SFML disappoints you you can go back to the original engine (still working as before, and unaffected by heavy refactorings) with no need to make compromises to support SFML.

Omae Wa Mou Shindeiru

6 hours ago, LorenzoGatti said:

Taking a step back, why do you need to support both Allegro and SFML? There seems to be significant overlap in features between the two libraries, you should probably use only one of them.

Yes it is. I decided to create a dummy vector class for interaction between engine parts. This vector is not dependent neither on SimpleMath::Vector2 nor sf::Vector2f. For the game part where code is more performance oriented I decided to use only sf::Vector2f as it's better integrated with Sfml and Thor.
If I decide that sf::Vector2f is not good enough I'll just switch this part without changing the engine.

This topic is closed to new replies.

Advertisement