-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DestroyBody / recycled bodies #9
Comments
Thanks for investigating. Yes, I saw something like this myself. Definitely need to figure out the best fix for it, and document it. I expect your technique is equivalent to invoking every setter with I think Box2D APIs are only half of the story — we also need to use Emscripten APIs. we use Emscripten to allocate new Box2D objects on the WebAssembly heap, so we need to use Emscripten to de-allocate them too. Maybe something like this? world.DestroyBody(body)
box2D.destroy(body); |
Does not work, throws: "Cannot destroy object, did you create yourself?" So it needs to be destroyed with world.DestroyBody But good point about box2D.destroy and the link in general, I did not realize I even have to destroy all b2Vec2 after use, but it makes sense. But this made me thinking, and this is where it gets complicated: |
Short answer, yes. Everything needs to be destroyed by hand. For fixtures, every body has a DestroyFixture method. So,
Or they cause crashes after a while, if you continue to create and destroy bodies, or just make a new b2Vec2 in your mainloop. Gotta clean up my code .. |
Things are complicated with wasm. Cleanly removing unused objects (box2D.destroy) results in massive performance drops when a lot is going on. Puffering the garbage objects and cleaning them up later, results in crashes. Could be bugs in my code though, but hard to figure out. |
Every Box2D object created with
DestroyBody deletes every fixture in the body's fixture list, so I don't think
If As for body definitions… when I haven't been able to figure out how the bodies you received via |
Well, just modifying the demo code a bit, with adding a createdBefore property and destroying it and checking after creating, gives me the same result:
|
And regarding freeing memory and reusing: I am working on a general approach for this and stop using new directly with all Box2D wasm objects.
A new b2Vec2(3,4) Likewise with any other object. This does work for me so far, but is not at all completely tested (I do only use a subset of box2D, no joints for example). |
Ok, so I integrated this approach and it works (with a minor fix) . But I am now not sure, whether destroy really was causing performance to drop, because I had some bugs in my code, where objects where destroyed too soon. Quick compare with recycling vs. destroying now shows no drastic difference, but I am going to do some measured testing at some point. |
To reiterate that point: managing all box2D wasm objects with the intermediary helper functions create and make is extremely helpful in tracking down memory leaks. (code updated above) |
Thanks for the repro. Okay yeah, if you create an object without using There is also reuse on the JavaScript side. The /** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant)
@param {*=} __class__ */
function getCache(__class__) {
return (__class__ || WrapperObject).__cache__;
}
Module['getCache'] = getCache;
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant)
@param {*=} __class__ */
function wrapPointer(ptr, __class__) {
var cache = getCache(__class__);
var ret = cache[ptr];
if (ret) return ret;
ret = Object.create((__class__ || WrapperObject).prototype);
ret.ptr = ptr;
return cache[ptr] = ret;
}
Module['wrapPointer'] = wrapPointer;
b2World.prototype['CreateBody'] = b2World.prototype.CreateBody = /** @suppress {undefinedVars, duplicate} @this{Object} */function(def) {
var self = this.ptr;
if (def && typeof def === 'object') def = def.ptr;
return wrapPointer(_emscripten_bind_b2World_CreateBody_1(self, def), b2Body);
};; In your repro (create + destroy body, 30 times), I found that all 30 bodies that had the same pointer, I think it's best to assume "I don't own this JS object", and store your metadata elsewhere. Box2D actually has a concept of userData (see Instead, let's make our own metadata system. /** @type {Object.<number, unknown}} */
const metadata = {}
// make falling boxes
const boxCount = 30;
for (let i = 0; i < boxCount; i++) {
const bd = new b2BodyDef();
bd.set_type(b2_dynamicBody);
bd.set_position(ZERO);
const body = world.CreateBody(bd);
// this condition doesn't get hit any more!
if(body.createdBefore)
debugger
body.CreateFixture(i % 2 ? square : circle, 1);
initPosition(body, i);
// create metadata for this body
metadata[getPointer(body)] = {
createdBefore: true
}
// whenever we destroy a body, we should also destroy its metadata
world.DestroyBody(body)
delete metadata[getPointer(body)]
} You could even skip the /** @type {Object.<Box2D.b2Body, unknown}} */
const metadata = {}
metadata[body] = {
createdBefore: true
}
world.DestroyBody(body)
delete metadata[body] And you can even create objects that look like "the original b2Body, plus the metadata": /** @type {Object.<Box2D.b2Body, Box2D.b2Body}} */
const hydratedInstances = {}
hydratedInstances[body] = Object.setPrototypeOf({
createdBefore: true
}, body)
// you can retrieve metadata
console.log(hydratedInstances[body].createdBefore)
// you can retrieve data from the body itself too
console.log(hydratedInstances[body].GetMass())
world.DestroyBody(body)
delete hydratedInstances[body] I think this "storing metadata outside of the instance" eliminates a lot of the complexity compared to "cleaning up instances that we know are going to be reused". |
Recognising memory leaks in Box2D is difficult. If you This is not necessarily a memory leak; your next Side-note: I'm not super convinced that Emscripten's |
I've added some new docs to describe these: https://github.com/Birch-san/box2d-wasm/tree/master/docs#reference |
So far, I still use Metadata in my Box2D objects, but since I recycle and clean them by myself, I have no issues. I think I make a small example project with this approach and see if anyone finds it useful. It is nice to have most of the recycling stuff done automatically. Something else, I decided to also compile it from source. Mainly because I do need to to change max_translation in the end, and I see if I can change the original code - so I can change it while running with a helper method. So I would be gracious for a few hints on how to set up the emscripten toolchain. Also, where would be a better place to discuss this? |
it can be made to work (that's what the library I publish is built with it's very fiddly to set up. the source maps are constructed relative to the folder in which you compile them, but you're expected to serve the source code relative to (I think) where
we could try out the GitHub Discussions beta: |
I got very strange bugs, which I tracked down to the fact, that apparently Box2D in general or just this port via emscripten does recycle bodies. (but I never had this issue with the port from actionscript)
Meaning, unlike I thought
does not necessarily gives you a fresh new body.
If you removed bodies before with
world.DestroyBody(body)
and those bodies had some custom set properties, those old properties are still there, when objects gets recycled!
(Which in my case led to bullets behaving like enemies for example)
This also means big memory leaks.
So the solution for me was to iterate through each ownproperty before destroying bodies and removing every custom property.
I believe this should go to the docs ...
The text was updated successfully, but these errors were encountered: