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:
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()
and setFoo()
instead of a .foo
getter/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 for Box2DClass.bar()
)
Pros:
MyBox2DClass
object to an internal method that takes a Box2DClass
and you know it'll workCons:
setPos()
or SetPosition()
?). 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. MyPhysicalClass
will have a property b2Body
). You're free to implement your own interface as you wish, and only what's necessary.
Pros:
b2Body
propertyCons:
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:
Empty
instance; a Class that just extends Object
and implements an empty getPostion()
function. This will be our benchmarkb2Body
instanceBox2DExtends
instance; a Class that extends b2Body
and implements a function getPosition()
that just returns GetPosition()
(the b2Body
function)Box2DExtendsOverrides
instance; a Class that extends b2Body
and overrides the GetPosition()
function (it simply returns super.GetPosition()
)Box2DComposition
instance; a Class that has a b2Body
property and a getPosition()
function that returns the b2Body's
GetPosition()
Box2DExtendsProperty
instance; a Class that extends b2Body
and adds a new Point
propertyBox2DCompositionProperty
instance; a Class that has both a b2Body
property and a Point
propertyAll 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 also Objects
will just result in a 4 byte
increase 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.sampler
package. If we sample the creation of our objects, and add up all the sizes in the NewObjectSample
objects, we'll get the full size of our object (NOTE: if you want to see what's created and the size, comment in the log
calls in the test file).
These sizes are all in bytes. Some points worth noting:
Object
size is 40
bytes, so just the class and nothing else is 16
bytes.20
bytes for Box2DComposition
come from 16
for the class and 4
for the pointer to the b2Body
propertyBox2DExtendsProperty
etc, you have 16
for the Point
class itself, 4
for the pointer to the Point
property, and 8
for each of the x
and y
property Numbers
= 36
bytes difference between that and Box2DExtends
So 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 of 10000
, itself looped 10
(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 of 1000000
, itself looped 10
(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 their getPosition()/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.000007ms
per call). The delay seems entirely down to the class inheritence - e.g. Box2DExtendsOverrides
simply calls super.GetPosition()
, yet is twice as slow as Box2DExtendsProperty
, which inherits GetPosition()
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 swfdump
in 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:
getlocal 4
callproperty :GetPosition (0)
coerce Box2D.Common.Math:b2Vec2
setlocal3
whilst var v:b2Vec2 = Box2DExtends.getPosition()
(getPosition()
returns GetPosition()
) is:
getlocal 5
callproperty :getPosition (0)
coerce Box2D.Common.Math:b2Vec2
setlocal3
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With