Box2d 2.3.0 rev(249) Module - Goes Open Source

BlitzMax Forums/BlitzMax Programming/Box2d 2.3.0 rev(249) Module - Goes Open Source

Armitage 1982(Posted 2012) [#1]
Everything is explained on the main page here : http://code.google.com/p/arme-mod/

I'm trying to update the Box2D module of Brucey and while I'm currently able to run a few testbed examples I'm stuck when it comes to use BBRETAIN on the C++ side.

You could also easily say that's I'm a bit lost in the way Fixture and Shape must be Initialized and returned to BlitzMax.

So if any of you have good knowledge in C++ and wrapping libraries in general, I'd love to get some help on this.

There is really advantages to use the last version of Box2d (through I can live without it...) and since the project is Open Source maybe we could sort problems more fast this way.

Thanks ;)


Matt Merkulov(Posted 2012) [#2]
rev 249 doesn't compile!
compiler said there are errors in glue.cpp
:(


Matt Merkulov(Posted 2012) [#3]
solved
installed latest MinGW version and it compiled

Last edited 2012


Captain Wicker (crazy hillbilly)(Posted 2012) [#4]
@arm42:
What can it do?


Armitage 1982(Posted 2012) [#5]
Sorry for not answering before I didn't spot the topic ^^

Don't expect to use this wrap, it's not complete at all!
I was just searching a bit of help to fix a problem I had while trying to port the last version of the C++ Box2D library.

Since I didn't get any clues on this I just drop the idea for now...

Just stick to the old BaH.Box2D if you want to use box2d with BlitzMax.
Sorry for any inconveniences.

Last edited 2012


Armitage 1982(Posted 2012) [#6]
Again I spent the whole night trying to find my mistake.
I did not make it! pfff

There is really nobody who could give me a hand on this problem?


AdamRedwoods(Posted 2012) [#7]
hmm. it seems i cannot even compile it, so i'll have to upgrade mingw first.


Armitage 1982(Posted 2012) [#8]
That's strange...
Maybe it's due to the last Mingw update mark recommend while using one of the few last BlitzMax version.


col(Posted 2012) [#9]
Hiya,

Compiles here ok with
BMax 1.48 and MinGW gcc 4.6.2, Win7x64.


AdamRedwoods(Posted 2012) [#10]
yup, i'm having utf-8/bom problems. not sure how to strip this stuff out.
i'm on gcc 3.4.5, not sure if the later version overcome these issues, but i'll upgrade it anyways and try that.


Armitage 1982(Posted 2012) [#11]
Ok I removed the UTF-8 & BOM encoding and References (ë in my first name) and put everything in AINSI format. Should be working fine now, whatever your gcc version.

Last edited 2012


col(Posted 2012) [#12]
Hiya,

Opening up the discussion for bug hunting...

I get a crash when testing the VaryingRestitution example. When I click on a 'ball' I get an EAV at line 224 in box2d.bmx.

So inside box2d.bmx:-

Does Function b2Fixture._create(b2ObjectPtr:Byte Ptr) get called from inside c++, if so where from? I've looked through and can't find anything obvious, I see its called from BMax.

A bug is highlighted when bmx_b2fixture_getmaxfixture(b2ObjectPtr) is called. There are a few places that I can see in the c++ that use fixture->GetUserData, but nowhere that I see that sets that variable by using fixture->SetUserData anywhere. This is more than likely the cause of an always invalid address returned from bmx_b2fixture_getmaxfixture(b2ObjectPtr) at line 224.

Do you have any clues as to where/what/how should be setting this variable at all? I may be reading it wrong. Are you also meant set the ->UserData inside the Function _create:b2Fixture(b2ObjectPtr:Byte Ptr) where you also set fixture.b2ObjectPtr = b2ObjectPtr ??

You do get a valid Fixture returned from the C++, so that's working ok but it's not being tied to the BMax instance. ( Is this what you have the ->*UserData for? ) Then later when the code is using ->GetUserData then becomes a problem.

Last edited 2012


AdamRedwoods(Posted 2012) [#13]
hi. I'm up and its working.

So right away, i see:
box2d.bmx

Type b2Fixture

	Field b2ObjectPtr:Byte Ptr
	Field userData:Object

	Function _create:b2Fixture(b2ObjectPtr:Byte Ptr)
		If b2ObjectPtr Then
			Local fixture:b2Fixture = b2Fixture(bmx_b2fixture_getmaxfixture(b2ObjectPtr))
			If Not fixture Then
				fixture = New b2Fixture
				fixture.b2ObjectPtr = b2ObjectPtr
			Else
				If Not fixture.b2ObjectPtr Then
					fixture.b2ObjectPtr = b2ObjectPtr
				EndIf
			End If
			Return fixture
		End If
	End Function


So the way it works, is you need to pass around the Byte Ptr to the object you create in the glue.cpp-- but you cannot cast it in Bmax.

So this won't work (* it sometimes will, but sometimes crashes randomly so we cannot guarantee it):
Local fixture:b2Fixture = b2Fixture(bmx_b2fixture_getmaxfixture(b2ObjectPtr))


I know you're trying to use BBObject, but then yes, you're going to get into problems with BBRETAIN and other GC problems. To be honest, I don't know enough about BMax's GC to trick it.

So my strategy would be to keep these class objects on the cpp side-- but this is different how you approached this originally.

Notice Type "b2BodyDef" in box2d.bmx, it only stores the byteptr to the cpp side and some Object for data on our side.
When we create b2BodyDef, it calls the cpp function which returns a pointer ("new" in cpp returns a pointer). BMax will keep this pointer around in it's GC since we are using a byteptr field.

So now, everytime we want to make a change to the cpp class, we need to pass the pointer to our "glue" cpp code. we can hide this pointer passing in our Bmax Type. Example:
	Rem
	bbdoc: The world position of the body.
	about: Avoid creating bodies at the origin since this can lead to many overlapping shapes.
	End Rem
	Method SetPositionXY(x:Float, y:Float)
		bmx_b2bodydef_setpositionxy(b2ObjectPtr, x, y)
	End Method

You can see how the above works. The glue code manages the class object.


Does that make sense? i could show you how to work with b2Fixture, but i'd need to decipher how box2d works.

Last edited 2012


Armitage 1982(Posted 2012) [#14]
Indeed, I was trying to cast the byte Ptr like any other object and discover that you can't.
After that, I was puzzled and nowhere.

Given that the cpp pointer is linked to a BlitzMax counterpart object.
Most of the time I'm passing the pointer to the glue cpp code, but in the specific case of b2Fixture it's kinda complicated.
You can create a Fixture and return them from many ways.
The one who's crashing currently is returning fixture from picking thanks to a casting ray on shapes and AABB.
The thing is it's using a query Callback function that will return cpp Fixtures but obviously, not it's BlitzMax counterpart object.

That's why I'm using a bmx_b2fixture_getmaxfixture inside cpp to retrieve the BlitzMax object.
Basically each Box2d object can store userData and when creating a new Fixture, I store the BlitzMax Fixture Object inside that userData.
That way in the cpp glue code I'm able to retrieve the BlitzMax object (thanks to fixture->GetUserData();) linked to that cpp fixture.
On the other hand, to reproduce this functionnality, the UserData in BlitzMax object really store user data and absolutely no cpp object or pointer.

Unfortunately, cpp can only return BlitzMax object as BBObject (not as b2Fixture).
That's why I'm casting the returned BBObject into a BlitzMax object:

Local fixture:b2Fixture = b2Fixture(bmx_b2fixture_getmaxfixture(b2ObjectPtr))


I guess it's crashing because, for a reason or another, bmx_b2fixture_getmaxfixture() return something that is not NULL nor a BlitzMax b2Fixture (BBObject to be casted).
It can return NULL (or &bbNullObject under cpp) because fixture can implicitely be created (from body factories or fixture Definition).
When it's the case the BlitzMax version of that fixture was never created and so never set in the cpp userdata.
In that specific case, if fixture->GetUserData() return null, then a BlitzMax fixture is created and its b2ObjectPtr Field is populated with the cpp fixture pointer.
That way everything should be linked again and be usable under Max.

When the BlitzMax object is created inside the glue file it's calling _arm_box2d_b2Body_MaxCreateFixture() from BlitzMax.
I tried to remove that step and directly passing an empty BlitzMax Fixture object but it didn't solve the problem.
b2Fixture * bmx_b2body_createfixture(b2Body * body, BBObject * fixture, b2FixtureDef * def) {
	b2Fixture * CPPfixture = body->CreateFixture(def);
	CPPfixture->SetUserData(fixture);
	return CPPfixture;
}

It's like if CPPfixture->GetUserData() was returning something else !?


And, yeah... It's quite complicated :)
Plus the fact that you can't debug cpp code from Max doesn't help.


col(Posted 2012) [#15]
Well spotted Adam, I didn't even think you couldn't do that for some reason, but thinking about it, it does make a lot sense, how is BMax supposed to know.

Plus the fact that you can't debug cpp code from Max doesn't help.

You can kindof, by casting pointers etc and outputting values to the console. Granted its not as nice as stepping through with the debug controls but it can still be an incredible help, as you can cross reference the c++ to and from the BMax counterparts...

In the c++...

#include <iostream>
using namespace std; // So we don't have to keep writing std::cout, std::endl etc

at the top of box2d.cpp, then use

cout << variable << endl;

you can cast pointers to an int if needed...

reinterpret_cast<int>(pointer)

for example using this function in place of the one of the at line 918 in glue.cpp


in BMax for eg...

Import BRL.Retro
Local ObjectPtr:Byte Ptr
Local Obj:Object

WriteStdout Hex(Int Byte Ptr Obj)+"~n"
WriteStdout Hex(Int ObjectPtr)+"~n"


I'd make a complete debug output library/class/method to spit everything out to the console and compare against the values in BMax to make sure all values tie up.

On with the real issue...

I understand how and why you're tieing the BMax instance to the c++ one. One way I got around this exact problem in my uiribbon code was when a BMax object was created and tied to a c++ object, I would store the BMax instance in a TMap with using the c++ pointer address as the key ( converted the Byte Ptr to an Int then to a string to make the address an object for the TMap requirements ). Then when the c++ called back into BMax with just a pointer to an existing c++ object ( meaning I now need tie up with the specific instance of the BMax object thats tied to the c++ object instance ) I use the c++ pointer ( again converted to an Int then to a string ) as the key to search the TMap and retrieve the unique BMax instance. My scenario is in a GUI so speed, although critical, isn't as crucial as yours.

Last edited 2012


Armitage 1982(Posted 2012) [#16]
Clever idea Dave!

Yeah, maybe all the converting things as well as searching into a TMap isn't the fastest solution with a physics engine because of the frequent callback per collisions.

I know it's possible to do it that way since it's based on the work of Brucey who came with this idea. I must fall into one of those cases where the solution should be way much simplified.


col(Posted 2012) [#17]
hmm,

I've had a little glance at tracing some other issues, and I can see some problems with using the incorrect parameters in the functions that are called from the c++.

As a quick example - the b2QueryCallback. In the c++ ( MaxQueryCallback class ) you're calling the BMax function _arm_box2d_b2QueryCallback__ReportFixture, which is ok, and the first parameter is a handle to a BMax object which is ok again, but the fixture parameter is using a c++ instance of a b2fixture, however in the BMax function the second parameter is a BMax object.

There may be more occurrances of this, I haven't had time to look just yet.

I did some testing this morning with storing BMax Objects inside a class and passing them about in other classes etc while using BBRETAIN and BBRELEASE accordingly, passing them back and forth, and destroying them and it works ok, but it can so easily go wrong if you miss one little piece of the party. I'd say it's quite stable to do that as long as you keep track of it properly using a VERY ROBUST create/delete setup.

The problem I can see at the moment with some of the code is passing around the wrong parameters or wrong instances, using c++ instance instead of BMax instances and visa-versa.


col(Posted 2012) [#18]
...posted too soon... ;-)

Last edited 2012


col(Posted 2012) [#19]
Am i correct in this thinking...

Whenever you create a BMax instance of a Fixture you need to store that BMax instance in the c++ b2Fixture->UserData() because later c++ may call back with just the c++ instance and you need to retrieve the BMax instance from c++ one? Your current code seems to reflect this, but I could be wrong.

Now when you use body.createfixture(def), c++ creates a new fixture from the definition, which goes through the fixture creation method as above, but then the BMax instance is lost as it drops out of scope as nothing holds a reference to it, but it needs to be retained because the c++ body has a reference to the c++ instance of the fixture. So my thinking is where will you hold this reference to the BMax fixture object using BBRETAIN so that later you can BBRELEASE it? It looks as though you were going to store it the fixture->UserData() but isnt that where the BMax instance of a Fixture is meant to be?

Or am I way off altogether ? :-)

EDIT:- I've managed to get it working by BBRETAIN all of the b2Fixtures in c++, but I'm sure this isn't correct as the GC will be holding references when it shouldn't be, but it hightlights the issue above.

box2d.zip

EDIT2:- I really don't mean to dampen your spirits because this would be really cool and you've come sooo far already, but there are memory leaks when you create a new BMax instance from an existing c++ instance. Imagine the scenario as it is at the moment... Box2D wants to debugdraw the spheres so it passes some b2Vec2's into the BMax debugdraw function. You are correct in the BMax b2Vec2._create to assign the b2Vec2 instance to the BMax one except when you create a new BMax instance you are also automatically creating another new c++ b2Vec2 instance too ( in the New() method ). This isn't as bad as it sounds but if you are going to re-assign the c++ instance inside the BMax instance then the 'New'ly created instance will need to deleted in c++ to prevent a memory leak or use a completely different approach.



This will apply to ALL types that create instances in that way.

Last edited 2012


AdamRedwoods(Posted 2012) [#20]
Re: memory leaks

For every New() Method you use in bmax, make a Delete() Method, where you are deleting the cpp pointer. Delete is called when the GC collects.

Method Delete()
     bmx_b2vec2_delete(this.b2ObjectPtr)
EndMethod



Armitage 1982(Posted 2012) [#21]
Hi Dave,
Very interesting talk. I will have a look at the Query Callback to see if I'm correctly using the right pointer.

About the trouble with the UserData() to sum it all quickly I would say that the C++ object will store a BBRETAINed pointer of the BlitzMax object connected to it, and BlitzMax field b2ObjectPtr will store the C++ object pointer to successfully call C++ methods & others.

The UserData Field in BlitzMax (Box2d) objects will never store anything related to that module because it is used by the users to store anything he want.

When I want to create a fixture I think I'm doing such:
Calling a Box2d methods to create the fixture and callback a BlitzMax function with the C++ fixture pointer as a parameter.
The BlitzMax callback create a BlitzMax Fixture, store the C++ pointer into b2ObjectPtr for later use and return itself to C++.
Finally, BBRETAIN is used on the BlitzMax object and stored into the Box2d Fixture UserData.

@Adam
Hum... Delete() Method as well as bmx_XXXXXX_delete() C++ function already exist and are correctly executed.

I'm not sure any memory leaks is done currently but if it's the case I will correct this when everything will be fixed.


col(Posted 2012) [#22]
Hiya,

Let's use the VaryingRestitution example and hopefully I won't bake your noodle, but offer some insight into why this can't be simply thrown together. Specifically the case of the b2Body groundBody fixture ( Line 27 ), initially what you had was...

Local b2:b2fixture = groundBody._CreateFixture(groundShape, 0.0)

So stepping through this starting from the BMax side...
1. b2Body._CreateFixture calls into the c wrapper function 'bmx_b2body__createfixture'
2. 'bmx_b2body__createfixture' then calls the Box2D 'body->CreateFixture' which returns you a valid b2Fixture object in c++, which in turn is returned to BMax.
3. Now back in BMax, this c++ pointer is passed as the parameter 'b2ObjectPtr' in 'b2Fixture._create'.
4. If the b2ObjectPtr is a NON-NULL value, you then call back into c++ using 'bmx_b2fixture_getmaxfixture' to check if the c++ ->UserData() contains a BMax instance. If it does you return that instance, otherwise you return a valid Null BMax instance.
5. If a Null instance is returned you create a New BMax b2Fixture and store the c++ pointer as the b2ObjectPtr.
6. You then return the New BMax instance.

All is great except what's missing here is storing this new BMax instance into the C++ instance ->UserData(), so I wrote a simple c++ 1 liner 'bmx_b2Fixture_setmaxfixture(b2ObjectPtr,fixture)' call in c++ with the c++ instance pointer and the BMax object pointer and set the UserData() accordingly.

Now the 2 are tied together and the BMax b2Fixture is returned into VaryingRestitution.Create method as a Local b2Fixture object. Take note as this next bit is VERY important as to crashing! What happens when this .Create method exits... all Local variables that are not either stored somewhere else under any means such as a Field, Global or returned as a parameter etc will go out of scope reducing the GC reference count to zero and be inaccessible. I'm not exactly sure how BMax GC handles this internally, whether this object is immediately inaccessible or put up for collecting, but it doesn't matter as now these Local(s) should be thought of as they don't exist anymore anywhere. BUT we have a reference to them in the c++ instance which doesn't know that the BMax instances don't exist anymore. Now when the c++ calls back into BMax and there is a reference in the ->UserData(), you try to cast to a BMax object that doesn't actually exist anymore = Kaboom!! This is where you would use BBRETAIN to hold an extra reference count to the object which in turn keeps the BMax object alive and kicking ( c++ 'bmx_b2Fixture_setmaxfixture' function ). The Box2D manual says that these b2body->b2fixture(s) can be released via code, or when the b2body is destroyed, so at this stage I'd say it's safe to leave the BBRETAIN in that place. Which brings me to the next issue that NEEDS to be thought long and hard about how to handle it...

You have the necessary code in place for when the programmer decides to destroy the fixture from the body - Body->DestroyFixture(b2Fixture), but what will happen when the coder doesn't do that and the b2Body is destroyed... Box2d will internally delete the c++ fixtures tied to the body. But we need a way to BBRELEASE the BMax instances stored in the c++ b2Fixture->UserData, otherwise another memory leak will be caused because of the BMax GC holding references. A perfect place would be in the c++ b2Fixture destructor. Now... you could write your own c++ class that will inherit from or wrap the original c++ b2Fixture and that will be a partial re-write in itself ( using your own b2Fixture all over your wrapper, which in this case WILL be so much safer as you will have full functionality control during the class instance lifetime including control of any BMax references at any time, this is similar to how some of wxWidgets is wrapped ), or do a nasty, keep your original setup and modify the original Box2D b2Fixure and write your own c++ b2Fixture destructor ... your own morals will decide that one ;-) ,of course you may have a better idea too, but hopefully you can see the possible pitfall. This is only an issue for when c++ objects hold onto references to BMax objects.

This is an example of what I mean by building a VERY robust Create/Delete system. Imagine if you get to the end, find the code is leaking memory like a sieve and you have to re-write the whole thing from the ground up!! :-(
Nipping these things in the bud should be the first course of foundation layed down and makes everything else safely lay down on top without falling over.

I hope that didn't hurt too much, but only served as food for thought and motivation to carry on.

Edited for punctuality.

Last edited 2012


Armitage 1982(Posted 2012) [#23]
I effectively had a small pointer error in my _ReportFixture callback Query Method. But this didn't solve anything :(

So I made a last few changes
- Method _ReportFixture:Int(QueryCallback:b2QueryCallback, fixture:b2Fixture)
+ Method _ReportFixture:Int(QueryCallback:b2QueryCallback, fixture:Byte Ptr)


I think it would be much, much more easier to port my engine to LibGDX, a Java 2D Framework actively supported featuring everything I need including the last version of Box2D libs.

I also read that Unity3D will probably add support for Box2D.
A Farseer Asset is already available as well (while a bit too slow unfortunately).

Too bad having to change a whole system just for an outdated libs :-(


col(Posted 2012) [#24]
I effectively had a small pointer error in my _ReportFixture callback Query Method. But this didn't solve anything :(


Did you take a look at the changed BMax TFixture in Here ?

This works without an EAV by setting the c++ fixture->UserData() to the BMax instance. I also changed the _ReportFixture callback Query Method to a Function as Methods are unsafe as a callback. However it does have the underlying issues explained above. For testing *cough* *cough* I'd personally write a destructor in the Box2D b2Fixture class to call the BBRELEASE for a BMax instance in the c++ ->UserData variable ( at least to get it all stable then work on some 'coder etiquette' later before releasing the port ).

To be honest you're almost there with the Box2D port, it's just the b2Fixture and 1 ( maybe 2 ) other classes that need this extra work for them to be safe. The others are fine and just need the creases ironed out.


Armitage 1982(Posted 2012) [#25]
Oups... !

In fact, I've read your post but focus a lot more on the next one and totally forget to check that link.

I came to the same conclusion about the C->Max & Max->C object linkage and cut the same solution, but where my entire problem was situated was in that (BBObject*)fixture->GetUserData() missing C++ Casting.

Certainly a lack of practice with C++ again.


Now about the memory leak it's certainly true that we need to rewrite the b2Fixture Destructor to remove the BlitzMax Object linked to it. The idea is to avoid at all cost any changes in the Box2D Code so I would certainly go with the first solution you were talking about.

But giving the fact that I'm not experimented with C++ and that I can't debug any C++ code in my current environment I feel that the task would be too cumbersome.
Yeah, I know it's only a few lines of C++ to add to a few extended Classes but I was thinking about porting the whole engine and not just the most used part of it like the one from Brucey is currently doing. Along the road I've spotted so much new Classes that I don't even know how I could test and wrap them efficiently (and trust me there is a lot more work to get it done).

But I'm extremely thankful to you for making me understand and find my errors.

On a side note:
3 years ago I would have continue my work on this but today everything indicates that I need to move on to something widely used and supported.
Simply because I'm not a developer, I'm just trying to make games ! Too bad BRL and Mark didn't catch this and prefer to re-create Java with Monkey...
Hope this time they think about something like JNI wrapper for their Monkey.


Armitage 1982(Posted 2012) [#26]
Oh by the way Dave, I forgot about it but there is a functionality inside Box2d that allow us to control the implicitly destroyed Fixtures or joints.
This class is called b2DestructionListener and is described in Chapter 11 Loose Ends of the Box2D Manual.

I already implemented this Class for the BlitzMax counterpart and normally it's working. So any memory leak "should" be avoided as long as the C++ object storing anything related to BlitzMax feature a b2DestructionListener.
This is the case for body, joint and fixture. Definition object are not kept in the simulation and should be destroyed manually (or Garbage Collected).


col(Posted 2012) [#27]
So any memory leak "should" be avoided as long as the C++ object storing anything related to BlitzMax feature a b2DestructionListener.

Ahh, that's great and certainly takes the sting out of trying to manage the BMax objects from Box2D c++. So using the b2DestructionListener you can simply make a call back in to c++ to release the reference to ->UserData, nice one!