Skip to content

Commit

Permalink
Persistent islands (#36)
Browse files Browse the repository at this point in the history
Persistent islands - islands are merged and split incrementally rather
than being rebuilt every time step.
Sleeping is now per island rather than per body.

Merging uses union-find. Each body created also creates an island. When
joints are created the islands are linked and merged. When contacts
begin touching the islands are linked and merged.

When a contact stops touching or is destroyed the island is flagged for
splitting. Likewise destroying a joint flags the island for splitting.
Only a single island may be split per time step. The largest island is
chosen. Island splitting is handled in the island job.

The parallel collide tasks generate state changes for contacts. These
are processed in deterministic order to maintain overall determinism.
  • Loading branch information
erincatto committed Jul 7, 2023
1 parent e105c0c commit 96c9978
Show file tree
Hide file tree
Showing 48 changed files with 2,616 additions and 1,777 deletions.
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ if (MSVC AND WIN32)
# Enable edit and continue only in debug due to perf hit in release
add_link_options($<$<CONFIG:Debug>:/INCREMENTAL>)
add_compile_options($<$<CONFIG:Debug>:/ZI>)

# this seems useless because it crashes in KernalBase.dll
# add_compile_options(/fsanitize=address)
endif()

if (BOX2D_PROFILE)
Expand Down
7 changes: 3 additions & 4 deletions include/box2d/box2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "box2d/api.h"
#include "box2d/id.h"
#include "box2d/joint_types.h"
#include "box2d/timer.h"
#include "box2d/types.h"

typedef struct b2Circle b2Circle;
Expand Down Expand Up @@ -33,12 +34,10 @@ BOX2D_API void b2World_Draw(b2WorldId worldId, b2DebugDraw* debugDraw);
BOX2D_API void b2World_EnableSleeping(b2WorldId worldId, bool flag);

/// Get the current profile.
BOX2D_API struct b2Profile* b2World_GetProfile(b2WorldId worldId);
BOX2D_API struct b2Profile b2World_GetProfile(b2WorldId worldId);

BOX2D_API struct b2Statistics b2World_GetStatistics(b2WorldId worldId);

BOX2D_API b2BodyId b2World_GetGroundBodyId(b2WorldId worldId);

/// Create a rigid body given a definition. No reference to the definition is retained.
/// @warning This function is locked during callbacks.
BOX2D_API b2BodyId b2World_CreateBody(b2WorldId worldId, const b2BodyDef* def);
Expand All @@ -53,7 +52,7 @@ BOX2D_API b2Vec2 b2Body_GetLocalPoint(b2BodyId bodyId, b2Vec2 globalPoint);

BOX2D_API b2BodyType b2Body_GetType(b2BodyId bodyId);
BOX2D_API float b2Body_GetMass(b2BodyId bodyId);
BOX2D_API void b2Body_SetAwake(b2BodyId bodyId, bool awake);
BOX2D_API void b2Body_Wake(b2BodyId bodyId);

/// Create a shape and attach it to a body. Contacts are not created until the next time step.
/// @warning This function is locked during callbacks.
Expand Down
14 changes: 8 additions & 6 deletions include/box2d/joint_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

#include "box2d/types.h"


/// A mouse joint is used to make a point on a body track a
/// specified world point. This a soft constraint with a maximum
/// force. This allows the constraint to stretch without
Expand All @@ -15,11 +14,13 @@
/// use the mouse joint, look at the samples app.
typedef struct b2MouseJointDef
{
/// The attached body. It should be dynamic.
b2BodyId bodyId;
/// The first attached body.
b2BodyId bodyIdA;

/// The second attached body.
b2BodyId bodyIdB;

/// The initial world target point. This is assumed
/// to coincide with the body anchor initially.
/// The initial target point in world space
b2Vec2 target;

/// The maximum constraint force that can be exerted
Expand All @@ -37,7 +38,8 @@ typedef struct b2MouseJointDef
static inline struct b2MouseJointDef b2DefaultMouseJointDef()
{
b2MouseJointDef def = {0};
def.bodyId = b2_nullBodyId;
def.bodyIdA = b2_nullBodyId;
def.bodyIdB = b2_nullBodyId;
def.target = B2_LITERAL(b2Vec2){0.0f, 0.0f};
def.maxForce = 0.0f;
def.stiffness = 0.0f;
Expand Down
2 changes: 2 additions & 0 deletions include/box2d/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "types.h"

/// Profiling data. Times are in milliseconds.
/// TODO_ERIN change to ticks due to variable frequency
typedef struct b2Profile
{
float step;
Expand All @@ -20,6 +21,7 @@ static const b2Profile b2_emptyProfile = {0};

typedef struct b2Statistics
{
int32_t islandCount;
int32_t bodyCount;
int32_t contactCount;
int32_t jointCount;
Expand Down
25 changes: 18 additions & 7 deletions include/box2d/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@
#define B2_MAYBE_UNUSED(x) ((void)(x))
#define B2_NULL_INDEX (-1)

// TODO_ERIN temp until github updates MSVC
//#if defined(_MSC_VER) && !defined(__clang__)
//#define B2_ATOMIC
//#else
#define B2_ATOMIC _Atomic
//#endif

/// 2D vector
typedef struct b2Vec2
{
Expand Down Expand Up @@ -75,7 +82,7 @@ typedef struct b2RayCastOutput

/// Task interface
/// This is prototype for a Box2D task
typedef void b2TaskCallback(int32_t startIndex, int32_t endIndex, void* context);
typedef void b2TaskCallback(int32_t startIndex, int32_t endIndex, uint32_t threadIndex, void* context);

/// These functions can be provided to Box2D to invoke a task system
typedef void b2EnqueueTaskCallback(b2TaskCallback* task, int32_t itemCount, int32_t minRange, void* taskContext, void* userContext);
Expand All @@ -93,22 +100,25 @@ typedef struct b2WorldDef
/// Can bodies go to sleep to improve performance
bool enableSleep;

/// initial capacity for bodies
/// Capacity for bodies. This may not be exceeded.
int32_t bodyCapacity;

/// initial capacity for joints
int32_t jointCapacity;

/// initial capacity for shapes
int32_t shapeCapacity;

/// Capacity for contacts. This may not be exceeded.
int32_t contactCapacity;

/// Capacity for joints
int32_t jointCapacity;

/// Stack allocator capacity. This controls how much space box2d reserves for per-frame calculations.
/// Larger worlds require more space. b2Statistics can be used to determine a good capacity for your
/// application.
int32_t stackAllocatorCapacity;

/// task system hookup
int32_t workerCount;
uint32_t workerCount;
b2EnqueueTaskCallback* enqueueTask;
b2FinishTasksCallback* finishTasks;
void* userTaskContext;
Expand Down Expand Up @@ -228,8 +238,9 @@ static inline b2WorldDef b2DefaultWorldDef()
def.restitutionThreshold = 1.0f * b2_lengthUnitsPerMeter;
def.enableSleep = true;
def.bodyCapacity = 8;
def.jointCapacity = 8;
def.shapeCapacity = 8;
def.contactCapacity = 8;
def.jointCapacity = 8;
def.stackAllocatorCapacity = 1024 * 1024;
return def;
}
Expand Down
11 changes: 4 additions & 7 deletions samples/collection/benchmark_barrel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
#include <GLFW/glfw3.h>
#include <imgui.h>

BOX2D_API bool g_velocityBlockSolve;

class BenchmarkBarrel : public Sample
{
public:
Expand All @@ -25,7 +23,8 @@ class BenchmarkBarrel : public Sample
e_maxRows = 130,
};

BenchmarkBarrel()
BenchmarkBarrel(const Settings& settings)
: Sample(settings)
{
float groundSize = 25.0f;

Expand Down Expand Up @@ -132,8 +131,6 @@ class BenchmarkBarrel : public Sample
changed = changed || ImGui::Combo("Shape", &shapeType, shapeTypes, IM_ARRAYSIZE(shapeTypes));
m_shapeType = ShapeType(shapeType);

ImGui::Checkbox("Block Solve", &g_velocityBlockSolve);

changed = changed || ImGui::Button("Reset Scene");

if (changed)
Expand All @@ -144,9 +141,9 @@ class BenchmarkBarrel : public Sample
ImGui::End();
}

static Sample* Create()
static Sample* Create(const Settings& settings)
{
return new BenchmarkBarrel;
return new BenchmarkBarrel(settings);
}

b2BodyId m_bodies[e_maxRows * e_maxColumns];
Expand Down
7 changes: 4 additions & 3 deletions samples/collection/benchmark_joint_grid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
class BenchmarkJointGrid : public Sample
{
public:
BenchmarkJointGrid()
BenchmarkJointGrid(const Settings& settings)
: Sample(settings)
{
constexpr float rad = 0.4f;
constexpr int32_t numi = g_sampleDebug ? 10 : 100;
Expand Down Expand Up @@ -79,9 +80,9 @@ class BenchmarkJointGrid : public Sample
b2Free(bodies);
}

static Sample* Create()
static Sample* Create(const Settings& settings)
{
return new BenchmarkJointGrid;
return new BenchmarkJointGrid(settings);
}
};

Expand Down
28 changes: 16 additions & 12 deletions samples/collection/benchmark_pyramid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
#include <GLFW/glfw3.h>
#include <imgui.h>

BOX2D_API bool g_velocityBlockSolve;
BOX2D_API bool g_positionBlockSolve;

class BenchmarkPyramid : public Sample
{
public:
Expand All @@ -21,7 +18,8 @@ class BenchmarkPyramid : public Sample
e_maxBodyCount = e_maxBaseCount * (e_maxBaseCount + 1) / 2
};

BenchmarkPyramid()
BenchmarkPyramid(const Settings& settings)
: Sample(settings)
{
float groundSize = 100.0f;
m_groundThickness = 1.0f;
Expand All @@ -39,7 +37,8 @@ class BenchmarkPyramid : public Sample
m_bodies[i] = b2_nullBodyId;
}

m_baseCount = g_sampleDebug ? 10 : e_maxBaseCount;
m_baseCount = g_sampleDebug ? 2 : 80;
m_bodyCount = 0;

CreateScene();
}
Expand All @@ -59,7 +58,7 @@ class BenchmarkPyramid : public Sample
float rad = 0.5f;
float shift = rad * 2.0f;
float centerx = shift * count / 2.0f;
float centery = shift / 2.0f + m_groundThickness + rad * 1.5f;
float centery = shift / 2.0f + m_groundThickness;// +rad * 1.5f;

b2BodyDef bd = b2DefaultBodyDef();
bd.type = b2_dynamicBody;
Expand All @@ -77,7 +76,7 @@ class BenchmarkPyramid : public Sample

for (int32_t i = 0; i < count; ++i)
{
float y = i * shift + centery;
float y = 1.0f + i * shift + centery;

for (int32_t j = i; j < count; ++j)
{
Expand All @@ -91,6 +90,8 @@ class BenchmarkPyramid : public Sample
index += 1;
}
}

m_bodyCount = index;
}

void UpdateUI() override
Expand All @@ -99,15 +100,17 @@ class BenchmarkPyramid : public Sample
ImGui::SetNextWindowSize(ImVec2(240.0f, 230.0f));
ImGui::Begin("Stacks", nullptr, ImGuiWindowFlags_NoResize);

ImGui::Checkbox("Vel Block Solve", &g_velocityBlockSolve);
ImGui::Checkbox("Pos Block Solve", &g_positionBlockSolve);

bool changed = false;
changed = changed || ImGui::SliderInt("Base Count", &m_baseCount, 1, e_maxBaseCount);

changed = changed || ImGui::SliderFloat("Round", &m_round, 0.0f, 0.4f, "%.1f");
changed = changed || ImGui::Button("Reset Scene");

if (ImGui::Button("Wake Top"))
{
b2Body_Wake(m_bodies[m_bodyCount - 1]);
}

if (changed)
{
CreateScene();
Expand All @@ -116,12 +119,13 @@ class BenchmarkPyramid : public Sample
ImGui::End();
}

static Sample* Create()
static Sample* Create(const Settings& settings)
{
return new BenchmarkPyramid;
return new BenchmarkPyramid(settings);
}

b2BodyId m_bodies[e_maxBodyCount];
int32_t m_bodyCount;
int32_t m_baseCount;
float m_round;
float m_groundThickness;
Expand Down
7 changes: 4 additions & 3 deletions samples/collection/benchmark_tumbler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ class BenchmarkTumbler : public Sample
{
public:

BenchmarkTumbler()
BenchmarkTumbler(const Settings& settings)
: Sample(settings)
{
b2BodyId groundId;
{
Expand Down Expand Up @@ -74,9 +75,9 @@ class BenchmarkTumbler : public Sample
}
}

static Sample* Create()
static Sample* Create(const Settings& settings)
{
return new BenchmarkTumbler;
return new BenchmarkTumbler(settings);
}

int32_t m_maxCount;
Expand Down
7 changes: 4 additions & 3 deletions samples/collection/sample_distance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
class SampleDistance : public Sample
{
public:
SampleDistance()
SampleDistance(const Settings& settings)
: Sample(settings)
{
m_circle1 = {{0.0f, 0.0f}, 0.5f};
m_circle2 = {{0.0f, 0.0f}, 1.0f};
Expand Down Expand Up @@ -407,9 +408,9 @@ class SampleDistance : public Sample
#endif
}

static Sample* Create()
static Sample* Create(const Settings& settings)
{
return new SampleDistance;
return new SampleDistance(settings);
}

b2Polygon m_box;
Expand Down
7 changes: 4 additions & 3 deletions samples/collection/sample_dynamic_tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ static float RayCallback(const b2RayCastInput* input, int32_t proxyId, void* use
class DynamicTree : public Sample
{
public:
DynamicTree()
DynamicTree(const Settings& settings)
: Sample(settings)
{
m_fill = 1.0f;
m_moveFraction = 0.0f;
Expand Down Expand Up @@ -248,9 +249,9 @@ class DynamicTree : public Sample
m_timeStamp += 1;
}

static Sample* Create()
static Sample* Create(const Settings& settings)
{
return new DynamicTree;
return new DynamicTree(settings);
}

b2DynamicTree m_tree;
Expand Down
Loading

0 comments on commit 96c9978

Please sign in to comment.