Advertisement

Memory Managment Box2D

Started by September 09, 2022 09:17 PM
3 comments, last by Alberth 2 years, 1 month ago

Hello,

I have a simple physics playground using SFML for the graphics and Box2D for the physics. However, I am unable to get a proper way to deal with memory management or garbage collection. A way to remove the bodies after they are no longer used. The code itself works. Just can't figure out a good way to deal with the bodies that are no longer on the screen or not needed.

Also, while I'm at, is there a better way to show code on the forums? Like a code-box? Instead of just high-lightingting the selected code.

#include <iostream>



#include <SFML\System.hpp>

#include <SFML\Graphics.hpp>



#include <Box2D\Box2D.h>



using namespace std;



const int MAX_BOX_BODIES = 10;

const int MAX_CIRCLE_BODIES = 10;



const float SCALE = 32.0f;



int box_bodies, circle_bodies;



void AddBox(b2World& world,int mx,int my);

void AddCircle(b2World& world, int mx,int my);

void AddPlatform(b2World& world, int x, int y);

void AddTrampoline(b2World& world, int x, int y);





sf::RenderWindow GameWin;

sf::Event event;



b2Body* bodies;



bool Running = true;

bool PhysicsPaused = true;



int main(int argc, char* argv[])

{

GameWin.create(sf::VideoMode(800, 600, 32), "Physics Test");



b2Vec2 graivty(0, 9.8f);

b2World world(graivty);



AddPlatform(world, 400, 5);

AddPlatform(world, 400, 580);

AddTrampoline(world, 400, 300);



while (GameWin.isOpen())

{

while (GameWin.pollEvent(event))

{

if (event.type == sf::Event::Closed)

{

GameWin.close();

}



if (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))

{

GameWin.close();

}



if (sf::Keyboard::isKeyPressed(sf::Keyboard::A))

{

int mx = sf::Mouse::getPosition(GameWin).x;

int my = sf::Mouse::getPosition(GameWin).y;



AddBox(world, mx, my);



}



if (sf::Keyboard::isKeyPressed(sf::Keyboard::S))

{

int mx = sf::Mouse::getPosition(GameWin).x;

int my = sf::Mouse::getPosition(GameWin).y;



AddCircle(world, mx, my);

}



}



world.Step(1 / 60.0f, 8, 3);



GameWin.clear();



for ( bodies = world.GetBodyList(); bodies != 0; bodies = bodies->GetNext())

{

if (bodies->GetType() == b2_dynamicBody && bodies->GetUserData() == "Box")

{

sf::RectangleShape rect;



rect.setSize(sf::Vector2f(10.0f, 10.0f));

rect.setOrigin(sf::Vector2f(16.0f, 16.0f));

rect.setFillColor(sf::Color(255, 0, 0));



rect.setPosition(SCALE *bodies->GetPosition().x, SCALE * bodies->GetPosition().y);

rect.setRotation(bodies->GetAngle() * 180 / b2_pi);



GameWin.draw(rect);





}

else if (bodies->GetType() == b2_dynamicBody && bodies->GetUserData() == "Circle")

{

sf::CircleShape circ;



circ.setRadius(10.0f);

circ.setFillColor(sf::Color(0, 255, 0));



circ.setPosition(bodies->GetPosition().x * SCALE, bodies->GetPosition().y * SCALE);

circ.setRotation(bodies->GetAngle() * 180 / b2_pi);



GameWin.draw(circ);





}

else if (bodies->GetType() == b2_staticBody && bodies->GetUserData() == "Platform")

{

sf::RectangleShape rect;



rect.setSize(sf::Vector2f(600, 10));

rect.setFillColor(sf::Color(0, 0, 255));



rect.setOrigin(rect.getLocalBounds().width / 2, rect.getLocalBounds().height / 2);

rect.setPosition(bodies->GetPosition().x * SCALE, bodies->GetPosition().y * SCALE);



GameWin.draw(rect);



box_bodies++;

}

else if (bodies->GetType() == b2_staticBody && bodies->GetUserData() == "Trampoline")

{

sf::RectangleShape rect;



rect.setSize(sf::Vector2f(100, 30));

rect.setFillColor(sf::Color(0, 0, 255));



rect.setOrigin(rect.getLocalBounds().width / 2, rect.getLocalBounds().height / 2);

rect.setPosition(bodies->GetPosition().x * SCALE, bodies->GetPosition().y * SCALE);



GameWin.draw(rect);



}

}



GameWin.display();

}



return 0;

}



void AddBox(b2World& world, int mx, int my)

{

b2BodyDef def;

def.position = b2Vec2(mx / SCALE, my / SCALE);

def.type = b2_dynamicBody;

def.userData = "Box";

b2Body* body = world.CreateBody(&def);



b2PolygonShape shape;

shape.SetAsBox((32.0f / 2) / SCALE, (32.0f / 2) / SCALE);



b2FixtureDef fix;

fix.density = 5.0f;

fix.friction = 1.0f;

fix.restitution = 0.1f;

fix.shape = &shape;

body->CreateFixture(&fix);



box_bodies++;



if (box_bodies == MAX_BOX_BODIES)

{

for (body = world.GetBodyList(); body = 0; body = body->GetNext())

{

world.DestroyBody(body);

}

}



}



void AddCircle(b2World& world,int mx,int my)

{

b2BodyDef def;

def.type = b2_dynamicBody;

def.position = b2Vec2(mx / SCALE, my / SCALE);

def.userData = "Circle";

b2Body* body = world.CreateBody(&def);



b2CircleShape circle;

circle.m_radius = 15.0f / SCALE;



b2FixtureDef fix;

fix.density = 5.0f;

fix.friction = 1.0f;

fix.restitution = 0.9f;



fix.shape = &circle;

body->CreateFixture(&fix);

}



void AddPlatform(b2World& world, int x, int y)

{

b2BodyDef def;

def.position = b2Vec2(x / SCALE, y / SCALE);

def.type = b2_staticBody;

def.userData = "Platform";

b2Body* body = world.CreateBody(&def);



b2PolygonShape shape;

shape.SetAsBox(10, 0);



b2FixtureDef fix;

fix.density = 2.0f;

fix.restitution = 0.1f;

fix.friction = 0.5f;

fix.shape = &shape;



body->CreateFixture(&fix);

}



void AddTrampoline(b2World& world, int x, int y)

{

b2BodyDef def;

def.position = b2Vec2(x / SCALE, y / SCALE);

def.type = b2_staticBody;

def.userData = "Trampoline";

b2Body* body = world.CreateBody(&def);



b2PolygonShape shape;

shape.SetAsBox(1, 0);



b2FixtureDef fix;

fix.density = 2.0f;

fix.restitution = 1.5f;

fix.friction = 0.5f;

fix.shape = &shape;



body->CreateFixture(&fix);

}

LeftyGuitar said:
Also, while I'm at, is there a better way to show code on the forums? Like a code-box? Instead of just high-lightingting the selected code.

Above the post that you write are icons for formatting stuff, including code.

Advertisement

Thanks I got it formatted in a codebox. Now I just need to solve the issue I'm having with memory.

Never used box2d, but as nobody else seems to have time, let's try.

I copy/pasted the code above and reformatted it to something readable. Compiling it fails with:

$ g++ something.cpp
something.cpp: In function ‘void AddBox(b2World&, int, int)’:
something.cpp:132:20: error: invalid conversion from ‘const void*’ to ‘void*’ [-fpermissive]
  132 |     def.userData = "Box";
      |                    ^~~~~
      |                    |
      |                    const void*

# and similar for the 3 other assignments to def.userData .

The string doesn't allow writing in it, while “def.userData” does allow writing into whatever it points to. The technical term for it is “const correctness”, you may want to read about it.

The more serious problem is in using these values, like

if (... && bodies->GetUserData() == "Box") {

This does NOT compare both strings (it compares memory addresses where the strings are stored). It can fail even if the strings are the same, as the compiler may store the first “Box” string at a different spot in memory than the second “Box” string.

As you don't use the the user data for storing data about the body, the simplest way out of this is to create a unique pointer for each shape. Something like

enum class Shape {
    box,
    circle,
    platform,
    trampoline,

    num_shapes, // Number of shapes.
};


Shape* get_shape(Shape shape) {
    static Shape shapes[static_cast<int>(Shape::num_shapes)]; // Not initialized as its values are not used.

    return &shapes[static_cast<int>(shape)];
}

// For the "Box" shape, assignement and comparison becomes
//   def.userData = get_shape(Shape::box);
//   if ( ... && bodies->GetUserData() == get_shape(Shape::box)) {
//
// And similar for the other shapes.

The internal “shapes” array contains a “Shape” value for each entry in the enumeration, so each address is unique. Also, as the only way to get a pointer is through the “get_shape” function, you are certain that 2 queries with the same Shape enumeration value will give you the same address, and a compare on address will work correctly. Do note that there is no sane value inside the array, that is “*(get_shape(Shape::box))” may give you any value, so don't use the data pointed to ?

I am very mystified by what you're doing with “box_bodies”. It is increments during creation and painting of boxes it seems, and is never reset. Thus it grows to INT_MAX (and then wraps to INT_MIN and increments to 0, but likely you want to fix this much earlier).

Also, avoid global variables as much as possible. Move them as close to their use as you can. It avoids that all kinds of nasty surprises if you start calling more functions that all use the same variable.

LeftyGuitar said:
However, I am unable to get a proper way to deal with memory management or garbage collection. A way to remove the bodies after they are no longer used. The code itself works. Just can't figure out a good way to deal with the bodies that are no longer on the screen or not needed.

And we arrive at your question. I am not sure I understand what you ask. As said, I have no experience with Box2D, but https://box2d.org/documentation/classb2_world.html​ seems to suggest that you use “void DestroyBody (b2Body *body)” to delete a body, and in fact you are already using that in the “box delete loop” near the bottom of “AddBox”.

Isn't it just a matter of finding the bodies you don't need and destroy them? What does “proper” mean?

This topic is closed to new replies.

Advertisement