如何正确删除版本:Box2dWeb-2.1.a.3、Box2D_v2.3.1r3 中的 box2d 主体? Box2D 错误?

Posted

技术标签:

【中文标题】如何正确删除版本:Box2dWeb-2.1.a.3、Box2D_v2.3.1r3 中的 box2d 主体? Box2D 错误?【英文标题】:How to properly delete a box2d body in version: Box2dWeb-2.1.a.3, Box2D_v2.3.1r3? Box2D bug? 【发布时间】:2013-12-28 18:15:01 【问题描述】:

更新

自从发现问题后,我还发现 Box2D for web 到处都在泄漏:/

为了展示这一点,我制作了一个在静态多边形中移动的简单圆圈,这是一段时间后的结果。

请注意以下项目是如何泄漏的,因为我没有创造任何身体或以任何方式改变世界:

b2Vec2 功能 b2ManifoldPoint b2ContactID b2歧管 b2ContactEdge b2PolyAndCircleContact 数组 ...

原帖

我遇到了问题,因为我正在分析我的游戏,而垃圾收集器并没有删除我的身体、联系人和其他内容。然后我查看了他们从 GC 中保留的内容以及 Box2D 本身。这可能会导致 2 个选项:我做得不好或 Box2D 正在泄漏。我认为是我的事业。

究竟是什么保留了它?

contact.m_nodeA.other 似乎是最常用于防止其被 GC 攻击的方法。 其他时候:m_fixtureB 在一个联系人中...参见图片

你可以看到 body 有一个 __destroyed 属性。这是在使用 world.DestroyBody(body) 删除之前手动设置的

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

正如您从 box2d 方法中看到的那样,它并没有摆脱其他变量,也没有将其更改为另一个主体,而我的主体不是 GC。

知道我在这里缺少什么吗?

现在我只能在没有运行 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;

将step改为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;

打开谷歌浏览器:

然后打开您的个人资料并制作快照。 现在在控制台中运行 c() 方法来创建 100 个实体 现在快照 2 在快照中搜索 b2Body,您会发现 100 个对象数 现在运行 d() 来删除你所有的身体; 通过点击垃圾箱强制进行垃圾收集 制作快照 3 搜索 b2Body,您还会找到 100 个对象数

在最后一步应该只有 0 个对象,因为它们已被销毁。而不是这个,你会发现这个:

现在您可以看到很多来自 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);

【问题讨论】:

你有没有设法解决这个问题?似乎他们从未修复过端口。 不,从那以后我没有使用 box2d... 很抱歉。 【参考方案1】:

我也有同样的问题,但我想我是从哪里来的。

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

【讨论】:

是一回事。 m_* 是属性(我想是私有的), Get* 是公共吸气剂。没有区别(现在我无法查看源代码)。【参考方案2】:

托蒂你有想过这个吗?看起来 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)

使用一个/两个夹具参数调用,重置在夹具/s 中传递,但也传递没有参数,它“空”所有 b2Contact 属性! (未经测试:)但我建议将您的 YOURcontactListener 类设置为处理所有联系人回调每次调用 Reset(??) 动态配置为逻辑要求每次调用(每一个世界步骤都比你想象的要多)。

同时采纳 Colt McAnlis 的聪明建议,并战略性地预先分配游戏生命周期所需的所有内存(通过创建游戏和 box2d 对象池,现在您知道对象可以重置),因此垃圾收集器永远不会运行,直到您销毁对象在您自己方便的时候池......即当您关闭标签或您的设备需要充电时! ;D [...更新] // 你可以定义和分配你自己的联系人监听器...通过...

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

// worldStep 中的 box2d 通过以下方式调用 YOURcontactlistener.update(): this.b2world.b2ContactManager.m_world.m_contactList.Update(this.m_contactListener) // this.m_contactListener 是你的 || b2_defaultListener;

// 实例化所有列出的泄漏对象,如下所示: b2Contact which instantiates b2ContactEdgeb2Manifold which instantiates b2ManifoldPointwhich instantiates m_id.key == ContactIDwhich instantiates 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) 但无论哪种方式,上面的函数(可能不完整)都是回调/处理程序,并且可以用来为遇到此问题的其他任何人恢复性能。很确定托蒂继续前进(不要忘记在回调中处理你的“this”范围)。

【讨论】:

以上是关于如何正确删除版本:Box2dWeb-2.1.a.3、Box2D_v2.3.1r3 中的 box2d 主体? Box2D 错误?的主要内容,如果未能解决你的问题,请参考以下文章

如何从从 git 克隆的项目中删除版本跟踪?

docker镜像删除还是历史版本

正确删除k8s版本jenkins

使用 Eclipse 在 Gradle 中选择正确的 JRE 版本

从 rbenv 卸载 ruby​​ 版本

完全干净卸载revit各个版本