0

I have a problem that I tried to solve multiple times but I continue to fail.

I am not sure whether the problem falls into the bucket "You don't understand JavaScript fundamentals" or into the bucket "You don't understand paper.js".

Background

  • The code is run in an node.js environment
  • I execute multiple functions simultaneously using async.each
  • Each function creates a paper.js project and then removes it at the end
  • To avoid interferences, I create a list of paperscope objects before I call async.each. Then each function gets called with one of the paperscope objects.

It looks something like this:

var list_of_paperscopes = [];

for (var i = 0; i < number_of_desired_results; i++){

    list_of_paperscopes.push( new paper.PaperScope() );

}

list_of_functions[1] = function (paperscope, callback) {

    paperscope.setup(new paperscope.Size(500, 500));

    // etc. etc.
    // export paper result

    paperscope.project.remove();

},

list_of_functions[2] = function (paperscope, callback) {

    paperscope.setup(new paperscope.Size(300, 300));

    // etc. etc.
    // export paper result

    paperscope.project.remove();

}

// ...

async.each(list_of_indices, function (index, asyncEachCallback) {

    var paperscope = list_of_paperscopes.pop();

    list_of_functions[index](paperscope, function (err, value) {

           // do something


// etc etc.

The problem

The general code works. But the different paper projects interfere with each other. Elements that were meant to be in all projects sometimes accumulate in a single one and lack in all others.

Question

How do I prevent simultaneously executed paper projects from interfering with each other? Is there a way that allows me to use async.each, or do I have to switch to a slow consecutive execution?

[EDIT:] Updated and more complete code example

// This is still a toy example
// That is why some things may appear nonsensical when - in fact - they are required

var list_of_functions = [];

list_of_functions[0] = function (paperscope, callback) {

    paperscope.setup(new paperscope.Size(300, 300));

    // etc. etc.
    // export paper result

    paperscope.project.remove();

}


list_of_functions[1] = function (paperscope, callback) {

    paperscope.setup(new paperscope.Size(300, 300));

    // The following text will appear multiple times in the same paper project but not the others for THIS function with ID 1
    var some_text = new paperscope.PointText({
        point: [240, 190],
        content: 'some text',
        fillColor: 'black',
        fontFamily: 'Courier New',
        fontSize: 7,
        justification: 'center'
    });

    // etc. etc.
    // export paper result

    paperscope.project.remove();

}

// This array determines how often we will call each function
var list_of_function_indices = [0, 1, 1, 1] // I want to execute function with ID 0 once, and the other function 3 times


async.each(list_of_function_indices, function (function_index, asyncEachCallback) {

    // Generate a new PaperScope() object
    var paperscope = new paper.PaperScope();

    list_of_functions[function_index](paperscope, function (err, value) {

        if (err) {
            sails.log.error(err);
        }

        // always continue
        asyncEachCallback();

    });


}, function (err) {

    if (err) {
        sails.log.debug('A function failed...');
    }

});

Is it that paper.js cannot be executed asynchronously in general? I don't fully understand this: http://paperjs.org/reference/paperscope/#project

pascal
  • 365
  • 1
  • 3
  • 16
  • 1
    Can you please post the full actual code? My guess would be that you're [creating closures in loops](https://stackoverflow.com/q/750486/1048572), but that doesn't happen in the code you posted – Bergi Feb 22 '18 at 04:07
  • @Bergi Thanks for commenting! The functions are actually defined just as stated in the code snippet above. The functions are **not** created in a loop but explicitly as `list_of_functions[1] = function ( ... )` and then `list_of_functions[2] = function ( ... )` etc. – pascal Feb 22 '18 at 11:40
  • @Bergi Ahh, but could it be that the `var paperscope = ...` becomes a global variable. So that the parameter `paperscope` passed to the functions now has global scope? – pascal Feb 22 '18 at 11:42
  • EDIT: That did not help either. I tried doing this: `list_of_functions[index](list_of_paperscopes.pop(), function (err, value) { ` But this did not solve the problem. – pascal Feb 22 '18 at 11:54
  • 1
    No, a `var` is not a global variable, it's local to the function. But again, please post your complete real code and state how exactly the papers are interfering with each other. The question cannot be answered in its current state. As it is, there are many things wrong with it (e.g. the list should be created using an array literal not assignment, and you shouldn't iterate a list of indices but the array itself), but it should probably work. It might even be a bug in paper.js. – Bergi Feb 22 '18 at 13:04
  • 1
    Have you tried creating the `PaperScope` instances inside the `each` callback, one at a time, instead of putting all of them in an array beforehand? – Bergi Feb 22 '18 at 13:05
  • @Bergi Thank you! I will post a more complete version of the code. Give me 30 minutes or so. I cannot post the full code "as is" for two reasons: It is more than 500 LOC overall and I am not in a position to publish the full code. Many thanks for your help. This problem has been occupying my brain for a number of days already. – pascal Feb 22 '18 at 13:10
  • Just saw your last comment. Excellent idea. I will try that as well. – pascal Feb 22 '18 at 13:11
  • @Bergi No, putting the `PaperScope` into the `each` callback did not help. But it made my code cleaner at least. – pascal Feb 22 '18 at 13:19
  • 1
    The help page about [mcve]s should give you an idea what we need – Bergi Feb 22 '18 at 13:19
  • @Bergi I updated the code example. It is not executable as is but should contain all relevant information, I hope. I am happy to buy you a coffee as a thank you if we can solve this riddle. :) – pascal Feb 22 '18 at 14:05
  • 1
    Where do the functions in the list call their `callback`? – Bergi Feb 22 '18 at 14:49
  • 2
    Uh oh, just read the linked docs and have forebodings now. "*through clever scoping the properties and methods of the active scope seem to become part of the global scope. […] The global `paper` object is simply a reference to the currently active PaperScope. […] `activate()` activates this PaperScope, so all newly created items will be placed in its active project.*" – Bergi Feb 22 '18 at 14:58
  • 1
    What @Bergi said is what is happening. You can either a) `activate()` your Scope before working with it - or b) reference the Scope directly instead of `paper`. Paper.js does some magic under the scenes when it comes to scope activation. – nicholaswmin Feb 23 '18 at 01:16
  • @NicholasKyriakides Thank you very much! Option (a) seems problematic to me since all functions are run at the same time and so activating a scope before using it might still cause interferences. Am I correct? Regarding option (b): How do I reference the scopes directly? How would that look like in my code? – pascal Feb 23 '18 at 01:42
  • 1
    @pascal For example, assume you want to add a `Path` to a `PaperScope`: Instead of `paper.project.activeLayer.addChild(path)` you can do `paperscopes[0].project.activeLayer.addChild(path)`. I'm sorry but I haven't read through your code. – nicholaswmin Feb 23 '18 at 04:13
  • Super helpful! Thank you, @NicholasKyriakides ! – pascal Feb 26 '18 at 16:52
  • 1
    You can write it up as an answer and self-accept it to remove it from the unanswered queue. – nicholaswmin Feb 26 '18 at 16:53
  • Thanks. I will do after I have tested the solution. – pascal Feb 26 '18 at 22:07

1 Answers1

1

Thanks to the help of @Bergi and @NicholasKyriakides, I was able to solve the problem.

The solution is to generate a list of PaperScope objects in advance, like so:

var list_of_paperscope_objects = [];

for (var i = 0; i < number_of_desired_paperscopes; i++){

    // Generate a new PaperScope() object
   list_of_paperscope_objects.push( new paper.PaperScope() );

}

In each of my async functions, I can then create and work with the different paperscope objects. For this, I pass the list of paperscope objects as well as the key (the index) to each function. Then I can do:

paperscopes[key].setup(new paper.Size(300, 200)); 
paperscopes[key].settings.insertItems = false; 

var rect = new paperscopes[key].Path.Rectangle({
    point: [0, 0],
    size: [300, 300]
});

paperscopes[key].project.activeLayer.addChild(rect);

Massive thanks for the help from the commenters, @Bergi and @NicholasKyriakides.

pascal
  • 365
  • 1
  • 3
  • 16