Skip to content

Commit c19a836

Browse files
committed
[upstream] Threading cleanup (erincatto/box2d#1049)
- Added internal scheduler so users don't need one. - Simplified b2EnqueueTaskCallback. Users are no longer responsible for providing parallel-for. - Simplified b2TaskCallback. Users no longer need to provide a thread/worker index. - Removed usage of enkiTS from the samples app. It was very useful, but I needed more control over threading. Users can now refer to scheduler.h for example task scheduler hook up. - Cleaned up and documented the solver tasking system - Added b2World_SetWorkerCount and b2World_GetWorkerCount so the worker count can be modified on an existing world - Added `B2_MAX_TASKS` so users can pre-allocate task arrays with stable pointers.
1 parent 89908ec commit c19a836

33 files changed

Lines changed: 1141 additions & 743 deletions

src/Box2D.NET.Samples/Primitives/SampleTask.cs

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/Box2D.NET.Samples/Primitives/TaskScheduler.cs

Lines changed: 0 additions & 81 deletions
This file was deleted.

src/Box2D.NET.Samples/SampleApp.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using Silk.NET.OpenGL.Extensions.ImGui;
2020
using Silk.NET.Windowing;
2121
using static Box2D.NET.B2Cores;
22+
using static Box2D.NET.B2Constants;
2223
using static Box2D.NET.B2Diagnostics;
2324
using static Box2D.NET.B2Buffers;
2425
using static Box2D.NET.B2MathFunction;
@@ -758,7 +759,7 @@ private unsafe void ScrollCallback(WindowHandle* window, double dx, double dy)
758759

759760
private void UpdateUI()
760761
{
761-
int maxWorkers = (int)(Environment.ProcessorCount * 1.5f);
762+
int maxWorkers = B2_MAX_WORKERS;
762763

763764
float fontSize = ImGui.GetFontSize();
764765
float menuWidth = 13.0f * fontSize;

src/Box2D.NET.Samples/Samples/Sample.cs

Lines changed: 5 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ namespace Box2D.NET.Samples.Samples;
2727

2828
public class Sample : IDisposable
2929
{
30-
public const int k_maxContactPoints = 12 * 2048;
31-
public const int m_maxTasks = 64;
30+
public const int m_maxTasks = 512;
3231
public const int m_maxThreads = 64;
3332
public const int m_profileCapacity = 512;
3433

@@ -42,11 +41,6 @@ public class Sample : IDisposable
4241
protected Camera m_camera;
4342
protected Draw m_draw;
4443

45-
private TaskScheduler m_scheduler;
46-
private SampleTask[] m_tasks;
47-
private int m_taskCount;
48-
protected int m_threadCount;
49-
5044
private B2BodyId m_mouseBodyId;
5145

5246
//
@@ -79,19 +73,6 @@ public Sample(SampleContext context)
7973
m_camera = context.camera;
8074
m_draw = context.draw;
8175

82-
m_scheduler = new TaskScheduler();
83-
m_scheduler.Initialize(m_context.workerCount);
84-
85-
m_tasks = new SampleTask[m_maxTasks];
86-
for (int i = 0; i < m_maxTasks; ++i)
87-
{
88-
m_tasks[i] = new SampleTask();
89-
}
90-
91-
m_taskCount = 0;
92-
93-
m_threadCount = 1 + m_context.workerCount;
94-
9576
m_worldId = b2_nullWorldId;
9677

9778
m_textIncrement = 26;
@@ -125,9 +106,7 @@ public virtual void Dispose()
125106
{
126107
// By deleting the world, we delete the bomb, mouse joint, etc.
127108
b2DestroyWorld(m_worldId);
128-
129-
// delete m_scheduler;
130-
// delete[] m_tasks;
109+
131110
}
132111

133112
public void CreateWorld()
@@ -140,8 +119,8 @@ public void CreateWorld()
140119

141120
B2WorldDef worldDef = b2DefaultWorldDef();
142121
worldDef.workerCount = m_context.workerCount;
143-
worldDef.enqueueTask = EnqueueTask;
144-
worldDef.finishTask = FinishTask;
122+
// worldDef.enqueueTask = EnqueueTask;
123+
// worldDef.finishTask = FinishTask;
145124
worldDef.userTaskContext = this;
146125
worldDef.enableSleep = m_context.enableSleep;
147126

@@ -340,41 +319,6 @@ private void DrawProfileSeries(ImDrawListPtr drawList, Vector2 origin, Vector2 s
340319
previous = current;
341320
}
342321
}
343-
344-
345-
private static object EnqueueTask(b2TaskCallback task, int itemCount, int minRange, object taskContext, object userContext)
346-
{
347-
Sample sample = userContext as Sample;
348-
if (sample.m_taskCount < m_maxTasks)
349-
{
350-
SampleTask sampleTask = sample.m_tasks[sample.m_taskCount];
351-
sampleTask.m_SetSize = itemCount;
352-
sampleTask.m_MinRange = minRange;
353-
sampleTask.m_task = task;
354-
sampleTask.m_taskContext = taskContext;
355-
sample.m_scheduler.AddTaskSetToPipe(sampleTask);
356-
++sample.m_taskCount;
357-
return sampleTask;
358-
}
359-
else
360-
{
361-
// This is not fatal but the maxTasks should be increased
362-
B2_ASSERT(false);
363-
task(0, itemCount, 0, taskContext);
364-
return null;
365-
}
366-
}
367-
368-
private static void FinishTask(object taskPtr, object userContext)
369-
{
370-
if (taskPtr != null)
371-
{
372-
SampleTask sampleTask = taskPtr as SampleTask;
373-
Sample sample = userContext as Sample;
374-
sample.m_scheduler.WaitforTask(sampleTask);
375-
}
376-
}
377-
378322
public void ResetText()
379323
{
380324
m_textLine = m_textIncrement;
@@ -568,7 +512,7 @@ public virtual void Step()
568512
for (int i = 0; i < 1; ++i)
569513
{
570514
b2World_Step(m_worldId, timeStep, m_context.subStepCount);
571-
m_taskCount = 0;
515+
// m_taskCount = 0;
572516
}
573517

574518
if (timeStep > 0.0f)

src/Box2D.NET/B2Arrays.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
// SPDX-License-Identifier: MIT
44

55
using System;
6-
using System.Runtime.InteropServices;
76
using System.Runtime.CompilerServices;
87
using static Box2D.NET.B2Constants;
98
using static Box2D.NET.B2Buffers;
10-
using static Box2D.NET.B2Diagnostics;
119

1210
namespace Box2D.NET
1311
{
@@ -205,5 +203,12 @@ public static void b2Array_Destroy<T>(ref B2Array<T> a)
205203
a.count = 0;
206204
a.capacity = 0;
207205
}
206+
207+
public static void b2Array_ResizeAndSetZero<T>(ref B2Array<T> a, int n) where T : new()
208+
{
209+
b2Array_Reserve(ref a, n);
210+
// memset(0, ...)
211+
a.count = n;
212+
}
208213
}
209-
}
214+
}

src/Box2D.NET/B2BroadPhases.cs

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using static Box2D.NET.B2ArenaAllocators;
1818
using static Box2D.NET.B2MathFunction;
1919
using static Box2D.NET.B2Shapes;
20+
using static Box2D.NET.B2ParallelFors;
2021

2122
namespace Box2D.NET
2223
{
@@ -341,12 +342,11 @@ public static bool b2PairQueryCallback(int proxyId, ulong userData, ref B2QueryP
341342
}
342343

343344

344-
public static void b2FindPairsTask(int startIndex, int endIndex, uint threadIndex, object context)
345+
public static void b2FindPairsTask(int startIndex, int endIndex, int workerIndex, object context)
345346
{
346-
b2TracyCZoneNC(B2TracyCZone.pair_task, "Pair", B2HexColor.b2_colorMediumSlateBlue, true);
347-
348-
B2_UNUSED(threadIndex);
347+
B2_UNUSED(workerIndex);
349348

349+
b2TracyCZoneNC(B2TracyCZone.pair_task, "Pair", B2HexColor.b2_colorMediumSlateBlue, true);
350350
B2World world = context as B2World;
351351
B2BroadPhase bp = world.broadPhase;
352352

@@ -406,12 +406,8 @@ public static void b2FindPairsTask(int startIndex, int endIndex, uint threadInde
406406
b2TracyCZoneEnd(B2TracyCZone.pair_task);
407407
}
408408

409-
public static void b2UpdateTreesTask(int startIndex, int endIndex, uint threadIndex, object context)
409+
public static void b2UpdateTreesTask(object context)
410410
{
411-
B2_UNUSED(startIndex);
412-
B2_UNUSED(endIndex);
413-
B2_UNUSED(threadIndex);
414-
415411
b2TracyCZoneNC(B2TracyCZone.tree_task, "Rebuild BVH", B2HexColor.b2_colorFireBrick, true);
416412

417413
B2World world = (B2World)context;
@@ -440,7 +436,7 @@ public static void b2UpdateBroadPhasePairs(B2World world)
440436
bp.moveResults = b2AllocateArenaItem<B2MoveResult>(alloc, moveCount, "move results");
441437

442438
// This capacity can be exceeded if there are many overlapping pairs (e.g. all shapes at the origin)
443-
bp.movePairCapacity = 8 * moveCount;
439+
bp.movePairCapacity = 32 * moveCount;
444440

445441
bp.movePairs = b2AllocateArenaItem<B2MovePair>(alloc, bp.movePairCapacity, "move pairs");
446442
b2AtomicStoreInt(ref bp.movePairIndex, 0);
@@ -451,20 +447,23 @@ public static void b2UpdateBroadPhasePairs(B2World world)
451447
#endif
452448

453449
int minRange = 64;
454-
object userPairTask = world.enqueueTaskFcn(b2FindPairsTask, moveCount, minRange, world, world.userTaskContext);
455-
if (userPairTask != null)
456-
{
457-
world.finishTaskFcn(userPairTask, world.userTaskContext);
458-
world.taskCount += 1;
459-
}
450+
b2ParallelFor(world, b2FindPairsTask, moveCount, minRange, world);
451+
452+
b2TracyCZoneNC(B2TracyCZone.create_contacts, "Create Contacts", B2HexColor.b2_colorCoral, true);
460453

461454
// Task that can be done in parallel with the narrow-phase
462455
// - rebuild the collision tree for dynamic and kinematic bodies to keep their query performance good
463-
world.userTreeTask = world.enqueueTaskFcn(b2UpdateTreesTask, 1, 1, world, world.userTaskContext);
464-
world.taskCount += 1;
465-
world.activeTaskCount += world.userTreeTask == null ? 0 : 1;
466-
467-
b2TracyCZoneNC(B2TracyCZone.create_contacts, "Create Contacts", B2HexColor.b2_colorCoral, true);
456+
if (world.taskCount < B2_MAX_TASKS)
457+
{
458+
world.userTreeTask = world.enqueueTaskFcn(b2UpdateTreesTask, world, world.userTaskContext);
459+
world.taskCount += 1;
460+
world.activeTaskCount += world.userTreeTask == null ? 0 : 1;
461+
}
462+
else
463+
{
464+
world.userTreeTask = null;
465+
b2UpdateTreesTask(world);
466+
}
468467

469468
// Single-threaded work
470469
// - Clear move flags
@@ -579,4 +578,4 @@ internal static void b2ValidateNoEnlarged(B2BroadPhase bp)
579578
#endif
580579
}
581580
}
582-
}
581+
}

src/Box2D.NET/B2Constants.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@ public static class B2Constants
1212
// problems, so 100km as a limit should be fine in all cases.
1313
public static float B2_HUGE => (100000.0f * b2GetLengthUnitsPerMeter());
1414

15-
// Maximum parallel workers. Used to size some static arrays.
16-
public const int B2_MAX_WORKERS = 64;
15+
// Maximum parallel workers. Used for some fixed size arrays.
16+
public const int B2_MAX_WORKERS = 32;
17+
18+
// Maximum number of tasks queued per world step. b2EnqueueTaskCallback will never be called
19+
// more than this per world step. This is related to B2_MAX_WORKERS. With 32 workers,
20+
// the maximum observed task count is 130. This allows an external task system to use a fixed
21+
// size array for Box2D task, which may help with creating stable user task pointers.
22+
public const int B2_MAX_TASKS = 256;
1723

1824
// Maximum number of colors in the constraint graph. Constraints that cannot
1925
// find a color are added to the overflow set which are solved single-threaded.
@@ -81,4 +87,4 @@ public static class B2Constants
8187
/// Simple djb2 hash function for determinism testing
8288
public const int B2_HASH_INIT = 5381;
8389
}
84-
}
90+
}

0 commit comments

Comments
 (0)