addFrameCallback and this.ship

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

Moderators: another_commander, winston, Getafix

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

addFrameCallback and this.ship

Post by Thargoid »

I'm just having a play with the new addFrameCallback method, and it's giving me problems.

Specifically if I try to use it to call a pre-existing function, then this.ship is not defined within that function (presumably as addFrameCallback is a method of Global?). This of course makes it rather difficult to actually use to animate a ship when the called function doesn't know what ship called it?

It passes down to the moveWings function fine and gives me access to the parameter delta, but this.ship is no longer defined. I tried to use a function definition as in the wiki example to call the moveWings function, but that failed too as it said this.moveWings was not a function?

What am I doing wrong here? Trying it with a modified version of the butterflies OXP. The FPS stuff etc may also not be right, but as I can't get the thing to run at all I can't test that yet...

Code: Select all

Exception: TypeError: this.ship is undefined
    butterflies_butterfly.js, line 97:
    	if(this.ship.AI == "dockingAI.plist" && this.ship.position.distanceTo(this.ship.target.position) < 1500)

Code: Select all

this.name               = "butterflies_butterfly";
this.author               = "Thargoid";
this.copyright            = "Creative Commons: attribution, non-commercial, sharealike.";
this.description         = "Butterfly wing sub-ent motion";
this.version            = "1.0";

this.shipSpawnedAsEscort = function()
	{
	this.ship.setAI("butterflies_butterflySwarmAI.plist");
	this.shipSpawned();
	}

this.shipSpawned = function()
	{
	this.wingCounter = 0;
	this.Q0 = new Quaternion([1,0,0,0]);
	this.Q1 = new Quaternion([0,0,0,1]);
	this.setColours(Math.floor(Math.random() * 8));
	this.callbackID = addFrameCallback(this.moveWings);
	}

this.setColours = function(selection)
	{   
	if(!selection || selection.isNaN || selection < 0 || selection > 8) { this.colorChoice = 0; }
		else { this.colorChoice = Math.floor(selection);}
      
	switch(this.colorChoice)
		{// if colorChoice is 0, revert to default colouration.
		case 0:
			{
			this.overrideMaterials(false);
			this.ship.displayName = this.ship.name;
			break;
			}
		case 1:
			{
			this.overrideMaterials("redColor");
			this.ship.displayName = "Red " + this.ship.name;
			break;
			}
		case 2:
			{
			this.overrideMaterials("cyanColor");
			this.ship.displayName = "Cyan " + this.ship.name;
			break;
			}
		case 3:
			{
			this.overrideMaterials ("grayColor");
			this.ship.displayName = "Gray " + this.ship.name;
			break;
			}
		case 4:
			{
			this.overrideMaterials("lightGrayColor");
			this.ship.displayName = "Light Gray " + this.ship.name;
			break;
			}
		case 5:
			{
			this.overrideMaterials("greenColor");
			this.ship.displayName = "Green " + this.ship.name;
			break;
			}
		case 6:
			{
			this.overrideMaterials("yellowColor");
			this.ship.displayName = "Yellow " + this.ship.name;
			break;
			}
		case 7:
			{
			this.overrideMaterials("purpleColor");
			this.ship.displayName = "Purple " + this.ship.name;
			break;
			}
		case 8:
			{
			this.overrideMaterials("magentaColor");
			this.ship.displayName = "Magenta " + this.ship.name;
			break;
			}			
		}   
	}

this.overrideMaterials = function(colName)
	{
//	this.ship.setMaterials({"butterflies_butterflyBody.png": { diffuse: colName }}); // body looks better dark - just change the wings
	this.ship.subEntities[0].setMaterials({"butterflies_butterflyWing.png": { diffuse: colName }});
	this.ship.subEntities[1].setMaterials({"butterflies_butterflyWing.png": { diffuse: colName }});
	}
   
this.moveWings = function(delta)
	{
	if(delta && delta === 0) { return; }
	
	if(this.ship.AI == "dockingAI.plist" && this.ship.position.distanceTo(this.ship.target.position) < 1500)
		{
		if(this.wingCounter != 0)
			{
			this.wingCounter = 0;
			this.ship.subEntities[0].orientation = this.Q0; // flatten the wings for docking
			this.ship.subEntities[1].orientation = this.Q1;
			if(this.callbackID && isValidFrameCallback(this.callbackID))
				{ removeFrameCallback(this.callbackID); }
			}
		return;
		}
	
	this.FPS = 1 / delta; 
	this.leftAngle = (Math.PI/4) * (Math.sin(this.wingCounter*(Math.PI/(this.FPS/2)))); // pi/4 to -pi/4 angle of rotation
	this.rightAngle = (Math.PI/4) * (Math.sin(Math.PI + this.wingCounter*(Math.PI/(this.FPS/2)))); // anti-phase with left wing
	if(this.leftAngle < 0) { this.leftAngle += (2*Math.PI) }; // keep angles clockwise only, for simplicity
	if(this.rightAngle < 0) { this.rightAngle += (2*Math.PI) }; // ditto
	this.ship.subEntities[0].orientation = this.Q0.rotateZ(this.leftAngle);
	this.ship.subEntities[1].orientation = this.Q1.rotateZ(this.rightAngle);
	
	if(this.wingCounter >= this.FPS)
		{
		this.wingCounter = 0;
		}
	else
		{
		this.wingCounter++;
		}
	}
   
this.shipDied = function (whom, why)
	{
	if(this.callbackID && isValidFrameCallback(this.callbackID))
		{ removeFrameCallback(this.callbackID); }
//	this.checkTimer.stop();
	}
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Re: addFrameCallback and this.ship

Post by JensAyton »

Try this.callbackID = addFrameCallback(this.moveWings.bind(this)).

In a normal method call, foo.bar(), this is bound to foo as an effect of the method call syntax. If you do:

Code: Select all

let bar = foo.bar;
bar();
the binding doesn’t happen. A frame callback is called in this way; you have to bind this it explicitly if you want it.

Another way to get at the ship would be:

Code: Select all

 this.shipSpawned = function ()
{
    // Do other setup
    let ship = this.ship;
    let wingCounter = 0;
    this.callbackID = addFrameCallback(function (delta)
    {
        // “ship” and “wingCounter” are in scope here.
    }
}
Incidentally, if(delta && delta === 0) { return; } never does anything, because 0 is a falsey value.
User avatar
Thargoid
Thargoid
Thargoid
Posts: 5525
Joined: Thu Jun 12, 2008 6:55 pm

Re: addFrameCallback and this.ship

Post by Thargoid »

The bind solved that issue, although when I followed the butterfly through a wormhole after it jumped then suddenly this.ship.subEntities became undefined for some reason. That said I've never done that follow with a normal butterfly to see if it replicates the problem, and now that we have the capability I would make them jump incapable anyway.

Also trying to kill the callback using shipDied doesn't seem to work (and the animation itself doesn't look right either). I can see this is going to be one to play with in more detail...
Switeck
---- E L I T E ----
---- E L I T E ----
Posts: 2412
Joined: Mon May 31, 2010 11:11 pm

Re: addFrameCallback and this.ship

Post by Switeck »

Thargoid wrote:
The bind solved that issue, although when I followed the butterfly through a wormhole after it jumped then suddenly this.ship.subEntities became undefined for some reason.
http://en.wikipedia.org/wiki/Butterfly_effect ?! :shock:
User avatar
Kaks
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 3009
Joined: Mon Jan 21, 2008 11:41 pm
Location: The Big Smoke

Re: addFrameCallback and this.ship

Post by Kaks »

Thargoid wrote:
The bind solved that issue, although when I followed the butterfly through a wormhole after it jumped then suddenly this.ship.subEntities became undefined for some reason. That said I've never done that follow with a normal butterfly to see if it replicates the problem, and now that we have the capability I would make them jump incapable anyway.

Also trying to kill the callback using shipDied doesn't seem to work (and the animation itself doesn't look right either). I can see this is going to be one to play with in more detail...
Not quite! I finally managed to look at what happens and what I discovered is:

Butterfly 1 leaves the system of its own accord, everything is fine.

Butterfly 2 is created immediately afterwards: Whenever a ship leaves the system using the AI command performHyperSpaceExit, the system populator adds a replacement with the same primary role. (You wouldn't believe how long it took me to figure out that one out! :p)

The player follows Butterfly 1.

Upon emerging from witchspace both butterfly1 & butterfly2 frame callbacks are reactivated. butterfly2 by now is a dead entity, and that's what caused the initial errors in the log.

The attempt to fix the issue by concentrating on buttefly 1 made things quite messy on the js side, so it all looked a lot more broken than it actually was...

The good news? It's all fixable via js, along the lines of what most OXPs were doing with timers until quite recently.

The bad news? Frame callbacks are going to stay as they are until MNSR. I'm loathe to refactor the frame callback code to delete itself when this goes out of scope, that way lies madness much potential breakage.

I'm going to do some more testing, just in case, but I should have a revised butterflies_butterfly.js in the next hour or so..
I'm fairly confident it should perform satisfactorily! ;)
Hey, free OXPs: farsun v1.05 & tty v0.5! :0)
User avatar
Thargoid
Thargoid
Thargoid
Posts: 5525
Joined: Thu Jun 12, 2008 6:55 pm

Re: addFrameCallback and this.ship

Post by Thargoid »

Fairy snuff. I'll give it a test and see, but it does sound plausible.

I'm sure I checked by targetting the butterfly in the new system and wasn't able to identify the sub-entities, but it's so long ago I might be misremembering.

But anyway thanks for the effort in testing!
User avatar
Kaks
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 3009
Joined: Mon Jan 21, 2008 11:41 pm
Location: The Big Smoke

Re: addFrameCallback and this.ship

Post by Kaks »

Also fair enough! :)

The codebase could also have changed from the time you checked it! :)
Hey, free OXPs: farsun v1.05 & tty v0.5! :0)
User avatar
Kaks
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 3009
Joined: Mon Jan 21, 2008 11:41 pm
Location: The Big Smoke

Re: addFrameCallback and this.ship

Post by Kaks »

Bump! :)

I was going to ask if you had a chance to test the updated script I sent you, but I've just had an idea!

Is that ok if I set up a quick link to the updated version of your frameCallback butterflies? That way the people on the forum will be able to test it, and will also be able to marvel at the smooth animation - I really like the new improved station approach one... :)
Hey, free OXPs: farsun v1.05 & tty v0.5! :0)
User avatar
Thargoid
Thargoid
Thargoid
Posts: 5525
Joined: Thu Jun 12, 2008 6:55 pm

Re: addFrameCallback and this.ship

Post by Thargoid »

I've got less than no time at the moment, not had a chance to even boot the game in a couple of weeks now.

So yes, no issue at all to post a temporary link up.
User avatar
Kaks
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 3009
Joined: Mon Jan 21, 2008 11:41 pm
Location: The Big Smoke

Re: addFrameCallback and this.ship

Post by Kaks »

Cool, I'll package the Kaks-ified butterflies WIP OXP & will provide a temp link in a short while.

In the meantime, if anyone else wants to play with frameCallbacks, they might want to read the following:



frameCallbacks do survive after the ship that created them dies/is left behind when jumping to another system.

Here's how to avoid problems with frameCallback:

Assuming you define your callback inside a ship script, like this:

Code: Select all

this.$callbackID = addFrameCallback(this.$myCallbackFunction.bind(this));
You can then have the callback remove itself automatically when its ship dies:

Code: Select all

this.$myCallbackFunction = function()
	{
	// Valid ship? Before rev4632 - & for exactly 1 frame - a dying ship would still be 'valid', but with no other valid properties.
	if(!this.ship.isValid || !this.ship.status)
		{
		// No valid ship? This callback needs to be removed! 
		removeFrameCallback(this.$callbackID); 
		delete this.$callbackID;
		return;
		}
	// The actual callback code...

		...
		
	}
 
Hey, free OXPs: farsun v1.05 & tty v0.5! :0)
User avatar
Kaks
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 3009
Joined: Mon Jan 21, 2008 11:41 pm
Location: The Big Smoke

Re: addFrameCallback and this.ship

Post by Kaks »

Ok, here's the temporary butterflies WIP link:

http://www.box.net/shared/lyupsybrilv7nv86x6zg

They're smoothly animated ( expecially as they dock! :P ), and you can test that smoothness by running Oolite with TAF x0.25 & slower.

You need to run the debug console to see these butterflies: have a look at the included README-TESTING.txt file to see exactly how.

Cheers,

Kaks.

PS: Thargoid, the .js file inside the temp link is a streamlined version of what I sent you the other day, & it's better to use the version in the link above, rather than the older one.
Hey, free OXPs: farsun v1.05 & tty v0.5! :0)
User avatar
Eric Walch
Slightly Grand Rear Admiral
Slightly Grand Rear Admiral
Posts: 5536
Joined: Sat Jun 16, 2007 3:48 pm
Location: Netherlands

Re: addFrameCallback and this.ship

Post by Eric Walch »

Kaks wrote:
frameCallbacks do survive after the ship that created them dies/is left behind when jumping to another system.

Here's how to avoid problems with frameCallback:

Assuming you define your callback inside a ship script, like this:

Code: Select all

this.$callbackID = addFrameCallback(this.$myCallbackFunction.bind(this));
You can then have the callback remove itself automatically when its ship dies:
Why not remove it on entity removal like:

Code: Select all

this.entityDestroyed = function ()
{
	if (this.$callbackID && isValidFrameCallback(this.$callbackID))
	{ 
		removeFrameCallback(this.$callbackID);
        delete this.$callbackID;
	}
}
It is just as good as doing inside the call back. And doing it on removal you know that you do it at the last moment the ship is still valid, but never to late.

The whole purpose of adding a this.entityDestroyed() method was for cleaning up timers and callbacks.
User avatar
Kaks
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 3009
Joined: Mon Jan 21, 2008 11:41 pm
Location: The Big Smoke

Re: addFrameCallback and this.ship

Post by Kaks »

Good reminder! :)
Hey, free OXPs: farsun v1.05 & tty v0.5! :0)
Post Reply