For a long time I’ve been trying to find a way to use Unity’s PhysX-based physics from multithreaded Burst code. And recently, while digging through UnityCsReference, I noticed that since Unity 2022 the ContactModifyEvent exposes raw pointers to PhysX actors. Looking at the PhysX SDK architecture, it turns out that one such pointer is enough. From a single PxActor* you can walk to PxScene, then to PxPhysics, and from there reach everything you need for most physics manipulation.
That observation became the foundation for C++ plugin that allows me to access internal PhysX types (like PxActor, PxScene, PxShape, PxJoint) of Unity objects inside current Physics Scene.
How it works:
Native C++ side: compiles against the pure NVIDIA PhysX 4.1 headers + simple helpers. No PhysX or Unity binary is linked. At runtime, when the library is loaded, the PhysX symbols are mapped to the library that Unity has already loaded into the process. However, as a limitation, I can only use virtual methods, which is enough.
C# bootstrap: spawns two rigidbodies with hasModifiableContacts = true, captures the first PxActor* from ModifiableContactPair, getting pointer using reflection and walks PxActor-> PxScene-> get* methods.
Unity bridge: uses reflection to call Unity’s own Physics.ResolveActorToInstanceID / ModifiableContactPair.ResolveActorToInstanceID to map raw PhysX pointer back to Unity instanceIDs.
The Question:
There is many limitations, and “very” unsafe parts, but at all it works stable if follow basic PhysX threading restrictions. So I wondering why nobody created something like this before? It looks pretty cool, especially since we don’t have an official implementation of low-level 3D physics yet.
Here very early working example of usage of this plugin.
Small, weird job that apply random impulses to cubes, using simple ECS.
Code:
[BurstCompile]
private struct TossJob : IJobParallelFor
{
[ReadOnly] public NativeFilter filter;
public int count;
public NativeStash<RandomImpulseTarget> stash;
[NativeDisableUnsafePtrRestriction]
public PxScene pxScene;
public float dt;
public float intervalMin;
public float intervalMax;
public float impulseMin;
public float impulseMax;
public float angularMin;
public float angularMax;
public float hemisphereBiasUp;
public uint seedSalt;
public unsafe void Execute(int index)
{
Entity entity = filter[index];
ref RandomImpulseTarget target = ref stash.Get(entity);
target.Timer -= dt;
if (target.Timer > 0f)
{
return;
}
// Weird part
if (target.Rng.state == 0u)
{
uint seed = (((uint)entity.Id + 1u) * 747796405u) ^ seedSalt;
if (seed == 0u)
{
seed = 1u;
}
target.Rng = new Unity.Mathematics.Random(seed);
}
float3 rawDirection = target.Rng.NextFloat3Direction();
if (rawDirection.y < 0f)
{
rawDirection.y = math.lerp(rawDirection.y, -rawDirection.y, hemisphereBiasUp);
}
float rawDirectionLengthSq = math.lengthsq(rawDirection);
if (rawDirectionLengthSq > 0f)
{
rawDirection *= math.rsqrt(rawDirectionLengthSq);
}
else
{
rawDirection = new float3(0f, 1f, 0f);
}
float impulseMagnitude = target.Rng.NextFloat(impulseMin, impulseMax);
float3 impulse = rawDirection * impulseMagnitude;
float3 angularDirection = target.Rng.NextFloat3Direction();
float angularMagnitude = target.Rng.NextFloat(angularMin, angularMax);
float3 angularVelocityDelta = angularDirection * angularMagnitude;
Vector3 impulseVector = new Vector3(impulse.x, impulse.y, impulse.z);
Vector3 angularVelocityDeltaVector = new Vector3(angularVelocityDelta.x, angularVelocityDelta.y,
angularVelocityDelta.z);
// The most interesting part
// Lock`s needed for multithreaded access test.
// Of course it creates a lot of overhead here but I was curious to see if it would even work at all and it does!
pxScene.LockWrite();
target.Actor.AddForce(impulseVector, ForceMode.Impulse);
target.Actor.AddTorque(angularVelocityDeltaVector, ForceMode.Impulse);
pxScene.UnlockWrite();
target.Timer = target.Rng.NextFloat(intervalMin, intervalMax);
}
}
I cannot provide any kind of video due to Unity Discussions Restriction but it works.
Hey the main restriction you’ll have is that you are using vanilla physx 4.1 while unity’s physx 4.1 fork has differences in both some apis and data.
So a number of problems can happen here:
- We don’t lock write/read by default in unity as most logic is heavily controlled when it goes off main thread
- You locking write/read doesn’t stop unity from doing changes on those objects – but at least physx will throw errors around, due to the locking mechanism still catching some of this.
- We remove the actor from the scene before calling .release() as such at any point you might end up removed from the scene itself which ain’t great. We also delete scenes off main-thread when they are created via local 3d physics mode (LoadScene params)
We are currently in the process of figuring out what we need to do for exposing some API + Container in a thread safe manner in newer versions of Unity.
Tho all in all if you are very careful with what you are doing you can in theory make a plugin like this to work with physx bits and bobs with some caveats with regards to safety between jobs touching those pieces of data.
One thing you can deffo make sure to do is add an extra ref to the objects you are caching so at least they can’t be released by unity in between your plugin accessing them. (sure you’ll still need to deal with things being removed from scenes but yeah… in theory you’ll be able to reconstruct state when things like that happen :3)
Cheers,
AlexRvn.
Thank you, really appreciate the detailed response!
I can’t wait for Unity to see a low-level API for 3D physics in action!
The caveats you mentioned are real, and I’ve already encountered them. Right now, I’m interested in determining the limits within which this plugin can run stably - and whether it can do so at all.
So far, as long as I don’t mix managed logic with threaded logic and keep track of the Unity object lifecycle, everything works fine.