one of my players always, consistently get a NullReferenceException in a "will be destroyed" behaviour. No other report were sent (they are automatic). Only from that device and I couldn't reproduce it.
My theory is that it's regarding some misterious behaviour of the garbage gollector and Unity's Destroy mechanism. (by the way I use IL2CPP)
Here's the setup:
All UI_TownLabels (MonoBehaviour desc.) reference a TownHallBehaviour. (MonoBehaviour desc.)
All TownHallBehaviour reference Tile (pure C#) object.
A Tile object holds references with anything regarding that Tile, including MonoBehaviour descendants, like TileBehaviour.
Here's the case:
When the user clicks on a button to end the game, a tween effect is started.
When it's finished, I destroy all TileBehaviours' gameObject, and all UI_TownLabels' gameObject.
Before being destroyed, UI_TownLabel's LateUpdate is still called as it should be, because Destroy is effective after the end of the current frame.
However here when it tries to get its TownHallBehaviour's Tile's TileBehaviour's Transform's Position, there is a NullReferenceException.
Strangely it seems that the TownHallBehaviour, the Tile, and the TileBehaviour are all NOT null. Only the transform of the TileBehaviour. At least this is what suspect because the exception comes from Tile.get_Position()
Here's what's in the Label's LateUpdate:
var targetPosition = TownHallBehaviour.Tile.Position;
Here's a snippet from Tile:
public class Tile : TileBase
{
private readonly TileBehaviour tileBehaviour;
public Tile(TileBehaviour tileBehaviour)
{
this.tileBehaviour = tileBehaviour;
}
public Vector3 Position
{
get { return Transform.position; }
}
public Transform Transform
{
get { return tileBehaviour.transform; }
}
}
(I thought for a time that maybe this LateUpdate is in the next frame... and there is a Label in the scene which isn't in the Label collection thus doesn't get destroyed...
... but the code for that is simple, it looks good. And even if it would be the case, there must would be other players affected, or at least it shouldn't occur always for this user. Right?)
Thanks in advance for all the help!
EDIT:
I added some logging to the LateUpdate method:
LogSystem.Log("TownHallBehaviour is " + TownHallBehaviour.TryToString());
LogSystem.Log("TownHallBehaviour.Tile is " + TownHallBehaviour.Tile.TryToString());
LogSystem.Log("TownHallBehaviour.Tile.TileBehaviour is " + TownHallBehaviour.Tile.TileBehaviour.TryToString());
Here is the TryToString() method:
public static string TryToString(this object obj)
{
return obj == null ? GetNullInfo(obj) : obj.ToString();
}
private static string GetNullInfo(object obj)
{
var literallyNull = System.Object.ReferenceEquals(obj, null);
var equalsNull = obj == null;
return $"<literally null: {literallyNull}, equals null: {equalsNull}>";
}
And suprisingly here is the log I received, also, I have now received this issue from additional devices...
TownHallBehaviour is (Town Hall on (-1, -1))
TownHallBehaviour.Tile is ((-1, -1), Plains, HasUnit)
TownHallBehaviour.Tile.TileBehaviour is null
TileBehaviour is null even though previously the Position getter was the source of the exception and not the Transform getter which is the one accessing TileBehaviour. And it doesn't even print the NullInfo.
When I tried using TryToString() on an object deliberately set to null, it displays the NullInfo as it should.
However when I destroy only the Tiles, and not the labels, the TryToString in LateUpdate returns "null" for TileBehaviour. However the exception printed is
The object of type 'TileBehaviour' has been destroyed but you are still trying to access it.
Did I implement this NullInfo method badly?
Object.ReferenceEquals(tileBehaviour, null)
, or just Unity's pseudo-null? That may help us narrow down the issue. Ordinarily, it should be impossible for a reference to be nulled by the garbage collector, but it can be put into Unity's pseudo-null state after a Destroy or DestroyImmediate while references are still pointing at it. Any logging you can add to confirm which object this is happening to and when would help thin the search space. \$\endgroup\$