hiding a library's dependencies from users of the library

Started by
8 comments, last by Kylotan 7 years, 6 months ago
I've begun working on the C++ 2D game framework I am talking about in this thread from a few weeks ago, and have run into a design issue for which there are a number of solutions. I'm seeking feedback on which solution would be best.
The issue is What is the best way to hide the dependencies of my library from the users of my library? -- in particular its usage of SFML as a hardware abstraction layer.
First of all why do I want to hide my framework's usage of SFML? The short answer is I want to provide the best API I can for building 2D games, given the mission statement / intent / philosophy of my framework , and I view the hardware abstraction layer, and other libraries, I use to do so as an implementation detail not a part of the framework. That is, in the future if I decided to switch to SDL or write my own hardware abstraction layer there is no reason doing so should break user code.
Say for example I want to have a myFramework::Sprite that has a has-a relationship with an Sfml::Sprite but don't want the users of the framework to have to link to SFML binaries or even know that SFML is involved in any way. Options I see:

  1. I could just use the Pimpl idiom. However, I see problems with use of vanilla Pimpl as it is standardly defined because I want users of my library to be able to inherit from myFramework::Sprite et al to make their-game specific classes but C++ does not allow inheritance from objects only inheritance from classes. If a myFramework::Sprite is exposed to the user via a creation function that creates a private implementation of a public myFramework::ISprite interface (or whatever) there would be no direct way for the user to inherit from/ implement ISprite while still getting the default myFramework::Sprite behavior. They would have to implement UserSprite that inherits from ISprite but also has a member variable UserSprite::impl that is created via the Sprite creation function my library exposes and then would have to forward calls to UserSprite to UserSprite::impl as appropriare. This just seems to push too much complexity on to the user for my tastes.
  2. Just forward declare sfml::sprite in myFramework_Sprite.h, have each myFramework::Sprite have a std::unique_ptr<sfml::Sprite> in the header, and include and use it in myFramework_Sprite.cpp. Sfml leaks out in this case as a name in a header. Also not totally sure this would even work ... can you forward declare a namespace marked-up name? (anybody?)
  3. Same as 2. but just make myFramework::Sprite::impl be a void* and then cast to sfml::Sprite* in the cpp file. This is untidy, un-C++ like e.g. would not be able to use an std::unique_ptr<T> (right?), would have to do a cast plus delete in the destructor.
  4. Have myFramework::Sprite implement an abstract interface, say, ISprite but also have an "impl" member variable of type unique_ptr<ISprite> and then in the CPP file implement a private class wrapper around sfml::Sprite and forward calls to it in the implementation of myFramework::Sprite. Essentially this is doing work similar to 1. but hiding it from the user. This is what I am currently doing it my codebase, it works but is verbose ... and after a while it just struck me that when it comes down to it this is essentially the same thing as 3., the void* solution, but with some interfaces throw in to disguise that fact and make me feel better about myself
  5. Make myFramework::Sprite have a private impl member variable of type boost::any, or my only implementation of a boost::any analog.

The other option is throw out one of the premises that led to the line of thought in 1. to 5. above.

6. Use vanilla Pimpl but don't allow, expect, or require users of the framework to inherit from framework classes. Require users to do all callbacks, extensions, and customization of framework objects via composition. I think this would work but it's hard to get my head around. Say you have a UserSprite that has-a framework sprite and you register the framework sprite to receive generic gameloop updates and register it to recieve mouse input updates. If the form of the mouse input callbacks and the generic gameloop updates are std::function<void(myFramwork::Sprite&, ...)> or whatever in that callback there would be no trivial or beginner-friendly way for the callback to retrieve the UserSprite given the myFramwork::Sprite at call time. One could do it by capturing the std::shared_ptr<UserSprite> in lambdas installed in the mouse callback hooks and the generic gameloop hooks, which I think would work, just seems ... i don't know ... confusing?

Thoughts appreciated...

Advertisement

The short answer is I want to provide the best API I can for building 2D games, given the mission statement/intent/p[hilosophy of my framework

This seems a good idea, but imho it does not imply you should go out of your way to hide something you have built on.

If you indeed reach your goal, any sane user will not even consider stepping down to the hardware abstraction layer, as they will miss all the great stuff in your framework. That would mean you're fighting a non-existent problem currently.

Another consideration is whether your choice of SFML will work for everybody. Maybe I have weird hardware, and it horribly fails with SFML, while it works with SDL, or so. Opening up the hardware abstraction layer gives users a choice what to use. Then your implementation is the default one, if you want a different solution, here is the API, have fun!

A final option is to explicitly state that you may change certain parts of the framework in the documentation, with a warning that using these features may cause the game to become incompatible with a future version of the frame work. That may not stop some people, but at least, you've warned them.

The solution highly depends on what do you want to allow users to do with that interface.

There are two options: Let user code use the underlying hardware abstraction layer (SFML) so that they can use all its features. You said you do not want this, so you probably want to go for the second option - let users do only things supported by your interface api.

This interface could work with images or with meshes. Working with images is easier for users of your library, meshes give them more capabilities. If you work with images, then user code will send data describing source filenames of images, their positions and other transformations and modifiers (uses alpha, is clipping/stencil mask). When working with meshes, user code will provide data about positions of vertices, vertex indices that form meshes, materials, shaders etc. It is more low level, it is better for 3D with higher visual quality and it in fact mirrors the approach of Opengl 4 and direct3d 11.

I would suggest to not be afraid to use some idioms from data driven programming. What I mean is that you should not have a class or interface with method 'render' and then traverse scene tree and let everyone render itself directly. You shoul rather have a class that could be called RenderDataSystem. This would store all the data needed for render which I described in previous paragraph (set of images and their positions or set of vertices, meshes, materials and shaders). You will have one instance of this class and objects in scene tree (for example myFramework::Sprite) will just modify data in this class. Then you can have a RenderImpl class. You can have more of these and choose different ones on different platforms. This RenderImpl class will once per frame read data from RenderDataSystem and will draw them to screen.

If you do it like this, then myFramework::Sprite will at most contain pointer to RenderDataSystem and all code that uses SFML will be in RenderImpl class. Users or the library will have no need to modify the RenderImpl implementation unless they know that they are doing something naughty :-).

Sort of tangential to the topic of API design, but one thing to note is that you may be required by the license of your dependencies to advertise that they are used in your framework.

This doesn't appear to be the case with SFML, but there are some common licenses you may encounter that have this requirement.

Eric Richards

SlimDX tutorials - http://www.richardssoftware.net/

Twitter - @EricRichards22

I'm not big on hiding this sort of thing from your customers. The code aspects aren't so great, but where you are really letting yourself in for a world of pain, is in needing to provide perfectly working development/release toolchains for every platform under the sun (i.e. you need to provide out-of-box toolchains for Android and iOS, since your customers can't get at the dependencies to do it themselves).

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

I'm not big on hiding this sort of thing from your customers. The code aspects aren't so great, but where you are really letting yourself in for a world of pain, is in needing to provide perfectly working development/release toolchains for every platform under the sun (i.e. you need to provide out-of-box toolchains for Android and iOS, since your customers can't get at the dependencies to do it themselves).

Hadnt really thought about this in that am only going to support Win32 and iOS out of the box because that is what I need personally and it doesnt seem complicated in these two cases (famous last words, maybe). But as you say Linux and Android might be a can of worms ... I don't know a lot about either platform, in terms of game programming anyway in the case of linux.
However to me, implications on the complexity of implementation aside, this is what I personally want out of some I kinds of frameworks: I want someone else to get this kind of stuff right for me. I was planning on having a Python script that will generate a solution/project files etc for you when you start a new project with this game framework. That script will be parametrized in part on platform(s) you want to support. It would just copy in the framework binaries and set up the solution/projects to statically link to them.

If however you want access to my usage of SFML and OpenAL and libJpeg and zlib etc. you can always pull all the code from github and build that way, changing whatever you like. That would just not be the default usage pattern. My general thought is your average user doesnt care that I am using SFML texture wrappers in there any more than he or she cares that I am using dropbox's Json11 to parse spritesheet metadata Json -- so why have a design that makes those headers and those binaries public to average users? if it is difficult to do this then so be it and maybe I will have to change my decision here for pragmatic reasons but I still think it is a laudable goal.

Don't hide dependencies. The user of your library will need to know ALL the dependencies when it comes time to deploy!

It is fine in your to not provide access to contexts and the like that you manage for any interaction with libraries you use but you should be up front with what is used

I could just use the Pimpl idiom. However, I see problems with use of vanilla Pimpl as it is standardly defined because I want users of my library to be able to inherit from myFramework::Sprite et al to make their-game specific classes but C++ does not allow inheritance from objects only inheritance from classes.


Sounds like you (or I!) have misunderstood how to implement the Pimpl idiom. I don't see any reason why they can't inherit from your public class given that it would be responsible for creating the private class (eg. in the constructor) and it's the private class that deals with SFML. All the issues regarding inheritance and ensuring behaviour is correct are separate from how the behaviour is eventually implemented.

I could just use the Pimpl idiom. However, I see problems with use of vanilla Pimpl as it is standardly defined because I want users of my library to be able to inherit from myFramework::Sprite et al to make their-game specific classes but C++ does not allow inheritance from objects only inheritance from classes.


Sounds like you (or I!) have misunderstood how to implement the Pimpl idiom. I don't see any reason why they can't inherit from your public class given that it would be responsible for creating the private class (eg. in the constructor) and it's the private class that deals with SFML. All the issues regarding inheritance and ensuring behaviour is correct are separate from how the behaviour is eventually implemented.

Yeah, I've looked into it and have come to the conclusion that my usage of the term "pimpl" is wrong or nonstandard. I thought it generally meant when you expose publically only an abstract interface and a public static member function that creates an instance of an object that implements the interface using a private class often defined completely in a cpp file. The factory function creates one of those thingies and returns that but as a pointer to the interface. This is a way a lot of algorithms are implemented in OpenCV and extensions people write to OpenCV for example (which I use a lot at work). I got it in my head that this is what people mean by "pimpl" but according to wikipedia pimpl is close to what I describe in 2. or 3. in the original post.

That sounds more like the Abstract Factory pattern. Obviously there are overlaps and similarities since they're mostly aimed at the same goal, i.e. separating out the interface that gets used from the classes that implement that interface.

This topic is closed to new replies.

Advertisement