In my current game project, something strange has happened as of a recent commit. When running dub test
, all the unittests appear to pass, but then after the last unittest has concluded an "Invalid memory operation" happens. Removing a few lines replaces this error with a segfault, but either way, these errors shouldn't be happening. Weirdly, the commit where these errors first appear did nothing but replace a single class-member function with a seemingly identical function through a mixin template.
The errors seem to be related to object deletion. The traceback output for the first error, and where GDB points to for the second error, is the destructor for my Unit
class.
You see, every Unit
object is associated with a particular Map
object and Faction
object, which it hold references to. Those objects in turn each have an array of Unit
objects that they are associated with. In the Unit
destructor, I have it call the removeUnit
function in both the associated Map
and Faction
objects. The errors are happening in either the Unit
destructor itself, or the removeUnit
function that it calls. Until the commit that introduced these errors, the removeUnit
function was written directly in the Map
class in it's module, but this commit replaced it with the mixin template UnitArrayManagement
, which Faction
also uses.
Unit
destructor:
~this() {
this.alive = false;
if (this.map !is null) this.map.removeUnit(this);
if (this.faction !is null) this.faction.removeUnit(this);
if (this.currentTile !is null) this.currentTile.occupant = null;
}
template UnitArrayManagement(alias Unit[] unitsArray) {
bool removeUnit(Unit unit) {
import std.algorithm.searching;
writeln("Doing `Map.removeUnit`");
Unit[] shiftedUnits = unitsArray.find(unit);
ushort unitKey = cast(ushort)(unitsArray.length - shiftedUnits.length);
debug {
writeln("unitsArray: ");
foreach (listedUnit; unitsArray) writeln(listedUnit.name~", ");
writeln("shiftedUnits: ");
foreach (listedUnit; shiftedUnits) writeln(listedUnit.name~", ");
}
if (shiftedUnits.length > 0) {
debug writeln("shiftedUnits.length > 0");
unitsArray[$-shiftedUnits.length] = null;
for (ushort i=0; i<shiftedUnits.length-1; i++) {
debug writeln("In loop. unitKey = ", unitKey, ", i = ", i);
unitsArray[unitKey+i] = unitsArray[unitKey+i+1];
}
unitsArray.length--;
return true;
} else return false;
}
}
The first error happens because I inserted some uses of writeln
for debugging purposes in the new version of removeUnit
(because I haven't figured out how to do the same thing with GDB), in which I try to get it to print the names of all the units in the array before deleting any of them. I suppose that it might get a Invalid memory operation
when trying to access a member of a Unit
object that no longer exists, but this shouldn't be happening. When that other Unit
object got destroyed, the destructor should have called this same removeUnit
function to remove it's reference from the array.
I read that the garbage collector sometimes but not always calls destructors on deletion, which sounds crazy to me. Is this a case of one unit being deleted without the destructor and then the next unit (of the same Map
object) having the destructor called?
Are destructors normally called after a program is concluded?
The second error, which can be achieved by removing the instances of writeln
in removeUnit
(making it seemingly identical now to the version of this function previously defined in the Map
class) is also strange. It seems to be a case of the Unit
object calling a Map
object that no longer exists. However, that's also strange, as the Map
object is supposed to delete all it's associated units on destruction.
I wrote these destructors so that objects wouldn't have references floating around on their deletion, yet now I'm getting errors from the destructors.
So why are these things even happening after the unittests have been run? What else do I need to know about object destruction? What may be happening?