Strange JS recursion errors in the log?

For test results, bug reports, announcements of new builds etc.

Moderators: winston, another_commander, Getafix

Post Reply
User avatar
Thargoid
Thargoid
Thargoid
Posts: 5528
Joined: Thu Jun 12, 2008 6:55 pm

Strange JS recursion errors in the log?

Post by Thargoid »

I'm seeing strange errors in the log complaining about recursion, but I can't see anywhere that it can be doing it?

Code: Select all

[ai.error.recursion]: ***** ERROR: AI reactToMessage: recursion in AI TCAT_droneAI.plist, state ATTACK_SHIP, aborting. It is not valid to call reactToMessage: FOO in state FOO.
[ai.error.recursion]: ***** ERROR: AI reactToMessage: recursion in AI TCAT_droneAI.plist, state LOOK_FOR_TARGETS, aborting. It is not valid to call reactToMessage: FOO in state FOO.
AI in question:

Code: Select all

{
	GLOBAL =
	{
		ENTER = ("setStateTo: LOOK_FOR_TARGETS");
	};
	
	"ATTACK_SHIP" =
	{
		ENTER = ("sendScriptMessage: validateTarget");
		"TARGET_VALID" = (performAttack);
		"TARGET_INVALID" = ("setStateTo: LOOK_FOR_TARGETS");
		"INCOMING_MISSILE" = (fightOrFleeMissile, setTargetToPrimaryAggressor, "setStateTo: ATTACK_SHIP");
		"TARGET_DESTROYED" = ("setStateTo: LOOK_FOR_TARGETS");
		"TARGET_FOUND" = (setTargetToFoundTarget, "setStateTo: ATTACK_SHIP");
		"TARGET_LOST" = ("setStateTo: LOOK_FOR_TARGETS");
		"NOTHING_FOUND" = ("setStateTo: LOOK_FOR_TARGETS");
		"INTERCEPT_TARGET" = ("setStateTo: GO_TO_TARGET");
	};

	"LOOK_FOR_TARGETS" =
	 {
		ENTER = ("sendScriptMessage: findNonThargoid");
		"TARGET_FOUND" = ("setStateTo: ATTACK_SHIP");
		"NOTHING_FOUND" = ("setStateTo: GO_HOME");
		"ATTACKED" = (setTargetToPrimaryAggressor, "setStateTo: ATTACK_SHIP");
		"INTERCEPT_TARGET" = ("setStateTo: GO_TO_TARGET");
		UPDATE = ("sendScriptMessage: findNonThargoid", "pauseAI: 10.0");
	};

	"GO_TO_TARGET" =
	{
		ENTER = (setDestinationToTarget, "setDesiredRangeTo: 20000.0", checkCourseToDestination);
		"COURSE_OK" = ("setSpeedFactorTo: 1.0", performFlyToRangeFromDestination);
		"WAYPOINT_SET" = ("setAITo: gotoWaypointAI.plist");
		"ATTACKED" = (setTargetToPrimaryAggressor, "setStateTo: ATTACK_SHIP");
		"GROUP_ATTACK_TARGET" = ("setStateTo: ATTACK_SHIP");
		"INCOMING_MISSILE" = (fightOrFleeMissile, setTargetToPrimaryAggressor, "setStateTo: ATTACK_SHIP");
		"DESIRED_RANGE_ACHIEVED" = ("setStateTo: ATTACK_SHIP");
		"INTERCEPT_TARGET" = (setDestinationToTarget, "setStateTo: GO_TO_TARGET");
		"TARGET_DESTROYED" = ("setStateTo: LOOK_FOR_TARGETS"); 
		"FRUSTRATED" = ("sendScriptMessage: findNonThargoid");
	};	
	
	"GO_HOME" = 
	{
		ENTER = (checkForMotherStation);
		"STATION_FOUND" = (setTargetToFoundTarget, "setAITo: TCAT_dockingAI.plist");
		"NOTHING_FOUND" = ("setStateTo: LOOK_FOR_STATION");
		"RESTARTED" = ("setStateTo: LOOK_FOR_TARGETS");
	};

	"LOOK_FOR_STATION" = 
	{
		ENTER = ("scanForNearestShipWithRole: TCAT_station");
		"TARGET_FOUND" = (setTargetToFoundTarget, "setAITo: TCAT_dockingAI.plist");
		"NOTHING_FOUND" = (becomeUncontrolledThargon);
		"RESTARTED" = ("setStateTo: LOOK_FOR_TARGETS");
	};
}

and the relevant script

Code: Select all

this.name           = "TCAT_thargoid";
this.author         = "Thargoid";
this.copyright		= "Creative Commons: attribution, non-commercial, sharealike with clauses - see readme.txt";
this.description    = "Ship script for general Thargoid vessels";
this.version        = "1.0";

this.spawnedAsEscort = function(mother)
	{
	if(mother.isThargoid)
		{
		this.ship.switchAI("TCAT_escortAI.plist");
		}
	}

this.findNonThargoid = function() 
	{ 
	if(this.ship.target)
		{
		this.ship.reactToAIMessage("TARGET_FOUND");
		return;
		}
	
	function targetShips(entity) {return entity.isShip && !entity.docked && entity.scanClass != "CLASS_ROCK" && entity.scanClass != "CLASS_CARGO" && !entity.isThargoid && !entity.isCloaked && entity.primaryRole != "TCAT_jumpGate"}; 
	this.nearArray = system.filteredEntities(this, targetShips, this.ship, 25600); 
	this.farArray = system.filteredEntities(this, targetShips, this.ship, 60000); 
 
	if(this.nearArray.length > 0)
		{
		this.targetNumber = Math.floor(Math.random() * this.nearArray.length);
		if(this.targetNumber > this.nearArray.length)
			{
			log(this.name + " script error, target number too high - please report");
			this.targetNumber = 0;
			}
		this.ship.target = this.nearArray[this.targetNumber];
        this.ship.reactToAIMessage("TARGET_FOUND");
		}
	else
		{
		if(this.farArray.length > 0)
			{
			this.ship.target = this.farArray[0]; // set to the closest target off-scanner but in range
			this.ship.reactToAIMessage("INTERCEPT_TARGET");
			}
		else
			{
			this.ship.reactToAIMessage("NOTHING_FOUND")
			}
		}
	}; 

this.validateTarget = function()
	{
	if(this.ship.target && this.ship.target.scanClass != "CLASS_THARGOID" && !this.ship.target.hasRole("thargoid") && !this.ship.target.hasRole("thargon"))
		{
		this.ship.reactToAIMessage("TARGET_VALID");
		}
	else
		{
		this.ship.reactToAIMessage("TARGET_INVALID");
		}
	}
	
this.findStation = function()
	{
	this.stationArray = system.shipsWithPrimaryRole("TCAT_navyStation");
	if(this.stationArray.length == 0)
		{
		this.ship.reactToAIMessage("NOTHING_FOUND");	
		}
	else
		{
		this.ship.target = this.stationArray[0];
		this.ship.reactToAIMessage("STATION_FOUND");
		}
	}
	
this.shipDied = function ()
	{
	this.ship.commsMessage(expandDescription("[thargoid_curses]"));
	};

This is the last remaining bug I can see in TCAT (other than blocking of auto-docking and docking clearance, but that's trunk-side) but I can't see why I'm getting recursion log entries? OK the reactToAIMessage message is of course in the same overall AI state as the function call, but isn't that was reactToAIMessage is for rather than changing the AI state?

The script is used by multiple ships (all of whom give this error) hence the "unused" functions in this particular case.
User avatar
Eric Walch
Slightly Grand Rear Admiral
Slightly Grand Rear Admiral
Posts: 5536
Joined: Sat Jun 16, 2007 3:48 pm
Location: Netherlands

Post by Eric Walch »

I pass. I spawned a few ships with version 1.01 of your oxp. Even switched reportAIMessages on for the ship but I never could trigger the recursion.

Maybe you could include a

Code: Select all

this.ship.reportAIMessages = true 
in the spawn actions. Probably the log-lines before will give a further clue about the sequence that triggers the recursion.

On testing I did notice a bug: you also use the script for the thargelts. But than you must do an additional check to avoid the thargoid curses on destruction for an inactive tharglet. I even think a tharglet should not send a curse at all.

And the code:

Code: Select all

if(this.ship.target && this.ship.target.scanClass != "CLASS_THARGOID" && !this.ship.target.hasRole("thargoid") && !this.ship.target.hasRole("thargon")) 
is inefficient. I think that just looking for scanclass should be enough. (Or the .isThargoid property as you do elsewhere in the code.) The further role checks are only a waist of cpu cycles as something without thargoid scanclass should also not have a thargoid or tharglet role.
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Post by JensAyton »

The recursion limiter doesn’t actually care about what state or handler is involved, it just cuts you off at 32 levels of nested reactToMessage:es. I’ll add some better diagnostics for tonight’s nightly.
User avatar
Eric Walch
Slightly Grand Rear Admiral
Slightly Grand Rear Admiral
Posts: 5536
Joined: Sat Jun 16, 2007 3:48 pm
Location: Netherlands

Post by Eric Walch »

Ahruman wrote:
The recursion limiter doesn’t actually care about what state or handler is involved, it just cuts you off at 32 levels of nested reactToMessage:es. I’ll add some better diagnostics for tonight’s nightly.
I forgot. With this knowledge, the recursion is caused by the following sequence:

Code: Select all

   "ATTACK_SHIP" = 
   { 
      ENTER = ("sendScriptMessage: validateTarget"); 
      "TARGET_INVALID" = ("setStateTo: LOOK_FOR_TARGETS"); 
   }; 

   "LOOK_FOR_TARGETS" = 
    { 
      ENTER = ("sendScriptMessage: findNonThargoid"); 
      "TARGET_FOUND" = ("setStateTo: ATTACK_SHIP"); 
  }; 
Under the right conditions it can go indefinitely between both states without hitting a pause.

I think we should also have a normal "message" in the JS environment as alternative to "reactToMessage", like with the plain AI. There is not always the need for an immediately reaction. Reacting on the next UPDATE is often enough.
User avatar
Thargoid
Thargoid
Thargoid
Posts: 5528
Joined: Thu Jun 12, 2008 6:55 pm

Post by Thargoid »

Eric Walch wrote:
On testing I did notice a bug: you also use the script for the thargelts. But than you must do an additional check to avoid the thargoid curses on destruction for an inactive tharglet. I even think a tharglet should not send a curse at all.

And the code:

Code: Select all

if(this.ship.target && this.ship.target.scanClass != "CLASS_THARGOID" && !this.ship.target.hasRole("thargoid") && !this.ship.target.hasRole("thargon")) 
is inefficient. I think that just looking for scanclass should be enough. (Or the .isThargoid property as you do elsewhere in the code.) The further role checks are only a waist of cpu cycles as something without thargoid scanclass should also not have a thargoid or tharglet role.
TCAT has a few things which are thargoid but without the scanclass (at least as set, although some of them do appear so on the scanner). isThargoid is new I think. The extra checks are needed for example for the jumpgate, which appears thargoid but isn't except by role.

As for the Tharglets, if you're referring to the drones (the ones whose AI I posted) then they only go uncontrolled if no station is around. Effectively they are defense ships in this case.
User avatar
Thargoid
Thargoid
Thargoid
Posts: 5528
Joined: Thu Jun 12, 2008 6:55 pm

Post by Thargoid »

Ahruman wrote:
The recursion limiter doesn’t actually care about what state or handler is involved, it just cuts you off at 32 levels of nested reactToMessage:es. I’ll add some better diagnostics for tonight’s nightly.
OK that's clearer, so what would be the better action to take for an invalid target if we're ending up with nesting in the current set-up? How is the recursion avoidable?
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Post by JensAyton »

Eric Walch wrote:
With this knowledge, the recursion is caused by the following sequence:
Bah. Here’s me writing shiny new tools, and they turn out not to be needed. :-p
Eric Walch wrote:
I think we should also have a normal "message" in the JS environment as alternative to "reactToMessage", like with the plain AI. There is not always the need for an immediately reaction. Reacting on the next UPDATE is often enough.
I kind of assumed we did. Anyway, now we do.
User avatar
Eric Walch
Slightly Grand Rear Admiral
Slightly Grand Rear Admiral
Posts: 5536
Joined: Sat Jun 16, 2007 3:48 pm
Location: Netherlands

Post by Eric Walch »

Thargoid wrote:
How is the recursion avoidable?
Difficult, so therefor its a good thing its detected by the code with a warning in the log.

Always check if a chain of events stops:
setStateTo: will always imediately switch stated and execute the ENTER message in that state. When that ENTER message contains a sendScriptMessage that generates a reactToMessage:, the message it is referring to is also executed immediately. And when that contains a setStateTo: the circle is closed.

Having a plain message: would break the loop because it is not executed immediately but added to the messages that must execute on the next update. (With the risk of loosing it when something else switches states in the meantime).
We did not have this but Ahruman just added a "sendAIMessage" to the trunk code, to send such delayed messages that evaluates after the pauseAI: time.

In your case the circle can be broken by removing the sendScriptMessage from the ENTER message in LOOK_FOR_TARGETS, as it will also be checked in the UPDATE of that state.
Last edited by Eric Walch on Sat Jun 19, 2010 2:06 pm, edited 1 time in total.
User avatar
Thargoid
Thargoid
Thargoid
Posts: 5528
Joined: Thu Jun 12, 2008 6:55 pm

Post by Thargoid »

I may also look at changing some of the reactToAIMessage entries in the script to AI state changes instead, where that is what the AI message does when it goes back into the AI. See if that helps too.

The one you mention I think is the main culprit, as that one happens repeatedly in the log (I just posted two examples).
User avatar
Thargoid
Thargoid
Thargoid
Posts: 5528
Joined: Thu Jun 12, 2008 6:55 pm

Post by Thargoid »

The "removal of call from ENTER" worked, so I've updated the OXP accordingly (TCAT is now 1.03).

I couldn't change the script as I thought, as the call-back has different actions for different ships.
Post Reply