In my game engine I use Box2D for physics. Box2D's naming conventions and poor commenting ruin the consistent and well documented remainder of my engine which is a little frustrating and presents poorly when you're using it.
I've considered making a set of wrapper classes for Box2D. That is, classes which extend each of the common Box2D objects and have their functions rewritten to follow the naming conventions of the rest of my engine, and to have them more clearly and consistently commented. I have even considered building ontop of some of the classes and adding some bits and pieces (like getters for pixel-based measurements in the b2Vec2 class).
This is fine but I am not 100% sure what the negative impacts of this would be and the degree to which those would affect my applications and games. I'm not sure if the compiler alleviates some of my concerns to a degree or whether I do need to be considerate when adding somewhat unnecessary classes for the sake of readability and consistency.
I have some suspicions:
- More memory consumption to accommodate the extra level of class structure.
- Performance impact when creating new objects due to initializing an extra level of members?
I am asking specifically about runtime impacts.
This is a pretty common problem when it comes to integrating third party libraries, especially libraries that are ports (as Box2DAS3 is), where they keep the coding and naming conventions of the parent language rather than fully integrating with the destination language (case in point: Box2DAS3 using
getFoo()andsetFoo()instead of a.foogetter/setter).To answer your question quickly, no, there will be no significant performance impact with making wrapper classes; no more than you'll see in the class hierarchy in your own project. Sure, if you time a loop of 5 million iterations, you might see a millisecond or two of difference, but in normal usage, you won't notice it.
"More memory consumption to accommodate the extra level of class structure." Like any language that has class inheritence, a vtable will be used behind the scenes, so you will have a small increase in memory/perf, but it's negligible.
"Performance impact when creating new objects due to initializing an extra level of members?" No more than normal instantiation, so not something to worry about unless you're creating a huge amount of objects.
Performance wise, you should generally have no problem (favour readability and usability over performance unless you actually have a problem with it), but I'd look at it more as an architectural problem and, with that in mind, what I would consider to be a negative impact of extending/modifying classes of an external library generally fall into 3 areas, depending on what you want to do:
Modify the libary
As Box2DAS3 is open source, there's nothing stopping you jumping in and refactoring all the class/function names to your hearts content. I've seriously considered doing this at times.
Pros:
Cons:
Extend the classes
Here, you simply make your own wrapper classes, which extend the base Box2D classes. You can add properties and functions as you want, including implementing your own naming scheme which translates to the base class (e.g.
MyBox2DClass.foo()could simply be a wrapper forBox2DClass.bar())Pros:
MyBox2DClassobject to an internal method that takes aBox2DClassand you know it'll workCons:
setPos()orSetPosition()?). Even if you're working on your own, when you come back to your class in 6 months, you'll have forgottensetPos()) while others use that of Box2D (SetPosition()))Composition with your own classes
You make your own classes, which internally hold a Box2D property (e.g.
MyPhysicalClasswill have a propertyb2Body). You're free to implement your own interface as you wish, and only what's necessary.Pros:
b2BodypropertyCons:
Out of the three, I prefer to go with composition, as it gives the most flexibility and keeps the modular nature of your engine intact, i.e. you have your core engine classes, and you extend functionality with external libraries. The fact that you can switch out libraries with minimal effort is a huge plus as well. This is the technique that I've employed in my own engine, and I've also extended it to other types of libraries - e.g. Ads - I have my engine Ad class, that can integrate with Mochi, Kongregate, etc as needed - the rest of my game doesn't care what I'm using, which lets me keep my coding style and consistency throughout the engine, whilst still being flexible and modular.
----- Update 20/9/2013 -----
Big update time! So I went back to do some testing on size and speed. The class I used is too big to paste here, so you can download it at http://divillysausages.com/files/TestExtendClass.as
In it, I test a number of classes:
Emptyinstance; a Class that just extendsObjectand implements an emptygetPostion()function. This will be our benchmarkb2BodyinstanceBox2DExtendsinstance; a Class that extendsb2Bodyand implements a functiongetPosition()that just returnsGetPosition()(theb2Bodyfunction)Box2DExtendsOverridesinstance; a Class that extendsb2Bodyand overrides theGetPosition()function (it simply returnssuper.GetPosition())Box2DCompositioninstance; a Class that has ab2Bodyproperty and agetPosition()function that returns theb2Body'sGetPosition()Box2DExtendsPropertyinstance; a Class that extendsb2Bodyand adds a newPointpropertyBox2DCompositionPropertyinstance; a Class that has both ab2Bodyproperty and aPointpropertyAll tests were done in the standalone player, FP v11.7.700.224, Windows 7, on a not-great laptop.
Test1: Size
AS3 is a bit annoying in that if you call
getSize(), it'll give you the size of the object itself, but any internal properties that are alsoObjectswill just result in a4 byteincrease as they're only counting the pointer. I can see why they do this, it just makes it a bit awkward to get the right size.Thus I turned to the
flash.samplerpackage. If we sample the creation of our objects, and add up all the sizes in theNewObjectSampleobjects, we'll get the full size of our object (NOTE: if you want to see what's created and the size, comment in thelogcalls in the test file).These sizes are all in bytes. Some points worth noting:
Objectsize is40bytes, so just the class and nothing else is16bytes.20bytes forBox2DCompositioncome from16for the class and4for the pointer to theb2BodypropertyBox2DExtendsPropertyetc, you have16for thePointclass itself,4for the pointer to thePointproperty, and8for each of thexandypropertyNumbers=36bytes difference between that andBox2DExtendsSo obviously the difference in size depends on the properties that you add, but all in all, pretty negligible.
Test 2: Creation Speed
For this, I simply used
getTimer(), with a loop of10000, itself looped10(so 100k) times to get the average.System.gc()was called between each set to minimise time due to garbage collection.There's not a whole pile to note here. The extending/composition classes take slightly longer, but it's like
0.000007ms(this is the creation time for 100,000 objects), so it's not really worth considering.Test 3: Call Speed
For this, I used
getTimer()again, with a loop of1000000, itself looped10(so 10m) times to get the average.System.gc()was called between each set to minimise time due to garbage collection. All the objects had theirgetPosition()/GetPosition()functions called, to see the difference between overriding and redirecting.This one surprised me a bit, with the difference between the times being ~2x (though that's still
0.000007msper call). The delay seems entirely down to the class inheritence - e.g.Box2DExtendsOverridessimply callssuper.GetPosition(), yet is twice as slow asBox2DExtendsProperty, which inheritsGetPosition()from its base class.I guess it has to do with the overhead of function lookups and calling, though I took a look at the generated bytecode using
swfdumpin the FlexSDK, and they're identical, so either it's lying to me (or doesn't include it), or there's something I'm missing :) While the steps might be the same, the time between them probably isn't (e.g. in memory, it's jumping to your class vtable, then jumping to the base class vtable, etc)The bytecode for
var v:b2Vec2 = b2Body.GetPosition()is simply:whilst
var v:b2Vec2 = Box2DExtends.getPosition()(getPosition()returnsGetPosition()) is:For the second example, it doesn't show the call to
GetPosition(), so I'm not sure how they're resolving that. The test file is available for download if someone wants to take a crack at explaining it.Some points to keep in mind:
GetPosition()doesn't really do anything; it's essentially a getter disguised as a function, which is one reason why the "extra class step penalty" appears so bigAll-in-all, I'd expect the same results from extending one of my own classes, so I wouldn't really worry about it. Implement the architecture that works the best for your solution.