I have a simple function in my Unity game code where I am looking for a "work spot" - an empty place for my NPCs to do some task.
This code works perfectly well 99.999% of the time, but throws a NULL REFERENCE EXCEPTION very occasionally and I can't for the life of me figure out where. I have tried to catch every possible place where something could be unassigned and can't find it. There must be some edge case I am missing, or something in my code that misbehaves under some strange circumstances.
So my code is flawed and I don't understand where. Help me improve it:
public Transform[] workSpots;
public Transform findAvailableWorkSpot() {
if (workSpots == null || workSpots.Length == 0) return null; // sometimes we have no work spots
int i=0;
while (i<10) {
Transform spot = workSpots[Random.Range(0, workSpots.Length)];
if (spot != null && spot.childCount == 0) {
return spot;
}
i ;
}
// couldn't find one randomly, let's just iterate over them:
foreach (Transform spot in workSpots) {
if (spot != null && spot.childCount == 0) {
return spot;
}
}
Debug.LogError("could not find free workspot on " gameObject.name);
return null;
}
Logic:
- first, try up to 10 times to get a free random work spot. I parent the NPC to the work spot when he is working there, so
spot.childCount == 0
is true if the spot has no NPC on it right now. - if that fails, then I just iterate over all of them and pick the first free one.
- it is ok to return null here, the calling code will handle that
The Diagnostic backend tells me that 2-4 times a day someone experiences a Null Reference Exception in this function, but diagnostics doesn't tell me the line number and I've never seen it locally. I'm looking at it again and again and I can't spot where it could be. Maybe something more fundamental is wrong with my code?
Additional Information:
- workspots is initialized in Awake() and is never again changed, so I am sure that the test at the beginning of the function works and it's not possible that it goes to null while the function is running.
CodePudding user response:
This is not directly an answer to where exactly the exception comes from.
But if you are open to using Linq you can simplyfy your code a lot!
I would simply use
using System.Linq;
...
public Transform[] workSpots;
public bool TryFindAvailableWorkSpot(out Transform freeSpot)
{
// First heck this component even is alive
if (!this || workSpots == null || workSpots.Length == 0)
{
freeSpot = null;
return false;
}
// First "Where" filters out only those spots that exist and have no children
// then "OrderBy" randomizes these results
// then we take the first one or null if there isn't any
freeSpot = workSpots.Where(spot => spot && spot.childCount == 0).OrderBy(spot => Random.value).FirstOrDefault();
// use the implicit bool operator for checking if we have found a valid reference
return freeSpot;
}
and then instead of using
var spot = yourClass.findAvailableWorkSpot();
and having to again check for null
and potential exceptions you would rather simply do the check in the same line using e.g.
if(yourClass.TryFindAvailableWorkSpot(out var spot))
{
... Do someting with spot
}
else
{
Debug.LogError("could not find free workspot");
}
See
CodePudding user response:
I don't think you are looking at the right place where the exception happened. The way you've written the code it's just not possible for workSpots
to be null or empty.
The only places where I can see a null reference exception happening is if you are trying to access workSpots[index]
-> and that returns a Transform object that is null and you try using it (but in your code you are prepared for that).
Maybe the places where you use that particular method do not check if the Transform that you are returning is null before trying to access it.
If your code is well written and you don't believe it then the null reference exception is probably caused by gameObject.name
when trying to log an error. If it isn't that then I have no idea honestly.