Skip to content

Document/unit test ability for clients to initialize global mode explicitly #1570

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
toolness opened this issue Aug 28, 2016 · 9 comments
Open

Comments

@toolness
Copy link
Member

The TL;DR here is that it'd be nice to be able to initialize p5 global mode explicitly at any point in a web page's lifetime.

Currently there is no documented way to do this, but there is an undocumented way to do it: simply call new p5(), without any arguments.

So this issue isn't actually about adding any new functionality--it's just about documenting and/or adding unit tests so that tools or sketches that depend on it don't break in the future.

Use case for the p5 widget

In the p5 widget, we're currently trying to support scenarios where widget embedders may want to specify p5 addon libraries to load.

Currently, the p5 widget's preview frame works like this:

  1. Dynamically add a <script> element to the page containing the user's sketch code (which expects to be run in global mode).
  2. Dynamically add a <script> element with its src attribute pointing at a version of p5.

As soon as p5.js loads, it sees the setup/draw functions defined by the user's sketch and initializes global mode.

However, because the p5 widget loads p5 asynchronously, adding support for p5 addon libraries poses a conundrum:

  • We can't load p5 addon libraries before loading p5 because they depend on e.g. p5.prototype.registerPreloadMethod() to function properly.
  • It's difficult to load p5 addon libraries after loading p5 because p5 will have noticed that setup/draw exists in the global namespace and will start the sketch immediately, before we have a chance to asynchronously load any addon libraries.

While there are some ways the p5 widget could change its architecture to work around this, the easiest solution would be the following:

  1. Asynchronously load p5.js, followed by any add-on libraries.
  2. Add a <script> element to the page containing the user's p5 global mode sketch.
  3. Activate global mode to start the sketch.

Step 3 is where we need ongoing support from p5. While it's possible right now, it'd be nice to have the functionality documented and/or unit tested, so that the widget doesn't break when used with newer versions of p5.

I've written a working proof-of-concept of this approach at toolness/p5.js-widget#63.

Other benefits

Aside from making the p5 widget's implementation easier, I can think of a few other benefits to being able to explicitly start p5 global mode:

  • It actually allows p5 global mode to be instantiated before page load, which means that folks who really want to work around the assigning variables using p5 functions and variables before setup limitation can write the following sorts of sketches:

    new p5();
    
    var boop = random(100);
    
    function setup() {
        createCanvas(100, 100);
    }
    
    function draw() {
        background(255, 0, boop);
    }

    Kind of weird, but it's nice to have it available as an option.

  • One might argue that the ability to control the activation of p5 global mode isn't a feature "advanced" JS developers would need, because such users should just use instance mode. However, the value of the p5 global mode's API to advanced developers shouldn't be underestimated. There's lots of reasons they might still prefer to use global mode:

    • It's very expressive, while having to prefix one's sketches with the sketch instance suddenly makes one's code very verbose and harder to read.
    • It's still possible to use lots of third-party libraries without namespace collisions if one uses a module loader like requireJS or browserify.
    • One might start writing their sketch in global mode because it's easier, and needing to convert one's code over to instance mode just to have a bit more control over the way their sketch is loaded feels like an unnecessary trade-off.
  • Advanced users and tools (like p5 widget, IDEs) can potentially improve page load times by asynchronously loading multiple p5 add-ons simultaneously. I don't think this is possible without those users/tools having the ability to explicitly control when p5 global mode is initialized, but I could be wrong.

@kaganjd
Copy link
Contributor

kaganjd commented Sep 11, 2016

I've consolidated parts of @toolness's notes above, made up the name asynchronous global mode (?), and propose the following edits/additions as documentation:

"However, this might be inconvenient if you are mixing with other JS libraries (synchronously or asynchronously) or writing long programs of your own. We currently support two ways around this problem: instance mode and asynchronous global mode.

In instance mode, all p5 functions are bound up in a single variable instead of polluting your global namespace...

Another option is asynchronous global mode. To initialize asynchronous global mode at any point in a web page's lifetime, simply call new p5() without any arguments. Calling p5 explicitly this way gives you more control over how your sketch is loaded, which is helpful when you’re working with other libraries.

Along these lines, asynchronous global mode also allows p5 to be instantiated before page load, so folks who really want to work around the assigning variables using p5 functions and variables before setup limitation can write the following sorts of sketches..." [code block from @toolness notes above]

  • this related FAQ

"Well, technically, you can by using asynchronous global mode. But that's a less common use of p5, so we'll explain that later and talk about the more common case first. In regular global mode...

[after last para]

We mentioned asynchronous global mode earlier. This mode is most useful when you're building a program that uses other libraries and you want to control how p5 is loaded on the page with the others. You can read more about it here [link]. But another interesting use of asynchronous global modeis the ability to call p5 explicitly and then use p5 functions outside of setup(). Here's an example [code block from @toolness notes above]:"

Seems like there could also be room in asynchronous-calls-and-file-loading for this?

Thoughts? I'm happy to make changes, write more/less, etc.

@kadamwhite
Copy link
Contributor

made up the name asynchronous global mode (?)

$0.02, maybe "on-demand global mode?" As a JS developer async makes me think of the callbacks I register to react to some change, like user input or an AJAX request; and putting on a newbie hat I'm not sure how I'd interpret "asynchronous" in this context.

@kaganjd
Copy link
Contributor

kaganjd commented Sep 11, 2016

@kadamwhite thanks for the feedback. I think that'd be a great change.

@kaganjd
Copy link
Contributor

kaganjd commented Sep 15, 2016

@lmccart @shiffman any thoughts?

@lmccart
Copy link
Member

lmccart commented Sep 17, 2016

@kaganjd this looks great, thanks for taking a crack at this. I like @kadamwhite's suggestion of "on demand". feel free to go ahead and make the proposed changes to the overview wiki page.

the only other point I'll mention is that this should also theoretically work without the new. something like:

p5();

var boop = random(100);

function setup() {
    createCanvas(100, 100);
}

function draw() {
    background(255, 0, boop);
}

I'm not sure if this looks or feels better? or if it adds confusion with instance mode? I don't think you need it there either btw, but we decided at some point that it felt like it made sense because it aligned with the pattern of constructor functions.

@kaganjd
Copy link
Contributor

kaganjd commented Sep 17, 2016

Ohhh, interesting! Hmm... I think the new feels helpful because it seems like it makes a new thing rather than doing the normal thing of calling an existing function. Even though that's not quite what's happening, it seems more intuitive ? But IDK. I'll update the overview with the new included and if more discussion happens here, I can always update again.

@lmccart
Copy link
Member

lmccart commented Sep 17, 2016

Sounds good. I think that's similar reasoning to why we included it for instance mode examples. Let's leave as is for now then at least.

@toolness
Copy link
Member Author

Hmm, does just calling p5() without new still work? When I try it on jsbin, I get a Script error. (line 0), but new p5() works fine.

Something I'm noticing now is that on-demand mode actually seems to make the friendly error system feature that warns when the user overwrites p5 globals, introduced in #1318, think that all the globals are being redefined. It's possible this is because p5 is actually initializing itself twice in this case (once on-demand, again on page load). We're seeing the same kind of behavior in the web IDE in processing/p5.js-web-editor#7 (comment) so I'll need to look into that...

Anyhow, another option: I wrote a mini library a few years ago called TinyTurtle and it had an on-demand mode which was invoked via the following:

TinyTurtle.apply(window);

This is basically equivalent to TinyTurtle(), and one thing I like about it is that it reads a bit more like it's actually doing something, e.g. "applying" TinyTurtle's API to window.

Analogously, p5.apply(window) is basically the same thing as p5(), so it might be a more expressive alternative. However, as I mentioned earlier, I also can't tell whether p5() without the new operator still works, so there's that too... Hmm.

Anyways, I agree with @lmccart and @kaganjd that just leaving it as new for now is good!

@lmccart
Copy link
Member

lmccart commented Sep 22, 2016

oops, @toolness I just tested and you're right it does require the new. I'm not sure why I thought it didn't. so we can ignore all that and keep as is. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

4 participants