Game Development Community

Crash after calling TriggerData::onLeaveTrigger

by Daniel Buckmaster · in Torque Game Engine · 12/12/2010 (12:58 am) · 0 replies

Background: I'm scripting a quick, hackish form of turret AI to get some gameplay going in my combat demo. I'm using triggers and scripted AI as a stand-in while I retool my Sensor class, because I'm impatient like that. To do the job, I've got a StaticTurretTrigger datablock which I create an instance of in the TurretData::onAdd function. Each Trigger stores a reference to the turret it's associated with.

I've just run into a fatal assert running the following script:
function StaticTurretTrigger::onLeaveTrigger(%this, %trigger, %obj)
{
	if(%trigger.getNumObjects() == 0)
	{
		//if(isObject(%trigger.turret))
			%trigger.turret.clearAim();
	}
	Parent::onLeaveTrigger(%this, %trigger, %obj);
}
StaticTurretTrigger is a TriggerData block. If I leave the script as-is, with the if-block commented out, I get a crash when I leave the mission, from this assert in console.h:
AssertFatal( dynamic_cast<className*>( object ), "Object passed to " #name " is not a " #className "!" );
It seems like it's complaining that I've called the onLeaveTrigger function on an object that's not a TriggerData instance.

However, if I uncomment the if block, the crash goes away. This is where I'm stumped - I've checked the value of the trigger's turret property, and it's always valid. But moreover, the code inside the outer if statement is never run. I've placed a breakpoint in Turret::clearAim, and in fact it's never called at all.

I'm somewhat able to believe that - it's possible that the Trigger itself would be deleted at the end of a mission before all of its triggering objects are. But what I really can't get my head around is why adding an if statement that is never examined should stop a fatal assert for something that should never happen.

I'm working with my own significantly-altered codebase here, but I haven't altered the Trigger files in any important way, and I don't think I've made any drastic changes to GameBase or anything higher up in the class tree. Next step is to try to replicate this in stock.

EDIT:
This is bizarre. I've narrowed it down to the script breaking when I make a typo... this script works:
function StaticTurretTrigger::onLeaveTrigger(%this, %trigger, %obj)
{
	if(%trigger.getNumObjects() == 0)
		%trigger.turret.clearAim();
	Parent::onLeaveTrigger(%this, %trigger, %obj);
}
But this one breaks:
function StaticTurretTrigger::onLeaveTrigger(%this, %trigger, %obj)
{
	if(%trigge.getNumObjects() == 0)
		%trigger.turret.clearAim();
	Parent::onLeaveTrigger(%this, %trigger, %obj);
}
So instead of catching the typo like TorqueScript usually does and shooting a 'can't find object', it's tripping the assert. WHY?

And I have no idea what the problem I was seeing before had to do with :P. I've cleaned up object adding/deletion in scripts (I had a few objects I was adding to MissionCleanup before that group existed - now I've made other objects responsible for deleting them), but this script now magically works like it's supposed to :P.

I'm getting a feeling it has something to do with triggerData::onLeaveTrigger being defined engine-side. I'll see what I can find out about that...

EDIT:
This is verified to happen in stock. I also commented out the code-side definition of onLeaveTrigger, and I get no problems (except, of course, a 'cannot find function' when I call a parent function that doesn't exist).

Initially, I thought it was a problem with calling a code-side function with the Parent:: syntax from script. But on reflection, it doesn't make sense that if that were the case, my example can run normally when you don't make any typos in the function space.

Interestingly, I just made a test and it seems that if I place the typo AFTER calling the parent function, everything's fine. So I must conclude then that making a typo in a function is an error that will actually corrupt the loaded parameters of the function. I wonder if the parent function will still execute correctly. The assert doesn't fire in release builds, so let's see...

EDIT:
Also, has anybody else encountered frequent bootings from your games due to not having the correct art assets when using triggers? (Invalid packet error.) I've been getting that error every other time I start a mission since starting to mess about with triggers.

EDIT:
Okay, making a typo after the parent function is called doesn't work after all. For some reason, it was fine the first few times, but now I'm getting consistently booted from the game whenever I leave a trigger. Problem goes away as soon as I remove offending code from onLeaveTrigger.

EDIT:
One final nugget of wisdom. In a release build, everything works perfectly. The parent function is called just fine, there's an error in the console log, and life carries on. Under a debug build though, if you make a typo anywhere in the function, things will hit the fan.

EDIT:
And maybe more problems.

1. I still don't understand my original code was failing, since there aren't errors like typos.
2. I went back and used the second function I posted, which I claimed worked. Under a debug build, I was consistently kicked from the game with an invalid packet from server error. I ran it in release, everything was fine. Ran it in debug... everything was fine!

So it seems there's even more dodginess going on here :P. I'll try to put together a test script for stock which highlights the issue.

Okay, here's a script you can execute to demonstrate the problem. Exec it from the console, then go into the mission editor and place a trigger with the ErrorProneTrigger datablock and run through it.
datablock TriggerData(ParentTrigger)
{
   tickPeriodMS = 100;
};

function ParentTrigger::onEnterTrigger(%this,%trigger,%obj)
{
   Parent::onEnterTrigger(%this,%trigger,%obj);
   error("ParentTrigger::onEnterTrigger(" @ %this @ ", " @ %trigger @ ", " @ %obj @ ");");
}

function ParentTrigger::onLeaveTrigger(%this,%trigger,%obj)
{
   Parent::onLeaveTrigger(%this,%trigger,%obj);
   error("ParentTrigger::onLeaveTrigger(" @ %this @ ", " @ %trigger @ ", " @ %obj @ ");");
}

function ParentTrigger::onTickTrigger(%this,%trigger)
{
   Parent::onTickTrigger(%this,%trigger);
   error("ParentTrigger::onTickTrigger(" @ %this @ ", " @ %trigger @ ");");
}

//-----------------------------------------------------------------------------

datablock TriggerData(ErrorProneTrigger)
{
   tickPeriodMS = 100;
   className = ParentTrigger;
};

function ErrorProneTrigger::onEnterTrigger(%this,%trigger,%obj)
{
   error(%trigger.getPosition());
   error("That worked, right?");
   Parent::onEnterTrigger(%this,%trigger,%obj);
}

function ErrorProneTrigger::onLeaveTrigger(%this,%trigger,%obj)
{
   error(%trigge.getPosition());
   error("That, however, will break your debug build.");
   Parent::onLeaveTrigger(%this,%trigger,%obj);
}

function ErrorProneTrigger::onTickTrigger(%this,%trigger)
{
   Parent::onTickTrigger(%this,%trigger);
}

EDIT: The invalid packet stuff may have been from my Turret class :P. Investigating that now. But the problem above still stands.

About the author

Studying mechatronic engineering and computer science at the University of Sydney. Game development is probably my most time-consuming hobby!