While investigating cook nondeterminism, I’ve stumbled upon an intermittent diff in the fields of UStaticMeshComponent::BodyInstance structure. Debugging that, I’ve seen that the contents of this field have a complicated fate (in my example, I have a level which contains several instances of a particular static mesh):
- First, the normal deserialization happens (in this example, component has BodyInstance.CollisionEnabled == NoCollision)
- Immediately after, UPrimitiveComponent::Serialize calls UPrimitiveComponent::Serialize -> FBodyInstance::LoadProfileData; in this example the CollisionProfileName field is set to “BlockAll”, so this field is overwritten by CollisionEnabled = QueryAndPhysics
- Later, just before the package is saved for cooking, there’s a chain of calls that ends up executing OnRegister for the component; this calls UStaticMeshComponent::UpdateCollisionFromStaticMesh. If the static mesh is already compiled (this may or may not happen, depending on timings of async operations), this then calls FBodyInstance::UseExternalCollisionProfile (passing the BodySetup from the StaticMesh to the StaticMeshComponent) - in this example the external collision profile is “NoCollision”, which sets the field back to CollisionEnabled = NoCollision
- At runtime, this isn’t a problem - because when mesh compilation finishes, UStaticMeshComponent::PostStaticMeshCompilation calls UpdateCollisionFromStaticMesh again. However, while cooking, the serialization for cook happens immediately, there is no wait.
I think there is a conceptual problem here, in that there are multiple sources of truth that don’t agree with each other:
- for CollisionEnabled field, that’s the serialized value of the field and the value from the collision profile
- for the entire BodyInstance field of the component, that’s serialized value of the component and the value in the mesh it references
- there doesn’t seem to be a mechanism to synchronize these values. Maybe resave commandlet can help, but I’m not confident (eg it probably won’t know how to wait for mesh compilation). Also, I don’t think there’s anything that propagates actual collision profile from the mesh to the component (and if there were no disagreement between profiles for mesh and component, there wouldn’t be an issue).
Ultimately, I don’t think it results in any runtime difference for these meshes (because the serialized values ultimately don’t matter, they get overridden on component registration) - but I can’t make them transient either (because in some other scenarios conceivably they could matter - eg. if profile is set to “custom”, fixup logic is not applied).