From 3084ec9c211d5c775a53eb00b3252c8a61604079 Mon Sep 17 00:00:00 2001 From: ikpil Date: Sun, 18 Jan 2026 18:06:30 +0900 Subject: [PATCH 1/2] refactor: Change `userData` type to avoid boxing #80 --- .../Primitives/CustomUserData.cs | 25 ----- .../Samples/Benchmarks/BenchmarkBarrel.cs | 2 +- .../Samples/Benchmarks/BenchmarkSensor.cs | 10 +- src/Box2D.NET.Samples/Samples/Car.cs | 2 +- .../Samples/Characters/Mover.cs | 6 +- .../Samples/Collisions/CastWorld.cs | 10 +- .../Samples/Collisions/OverlapWorld.cs | 6 +- .../Samples/Continuous/BounceHumans.cs | 2 +- .../Samples/Continuous/Drop.cs | 2 +- src/Box2D.NET.Samples/Samples/Donut.cs | 2 +- .../Samples/Events/BodyMove.cs | 8 +- .../Samples/Events/ContactEvent.cs | 16 +-- .../Samples/Events/JointEvent.cs | 16 +-- .../Samples/Events/SensorFunnel.cs | 41 +++++--- .../Samples/Issues/ShapeCastChain.cs | 6 +- .../Samples/Joints/Driving.cs | 2 +- .../Samples/Joints/Ragdoll.cs | 2 +- .../Samples/Joints/ScaleRagdoll.cs | 2 +- .../Samples/Joints/ScissorLift.cs | 2 +- .../Samples/Joints/SoftBody.cs | 2 +- .../Samples/Shapes/CustomFilter.cs | 17 ++-- .../Samples/Stackings/CircleStack.cs | 12 +-- src/Box2D.NET.Samples/Samples/Truck.cs | 2 +- .../Samples/Worlds/LargeWorld.cs | 6 +- src/Box2D.NET.Shared/Benchmarks.cs | 2 +- src/Box2D.NET.Shared/Humans.cs | 2 +- src/Box2D.NET/B2Bodies.cs | 4 +- src/Box2D.NET/B2Body.cs | 2 +- src/Box2D.NET/B2BodyDef.cs | 2 +- src/Box2D.NET/B2BodyMoveEvent.cs | 2 +- src/Box2D.NET/B2ChainDef.cs | 2 +- src/Box2D.NET/B2Joint.cs | 2 +- src/Box2D.NET/B2JointDef.cs | 2 +- src/Box2D.NET/B2JointEvent.cs | 2 +- src/Box2D.NET/B2Joints.cs | 4 +- src/Box2D.NET/B2Shape.cs | 2 +- src/Box2D.NET/B2ShapeDef.cs | 2 +- src/Box2D.NET/B2Shapes.cs | 4 +- src/Box2D.NET/B2UserData.cs | 99 +++++++++++++++++++ src/Box2D.NET/B2UserDataType.cs | 11 +++ src/Box2D.NET/B2World.cs | 4 +- src/Box2D.NET/B2WorldDef.cs | 2 +- src/Box2D.NET/B2Worlds.cs | 4 +- test/Box2D.NET.Test/B2WorldTest.cs | 6 +- 44 files changed, 228 insertions(+), 133 deletions(-) delete mode 100644 src/Box2D.NET.Samples/Primitives/CustomUserData.cs create mode 100644 src/Box2D.NET/B2UserData.cs create mode 100644 src/Box2D.NET/B2UserDataType.cs diff --git a/src/Box2D.NET.Samples/Primitives/CustomUserData.cs b/src/Box2D.NET.Samples/Primitives/CustomUserData.cs deleted file mode 100644 index d9499edd..00000000 --- a/src/Box2D.NET.Samples/Primitives/CustomUserData.cs +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto -// SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) -// SPDX-License-Identifier: MIT - -namespace Box2D.NET.Samples.Primitives; - -public static class CustomUserData -{ - public static CustomUserData Create(T value) - { - var userData = new CustomUserData(); - userData.Value = value; - - return userData; - } -} -public class CustomUserData -{ - public T Value; - - internal CustomUserData() - { - - } -} diff --git a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkBarrel.cs b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkBarrel.cs index 0e7ad7e3..81328403 100644 --- a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkBarrel.cs +++ b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkBarrel.cs @@ -298,7 +298,7 @@ void CreateScene() float jointFriction = 0.05f; float jointHertz = 5.0f; float jointDamping = 0.5f; - CreateHuman(ref m_humans[index], m_worldId, bodyDef.position, scale, jointFriction, jointHertz, jointDamping, index + 1, null, false); + CreateHuman(ref m_humans[index], m_worldId, bodyDef.position, scale, jointFriction, jointHertz, jointDamping, index + 1, B2UserData.Empty, false); } index += 1; diff --git a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkSensor.cs b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkSensor.cs index 07542756..e2420d34 100644 --- a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkSensor.cs +++ b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkSensor.cs @@ -60,7 +60,7 @@ public BenchmarkSensor(SampleContext context) : base(context) B2ShapeDef shapeDef = b2DefaultShapeDef(); shapeDef.isSensor = true; shapeDef.enableSensorEvents = true; - shapeDef.userData = m_activeSensor; + shapeDef.userData = B2UserData.Ref(m_activeSensor); float y = 0.0f; float x = -40.0f * gridSize; @@ -90,7 +90,7 @@ public BenchmarkSensor(SampleContext context) : base(context) m_passiveSensors[j] = new ShapeUserData(); m_passiveSensors[j].row = j; m_passiveSensors[j].active = false; - shapeDef.userData = m_passiveSensors[j]; + shapeDef.userData = B2UserData.Ref(m_passiveSensors[j]); if (j == m_filterRow) { @@ -148,11 +148,11 @@ private bool Filter(in B2ShapeId idA, in B2ShapeId idB) ShapeUserData userData = null; if (b2Shape_IsSensor(idA)) { - userData = b2Shape_GetUserData(idA) as ShapeUserData; + userData = b2Shape_GetUserData(idA).GetRef(); } else if (b2Shape_IsSensor(idB)) { - userData = b2Shape_GetUserData(idB) as ShapeUserData; + userData = b2Shape_GetUserData(idB).GetRef(); } if (userData != null) @@ -186,7 +186,7 @@ public override void Step() ref B2SensorBeginTouchEvent @event = ref events.beginEvents[i]; // shapes on begin touch are always valid - ShapeUserData userData = (ShapeUserData)b2Shape_GetUserData(@event.sensorShapeId); + ShapeUserData userData = b2Shape_GetUserData(@event.sensorShapeId).GetRef(); if (userData.active) { zombies.Add(b2Shape_GetBody(@event.visitorShapeId)); diff --git a/src/Box2D.NET.Samples/Samples/Car.cs b/src/Box2D.NET.Samples/Samples/Car.cs index 531d5515..8d159d67 100644 --- a/src/Box2D.NET.Samples/Samples/Car.cs +++ b/src/Box2D.NET.Samples/Samples/Car.cs @@ -24,7 +24,7 @@ public struct Car public B2JointId m_frontAxleId; public bool m_isSpawned; - public void Spawn(B2WorldId worldId, B2Vec2 position, float scale, float hertz, float dampingRatio, float torque, object userData) + public void Spawn(B2WorldId worldId, B2Vec2 position, float scale, float hertz, float dampingRatio, float torque, B2UserData userData) { B2_ASSERT(m_isSpawned == false); diff --git a/src/Box2D.NET.Samples/Samples/Characters/Mover.cs b/src/Box2D.NET.Samples/Samples/Characters/Mover.cs index 501ccddb..b98269c4 100644 --- a/src/Box2D.NET.Samples/Samples/Characters/Mover.cs +++ b/src/Box2D.NET.Samples/Samples/Characters/Mover.cs @@ -229,7 +229,7 @@ public Mover(SampleContext context) : base(context) m_friendlyShape.clipVelocity = false; shapeDef.filter = new B2Filter(MoverBit, AllBits, 0); - shapeDef.userData = m_friendlyShape; + shapeDef.userData = B2UserData.Ref(m_friendlyShape); B2BodyId bodyId = b2CreateBody(m_worldId, bodyDef); b2CreateCapsuleShape(bodyId, shapeDef, m_capsule); } @@ -262,7 +262,7 @@ public Mover(SampleContext context) : base(context) }; B2ShapeDef shapeDef = b2DefaultShapeDef(); shapeDef.filter = new B2Filter(DynamicBit, AllBits, 0); - shapeDef.userData = m_elevatorShape; + shapeDef.userData = B2UserData.Ref(m_elevatorShape); B2Polygon box = b2MakeBox(2.0f, 0.1f); b2CreatePolygonShape(m_elevatorId, shapeDef, box); @@ -504,7 +504,7 @@ static bool PlaneResultFcn(in B2ShapeId shapeId, ref B2PlaneResult planeResult, Mover self = (Mover)context; float maxPush = float.MaxValue; bool clipVelocity = true; - ShapeUserData userData = (ShapeUserData)b2Shape_GetUserData(shapeId); + ShapeUserData userData = b2Shape_GetUserData(shapeId).GetRef(); if (userData != null) { maxPush = userData.maxPush; diff --git a/src/Box2D.NET.Samples/Samples/Collisions/CastWorld.cs b/src/Box2D.NET.Samples/Samples/Collisions/CastWorld.cs index fcc0bce0..17147af6 100644 --- a/src/Box2D.NET.Samples/Samples/Collisions/CastWorld.cs +++ b/src/Box2D.NET.Samples/Samples/Collisions/CastWorld.cs @@ -194,7 +194,7 @@ void Create(int index) m_bodyIds[m_bodyIndex] = b2CreateBody(m_worldId, bodyDef); B2ShapeDef shapeDef = b2DefaultShapeDef(); - shapeDef.userData = m_userData[m_bodyIndex]; + shapeDef.userData = B2UserData.Ref(m_userData[m_bodyIndex]); m_userData[m_bodyIndex].ignore = false; if (m_bodyIndex == m_ignoreIndex) { @@ -560,7 +560,7 @@ static float RayCastClosestCallback(in B2ShapeId shapeId, B2Vec2 point, B2Vec2 n { CastContext rayContext = (CastContext)context; - ShapeUserData userData = (ShapeUserData)b2Shape_GetUserData(shapeId); + ShapeUserData userData = b2Shape_GetUserData(shapeId).GetRef(); // Ignore a specific shape. Also ignore initial overlap. if ((userData != null && userData.ignore) || fraction == 0.0f) @@ -588,7 +588,7 @@ static float RayCastAnyCallback(in B2ShapeId shapeId, B2Vec2 point, B2Vec2 norma { CastContext rayContext = (CastContext)context; - ShapeUserData userData = (ShapeUserData)b2Shape_GetUserData(shapeId); + ShapeUserData userData = b2Shape_GetUserData(shapeId).GetRef(); // Ignore a specific shape. Also ignore initial overlap. if ((userData != null && userData.ignore) || fraction == 0.0f) @@ -618,7 +618,7 @@ static float RayCastMultipleCallback(in B2ShapeId shapeId, B2Vec2 point, B2Vec2 { CastContext rayContext = (CastContext)context; - ShapeUserData userData = (ShapeUserData)b2Shape_GetUserData(shapeId); + ShapeUserData userData = b2Shape_GetUserData(shapeId).GetRef(); // Ignore a specific shape. Also ignore initial overlap. if ((userData != null && userData.ignore) || fraction == 0.0f) @@ -652,7 +652,7 @@ static float RayCastSortedCallback(in B2ShapeId shapeId, B2Vec2 point, B2Vec2 no { CastContext rayContext = (CastContext)context; - ShapeUserData userData = (ShapeUserData)b2Shape_GetUserData(shapeId); + ShapeUserData userData = b2Shape_GetUserData(shapeId).GetRef(); // Ignore a specific shape. Also ignore initial overlap. if ((userData != null && userData.ignore) || fraction == 0.0f) diff --git a/src/Box2D.NET.Samples/Samples/Collisions/OverlapWorld.cs b/src/Box2D.NET.Samples/Samples/Collisions/OverlapWorld.cs index 9ef77475..89edd207 100644 --- a/src/Box2D.NET.Samples/Samples/Collisions/OverlapWorld.cs +++ b/src/Box2D.NET.Samples/Samples/Collisions/OverlapWorld.cs @@ -66,7 +66,7 @@ private static Sample Create(SampleContext context) static bool OverlapResultFcn(in B2ShapeId shapeId, object context) { - ShapeUserData userData = (ShapeUserData)b2Shape_GetUserData(shapeId); + ShapeUserData userData = b2Shape_GetUserData(shapeId).GetRef(); if (userData != null && userData.ignore) { // continue the query @@ -177,7 +177,7 @@ void Create(int index) m_bodyIds[m_bodyIndex] = b2CreateBody(m_worldId, bodyDef); B2ShapeDef shapeDef = b2DefaultShapeDef(); - shapeDef.userData = m_userData[m_bodyIndex]; + shapeDef.userData = B2UserData.Ref(m_userData[m_bodyIndex]); m_userData[m_bodyIndex].index = m_bodyIndex; m_userData[m_bodyIndex].ignore = false; if (m_bodyIndex == m_ignoreIndex) @@ -373,7 +373,7 @@ public override void Step() for (int i = 0; i < m_doomCount; ++i) { B2ShapeId shapeId = m_doomIds[i]; - ShapeUserData userData = (ShapeUserData)b2Shape_GetUserData(shapeId); + ShapeUserData userData = b2Shape_GetUserData(shapeId).GetRef(); if (userData == null) { continue; diff --git a/src/Box2D.NET.Samples/Samples/Continuous/BounceHumans.cs b/src/Box2D.NET.Samples/Samples/Continuous/BounceHumans.cs index 6060bc67..7bef0f9d 100644 --- a/src/Box2D.NET.Samples/Samples/Continuous/BounceHumans.cs +++ b/src/Box2D.NET.Samples/Samples/Continuous/BounceHumans.cs @@ -85,7 +85,7 @@ public override void Step() float jointDampingRatio = 0.1f; CreateHuman(ref m_humans[m_humanCount], m_worldId, new B2Vec2(0.0f, 5.0f), 1.0f, jointFrictionTorque, jointHertz, - jointDampingRatio, 1, null, true); + jointDampingRatio, 1, B2UserData.Empty, true); // Human_SetVelocity( m_humans + m_humanCount, { 10.0f - 5.0f * m_humanCount, -20.0f + 5.0f * m_humanCount } ); m_countDown = 2.0f; diff --git a/src/Box2D.NET.Samples/Samples/Continuous/Drop.cs b/src/Box2D.NET.Samples/Samples/Continuous/Drop.cs index e2df8f51..0de0bc94 100644 --- a/src/Box2D.NET.Samples/Samples/Continuous/Drop.cs +++ b/src/Box2D.NET.Samples/Samples/Continuous/Drop.cs @@ -216,7 +216,7 @@ void Scene3() float jointHertz = 1.0f; float jointDampingRatio = 0.5f; - CreateHuman(ref m_human, m_worldId, new B2Vec2(0.0f, 40.0f), 1.0f, jointFrictionTorque, jointHertz, jointDampingRatio, 1, null, true); + CreateHuman(ref m_human, m_worldId, new B2Vec2(0.0f, 40.0f), 1.0f, jointFrictionTorque, jointHertz, jointDampingRatio, 1, B2UserData.Empty, true); m_frameCount = 1; } diff --git a/src/Box2D.NET.Samples/Samples/Donut.cs b/src/Box2D.NET.Samples/Samples/Donut.cs index 7568ea40..1a1d85b0 100644 --- a/src/Box2D.NET.Samples/Samples/Donut.cs +++ b/src/Box2D.NET.Samples/Samples/Donut.cs @@ -27,7 +27,7 @@ public Donut() B2_ASSERT(m_sides == B2FixedArray7.Size); } - public void Create(B2WorldId worldId, B2Vec2 position, float scale, int groupIndex, bool enableSensorEvents, object userData) + public void Create(B2WorldId worldId, B2Vec2 position, float scale, int groupIndex, bool enableSensorEvents, B2UserData userData) { B2_ASSERT(m_isSpawned == false); diff --git a/src/Box2D.NET.Samples/Samples/Events/BodyMove.cs b/src/Box2D.NET.Samples/Samples/Events/BodyMove.cs index 237b1a93..8d053bc4 100644 --- a/src/Box2D.NET.Samples/Samples/Events/BodyMove.cs +++ b/src/Box2D.NET.Samples/Samples/Events/BodyMove.cs @@ -95,7 +95,7 @@ void CreateBodies() { bodyDef.position = new B2Vec2(x, y); bodyDef.isBullet = (m_count % 12 == 0); - bodyDef.userData = CustomUserData.Create(m_count); + bodyDef.userData = B2UserData.Signed(m_count); m_bodyIds[m_count] = b2CreateBody(m_worldId, bodyDef); m_sleeping[m_count] = false; @@ -140,7 +140,7 @@ public override void Step() { ref readonly B2BodyMoveEvent @event = ref events.moveEvents[i]; - if (@event.userData == null) + if (@event.userData.IsEmpty()) { // The mouse joint body has no user data continue; @@ -157,9 +157,9 @@ public override void Step() // this shows a somewhat contrived way to track body sleeping //B2BodyId bodyId = (B2BodyId)@event.userData; // todo: @ikpil check struct casting - var diff = (CustomUserData)@event.userData; + var diff = @event.userData.GetSigned(-1); //ptrdiff_t diff = bodyId - m_bodyIds; - ref bool sleeping = ref m_sleeping[diff.Value]; + ref bool sleeping = ref m_sleeping[diff]; if (@event.fellAsleep) { diff --git a/src/Box2D.NET.Samples/Samples/Events/ContactEvent.cs b/src/Box2D.NET.Samples/Samples/Events/ContactEvent.cs index fd6cc8ba..bca50d2d 100644 --- a/src/Box2D.NET.Samples/Samples/Events/ContactEvent.cs +++ b/src/Box2D.NET.Samples/Samples/Events/ContactEvent.cs @@ -32,7 +32,7 @@ public class ContactEvent : Sample private B2BodyId m_playerId; private B2ShapeId m_coreShapeId; private B2BodyId[] m_debrisIds = new B2BodyId[e_count]; - private CustomUserData[] m_bodyUserData = new CustomUserData[e_count]; + private B2UserData[] m_bodyUserData = new B2UserData[e_count]; private float m_force; private float m_wait; @@ -86,7 +86,7 @@ public ContactEvent(SampleContext context) : base(context) for (int i = 0; i < e_count; ++i) { m_debrisIds[i] = b2_nullBodyId; - m_bodyUserData[i] = CustomUserData.Create(i); + m_bodyUserData[i] = B2UserData.Signed(i); } m_wait = 0.5f; @@ -273,8 +273,8 @@ public override void Step() if (B2_ID_EQUALS(bodyIdA, m_playerId)) { - CustomUserData userDataB = b2Body_GetUserData(bodyIdB) as CustomUserData; - if (userDataB == null) + var userDataB = b2Body_GetUserData(bodyIdB); + if (userDataB.IsEmpty()) { if (B2_ID_EQUALS(@event.shapeIdA, m_coreShapeId) == false && destroyCount < e_count) { @@ -300,7 +300,7 @@ public override void Step() } else if (attachCount < e_count) { - debrisToAttach[attachCount] = userDataB.Value; + debrisToAttach[attachCount] = (int)userDataB.GetSigned(-1); attachCount += 1; } } @@ -308,8 +308,8 @@ public override void Step() { // Only expect events for the player B2_ASSERT(B2_ID_EQUALS(bodyIdB, m_playerId)); - CustomUserData userDataA = b2Body_GetUserData(bodyIdA) as CustomUserData; - if (userDataA == null) + var userDataA = b2Body_GetUserData(bodyIdA); + if (userDataA.IsEmpty()) { if (B2_ID_EQUALS(@event.shapeIdB, m_coreShapeId) == false && destroyCount < e_count) { @@ -335,7 +335,7 @@ public override void Step() } else if (attachCount < e_count) { - debrisToAttach[attachCount] = userDataA.Value; + debrisToAttach[attachCount] = (int)userDataA.GetSigned(-1); attachCount += 1; } } diff --git a/src/Box2D.NET.Samples/Samples/Events/JointEvent.cs b/src/Box2D.NET.Samples/Samples/Events/JointEvent.cs index f5eb0985..26e80d8a 100644 --- a/src/Box2D.NET.Samples/Samples/Events/JointEvent.cs +++ b/src/Box2D.NET.Samples/Samples/Events/JointEvent.cs @@ -80,7 +80,7 @@ public JointEvent(SampleContext context) : base(context) jointDef.@base.forceThreshold = forceThreshold; jointDef.@base.torqueThreshold = torqueThreshold; jointDef.@base.collideConnected = true; - jointDef.@base.userData = CustomUserData.Create(index); + jointDef.@base.userData = B2UserData.Signed(index); m_jointIds[index] = b2CreateDistanceJoint(m_worldId, jointDef); } @@ -104,7 +104,7 @@ public JointEvent(SampleContext context) : base(context) jointDef.@base.forceThreshold = forceThreshold; jointDef.@base.torqueThreshold = torqueThreshold; jointDef.@base.collideConnected = true; - jointDef.@base.userData = CustomUserData.Create(index); + jointDef.@base.userData = B2UserData.Signed(index); m_jointIds[index] = b2CreateMotorJoint(m_worldId, jointDef); } @@ -128,7 +128,7 @@ public JointEvent(SampleContext context) : base(context) jointDef.@base.forceThreshold = forceThreshold; jointDef.@base.torqueThreshold = torqueThreshold; jointDef.@base.collideConnected = true; - jointDef.@base.userData = CustomUserData.Create(index); + jointDef.@base.userData = B2UserData.Signed(index); m_jointIds[index] = b2CreatePrismaticJoint(m_worldId, jointDef); } @@ -152,7 +152,7 @@ public JointEvent(SampleContext context) : base(context) jointDef.@base.forceThreshold = forceThreshold; jointDef.@base.torqueThreshold = torqueThreshold; jointDef.@base.collideConnected = true; - jointDef.@base.userData = CustomUserData.Create(index); + jointDef.@base.userData = B2UserData.Signed(index); m_jointIds[index] = b2CreateRevoluteJoint(m_worldId, jointDef); } @@ -178,7 +178,7 @@ public JointEvent(SampleContext context) : base(context) jointDef.@base.forceThreshold = forceThreshold; jointDef.@base.torqueThreshold = torqueThreshold; jointDef.@base.collideConnected = true; - jointDef.@base.userData = CustomUserData.Create(index); + jointDef.@base.userData = B2UserData.Signed(index); m_jointIds[index] = b2CreateWeldJoint(m_worldId, jointDef); } @@ -210,7 +210,7 @@ public JointEvent(SampleContext context) : base(context) jointDef.@base.forceThreshold = forceThreshold; jointDef.@base.torqueThreshold = torqueThreshold; jointDef.@base.collideConnected = true; - jointDef.@base.userData = CustomUserData.Create(index); + jointDef.@base.userData = B2UserData.Signed(index); m_jointIds[index] = b2CreateWheelJoint(m_worldId, jointDef); } @@ -231,8 +231,8 @@ public override void Step() if (b2Joint_IsValid(@event.jointId)) { - CustomUserData userData = @event.userData as CustomUserData; - int index = userData?.Value ?? -1; + var userData = @event.userData.GetSigned(-1); + var index = userData; B2_ASSERT(0 <= index && index < e_count); b2DestroyJoint(@event.jointId, true); m_jointIds[index] = b2_nullJointId; diff --git a/src/Box2D.NET.Samples/Samples/Events/SensorFunnel.cs b/src/Box2D.NET.Samples/Samples/Events/SensorFunnel.cs index 61467b28..0fbf3dfe 100644 --- a/src/Box2D.NET.Samples/Samples/Events/SensorFunnel.cs +++ b/src/Box2D.NET.Samples/Samples/Events/SensorFunnel.cs @@ -20,7 +20,8 @@ namespace Box2D.NET.Samples.Samples.Events; public class SensorFunnel : Sample { - private static readonly int SampleSensorBeginEvent = SampleFactory.Shared.RegisterSample("Events", "Sensor Funnel", Create); + private static readonly int SampleSensorBeginEvent = + SampleFactory.Shared.RegisterSample("Events", "Sensor Funnel", Create); private const int e_donut = 1; private const int e_human = 2; @@ -62,12 +63,18 @@ public SensorFunnel(SampleContext context) : base(context) B2Vec2[] points = new B2Vec2[] { - new B2Vec2(-16.8672504f, 31.088623f), new B2Vec2(16.8672485f, 31.088623f), new B2Vec2(16.8672485f, 17.1978741f), - new B2Vec2(8.26824951f, 11.906374f), new B2Vec2(16.8672485f, 11.906374f), new B2Vec2(16.8672485f, -0.661376953f), - new B2Vec2(8.26824951f, -5.953125f), new B2Vec2(16.8672485f, -5.953125f), new B2Vec2(16.8672485f, -13.229126f), - new B2Vec2(3.63799858f, -23.151123f), new B2Vec2(3.63799858f, -31.088623f), new B2Vec2(-3.63800049f, -31.088623f), - new B2Vec2(-3.63800049f, -23.151123f), new B2Vec2(-16.8672504f, -13.229126f), new B2Vec2(-16.8672504f, -5.953125f), - new B2Vec2(-8.26825142f, -5.953125f), new B2Vec2(-16.8672504f, -0.661376953f), new B2Vec2(-16.8672504f, 11.906374f), + new B2Vec2(-16.8672504f, 31.088623f), new B2Vec2(16.8672485f, 31.088623f), + new B2Vec2(16.8672485f, 17.1978741f), + new B2Vec2(8.26824951f, 11.906374f), new B2Vec2(16.8672485f, 11.906374f), + new B2Vec2(16.8672485f, -0.661376953f), + new B2Vec2(8.26824951f, -5.953125f), new B2Vec2(16.8672485f, -5.953125f), + new B2Vec2(16.8672485f, -13.229126f), + new B2Vec2(3.63799858f, -23.151123f), new B2Vec2(3.63799858f, -31.088623f), + new B2Vec2(-3.63800049f, -31.088623f), + new B2Vec2(-3.63800049f, -23.151123f), new B2Vec2(-16.8672504f, -13.229126f), + new B2Vec2(-16.8672504f, -5.953125f), + new B2Vec2(-8.26825142f, -5.953125f), new B2Vec2(-16.8672504f, -0.661376953f), + new B2Vec2(-16.8672504f, 11.906374f), new B2Vec2(-8.26825142f, 11.906374f), new B2Vec2(-16.8672504f, 17.1978741f), }; @@ -199,7 +206,7 @@ void CreateElement() { ref Donut donut = ref m_donuts[index]; // donut->Spawn(m_worldId, center, index + 1, donut); - donut.Create(m_worldId, center, 1.0f, 0, true, CustomUserData.Create(index)); + donut.Create(m_worldId, center, 1.0f, 0, true, B2UserData.Signed(index)); } else { @@ -209,7 +216,8 @@ void CreateElement() float jointHertz = 6.0f; float jointDamping = 0.5f; bool colorize = true; - CreateHuman(ref human, m_worldId, center, scale, jointFriction, jointHertz, jointDamping, index + 1, CustomUserData.Create(index), colorize); + CreateHuman(ref human, m_worldId, center, scale, jointFriction, jointHertz, jointDamping, + index + 1, B2UserData.Signed(index), colorize); Human_EnableSensorEvents(ref human, true); } @@ -259,7 +267,8 @@ public override void UpdateGui() float fontSize = ImGui.GetFontSize(); float height = 90.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); + ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), + ImGuiCond.Once); ImGui.SetNextWindowSize(new Vector2(140.0f, height)); ImGui.Begin("Sensor Event", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); @@ -299,10 +308,10 @@ public override void Step() if (m_type == (int)e_donut) { - CustomUserData donut = b2Body_GetUserData(bodyId) as CustomUserData; - if (donut != null) + var donut = b2Body_GetUserData(bodyId); + if (!donut.IsEmpty()) { - int index = donut.Value; + var index = donut.GetSigned(-1); B2_ASSERT(0 <= index && index < (int)e_count); // Defer destruction to avoid double destruction and event invalidation (orphaned shape ids) @@ -311,10 +320,10 @@ public override void Step() } else { - CustomUserData human = b2Body_GetUserData(bodyId) as CustomUserData; - if (human != null) + var human = b2Body_GetUserData(bodyId); + if (!human.IsEmpty()) { - int index = human.Value; + var index = human.GetSigned(-1); B2_ASSERT(0 <= index && index < (int)e_count); // Defer destruction to avoid double destruction and event invalidation (orphaned shape ids) diff --git a/src/Box2D.NET.Samples/Samples/Issues/ShapeCastChain.cs b/src/Box2D.NET.Samples/Samples/Issues/ShapeCastChain.cs index 0d6d8996..af8233fe 100644 --- a/src/Box2D.NET.Samples/Samples/Issues/ShapeCastChain.cs +++ b/src/Box2D.NET.Samples/Samples/Issues/ShapeCastChain.cs @@ -69,7 +69,7 @@ public ShapeCastChain(SampleContext context) : base(context) }; B2ChainDef worldChainDef = b2DefaultChainDef(); - worldChainDef.userData = null; + worldChainDef.userData = B2UserData.Empty; worldChainDef.points = points; worldChainDef.count = 4; worldChainDef.filter.categoryBits = 0x1; @@ -90,14 +90,14 @@ public ShapeCastChain(SampleContext context) : base(context) characterBodyDef.isAwake = false; characterBodyDef.motionLocks.angularZ = true; characterBodyDef.isEnabled = true; - characterBodyDef.userData = null; + characterBodyDef.userData = B2UserData.Empty; characterBodyDef.type = B2BodyType.b2_kinematicBody; characterBodyId_ = b2CreateBody(m_worldId, characterBodyDef); B2ShapeDef characterShapeDef = b2DefaultShapeDef(); - characterShapeDef.userData = null; + characterShapeDef.userData = B2UserData.Empty; characterShapeDef.filter.categoryBits = 0x1; characterShapeDef.filter.maskBits = 0x1; characterShapeDef.filter.groupIndex = 0; diff --git a/src/Box2D.NET.Samples/Samples/Joints/Driving.cs b/src/Box2D.NET.Samples/Samples/Joints/Driving.cs index cb3d744c..6aa07904 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/Driving.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/Driving.cs @@ -208,7 +208,7 @@ public Driving(SampleContext context) : base(context) m_hertz = 5.0f; m_dampingRatio = 0.7f; - m_car.Spawn(m_worldId, new B2Vec2(0.0f, 0.0f), 1.0f, m_hertz, m_dampingRatio, m_torque, null); + m_car.Spawn(m_worldId, new B2Vec2(0.0f, 0.0f), 1.0f, m_hertz, m_dampingRatio, m_torque, B2UserData.Empty); } public override void UpdateGui() diff --git a/src/Box2D.NET.Samples/Samples/Joints/Ragdoll.cs b/src/Box2D.NET.Samples/Samples/Joints/Ragdoll.cs index cb057aba..36f71be4 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/Ragdoll.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/Ragdoll.cs @@ -57,7 +57,7 @@ public Ragdoll(SampleContext context) : base(context) void Spawn() { - CreateHuman(ref m_human, m_worldId, new B2Vec2(0.0f, 25.0f), 1.0f, m_jointFrictionTorque, m_jointHertz, m_jointDampingRatio, 1, null, false); + CreateHuman(ref m_human, m_worldId, new B2Vec2(0.0f, 25.0f), 1.0f, m_jointFrictionTorque, m_jointHertz, m_jointDampingRatio, 1, B2UserData.Empty, false); //Human_ApplyRandomAngularImpulse(ref m_human, 10.0f); } diff --git a/src/Box2D.NET.Samples/Samples/Joints/ScaleRagdoll.cs b/src/Box2D.NET.Samples/Samples/Joints/ScaleRagdoll.cs index 31b87ed8..de718a38 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/ScaleRagdoll.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/ScaleRagdoll.cs @@ -55,7 +55,7 @@ void Spawn() float jointFrictionTorque = 0.03f; float jointHertz = 1.0f; float jointDampingRatio = 0.5f; - CreateHuman(ref m_human, m_worldId, new B2Vec2(0.0f, 5.0f), m_scale, jointFrictionTorque, jointHertz, jointDampingRatio, 1, null, false); + CreateHuman(ref m_human, m_worldId, new B2Vec2(0.0f, 5.0f), m_scale, jointFrictionTorque, jointHertz, jointDampingRatio, 1, B2UserData.Empty, false); Human_ApplyRandomAngularImpulse(ref m_human, 10.0f); } diff --git a/src/Box2D.NET.Samples/Samples/Joints/ScissorLift.cs b/src/Box2D.NET.Samples/Samples/Joints/ScissorLift.cs index 47bca67f..928648a4 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/ScissorLift.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/ScissorLift.cs @@ -200,7 +200,7 @@ public ScissorLift(SampleContext context) : base(context) m_liftJointId = b2CreateDistanceJoint(m_worldId, distanceDef); Car car = new Car(); - car.Spawn(m_worldId, new B2Vec2(0.0f, y + 2.0f), 1.0f, 3.0f, 0.7f, 0.0f, null); + car.Spawn(m_worldId, new B2Vec2(0.0f, y + 2.0f), 1.0f, 3.0f, 0.7f, 0.0f, B2UserData.Empty); } } diff --git a/src/Box2D.NET.Samples/Samples/Joints/SoftBody.cs b/src/Box2D.NET.Samples/Samples/Joints/SoftBody.cs index adff6334..4fce83ac 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/SoftBody.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/SoftBody.cs @@ -36,6 +36,6 @@ public SoftBody(SampleContext context) : base(context) } m_donut = new(); - m_donut.Create(m_worldId, new B2Vec2(0.0f, 10.0f), 2.0f, 0, false, null); + m_donut.Create(m_worldId, new B2Vec2(0.0f, 10.0f), 2.0f, 0, false, B2UserData.Empty); } } \ No newline at end of file diff --git a/src/Box2D.NET.Samples/Samples/Shapes/CustomFilter.cs b/src/Box2D.NET.Samples/Samples/Shapes/CustomFilter.cs index 49002628..7cd0d6b5 100644 --- a/src/Box2D.NET.Samples/Samples/Shapes/CustomFilter.cs +++ b/src/Box2D.NET.Samples/Samples/Shapes/CustomFilter.cs @@ -14,7 +14,8 @@ namespace Box2D.NET.Samples.Samples.Shapes; // This shows how to use custom filtering public class CustomFilter : Sample { - private static readonly int SampleCustomFilter = SampleFactory.Shared.RegisterSample("Shapes", "Custom Filter", Create); + private static readonly int SampleCustomFilter = + SampleFactory.Shared.RegisterSample("Shapes", "Custom Filter", Create); public const int e_count = 10; @@ -60,7 +61,7 @@ public CustomFilter(SampleContext context) : base(context) bodyDef.position = new B2Vec2(x, 5.0f); m_bodyIds[i] = b2CreateBody(m_worldId, bodyDef); - shapeDef.userData = i + 1; + shapeDef.userData = B2UserData.Signed(i + 1); m_shapeIds[i] = b2CreatePolygonShape(m_bodyIds[i], shapeDef, box); x += 2.0f; } @@ -74,16 +75,16 @@ public override void Step() bool ShouldCollide(in B2ShapeId shapeIdA, in B2ShapeId shapeIdB) { - object userDataA = b2Shape_GetUserData(shapeIdA); - object userDataB = b2Shape_GetUserData(shapeIdB); + var userDataA = b2Shape_GetUserData(shapeIdA); + var userDataB = b2Shape_GetUserData(shapeIdB); - if (userDataA == null || userDataB == null) + if (userDataA.IsEmpty() || userDataB.IsEmpty()) { return true; } - int indexA = (int)userDataA; - int indexB = (int)userDataB; + int indexA = (int)userDataA.GetSigned(-1); + int indexB = (int)userDataB.GetSigned(-1); return ((indexA & 1) + (indexB & 1)) != 1; } @@ -100,7 +101,7 @@ public override void Draw() base.Draw(); DrawTextLine("Custom filter disables collision between odd and even shapes"); - + for (int i = 0; i < e_count; ++i) { diff --git a/src/Box2D.NET.Samples/Samples/Stackings/CircleStack.cs b/src/Box2D.NET.Samples/Samples/Stackings/CircleStack.cs index 4ea5e0b3..34f7fbdb 100644 --- a/src/Box2D.NET.Samples/Samples/Stackings/CircleStack.cs +++ b/src/Box2D.NET.Samples/Samples/Stackings/CircleStack.cs @@ -48,7 +48,7 @@ public CircleStack(SampleContext context) : base(context) B2BodyId groundId = b2CreateBody(m_worldId, bodyDef); B2ShapeDef shapeDef = b2DefaultShapeDef(); - shapeDef.userData = shapeIndex; + shapeDef.userData = B2UserData.Signed(shapeIndex); shapeIndex += 1; B2Segment segment = new B2Segment(new B2Vec2(-10.0f, 0.0f), new B2Vec2(10.0f, 0.0f)); @@ -78,7 +78,7 @@ public CircleStack(SampleContext context) : base(context) B2BodyId bodyId = b2CreateBody(m_worldId, bodyDef); - shapeDef.userData = shapeIndex; + shapeDef.userData = B2UserData.Signed(shapeIndex); shapeDef.density = 1.0f + 4.0f * i; shapeIndex += 1; b2CreateCircleShape(bodyId, shapeDef, circle); @@ -97,10 +97,10 @@ public override void Draw() { ref B2ContactHitEvent @event = ref events.hitEvents[i]; - object userDataA = b2Shape_GetUserData(@event.shapeIdA); - object userDataB = b2Shape_GetUserData(@event.shapeIdB); - int indexA = (int)userDataA; - int indexB = (int)userDataB; + var userDataA = b2Shape_GetUserData(@event.shapeIdA); + var userDataB = b2Shape_GetUserData(@event.shapeIdB); + int indexA = (int)userDataA.GetSigned(-1); + int indexB = (int)userDataB.GetSigned(-1); DrawPoint(m_draw, @event.point, 10.0f, B2HexColor.b2_colorWhite); diff --git a/src/Box2D.NET.Samples/Samples/Truck.cs b/src/Box2D.NET.Samples/Samples/Truck.cs index 5b5fd21b..f400b0b9 100644 --- a/src/Box2D.NET.Samples/Samples/Truck.cs +++ b/src/Box2D.NET.Samples/Samples/Truck.cs @@ -28,7 +28,7 @@ public Truck() { } - public void Spawn(B2WorldId worldId, B2Vec2 position, float scale, float hertz, float dampingRatio, float torque, float density, object userData) + public void Spawn(B2WorldId worldId, B2Vec2 position, float scale, float hertz, float dampingRatio, float torque, float density, B2UserData userData) { B2_ASSERT(m_isSpawned == false); diff --git a/src/Box2D.NET.Samples/Samples/Worlds/LargeWorld.cs b/src/Box2D.NET.Samples/Samples/Worlds/LargeWorld.cs index 296deb1b..3b3cf668 100644 --- a/src/Box2D.NET.Samples/Samples/Worlds/LargeWorld.cs +++ b/src/Box2D.NET.Samples/Samples/Worlds/LargeWorld.cs @@ -140,7 +140,7 @@ public LargeWorld(SampleContext context) : base(context) for (int i = 0; i < 5; ++i) { Human human = new Human(); - CreateHuman(ref human, m_worldId, position, 1.5f, 0.05f, 0.0f, 0.0f, humanIndex + 1, null, false); + CreateHuman(ref human, m_worldId, position, 1.5f, 0.05f, 0.0f, 0.0f, humanIndex + 1, B2UserData.Empty, false); humanIndex += 1; position.X += 1.0f; } @@ -152,13 +152,13 @@ public LargeWorld(SampleContext context) : base(context) for (int i = 0; i < 5; ++i) { Donut donut = new Donut(); - donut.Create(m_worldId, position, 0.75f, 0, false, null); + donut.Create(m_worldId, position, 0.75f, 0, false, B2UserData.Empty); position.X += 2.0f; } } } - m_car.Spawn(m_worldId, new B2Vec2(xStart + 20.0f, 40.0f), 10.0f, 2.0f, 0.7f, 2000.0f, null); + m_car.Spawn(m_worldId, new B2Vec2(xStart + 20.0f, 40.0f), 10.0f, 2.0f, 0.7f, 2000.0f, B2UserData.Empty); m_cycleIndex = 0; m_speed = 0.0f; diff --git a/src/Box2D.NET.Shared/Benchmarks.cs b/src/Box2D.NET.Shared/Benchmarks.cs index e9fd32d0..79541899 100644 --- a/src/Box2D.NET.Shared/Benchmarks.cs +++ b/src/Box2D.NET.Shared/Benchmarks.cs @@ -270,7 +270,7 @@ public static void CreateGroup(RainData rainData, B2WorldId worldId, int rowInde for (int i = 0; i < (int)RainConstants.RAIN_GROUP_SIZE; ++i) { ref Human human = ref rainData.groups[groupIndex].humans[i]; - CreateHuman(ref human, worldId, position, scale, jointFriction, jointHertz, jointDamping, i + 1, null, false); + CreateHuman(ref human, worldId, position, scale, jointFriction, jointHertz, jointDamping, i + 1, B2UserData.Empty, false); position.X += 0.5f; } } diff --git a/src/Box2D.NET.Shared/Humans.cs b/src/Box2D.NET.Shared/Humans.cs index f57d9757..3c4631f2 100644 --- a/src/Box2D.NET.Shared/Humans.cs +++ b/src/Box2D.NET.Shared/Humans.cs @@ -20,7 +20,7 @@ namespace Box2D.NET.Shared public static class Humans { public static void CreateHuman(ref Human human, B2WorldId worldId, B2Vec2 position, float scale, float frictionTorque, float hertz, float dampingRatio, - int groupIndex, object userData, bool colorize) + int groupIndex, B2UserData userData, bool colorize) { B2_ASSERT(human.isSpawned == false); diff --git a/src/Box2D.NET/B2Bodies.cs b/src/Box2D.NET/B2Bodies.cs index 2edc7008..f0b59b17 100644 --- a/src/Box2D.NET/B2Bodies.cs +++ b/src/Box2D.NET/B2Bodies.cs @@ -1427,7 +1427,7 @@ public static string b2Body_GetName(B2BodyId bodyId) } /// Set the user data for a body - public static void b2Body_SetUserData(B2BodyId bodyId, object userData) + public static void b2Body_SetUserData(B2BodyId bodyId, B2UserData userData) { B2World world = b2GetWorld(bodyId.world0); B2Body body = b2GetBodyFullId(world, bodyId); @@ -1435,7 +1435,7 @@ public static void b2Body_SetUserData(B2BodyId bodyId, object userData) } /// Get the user data stored in a body - public static object b2Body_GetUserData(B2BodyId bodyId) + public static B2UserData b2Body_GetUserData(B2BodyId bodyId) { B2World world = b2GetWorld(bodyId.world0); B2Body body = b2GetBodyFullId(world, bodyId); diff --git a/src/Box2D.NET/B2Body.cs b/src/Box2D.NET/B2Body.cs index c85ab5d0..2a6495e1 100644 --- a/src/Box2D.NET/B2Body.cs +++ b/src/Box2D.NET/B2Body.cs @@ -9,7 +9,7 @@ public class B2Body { public string name; - public object userData; + public B2UserData userData; // index of solver set stored in b2World // may be B2_NULL_INDEX diff --git a/src/Box2D.NET/B2BodyDef.cs b/src/Box2D.NET/B2BodyDef.cs index 39f43973..b8d5626f 100644 --- a/src/Box2D.NET/B2BodyDef.cs +++ b/src/Box2D.NET/B2BodyDef.cs @@ -51,7 +51,7 @@ public struct B2BodyDef public string name; /// Use this to store application specific body data. - public object userData; + public B2UserData userData; /// Motions locks to restrict linear and angular movement. /// Caution: may lead to softer constraints along the locked direction diff --git a/src/Box2D.NET/B2BodyMoveEvent.cs b/src/Box2D.NET/B2BodyMoveEvent.cs index 6d7d6cca..16045e25 100644 --- a/src/Box2D.NET/B2BodyMoveEvent.cs +++ b/src/Box2D.NET/B2BodyMoveEvent.cs @@ -16,7 +16,7 @@ namespace Box2D.NET /// @note If sleeping is disabled all dynamic and kinematic bodies will trigger move events. public struct B2BodyMoveEvent { - public object userData; + public B2UserData userData; public B2Transform transform; public B2BodyId bodyId; public bool fellAsleep; diff --git a/src/Box2D.NET/B2ChainDef.cs b/src/Box2D.NET/B2ChainDef.cs index 715859fc..02493c81 100644 --- a/src/Box2D.NET/B2ChainDef.cs +++ b/src/Box2D.NET/B2ChainDef.cs @@ -22,7 +22,7 @@ namespace Box2D.NET public struct B2ChainDef { /// Use this to store application specific shape data. - public object userData; + public B2UserData userData; /// An array of at least 4 points. These are cloned and may be temporary. public B2Vec2[] points; diff --git a/src/Box2D.NET/B2Joint.cs b/src/Box2D.NET/B2Joint.cs index 8583c901..5d1ecae3 100644 --- a/src/Box2D.NET/B2Joint.cs +++ b/src/Box2D.NET/B2Joint.cs @@ -7,7 +7,7 @@ namespace Box2D.NET // Map from b2JointId to b2Joint in the solver sets public class B2Joint { - public object userData; + public B2UserData userData; // index of simulation set stored in b2World // B2_NULL_INDEX when slot is free diff --git a/src/Box2D.NET/B2JointDef.cs b/src/Box2D.NET/B2JointDef.cs index 9a2ba664..0696fc9d 100644 --- a/src/Box2D.NET/B2JointDef.cs +++ b/src/Box2D.NET/B2JointDef.cs @@ -11,7 +11,7 @@ namespace Box2D.NET public struct B2JointDef { /// User data pointer - public object userData; + public B2UserData userData; /// The first attached body public B2BodyId bodyIdA; diff --git a/src/Box2D.NET/B2JointEvent.cs b/src/Box2D.NET/B2JointEvent.cs index 98d94eb2..e1138b35 100644 --- a/src/Box2D.NET/B2JointEvent.cs +++ b/src/Box2D.NET/B2JointEvent.cs @@ -12,6 +12,6 @@ public struct B2JointEvent public B2JointId jointId; /// The user data from the joint for convenience - public object userData; + public B2UserData userData; } } diff --git a/src/Box2D.NET/B2Joints.cs b/src/Box2D.NET/B2Joints.cs index 699e0369..4f195ea9 100644 --- a/src/Box2D.NET/B2Joints.cs +++ b/src/Box2D.NET/B2Joints.cs @@ -881,14 +881,14 @@ public static bool b2Joint_GetCollideConnected(B2JointId jointId) return joint.collideConnected; } - public static void b2Joint_SetUserData(B2JointId jointId, object userData) + public static void b2Joint_SetUserData(B2JointId jointId, B2UserData userData) { B2World world = b2GetWorld(jointId.world0); B2Joint joint = b2GetJointFullId(world, jointId); joint.userData = userData; } - public static object b2Joint_GetUserData(B2JointId jointId) + public static B2UserData b2Joint_GetUserData(B2JointId jointId) { B2World world = b2GetWorld(jointId.world0); B2Joint joint = b2GetJointFullId(world, jointId); diff --git a/src/Box2D.NET/B2Shape.cs b/src/Box2D.NET/B2Shape.cs index 2c53dec7..b6a53b34 100644 --- a/src/Box2D.NET/B2Shape.cs +++ b/src/Box2D.NET/B2Shape.cs @@ -22,7 +22,7 @@ public class B2Shape public int proxyKey; public B2Filter filter; - public object userData; + public B2UserData userData; // TODO: @ikpil, check union public B2ShapeUnion us; diff --git a/src/Box2D.NET/B2ShapeDef.cs b/src/Box2D.NET/B2ShapeDef.cs index 98ab9529..b7abb6e8 100644 --- a/src/Box2D.NET/B2ShapeDef.cs +++ b/src/Box2D.NET/B2ShapeDef.cs @@ -12,7 +12,7 @@ namespace Box2D.NET public struct B2ShapeDef { /// Use this to store application specific shape data. - public object userData; + public B2UserData userData; /// The surface material for this shape. public B2SurfaceMaterial material; diff --git a/src/Box2D.NET/B2Shapes.cs b/src/Box2D.NET/B2Shapes.cs index ff0eccc9..429667f6 100644 --- a/src/Box2D.NET/B2Shapes.cs +++ b/src/Box2D.NET/B2Shapes.cs @@ -1029,14 +1029,14 @@ public static B2WorldId b2Shape_GetWorld(in B2ShapeId shapeId) return new B2WorldId((ushort)(shapeId.world0 + 1), world.generation); } - public static void b2Shape_SetUserData(in B2ShapeId shapeId, object userData) + public static void b2Shape_SetUserData(in B2ShapeId shapeId, B2UserData userData) { B2World world = b2GetWorld(shapeId.world0); B2Shape shape = b2GetShape(world, shapeId); shape.userData = userData; } - public static object b2Shape_GetUserData(in B2ShapeId shapeId) + public static B2UserData b2Shape_GetUserData(in B2ShapeId shapeId) { B2World world = b2GetWorld(shapeId.world0); B2Shape shape = b2GetShape(world, shapeId); diff --git a/src/Box2D.NET/B2UserData.cs b/src/Box2D.NET/B2UserData.cs new file mode 100644 index 00000000..8d494e3a --- /dev/null +++ b/src/Box2D.NET/B2UserData.cs @@ -0,0 +1,99 @@ +using System.Runtime.InteropServices; + +namespace Box2D.NET +{ + [StructLayout(LayoutKind.Explicit)] + public readonly struct B2UserData + { + public static B2UserData Empty => default; + + // Value types (Overlap at offset 0) + [FieldOffset(0)] public readonly double dValue; + [FieldOffset(0)] public readonly long iValue; + [FieldOffset(0)] public readonly ulong ulValue; + + // Reference type (Offset 8 to avoid GC aliasing) + [FieldOffset(8)] public readonly object oValue; + + // Type flag (Offset 16) + [FieldOffset(16)] public readonly B2UserDataType type; + + public B2UserData(long i) : this() + { + type = B2UserDataType.Signed; + iValue = i; + } + + public B2UserData(ulong ul) : this() + { + type = B2UserDataType.Unsigned; + ulValue = ul; + } + + public B2UserData(double d) : this() + { + type = B2UserDataType.Double; + dValue = d; + } + + public B2UserData(object o) : this() + { + type = B2UserDataType.Ref; + oValue = o; + } + + + public bool IsEmpty() + { + return type == B2UserDataType.None; + } + + public long GetSigned(long def = 0) + { + return type == B2UserDataType.Signed + ? iValue + : def; + } + + public ulong GetUnsigned(ulong def = 0) + { + return type == B2UserDataType.Unsigned + ? ulValue + : def; + } + + public double GetDouble(double def = 0) + { + return type == B2UserDataType.Double + ? dValue + : def; + } + + public T GetRef() where T : class + { + return type == B2UserDataType.Ref + ? oValue as T + : null; + } + + public static B2UserData Signed(long i) + { + return new B2UserData(i); + } + + public static B2UserData Unsinged(ulong u) + { + return new B2UserData(u); + } + + public static B2UserData Double(double d) + { + return new B2UserData(d); + } + + public static B2UserData Ref(T o) where T : class + { + return new B2UserData(o); + } + } +} \ No newline at end of file diff --git a/src/Box2D.NET/B2UserDataType.cs b/src/Box2D.NET/B2UserDataType.cs new file mode 100644 index 00000000..abfeb3cf --- /dev/null +++ b/src/Box2D.NET/B2UserDataType.cs @@ -0,0 +1,11 @@ +namespace Box2D.NET +{ + public enum B2UserDataType : byte // must be byte!! + { + None = 0, + Signed = 1, + Unsigned = 2, + Double = 3, + Ref = 4, + } +} \ No newline at end of file diff --git a/src/Box2D.NET/B2World.cs b/src/Box2D.NET/B2World.cs index a7078151..b633d63c 100644 --- a/src/Box2D.NET/B2World.cs +++ b/src/Box2D.NET/B2World.cs @@ -133,7 +133,7 @@ public class B2World public object userTaskContext; public object userTreeTask; - public object userData; + public B2UserData userData; // Remember type step used for reporting forces and torques // inverse sub-step @@ -242,7 +242,7 @@ public void Clear() userTaskContext = null; userTreeTask = null; - userData = null; + userData = B2UserData.Empty; inv_h = 0.0f; inv_dt = 0.0f; diff --git a/src/Box2D.NET/B2WorldDef.cs b/src/Box2D.NET/B2WorldDef.cs index 34ca3603..8ca15c94 100644 --- a/src/Box2D.NET/B2WorldDef.cs +++ b/src/Box2D.NET/B2WorldDef.cs @@ -68,7 +68,7 @@ public struct B2WorldDef public object userTaskContext; /// User data - public object userData; + public B2UserData userData; /// Used internally to detect a valid definition. DO NOT SET. public int internalValue; diff --git a/src/Box2D.NET/B2Worlds.cs b/src/Box2D.NET/B2Worlds.cs index 5e1a3088..dd8c1297 100644 --- a/src/Box2D.NET/B2Worlds.cs +++ b/src/Box2D.NET/B2Worlds.cs @@ -1667,13 +1667,13 @@ public static B2Counters b2World_GetCounters(B2WorldId worldId) return s; } - public static void b2World_SetUserData(B2WorldId worldId, object userData) + public static void b2World_SetUserData(B2WorldId worldId, B2UserData userData) { B2World world = b2GetWorldFromId(worldId); world.userData = userData; } - public static object b2World_GetUserData(B2WorldId worldId) + public static B2UserData b2World_GetUserData(B2WorldId worldId) { B2World world = b2GetWorldFromId(worldId); return world.userData; diff --git a/test/Box2D.NET.Test/B2WorldTest.cs b/test/Box2D.NET.Test/B2WorldTest.cs index 5a6803d2..9ae9c4da 100644 --- a/test/Box2D.NET.Test/B2WorldTest.cs +++ b/test/Box2D.NET.Test/B2WorldTest.cs @@ -319,9 +319,9 @@ public void TestWorldCoverage() int count = b2World_GetAwakeBodyCount(worldId); Assert.That(count, Is.EqualTo(0)); - b2World_SetUserData(worldId, value); - object userData = b2World_GetUserData(worldId); - Assert.That((float)userData, Is.EqualTo(value)); + b2World_SetUserData(worldId, B2UserData.Double(value)); + var userData = b2World_GetUserData(worldId); + Assert.That((float)userData.GetDouble(), Is.EqualTo(value)); b2World_Step(worldId, 1.0f, 1); From 2742e71bdf00da3d492c678e8da25b525199204d Mon Sep 17 00:00:00 2001 From: ikpil Date: Mon, 19 Jan 2026 23:36:07 +0900 Subject: [PATCH 2/2] added unittest for B2UserData --- src/Box2D.NET/B2UserData.cs | 2 +- test/Box2D.NET.Test/B2UserDataTest.cs | 97 +++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 test/Box2D.NET.Test/B2UserDataTest.cs diff --git a/src/Box2D.NET/B2UserData.cs b/src/Box2D.NET/B2UserData.cs index 8d494e3a..746644c2 100644 --- a/src/Box2D.NET/B2UserData.cs +++ b/src/Box2D.NET/B2UserData.cs @@ -81,7 +81,7 @@ public static B2UserData Signed(long i) return new B2UserData(i); } - public static B2UserData Unsinged(ulong u) + public static B2UserData Unsigned(ulong u) { return new B2UserData(u); } diff --git a/test/Box2D.NET.Test/B2UserDataTest.cs b/test/Box2D.NET.Test/B2UserDataTest.cs new file mode 100644 index 00000000..c31e3ef0 --- /dev/null +++ b/test/Box2D.NET.Test/B2UserDataTest.cs @@ -0,0 +1,97 @@ +using NUnit.Framework; +using Box2D.NET; + +namespace Box2D.NET.Test +{ + public class B2UserDataTest + { + [Test] + public void TestEmpty() + { + var userData = B2UserData.Empty; + Assert.That(userData.IsEmpty(), Is.True); + Assert.That(userData.GetSigned(), Is.EqualTo(0)); + Assert.That(userData.GetUnsigned(), Is.EqualTo(0)); + Assert.That(userData.GetDouble(), Is.EqualTo(0.0)); + Assert.That(userData.GetRef(), Is.Null); + } + + [Test] + public void TestSigned() + { + long value = -1234567890L; + var userData = B2UserData.Signed(value); + + Assert.That(userData.IsEmpty(), Is.False); + Assert.That(userData.type, Is.EqualTo(B2UserDataType.Signed)); + Assert.That(userData.GetSigned(), Is.EqualTo(value)); + + // Default value check + Assert.That(userData.GetUnsigned(123), Is.EqualTo(123)); + Assert.That(userData.GetDouble(1.23), Is.EqualTo(1.23)); + Assert.That(userData.GetRef(), Is.Null); + } + + [Test] + public void TestUnsigned() + { + ulong value = 1234567890UL; + var userData = B2UserData.Unsigned(value); + + Assert.That(userData.IsEmpty(), Is.False); + Assert.That(userData.type, Is.EqualTo(B2UserDataType.Unsigned)); + Assert.That(userData.GetUnsigned(), Is.EqualTo(value)); + + // Default value check + Assert.That(userData.GetSigned(-1), Is.EqualTo(-1)); + Assert.That(userData.GetDouble(1.23), Is.EqualTo(1.23)); + Assert.That(userData.GetRef(), Is.Null); + } + + [Test] + public void TestDouble() + { + double value = 3.14159; + var userData = B2UserData.Double(value); + + Assert.That(userData.IsEmpty(), Is.False); + Assert.That(userData.type, Is.EqualTo(B2UserDataType.Double)); + Assert.That(userData.GetDouble(), Is.EqualTo(value)); + + // Default value check + Assert.That(userData.GetSigned(-1), Is.EqualTo(-1)); + Assert.That(userData.GetUnsigned(123), Is.EqualTo(123)); + Assert.That(userData.GetRef(), Is.Null); + } + + [Test] + public void TestRef() + { + string value = "Hello Box2D"; + var userData = B2UserData.Ref(value); + + Assert.That(userData.IsEmpty(), Is.False); + Assert.That(userData.type, Is.EqualTo(B2UserDataType.Ref)); + Assert.That(userData.GetRef(), Is.EqualTo(value)); + + // Default value check + Assert.That(userData.GetSigned(-1), Is.EqualTo(-1)); + Assert.That(userData.GetUnsigned(123), Is.EqualTo(123)); + Assert.That(userData.GetDouble(1.23), Is.EqualTo(1.23)); + } + + [Test] + public void TestRefTypeMismatch() + { + string value = "Hello Box2D"; + var userData = B2UserData.Ref(value); + + // Trying to get as int (boxed) should return null because stored object is string + Assert.That(userData.GetRef(), Is.EqualTo(value)); + + // This cast will fail inside GetRef and return null + var intRef = userData.GetRef(); + Assert.That(intRef, Is.Null); + } + } +} \ No newline at end of file