Skip to main content

News

Topic: What's wrong here? (Read 9513 times) previous topic - next topic

0 Members and 2 Guests are viewing this topic.
  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
What's wrong here?
Okay, I'm completely stumped here.  As it turns out, the Spectacles threader doesn't run on minisphere.  When it comes time to render or update threads, I get errors because, for some reason, the thread objects are being scrambled and some are coming back undefined.  At first I suspected a Duktape bug, but as it turns out, guess what?  The same exact thing happens, in the same exact place, when run in Sphere-SFML!  This obviously isn't a coincidence; SpiderMonkey in Sphere 1.5 implements ES3 while both Duktape and Jurassic are ES5-compliant.  Meaning there's something in there that's screwing up under ES5, but I can't figure out what, and I've combed the code over a dozen times.

Maybe somebody else could figure it out?  Here's the source for the Specs threader:

Code: (javascript) [Select]
/***
* Specs Engine v6: Spectacles Saga Game Engine
  *           Copyright (C) 2012 Power-Command
***/

// Threads object
// Represents the Specs Engine thread manager.
Threads = new (function()
{
this.hasUpdated = false;
this.nextThreadID = 1;
this.threads = [];
})();

// .initialize() method
// Initializes the thread manager.
Threads.initialize = function()
{
this.threadSorter = function(a, b) {
return a.priority != b.priority ?
a.priority - b.priority :
a.id - b.id;
};
}

// .create() method
// Creates a thread and begins running it.
// Arguments:
//     o:            The object to pass as 'this' to the updater/renderer/input handler. May be null.
//     updater:      The update function for the new thread.
//     renderer:     Optional. The render function for the new thread.
//     priority:     Optional. The render priority for the new thread. Higher-priority threads are rendered
//                   later in a frame than lower-priority ones. Ignored if no renderer is provided. (default: 0)
//     inputHandler: Optional. The input handler for the new thread.
Threads.create = function(o, updater, renderer, priority, inputHandler)
{
renderer = renderer !== void null ? renderer : null;
inputHandler = inputHandler !== void null ? inputHandler : null;
priority = priority !== void null ? priority : 0;

updater = updater.bind(o);
renderer = renderer !== null ? renderer.bind(o) : null;
inputHandler = inputHandler !== null ? inputHandler.bind(o) : null;
var newThread = {
id: this.nextThreadID,
inputHandler: inputHandler,
isUpdating: false,
priority: priority,
renderer: renderer,
updater: updater
};
this.threads.push(newThread);
++this.nextThreadID;
return newThread.id;
};

// .createEntityThread() method
// Creates a thread for a specified entity.
// Arguments:
//     entity:   The entity for which to create the thread. This should be an object having .update() and
//               optionally, .render() and .getInput() methods. Each of these will be called once
//               per frame until the thread either finishes (entity.update() returns false) or is terminated.
//     priority: Optional. The render priority for the new thread. Higher-priority threads are rendered
//               later in a frame than lower-priority ones. Ignored if no renderer is provided. (default: 0)
Threads.createEntityThread = function(entity, priority)
{
priority = priority !== void null ? priority : 0;

var updater = entity.update;
var renderer = (typeof entity.render === 'function') ? entity.render : null;
var inputHandler = (typeof entity.getInput === 'function') ? entity.getInput : null;
return this.create(entity, updater, renderer, priority, inputHandler);
};

// .doFrame() method
// Performs update and render processing for a single frame.
Threads.doFrame = function()
{
if (IsMapEngineRunning()) {
RenderMap();
} else {
this.renderAll();
}
FlipScreen();
if (IsMapEngineRunning()) {
this.hasUpdated = false;
UpdateMapEngine();
if (!this.hasUpdated) {
this.updateAll();
}
} else {
this.updateAll();
}
};

// .doWith() method
// Creates an improvised thread running in the context of a specified object.
// Arguments:
//     o:            The object to pass as 'this' to the updater/renderer.
//     updater:      The update function for the new thread.
//     renderer:     Optional. The render function for the new thread.
//     priority:     Optional. The render priority for the new thread. Higher-priority threads are rendered
//                   later in a frame (closer to the player) than lower-priority ones. (default: 0)
//     inputHandler: Optional. The input handler for the new thread.
Threads.doWith = function(o, updater, renderer, priority, inputHandler)
{
return this.create(o, updater, renderer, priority, inputHandler);
};

// .isRunning() method
// Determines whether a thread is still running.
// Arguments:
//     threadID: The ID of the thread to check.
Threads.isRunning = function(threadID)
{
if (threadID === 0) {
return false;
}
for (var i = 0; i < this.threads.length; ++i) {
if (this.threads[i].id == threadID) {
return true;
}
}
return false;
};

// .kill() method
// Prematurely terminates a thread.
// Arguments:
//     threadID: The ID of the thread to terminate.
Threads.kill = function(threadID)
{
for (var i = 0; i < this.threads.length; ++i) {
if (threadID == this.threads[i].id) {
this.threads.splice(i, 1);
--i;
}
}
};

// .renderAll() method
// Renders the current frame by calling all active threads' renderers.
Threads.renderAll = function()
{
Link(Link(this.threads).sort(this.threadSorter))
.where(function(thread) { return thread.renderer !== null })
.each(function(thread)
{
thread.renderer();
});
};

// .synchronize() method
// Waits for multiple threads to finish.
// Arguments:
//     threadIDs: An array of thread IDs specifying the threads to wait for.
Threads.synchronize = function(threadIDs)
{
var isFinished;
do {
this.doFrame();
isFinished = true;
for (var i = 0; i < threadIDs.length; ++i) {
isFinished = isFinished && !this.isRunning(threadIDs[i]);
}
} while (!isFinished);
};

// .updateAll() method
// Updates all active threads for the next frame.
Threads.updateAll = function()
{
var threadsEnding = [];
Link(Link(this.threads).sort(this.threadSorter))
.each(function(thread)
{
var stillRunning = true;
if (!thread.isUpdating) {
thread.isUpdating = true;
stillRunning = thread.updater();
if (thread.inputHandler !== null && stillRunning) {
thread.inputHandler();
}
thread.isUpdating = false;
}
if (!stillRunning) {
threadsEnding.push(thread.id);
}
});
for (var i = 0; i < threadsEnding.length; ++i) {
this.kill(threadsEnding[i]);
}
this.hasUpdated = true;
};

// .waitFor() method
// Waits for a thread to terminate.
// Arguments:
//     threadID: The ID of the thread to wait for.
Threads.waitFor = function(threadID)
{
var i = 0;
while (this.isRunning(threadID)) {
this.doFrame();
}
};
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: What's wrong here?
Reply #1
So after further testing, I think it might just be a bug in Link that's causing this.  See, if I change this:

Code: (javascript) [Select]
Link(Link(this.threads).sort(this.threadSorter))

to this:
Code: (javascript) [Select]
Link(this.threads)


It works just fine, albeit with the threads rendered out of order.  Interestingly enough, however, the following ALSO causes the error to show up:
Code: (javascript) [Select]
Link(Link(this.threads).toArray())


Which should definitely not cause an issue since Link(this.threads) works normally.
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: What's wrong here?
Reply #2
.sort calls the array.sort method, which might be implemented differently in SM v1.5 vs. other JS engines?

Code: (javascript) [Select]

function Sort(f) {
var v = this.toArray();
if (f) v.sort(f); else v.sort();
return v;
}


I would have made my own sort method, but for now the internal JS version was faster and, well, got the job done. I guess the other engines really are to blame? I'd give this a test. Test the array.sort method and see what happens under certain conditions. I think tie breakers are what's broken since the specs don't really govern that and it's really up to the implementer's judgement.
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: What's wrong here?
Reply #3
As mentioned above, the error also happens if I replace the .sort() with .toArray().  So it's not specific to sorting, something's borked in Link:toArray (which Link:sort uses).
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

Re: What's wrong here?
Reply #4
Just a cursory look, but I wonder if the issue is somewhere in here...
Code: (javascript) [Select]

function Run(point) {
this.env.stop = false;
this.env.skip = 0;
if (point) this.pushPoint(point);
var start = this.points[0];

// reset the points that store data between runs
for (var i = 0; i < this.points.length; ++i) {
var p = this.points[i];
if (p.reset) p.reset();
}

// kick-start points that have a runner tied to them:
if (start.run) { start.run(this.target); }
else {
var a = this.target, l = a.length, i = 0, e = this.env;
if (e.take)
while (i < l && !e.stop) start.exec(a[i++]);
else
while (i < l) start.exec(a[i++]);
}
this.points.length--;
}


Maybe Jurassic and Duktape don't appreciate decrementing the length of an Array that way?

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: What's wrong here?
Reply #5
No, it works correctly.

Code: (javascript) [Select]

var n = Link([0, 1, 2]).map(function(n) { return n+1; }).map(function(n) { return n-1; });
n.each(function() {});
n.each(function() {});
n.toArray();

Abort(n.points.length + ", looks like: " + n.toArray());


Changing the number of times n.each() is called, or n.toArray() doesn't affect the size of the array. That's why array.length - 1 is used, so you can keep the chain from n-1 and use different end points on it.
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: What's wrong here?
Reply #6
Found the bug!  For some reason toArray() is prepending an extra element to the array under ES5 engines.  See screenshot below.

Code: (javascript) [Select]
var foo = [ 5, 7, 3, 8, 1, 2, 4, 6 ];
var bar = Link(foo).toArray();
var baz = Link(bar).sort();
Abort("foo: [ " + foo.toString() + " ]\nbar: [ " + bar.toString() + " ]\n" + "baz: [ " + baz.toString() + " ]");
  • Last Edit: February 15, 2015, 02:22:04 am by Lord English
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: What's wrong here?
Reply #7
Fixed it.

old:
Code: (javascript) [Select]

AllPoint.prototype.run = function(a) {
var i = 0, l = a.length, b = this.array;
while (i < l) { b[i] = a[i++]; }
this.array = b;
}


new:
Code: (javascript) [Select]

AllPoint.prototype.run = function(a) {
var i = 0, l = a.length, b = this.array;
while (i < l) { b[i] = a[i]; i++; }
this.array = b;
}


I thought it would work correctly but unlike arithmetic, variable assignment goes the opposite way in the order of operations. So, I have to make sure incrementation happens after all is said and done just to make doubly sure.
  • Last Edit: February 15, 2015, 02:31:58 am by Radnen
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: What's wrong here?
Reply #8
How come it worked okay under Sphere 1.5?  Did ES5 change the operator precedence rules?
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: What's wrong here?
Reply #9
And, so yes, this fixes it too, and breaks it in Sphere 1.5! :P

Code: (javascript) [Select]

AllPoint.prototype.run = function(a) {
var i = 0, l = a.length, b = this.array;
while (i < l) { b[i++] = a[i]; }
this.array = b;
}


So, the order of ops on this is incorrect. I'll have to take a look to see what is correct, but I'm thinking the ECMA 5 engines are correct.
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

Re: What's wrong here?
Reply #10
Does it work correctly in modern Chrome or Fx?

I wonder if it might be a bug with the SM 1.5 uses. If you upload a test SPK or game, I can test with some of my mac 1.6+ builds that have other versions of libjs (1.3-1.8 ).

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: What's wrong here?
Reply #11

And, so yes, this fixes it too, and breaks it in Sphere 1.5! :P

Code: (javascript) [Select]

AllPoint.prototype.run = function(a) {
var i = 0, l = a.length, b = this.array;
while (i < l) { b[i++] = a[i]; }
this.array = b;
}



Haha, no kidding. :D  That's actually even worse than the original bug--items get deleted!
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: What's wrong here?
Reply #12

Does it work correctly in modern Chrome or Fx?

I wonder if it might be a bug with the SM 1.5 uses. If you upload a test SPK or game, I can test with some of my mac 1.6+ builds that have other versions of libjs (1.3-1.8 ).


My minimal foo/bar/baz code above is enough of a test, you just need a game() function with that along with a RequireScript for link.js.  The bug is painfully obvious just by printing out the array.
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

Re: What's wrong here?
Reply #13
Under TurboSphere with SM 37, using Link 0.2.15 from github:

Code: [Select]

foo: [ 5,7,3,8,1,2,4,6 ]
bar: [ 5,7,3,8,1,2,4,6 ]
baz: [ 1,2,3,4,5,6,7,8 ]


So I would suspect this is a bug, or two bugs with similar triggers, in both Duktape and Jurassic. SM 37 shares hardly anything with SM 1.8, and I am using the ES5/Harmony profile.
  • Last Edit: February 15, 2015, 03:35:07 am by Flying Jester

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: What's wrong here?
Reply #14
In Chrome, this doesn't work
Code: (javascript) [Select]

while (i < l) { b[i++] = a[i]; }


Maybe Duktape and Jurassic assumed the wrong order of operations? You'd think Google will have the correct method since as a web browser, it can't be allowed to break peoples code. To check, Firefox and IE produce same result.

So, that's weird!

Edit:
ninja'd by FJ, but I added more tests.

Edit 2:
I will say that in usual circumstances Jurassic and Duktape have the right of it. Variable assignment is usually right to left while other operations are left to right. However, it seems JavaScript wants it to go left to right as well, and the two ECMA5 engines just don't comply. Still don't know if it is an ECMA5 spec mishap or not.
  • Last Edit: February 15, 2015, 03:42:36 am by Radnen
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here