Several getters return const T* pointing at a thread_local snapshot rather than at the object's own data. A second call on the same thread overwrites the first result, so a = Get(x); b = Get(y); leaves both showing y.
Confirmed cases:
| function |
what is shared |
Skeleton_GetJoint |
joint->name and parentName point into two thread_local std::strings |
Ragdoll_GetRagdollSettings |
returns &tl_rs, one static thread_local wrapper for every ragdoll |
WheelSettingsWV_GetLongitudinalFriction / GetLateralFriction |
shared snapshot |
WheeledVehicleController_GetTransmission and the settings equivalent |
shared snapshot |
The const T* signature reads like a borrowed reference to live data. It is a copy with a lifetime of "until the next call on this thread", and nothing in the headers says so.
This bites hardest in a managed layer. A C# binding that copies the struct and marshals the strings later reads whichever name was fetched most recently — the failure is a wrong value, not a crash, and it will look like a data bug anywhere but here.
Ragdoll_GetRagdollSettings has a second edge: passing its result to RagdollSettings_Destroy would drop a reference the Ragdoll still owns. Only a comment prevents that today.
The tests in test_skeleton_extra.c and test_vehicle_extra.c read each result before the next call, which is a workaround rather than a fix.
Several getters return
const T*pointing at athread_localsnapshot rather than at the object's own data. A second call on the same thread overwrites the first result, soa = Get(x); b = Get(y);leaves both showingy.Confirmed cases:
Skeleton_GetJointjoint->nameandparentNamepoint into twothread_localstd::stringsRagdoll_GetRagdollSettings&tl_rs, onestatic thread_localwrapper for every ragdollWheelSettingsWV_GetLongitudinalFriction/GetLateralFrictionWheeledVehicleController_GetTransmissionand the settings equivalentThe
const T*signature reads like a borrowed reference to live data. It is a copy with a lifetime of "until the next call on this thread", and nothing in the headers says so.This bites hardest in a managed layer. A C# binding that copies the struct and marshals the strings later reads whichever name was fetched most recently — the failure is a wrong value, not a crash, and it will look like a data bug anywhere but here.
Ragdoll_GetRagdollSettingshas a second edge: passing its result toRagdollSettings_Destroywould drop a reference theRagdollstill owns. Only a comment prevents that today.The tests in
test_skeleton_extra.candtest_vehicle_extra.cread each result before the next call, which is a workaround rather than a fix.