In my game, I want to have a set of instructions collected into scripts held by a script runner (script data + instruction pointer and context) bundled into a script manager and finally referenced by the game mode. I decided to make them actors so that they could be inspected, customized in blueprints, spawned, and debugged. This seems to work great, except when I want to clean up scripts.
In my script manager, I have this:
Runner->Destroy();
and I’ve verified that both the script runners and script themselves are being cleaned up effectively. but not the instructions. That surprises me because the scripts seem to be cleaned up just fine with this code:
AScriptRunner::~AScriptRunner() {
if (Script) Script->Destroy();
}
// Not sure if this is needed, but oh well
void AScriptRunner::EndPlay(const EEndPlayReason::Type EndPlayReason) {
Super::EndPlay(EndPlayReason);
if (Script) Script->Destroy();
}
Yet doing the same in the script doesn’t work.
AInScript::~AInScript() {
for (AInstruction* I : Instructions) if (I) I->Destroy(); // (1)
}
void AInScript::EndPlay(const EEndPlayReason::Type EndPlayReason) {
Super::EndPlay(EndPlayReason);
for (AInstruction* I : Instructions) if (I) I->Destroy();
}
The instructions can clearly be seen in the editor even after the script no longer exists, until I end the session. When I do end the session, it often crashes on line (1).
Unhandled Exception: EXCEPTION_ACCESS_VIOLATION 0x0000000000000000
And that’s despite checking for nullptr! At first I thought maybe this is due a race condition with parallel GC (Check I isn’t null passes, GC runs and nulls it, then we try I->Destroy()), but I disabled it
and it still happens.
I read about enabling GC clusters, overriding CanBeInCluster() for both instructions and scripts, and AActor::AddToCluster(passing the script which the instruction belongs to), though information about this method seemed very slim and it affected no change.