如何在版本为Box2dWeb-2.1.a.3和Box2D_v2.3.1r3中正确删除Box2d物体?Box2D存在Bug吗?

5

更新

问题已经找到,我也发现Box2D for web在每一方面都有泄漏 :/

为了展示这个问题,我制作了一个简单的圆在一个静态多边形中移动,并在一段时间后得出了以下结果。

enter image description here

注意,以下项目正在泄漏,因为我没有创建任何主体或以任何方式改变世界:

  • b2Vec2
  • 特征
  • b2ManifoldPoint
  • b2ContactID
  • b2Manifold
  • b2ContactEdge
  • b2PolyAndCircleContact
  • 阵列
  • ...

原帖

我有一个问题,因为我正在分析我的游戏,但垃圾回收器没有删除我的主体、联系人和其他东西。然后我看了看它们从GC保留了什么,发现是Box2D自身。这可能导致两种选择:我的错还是Box2D泄漏了。我认为是我的原因。

究竟是什么让它保持不变?

  • contact.m_nodeA.other似乎是最常用的保留来自GC的东西。
  • 其他时候:在联系人中的m_fixtureB...请参见图片

enter image description here enter image description here

您可以看到该主体具有__destroyed属性。在使用world.DestroyBody(body)手动删除之前,将其手动设置。

当我销毁一个主体时,我在调用世界上的step方法之后调用它。

从Box2D方法中可以看出,它不会摆脱其他变量,也不会将其更改为另一个主体,因此我的主体没有进行垃圾回收。

这里我错过了什么想法吗?

现在只有在未运行world.Step时才能解决问题:

var gravity = new Box2D.Vec2(0, 0);
var doSleep = true;
var world = new Box2D.World(gravity, doSleep);
var step = false;

var fixtureDef = new Box2D.FixtureDef();
fixtureDef.density = 1.0;
fixtureDef.friction = 0.5;
fixtureDef.restitution = 0.2;
fixtureDef.shape = new Box2D.PolygonShape();
fixtureDef.shape.SetAsBox(1, 1);
var bodyDef = new Box2D.BodyDef;
bodyDef.type = Box2D.Body.b2_dynamicBody;
bodyDef.position.x = 0.4;
bodyDef.position.y = 0.4;

var bodies = []
var fix = [];
window.c = function(){
    for(var i = 0; i < 100; i++){
        var body = world.CreateBody(bodyDef);
        body._id = i;

        fix.push(body.CreateFixture(fixtureDef));
        bodies.push(body);

    }
    if(step){world.Step(1/60, 3, 3); world.ClearForces();}
    console.log('Created', bodies)
    fixtureDef = null;
    bodyDef = null;
}

window.d = function(){
    _.each(bodies, function(body, i){
        body.DestroyFixture(fix[i]);
        world.DestroyBody(body);

        fix[i] = null;
        bodies[i] = null;
    })
    if(step){world.Step(1/60, 3, 3); world.ClearForces();}
    bodies = null;
    fix = null;
}

将步骤更改为true,内存泄漏问题再次出现。

复现内存泄漏问题:

您的文件中的代码:

var gravity = new Box2D.Vec2(0, 0);
var doSleep = true;
var world = new Box2D.World(gravity, doSleep);

var bodies = []
window.c = function(){
    for(var i = 0; i < 100; i++){
        var bodyDef = new Box2D.BodyDef();
        bodyDef.type = 2;

        var shape = new Box2D.PolygonShape();
        shape.SetAsBox(1, 1);

        var fixtureDef   = new Box2D.FixtureDef();
        fixtureDef.shape = shape;
        var body = world.CreateBody(bodyDef);
        body._id = i;
        body.CreateFixture(fixtureDef);
        bodies.push(body);
    }
    world.Step(0.3, 3, 3);
    console.log('Created', bodies)
}
window.d = function(){
    _.each(bodies, function(body, i){
        world.DestroyBody(body);
        bodies[i] = null;
    })
    world.Step(0.3, 3, 3);
    bodies = null;
}

打开 Google Chrome:

  • 然后打开您的个人资料并拍摄快照。
  • 现在在控制台中运行 c() 方法以创建 100 个物体。
  • 现在拍摄第二个快照。
  • 在快照中搜索 b2Body,您将找到 100 个对象计数。
  • 现在运行 d() 方法以删除所有物体;
  • 通过单击垃圾桶来强制进行垃圾回收。
  • 拍摄快照 3。
  • 搜索 b2Body,您还会发现 100 个对象计数。

在最后一步,应该只有 0 个对象,因为它们已被销毁。但实际上你会发现这个: enter image description here

现在您可以看到有许多来自 b2ContactEdge 的引用。如果您删除代码中的 world.Step 部分,则只会看到对该物体的 2 个引用。

如果您删除此行

body.CreateFixture(fixtureDef);

或将身体静止不再泄漏。

我的游戏循环

...gameLoop = function(o){
    // used a lot here
    var world = o.world;

    // calculate the new positions
    var worldStepSeconds = o.worldStepMs / 1000;

    // step world
    world.Step(worldStepSeconds, o.velocityIterations, o.positionIterations)

    // render debug
    if(o.renderDebug){
        world.DrawDebugData();
    }

    // always to not accumulate forces, maybe some bug occurs
    world.ClearForces();

    // tick all ticking entities
    _.each(o.getTickEntitiesFn(), function(actor){
        if(!actor) return;
        actor.tick(o.worldStepMs, o.lastFrameMs);
    })


    // update PIXI entities
    var body = world.GetBodyList();
    var worldScale = world.SCALE;
    var destroyBody = world.DestroyBody.bind(world);
    while(body){
        var actor = null;
        var visualEntity = null;
        var box2DEntity = o.getBox2DEntityByIdFn(body.GetUserData());
        if(box2DEntity){
            visualEntity = o.getVisualEntityByIdFn(box2DEntity.getVisualEntityId());
            if(box2DEntity.isDestroying()){
                // optimization
                body.__destroyed = true;
                world.DestroyBody(body);
                box2DEntity.completeDestroy();
            }
        }
        if(visualEntity){
            if(visualEntity.isDestroying()){
                visualEntity.completeDestroy();
            }else{
                var inverseY = true;
                var bodyDetails = Utils.getScreenPositionAndRotationOfBody(world, body, inverseY);
                visualEntity.updateSprite(bodyDetails.x, bodyDetails.y, bodyDetails.rotation);
            }
        }
        // this delegates out functionality for each body processed
        if(o.triggersFn.eachBody) o.triggersFn.eachBody(world, body, visualEntity);

        body = body.GetNext();
    }

    // when a joint is created is then also created it's visual counterpart and then set to userData.
    var joint = world.GetJointList();
    while(joint){
        var pixiGraphics = joint.GetUserData();
        if(pixiGraphics){
            // In order to draw a distance joint we need to know the start and end positions.
            // The joint saves the global (yes) anchor positions for each body.
            // After that we need to scale to our screen and invert y axis.
            var anchorA           = joint.GetAnchorA();
            var anchorB           = joint.GetAnchorB();
            var screenPositionA = anchorA.Copy();
            var screenPositionB = anchorB.Copy();
            // scale
            screenPositionA.Multiply(world.SCALE);
            screenPositionB.Multiply(world.SCALE);
            // invert y
            screenPositionA.y = world.CANVAS_HEIGHT - screenPositionA.y
            screenPositionB.y = world.CANVAS_HEIGHT - screenPositionB.y

            // draw a black line
            pixiGraphics.clear();
            pixiGraphics.lineStyle(1, 0x000000, 0.7);
            pixiGraphics.moveTo(screenPositionA.x, screenPositionA.y);
            pixiGraphics.lineTo(screenPositionB.x, screenPositionB.y);
        }
        joint = joint.GetNext();
    }

    // render the PIXI scene
    if(o.renderPixi){
        o.renderer.render(o.stage)
    }

    // render next frame
    requestAnimFrame(o.requestAnimFrameFn);
}

来自Box2d的代码:

b2ContactManager.prototype.Destroy = function (c) {
var fixtureA = c.GetFixtureA();
var fixtureB = c.GetFixtureB();
var bodyA = fixtureA.GetBody();
var bodyB = fixtureB.GetBody();
if (c.IsTouching()) {
this.m_contactListener.EndContact(c);
}
if (c.m_prev) {
c.m_prev.m_next = c.m_next;
}
if (c.m_next) {
c.m_next.m_prev = c.m_prev;
}
if (c == this.m_world.m_contactList) {
this.m_world.m_contactList = c.m_next;
}
if (c.m_nodeA.prev) {
c.m_nodeA.prev.next = c.m_nodeA.next;
}
if (c.m_nodeA.next) {
c.m_nodeA.next.prev = c.m_nodeA.prev;
}
if (c.m_nodeA == bodyA.m_contactList) {
bodyA.m_contactList = c.m_nodeA.next;
}
if (c.m_nodeB.prev) {
c.m_nodeB.prev.next = c.m_nodeB.next;
}
if (c.m_nodeB.next) {
c.m_nodeB.next.prev = c.m_nodeB.prev;
}
if (c.m_nodeB == bodyB.m_contactList) {
bodyB.m_contactList = c.m_nodeB.next;
}
this.m_contactFactory.Destroy(c);
--this.m_contactCount;
}


b2ContactFactory.prototype.Destroy = function (contact) {
    if (contact.m_manifold.m_pointCount > 0) {
        contact.m_fixtureA.m_body.SetAwake(true);
        contact.m_fixtureB.m_body.SetAwake(true);
    }
    var type1 = parseInt(contact.m_fixtureA.GetType());
    var type2 = parseInt(contact.m_fixtureB.GetType());
    var reg = this.m_registers[type1][type2];
    if (true) {
        reg.poolCount++;
        contact.m_next = reg.pool;
        reg.pool = contact;
    }
    var destroyFcn = reg.destroyFcn;
    destroyFcn(contact, this.m_allocator);
}

你曾经解决过这个问题吗?看起来他们从未修复过这个端口。 - Derek 朕會功夫
不,我从那以后没有使用box2d了...很抱歉听到这个消息。 - Totty.js
2个回答

0

我有同样的问题,但我认为我找到了它的来源。

不要使用 m_*,而是使用函数,例如使用 GetFixtureA() 而不是 m_fixtureA


是同一件事。m_* 是属性(我猜是私有的),而 Get* 是公共的 getter。没有区别(现在我无法查看源代码)。 - Totty.js

-2

Totti,你解决了吗?看起来box2dweb需要手动销毁和内存管理。

我想我已经找到了你的泄漏问题,未实现(静态类)销毁函数:

b2Joint.Destroy = function (joint, allocator) {}
b2CircleContact.Destroy = function (contact, allocator) {}< 
b2PolygonContact.Destroy = function (contact, allocator) {}
b2EdgeAndCircleContact.Destroy = function (contact, allocator) {}<
b2PolyAndCircleContact.Destroy = function (contact, allocator) {}
b2PolyAndEdgeContact.Destroy = function (contact, allocator) {}
[UPDATE...]     
b2DestructionListener.b2DestructionListener = function () {};
b2DestructionListener.prototype.SayGoodbyeJoint = function (joint) {}
b2DestructionListener.prototype.SayGoodbyeFixture = function (fixture) {}


b2Contact.prototype.Reset(fixtureA, fixtureB)

使用一个/两个fixture参数调用将重置传递的fixture,但是如果不传递任何参数,则会“null”所有b2Contact属性!(未经测试:)但我建议设置YOURcontactListener类以处理每次调用Reset(??)的所有联系回调,动态配置为逻辑所需的每个调用(比您想象的更多,每个世界步骤都有)。

此外,采纳Colt McAnlis的聪明建议,通过创建游戏和box2d对象池来预先分配游戏生命周期所需的所有内存(现在您知道对象可以重置),因此垃圾收集器永远不会运行,直到您在自己方便的时候销毁对象池....即当您关闭选项卡或设备需要充电时!;D
[...更新]

// 您可以通过...via...定义和分配自己的联系人监听器

YOUR.b2world.b2ContactManager.m_world.m_contactList = new YOURcontactlistener();<br>[edit]...if you dont it actually does have Box2D.Dynamics.b2ContactListener.b2_defaultListener.

// box2d在worldStep调用YOURcontactlistener.update(),方法如下: this.b2world.b2ContactManager.m_world.m_contactList.Update(this.m_contactListener)
// 这里的m_contactListener是YOURS || b2_defaultListener;

// 它会通过实例化所有列出的泄漏对象来工作: {b2Contact 会实例化 {b2ContactEdge}}{b2Manifold 会实例化 {b2ManifoldPoint{它实例化 m_id.key == ContactID{它实例化 Features}}}} 以及 {B2Vec2} 在b2ContactResult中被实例化......我无法找到它,但可以假设它必须在Solver中被实例化。

//Contacts.destroyFcn回调函数是在哪里创建的?

b2ContactFactory.prototype.Destroy = function (contact) {...}

// 然后Contacts.destroyFcn回调函数被私下注册在....

b2ContactFactory.prototype.InitializeRegisters() {...}

...通过...

this.AddType = function (createFcn, destroyFcn, type1, type2) {...}

…但是…那些私有注册的是上述四个未实现的静态类函数之一…

b2PolygonContact.Destroy = function (contact, allocator) {}
b2EdgeAndCircleContact.Destroy = function (contact, allocator) {}
b2PolyAndCircleContact.Destroy = function (contact, allocator) {} 
b2PolyAndEdgeContact.Destroy = function (contact, allocator) {}

所以我还没有测试过,但看起来box2dweb只是提供了Destroy回调/处理程序函数,您必须阅读源代码以找到所有需要置空的属性。[编辑]与b2Contact.prototype.Reset(fixtureA, fixtureB)结合使用

但无论如何,我相当有信心上述功能(可能不完整)都是回调/处理程序,并且可以用于将您带回性能,以供其他遇到此问题的人使用。相当确定Totti已经离开了(不要忘记在回调中处理“this”范围)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接