About this blog
A journey through the thoughts of a practical C++ library developer.
Entries in this blog
Huge update concerning the inner workings of the sound managers I mentioned in my last update here, particularly for those interested in making their own personal implementations.
Overview for those unbriefed:
The purpose of the sound manager types, as explained in my previous post, is to provide a free and portable encapsulation of the concept of the play and management of 3d sounds and listeners in a C++ environment. The end result is a system for quickly and safely setting up 3d sound on a target platform without having to directly work with non-portable code. The idea is obviously nothing new to the world of programming. This particular application of abstraction is the basis for the 3d portion the FSOUND API of fmod, as well as OpenAL, only I'm taking it to an even higher level of abstraction. More low-level concepts are abstracted away, and, as well, initialization and destruction of objects such as sound generators are handled automatically through C++ objects, leaving little room for programmer error (since it's a C++ API, not a C portable API such as those I just mentioned). Since the abstraction is a layer above that of most other sound APIs, the implementation of a conforming sound manager can internally use DirectMusic and DirectSound, or OpenAL, or fmod, without impacting the standard external interface of the type. The most notable high-level feature in my proposed standard that is not a part of OpenAL or fmod include automatic management of sound generators based on user-defined priority types and comparison predicates.
Anyway, for the passed couple of weeks, up until a few hours ago, I have been working with this idea directly as stated, towards an end result where I would have a group of types and templates that would be implementation specific, yet all share a common interface, such that you download an OpenAL implementation if you are working on linux or possibly the DirectSound and DirectMusic implementation if you were running windows. Each implementation just goes into the same namespace with the same type and template names.
The problem, as I've realized for some time yet just accepted for some reason, is that this limits you to having one implementation installed on your system at any given time (unless you just change your include directory settings depending on the implementation you wish to use). The more I thought about it, the more I realized that this is totally unacceptable. It is very conceivable that one might want to have different implementations installed, and going further, one might even want to use multiple implementations in the same application, similar to having an engine which can optionally render with Direct3D or OpenGL.
So, I decided to change the concept from a set of absolute types and templates in a common namespace with a standardized interface, to a pure abstraction concept, where not even the location of the object is standardized; they just must conform to the interface. The types and templates can exist anywhere and have any name. This way, multiple implementations can exist on the same computer. No revolutionary idea there, just a change in my approach.
However, I also realized that the addition of a priority concept as well as the standard way I provide of handling the insertion of new generators in a saturated manager, could be completely and portably implemented on top of a much more simple pure abstraction than what I described earlier. Since all of the high-level functionality can be implemented via the interface of the provided abstraction, default implementations of the high-level priority and basic sound manager concepts can exist for any system by just being templated with one parameter representing the type which conforms to the abstraction to be used for implementation!
This has some very powerful results. Now, implementing different versions of the sound managers is even more simple than I had planned. All the implementor must do is provide a very basic low-level abstraction and now those types can be directly used by the high-level templates without any changes. Further, if the internal API already allows things such as priorities, like fmod, there is nothing stopping someone from partially specializing the encapsulator template to take advantage of it (though in the case of fmod only integral priorities in ascending order are supported whereas in my implementation both the priority type as well as the comparison predicate are template parameters, so it would only be a partial specialization for integral types where the Predicate defines an ascending priority organization).
Not only this, but that also brings back the fact that the overall types used are in the same exact namespace regardless of implementation, though now without conflict since you use the encapsulator types instead of the implementation-specific types. So now you have a common set of templates which exist in a standard namespace, have a standard interface, and even have a portable default implementation based off of a given pure abstraction that you can use portably with a provided underlying implementation (of which I personally will be providing a DirectMusic and DirectSound implementation as well as an OpenAL implementation)!
So, as an example:
// Implementation whose interface
// conforms to the standardized
// simple, low-level pure abstraction.
namespace audio_library // I haven't decided on a namespace name yet
// Default implementation uses
// the provided pure abstraction.
// This version doesn't use priorities.
template , typename PriorityType = unsigned int
, typename PriorityPredicate = ::std::less
// Default implementation uses
// the provided pure abstraction.
// This version uses priorities.
Aside from this, I have also developed what I feel is the best way to handle extensions for the managers. This is, again, nothing revolutionary, though I feel it is important that I standardize the concept for the project.
Since different audio APIs provide different ways of dealing with certain concepts which can't necessarily be universally translated between, such as environmental effects and lower-level audio effects, it becomes necessary to expose those interfaces in the high-level encapsolator. The problem is, since I made the high-level encapsulator have a default implementation that I want people to be able to use even if their implementation has extensions, how do I create a way of exposing all of those extensions through the encapsolator types?
Here is the solution I came up with:
For the case of functions which have a high-level implementation that internally must call the lower-level version as a part of the process (IE initialization), there is an extra type at the end of the function parameter list which is declared in the low-level implementation type (I described this in my previous post). The argument must have a default value to conform to the standard. If someone needs to pass implementation-specific initialization information (such as a window handle), then they just explicitly pass the parameter. Again, this is simple and nothing special, but succeeds at providing a way to expose internal initialization without changing the implementation of the high-level encapsulator template.
In cases where there is no high-level function that corresponds to low-level extensions, such as specific effects applied to sounds, an "extension" member function template is used. This function is an instantiation of a template whose sole template parameter is a tag type which specifies the extension being used. If the extension is supported then the function is defined, otherwise, the user will get a compiler error (which is good). Right now, I am leaning towards using SFINAE to allow the functions to actually take the amount of arguments required for the extension, however, since lots of compilers do not fully support the proper use of SFINAE, I may end up having the function always take one parameter and rely on explicit specialization instead.
So, for example, to use an I3DL2 extension for environmental effects on a sound generator with a compiler that has fully compliant SFINAE support, you'd simply do something like:
The corresponding code for a non-SFINAE compliant compiler would just pass a structure with all of the parameters.
What's good about this design is that since it uses a compile-time tagging system and since the varying implementations are known at compile-time, you will get an error at compile time if the extension you are attempting to use is not supported rather than finding out at runtime.
That's it for now. Any ideas are welcome and feedback is appreciated.
Don't stop reading yet -- while the first paragraph of this post is my obligatory praise of Gamedev.net, I am also foreshadowing a future article I plan on writing which will probably be of use to many of you out there -- particularly those who work with COM (IE DirectX) in C++, and some bonus information about a library I'm working on to make 3D audio simple to work with and manage in a complex simulation. So keep reading!
Ahh, my first developer journal post. Fun stuff, eh? I love the way Gamedev is headed with their GDNet+ membership and everything it provides. I will be using this journal for talking about any projects I am currently working on, and will also be using it to get feedback on where people wish me to direct my attention in future projects. I am very open to suggestions and am willing to develop just about any library that would be useful for many types of projects that plenty of developers can benefit from (within reason -- IE I'm not going to program the next Unreal engine... and if I did, I wouldn't upload it here ;).
Anyway, my first order of duty here is to provide the community with an article that I personally feel is almost a necessity to anyone planning on or currently working with DirectX, or any COM library for that matter, in C++. The planned title is something along the lines of "Why ATL's CComPtr is helpful and why you should never use it," which may give you a general idea of the direction I'll be taking.
With the article, I will be talking about why smart pointers are extremely useful when working with COM and why Microsoft's ATL COM smart pointer templates get it wrong in some areas from a C++ programmer's perspective. In addition, I will provide a suggested coded solution for what ATL attempts to provide, along with C++ wrapper functions and objects for Microsoft's procedural COM library featuring exception throwing instead of HRESULT returns and an easier method for dealing with factories. As well, I will also provide exception safe copyable objects for dealing with COM construction and destruction which allow you to easily determine if the COM settings in your current thread are acceptible for your application (which normally can be quite a hassle).
So, hopefully I'll be able to bring you some info that you may not have known about COM, or perhaps knew yet didn't give much thought as to how it should impact your development.
For those who are interested, be warned that I use a lot of advanced C++ template features, particularly a reliance on the SFINAE principle in conjunction with partial specialization. While everything I do is 100% standard C++, some compilers may have trouble with them. My current target for these libraries is Microsoft Visual C++ .NET 2003 as most people who work with COM are working on a Microsoft compiler to begin with, coupled with the fact that their template support is very compliant (for instance, I know that if I port the code to be used with GCC I will have to make some work-arounds for G++'s partially broken SFINAE implementation).
So, anyone who is interested in this project, please let me know. I'm happy to answer any questions you may have and would be willing to take suggestions both for the planned article as well as ideas for the project and even future projects.
My next project (which is already pretty far along in development) is a complete 3D sound manager built around DirectMusic providing facilities for loading sounds and creating generators positioned and oriented in 3D. Supported features include everything that DirectMusic can provide through DirectSound, particularly environmental effects, doppler effects, manual pitch and volume adjustments on both a persound or pergenerator basis, etc. The more abstract feature, and potentially the most useful, is the ability to set the maximum amount of active generators available as well as the ability to create any amount of generators you wish coupled with user-defined priorities (the type of which you can specify through a template parameter making customizability to your application a snap). The manager will automatically move your extra generators into and out of the active generator container based on what priority they have, so you can have lots of objects in a level and have a very simple way of ensuring that the most important sounds (usually the closest sounds or explosions, etc) always get heard without having to manually deal with the intricacies of managing activation and deactivation of generators. The order of an operation which switches the priorities of an already created generator is proportional to log(n), where n is the amount of different priority values that are currently being used. For speed purposes, there will also be a form without priorities that is slightly more optimized than having a manager with priorities where all the priorities are the same. Of course, the latter's uses are much less general than the former. Due to the nature of this project, I'll probably upload it along with documentation, but not make an article about it (unless, of course, I get enough positive feedback to warrant one).
Also, just so I can see if people (and which people) actually read this, please leave a comment of some sort -- even if it's just to say hi. Thanks.