Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/Box2D.NET/B2Atomics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,44 @@
// SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com)
// SPDX-License-Identifier: MIT

using System.Runtime.CompilerServices;
using System.Threading;

namespace Box2D.NET
{
public static class B2Atomics
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void b2AtomicStoreInt(ref B2AtomicInt a, int value)
{
Interlocked.Exchange(ref a.value, value);
}


[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int b2AtomicLoadInt(ref B2AtomicInt a)
{
return Interlocked.CompareExchange(ref a.value, 0, 0);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int b2AtomicFetchAddInt(ref B2AtomicInt a, int increment)
{
return Interlocked.Add(ref a.value, increment) - increment;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool b2AtomicCompareExchangeInt(ref B2AtomicInt a, int expected, int desired)
{
return expected == Interlocked.CompareExchange(ref a.value, desired, expected);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void b2AtomicStoreU32(ref B2AtomicU32 a, uint value)
{
Interlocked.Exchange(ref a.value, value);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint b2AtomicLoadU32(ref B2AtomicU32 a)
{
return (uint)Interlocked.Read(ref a.value);
Expand Down
79 changes: 79 additions & 0 deletions test/Box2D.NET.Test/B2AtomicTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System;
using System.Threading;
using NUnit.Framework;
using static Box2D.NET.B2Atomics;

namespace Box2D.NET.Test;

public class B2AtomicTests
{
[Test]
public void Test_b2Atomic_Int_Store_And_Load()
{
B2AtomicInt atomic = new B2AtomicInt();
b2AtomicStoreInt(ref atomic, 42);
Assert.That(b2AtomicLoadInt(ref atomic), Is.EqualTo(42));
}

[Test]
public void Test_b2Atomic_Int_FetchAdd()
{
B2AtomicInt atomic = new B2AtomicInt();
b2AtomicStoreInt(ref atomic, 10);
int original = b2AtomicFetchAddInt(ref atomic, 5);
Assert.That(original, Is.EqualTo(10));
Assert.That(b2AtomicLoadInt(ref atomic), Is.EqualTo(15));
}

[Test]
public void Test_b2Atomic_Int_CompareExchange()
{
B2AtomicInt atomic = new B2AtomicInt();
b2AtomicStoreInt(ref atomic, 100);

// Success case
bool exchanged = b2AtomicCompareExchangeInt(ref atomic, 100, 200);
Assert.That(exchanged, Is.True);
Assert.That(b2AtomicLoadInt(ref atomic), Is.EqualTo(200));

// Fail case
exchanged = b2AtomicCompareExchangeInt(ref atomic, 100, 300);
Assert.That(exchanged, Is.False);
Assert.That(b2AtomicLoadInt(ref atomic), Is.EqualTo(200));
}

[Test]
public void Test_b2Atomic_U32_Store_And_Load()
{
B2AtomicU32 atomic = new B2AtomicU32();
b2AtomicStoreU32(ref atomic, 123456789u);
Assert.That(b2AtomicLoadU32(ref atomic), Is.EqualTo(123456789u));
}

[Test]
public void Test_b2Atomic_Int_FetchAdd_IsThreadSafe()
{
B2AtomicInt atomic = new B2AtomicInt();
int threadCount = (int)Math.Round(Environment.ProcessorCount * 2.5f);
int addsPerThread = 100_000;
Thread[] threads = new Thread[threadCount];

for (int i = 0; i < threadCount; i++)
{
threads[i] = new Thread(() =>
{
for (int j = 0; j < addsPerThread; j++)
{
b2AtomicFetchAddInt(ref atomic, 1);
}
});
threads[i].Start();
}

foreach (Thread t in threads)
t.Join();

Assert.That(b2AtomicLoadInt(ref atomic), Is.EqualTo(threadCount * addsPerThread));
}

}
Loading