HashCode randomly changing on structures.

I've never in my career of coding have seen the behavior I'm about to describe so I'm hoping to learn something new... this is Unity3D btw... In my code I am iterating a collection built on creation. I've confirmed that the objects are not being rebuilt at any time, and also the GetHashCode shown in this segment returns the same every time. The code below does not really matter a whole lot, just showing my work flow.
for (int i = 0; i < _rigidbodyDatas.Count; i++)
{
Debug.Log("Changing kinematic on " + _rigidbodyDatas[i].GetHashCode() + " to " + isKinematic);
if (!_rigidbodyDatas[i].SetIsKinematic(isKinematic))
{
_rigidbodyDatas.RemoveAt(i);
i--;
}
}
for (int i = 0; i < _rigidbodyDatas.Count; i++)
{
Debug.Log("Changing kinematic on " + _rigidbodyDatas[i].GetHashCode() + " to " + isKinematic);
if (!_rigidbodyDatas[i].SetIsKinematic(isKinematic))
{
_rigidbodyDatas.RemoveAt(i);
i--;
}
}
This is the SetIsKinematic method....
public bool SetIsKinematic(bool isKinematic)
{
if (_rigidbody == null)
return false;

if (isKinematic)
{
_velocity = _rigidbody.velocity;
Debug.Log("Cached velocity as " + _velocity + " on " + GetHashCode());
_angularVelocity = _rigidbody.angularVelocity;
_rigidbody.isKinematic = true;
}
else
{
Debug.Log("Read velocity as " + _velocity + " on " + GetHashCode());
_rigidbody.isKinematic = false;
_rigidbody.AddForce(_velocity, ForceMode.Impulse);
_rigidbody.AddTorque(_angularVelocity, ForceMode.Impulse);
}

return true;
}
public bool SetIsKinematic(bool isKinematic)
{
if (_rigidbody == null)
return false;

if (isKinematic)
{
_velocity = _rigidbody.velocity;
Debug.Log("Cached velocity as " + _velocity + " on " + GetHashCode());
_angularVelocity = _rigidbody.angularVelocity;
_rigidbody.isKinematic = true;
}
else
{
Debug.Log("Read velocity as " + _velocity + " on " + GetHashCode());
_rigidbody.isKinematic = false;
_rigidbody.AddForce(_velocity, ForceMode.Impulse);
_rigidbody.AddTorque(_angularVelocity, ForceMode.Impulse);
}

return true;
}
What's strange is whenever _rigidbody.Velocity (a structure) contains value the GetHashCode returns different. EG: if _rigidbody.velocity is Vector3.zero the hashcode is the same as what was called during the loop, but if it differs the hashcode does as well. I've found simply changing the struct to a class resolves the issue but I'm not sure why. The read velocity segment also always has the correct hashcode either way.
6 Replies
FirstGearGames
FirstGearGamesOP3y ago
and the REALLY stupid part is I have another variant of the same structure that works fine.
undisputed world champions
how is GetHashCode() implemented? do you rely on the default implementation? the default-implementation for GetHashCode() in structs says:
Action: Our algorithm for returning the hashcode is a little bit complex. We look for the first non-static field and get it's hashcode. If the type has no non-static fields, we return the hashcode of the type. We can't take the hashcode of a static member because if that member is of the same type as the original type, we'll end up in an infinite loop.
FirstGearGames
FirstGearGamesOP3y ago
I'm not using any overrides but the value is definitely disappearing somewhere. The object the value was 'cached' on would change when a non-default value was being written. I've never seen anything like this before. I resolved it using a work-around. I'm going to share the information here to potentially help others. Here's my structure before the fix. Minding, all I was doing was calling SetIsKinematic(true/false) that was causing the issue.
public class RigidbodyPauser
{
#region Types.
/// <summary>
/// Data for a rigidbody before being set kinematic.
/// </summary>
private struct RigidbodyData
{
/// <summary>
/// Rigidbody for data.
/// </summary>
private Rigidbody _rigidbody;
/// <summary>
/// Cached velocity when being set kinematic.
/// </summary>
private Vector3 _velocity;
/// <summary>
/// Cached velocity when being set kinematic.
/// </summary>
private Vector3 _angularVelocity;

public RigidbodyData(Rigidbody rigidbody)
{
_rigidbody = rigidbody;
_rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
_velocity = Vector3.zero;
_angularVelocity = Vector3.zero;
}

/// <summary>
/// Sets isKinematic status and returns if successful.
/// </summary>
public bool SetIsKinematic(bool isKinematic)
{
if (_rigidbody == null)
return false;

if (isKinematic)
{
_velocity = _rigidbody.velocity;
//_VELOCITY was being set to the wrong object hashcode if it contained anything other than Vector3.zero.
_angularVelocity = _rigidbody.angularVelocity;
_rigidbody.isKinematic = true;
}
else
{
_rigidbody.isKinematic = false;
_rigidbody.AddForce(_velocity, ForceMode.Impulse);
_rigidbody.AddTorque(_angularVelocity, ForceMode.Impulse);
}

return true;
}
}
public class RigidbodyPauser
{
#region Types.
/// <summary>
/// Data for a rigidbody before being set kinematic.
/// </summary>
private struct RigidbodyData
{
/// <summary>
/// Rigidbody for data.
/// </summary>
private Rigidbody _rigidbody;
/// <summary>
/// Cached velocity when being set kinematic.
/// </summary>
private Vector3 _velocity;
/// <summary>
/// Cached velocity when being set kinematic.
/// </summary>
private Vector3 _angularVelocity;

public RigidbodyData(Rigidbody rigidbody)
{
_rigidbody = rigidbody;
_rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
_velocity = Vector3.zero;
_angularVelocity = Vector3.zero;
}

/// <summary>
/// Sets isKinematic status and returns if successful.
/// </summary>
public bool SetIsKinematic(bool isKinematic)
{
if (_rigidbody == null)
return false;

if (isKinematic)
{
_velocity = _rigidbody.velocity;
//_VELOCITY was being set to the wrong object hashcode if it contained anything other than Vector3.zero.
_angularVelocity = _rigidbody.angularVelocity;
_rigidbody.isKinematic = true;
}
else
{
_rigidbody.isKinematic = false;
_rigidbody.AddForce(_velocity, ForceMode.Impulse);
_rigidbody.AddTorque(_angularVelocity, ForceMode.Impulse);
}

return true;
}
}
Here's the working version. I just moved the method call out.
private struct RigidbodyData
{
/// <summary>
/// Rigidbody for data.
/// </summary>
public Rigidbody Rigidbody;
/// <summary>
/// Cached velocity when being set kinematic.
/// </summary>
public Vector3 Velocity;
/// <summary>
/// Cached velocity when being set kinematic.
/// </summary>
public Vector3 AngularVelocity;

public RigidbodyData(Rigidbody rigidbody)
{
Rigidbody = rigidbody;
Rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
Velocity = Vector3.zero;
AngularVelocity = Vector3.zero;
}
}
private struct RigidbodyData
{
/// <summary>
/// Rigidbody for data.
/// </summary>
public Rigidbody Rigidbody;
/// <summary>
/// Cached velocity when being set kinematic.
/// </summary>
public Vector3 Velocity;
/// <summary>
/// Cached velocity when being set kinematic.
/// </summary>
public Vector3 AngularVelocity;

public RigidbodyData(Rigidbody rigidbody)
{
Rigidbody = rigidbody;
Rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
Velocity = Vector3.zero;
AngularVelocity = Vector3.zero;
}
}
for (int i = 0; i < _rigidbodyDatas.Count; i++)
{
if (!SetIsKinematic(isKinematic, i))
{
_rigidbodyDatas.RemoveAt(i);
i--;
}
}

//Sets isKinematic status and returns if successful.
bool SetIsKinematic(bool isZKinematic, int index)
{
RigidbodyData rbData = _rigidbodyDatas[index];
Rigidbody rb = rbData.Rigidbody;
if (rb == null)
return false;

if (isZKinematic)
{
rbData.Velocity = rb.velocity;
rbData.AngularVelocity = rb.angularVelocity;
rb.isKinematic = true;

//Update data.
_rigidbodyDatas[index] = rbData;
}
else
{
rb.isKinematic = false;
rb.AddForce(rbData.Velocity, ForceMode.Impulse);
rb.AddTorque(rbData.AngularVelocity, ForceMode.Impulse);
}

return true;
}
for (int i = 0; i < _rigidbodyDatas.Count; i++)
{
if (!SetIsKinematic(isKinematic, i))
{
_rigidbodyDatas.RemoveAt(i);
i--;
}
}

//Sets isKinematic status and returns if successful.
bool SetIsKinematic(bool isZKinematic, int index)
{
RigidbodyData rbData = _rigidbodyDatas[index];
Rigidbody rb = rbData.Rigidbody;
if (rb == null)
return false;

if (isZKinematic)
{
rbData.Velocity = rb.velocity;
rbData.AngularVelocity = rb.angularVelocity;
rb.isKinematic = true;

//Update data.
_rigidbodyDatas[index] = rbData;
}
else
{
rb.isKinematic = false;
rb.AddForce(rbData.Velocity, ForceMode.Impulse);
rb.AddTorque(rbData.AngularVelocity, ForceMode.Impulse);
}

return true;
}
mtreit
mtreit3y ago
Sounds like velocity is part of the hashcode calculation. Comparing the hash code of a struct to a class is kind of non-sensical since the default implementation for classes is just to use the reference instead of the actual data encapsulated by the class HashCode calculations should really only be done on immutable data Mutable structs are a common cause of issues related to HashCode calculations, like "losing" data inserted into a dictionary
FirstGearGames
FirstGearGamesOP3y ago
@mtreit Still interesting that it works on one struct but not the other, and that moving the method out of the struct solved it. The other struct had different value types but otherwise was exactly the same.
private struct Rigidbody2DData
{
/// <summary>
/// Rigidbody for data.
/// </summary>
public Rigidbody2D Rigidbody2d;
/// <summary>
/// Cached velocity when being set kinematic.
/// </summary>
public Vector2 Velocity;
/// <summary>
/// Cached velocity when being set kinematic.
/// </summary>
public float AngularVelocity;
private struct Rigidbody2DData
{
/// <summary>
/// Rigidbody for data.
/// </summary>
public Rigidbody2D Rigidbody2d;
/// <summary>
/// Cached velocity when being set kinematic.
/// </summary>
public Vector2 Velocity;
/// <summary>
/// Cached velocity when being set kinematic.
/// </summary>
public float AngularVelocity;
my RigidbodyData was being weird with the hashcodes while my Rigidbody2DData was not.
undisputed world champions
this probably depends on how Rigidbody/Rigidbody2D calculate their HashCodes since their HashCode would be included in the HashCode of their *Data-wrappers
Want results from more Discord servers?
Add your server