Reputation: 39456
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.
Upvotes: 8
Views: 656
Reputation: 8033
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.
Upvotes: 6
Reputation: 23652
Some great answers here but I'm going to throw my two cents in.
There are two different concepts you have to recognize: when you extend a class and when you implement a class.
Here is an example of extending MovieClip
public class TrickedOutClip extends MovieClip {
private var rims = 'extra large'
public function TrickedOutClip() {
super();
}
}
Here is an example of implementing MovieClip
public class pimpMyClip {
private var rims = 'extra large';
private var pimpedMovieClip:MovieClip;
public function pimpMyClip() {
pimpedMovieClip = new MovieClip();
pimpedMovieClip.bling = rims;
}
public function getPimpedClip() {
return pimpedMovieClip;
}
}
I think you probably do not want to extend these box2D classes but implement them. Here's a rough outline:
public class myBox2DHelper {
private var box2d = new Box2D(...);
public function MyBox2DHelper(stage) {
}
public function makeBox2DDoSomeTrickyThing(varA:String, varB:Number) {
// write your custom code here
}
public function makeBox2DDoSomethingElse(varC:MovieClip) {
// write your custom code here
}
}
Good luck.
Upvotes: 0
Reputation: 13529
I don't think that extending will impact the performance a lot. Yes, there is some cost, but it's not so high as long as you don't use composition. I.e. instead of extending Box2d classes directly, you create an instance of that classes and work with it inside your class. For example this
public class Child extends b2Body {
public function Child() {
// do some stuff here
}
}
instead of this
public class Child {
private var _body:b2Body;
public function Child() {
...
_body = _world.CreateBody(...);
...
}
}
I guess you know that as less objects as you create the better. As long as you keep the number of created instances you will have same performance.
From another point of view: a) adding one more layer of abstractions may change the Box2d a lot. If you work in a team this may be an issue, because the other developers should learn your naming b) be careful about Middle Man code smell. Usually when you start wrapping already existing functionality you end up with classes which are just delegators.
Upvotes: 0
Reputation: 7193
I don't know if there is a big impact for instanciation time, but I will answer your question differently: what are your other options? Do they seem they'll do better?
There is a beautiful benchmark made by Jackson Dunstan about function performance: http://jacksondunstan.com/articles/1820
To sum it up:
So, if you want to not use inheritance, maybe you'll have to replace it with static calls, and it is bad for performance. Personally, I'll extend those classes and add an eager instanciation of all objects I'll need at runtime: if it is big, make a beautiful loading screen...
Also, take a look at post bytecode optimizations such as apparat: http://code.google.com/p/apparat/
Upvotes: 0
Reputation: 5068
I know this answer will not qualify for the bounty as I am way to lazy to write benchmarks. But having worked on the Flash code base I can maybe give some hints:
The avm2 is a dynamic language, so the compiler will not optimize anything in this case. Wrapping a call as a sub class call will have a cost. However that cost will be constant time and small.
Object creation cost will also at most be affected by a constant amount of time and memory. Also the time and amount will probably be insignificant compared to the base cost.
But, as with many things the devil is in the details. I never used box2d, but if it does any kind of object pooling things might not work well anymore. In general games should try to run without object allocations at play time. So be very careful not to add functions that allocate objects just to be prettier.
function addvectors(a:vec,b:vec,dest:vec):void
Might be ugly but is much faster than
function addvectors(a:vec,b:vec):vec
(I hope I got my AS3 syntax right...). Even more useful and more ugly might be
function addvectors(a:Vector.<vec>, b:Vector.<vec>, dest:Vector.<vec>, offset:int, count:int):void
So my answer is, if you are only wrapping for readability, go for it. It's a small, but constant cost. But be very, very careful to change how functions work.
Upvotes: 1