p5.js icon indicating copy to clipboard operation
p5.js copied to clipboard

Integration Question - namespace other than window

Open s-cork opened this issue 4 years ago • 12 comments

  • [x] Core/Environment/Rendering

I have an integration question. I've looked at integrating p5 into skulpt. Skulpt is a python to javascript compiler. The idea being that p5 can be run using python code - super handy for educators that teach python.

I have proof of concept and it works well on p5 examples.

The problem I faced was working out how to run the global mode on a namespace other than window. I ended up digging into the core since I couldn't find an obvious way to do this.

The instance mode for p5 worked out the box - while this is great for someone integrating p5 into their own project, it wasn't convincing here since the purpose is for users to write p5 code in python. The global mode trumps instance mode as being user friendly for novices.

What I ended up doing

  • I created a p5 instance and attached all the methods to the namespace (not window)
  • I delayed the _start function (hacking the prototype and preventing this._start from being set)
  • I added a run method which would call the _start method after attaching the relevant p5 functions to the p5 instance.
  • I overrode the _setProperty method to attach these properties to the namespace being used.

For those interested here's what I ended up with: https://github.com/skulpt/skulpt/blob/d32562b1880c56759116fade210804bb0b6bc279/src/lib/p5.js

I don't like that i've had to override private methods, but I couldn't see that there were any hooks to achieve what I needed. Any suggestions would be very welcome. My use case is perhaps uncommon but an enhancement might be to allow an options to:

  • delay the call to _start,
  • provide a namespace other than window (I actually needed to point the p5 instance to two namespaces in the end).

s-cork avatar Mar 18 '21 03:03 s-cork

Welcome! 👋 Thanks for opening your first issue here! And to ensure the community is able to respond to your issue, be sure to follow the issue template if you haven't already.

welcome[bot] avatar Mar 18 '21 03:03 welcome[bot]

I don't quite understand what you mean by running global mode on a namespace that isn't window since running on window is how global mode works.

limzykenneth avatar Mar 19 '21 14:03 limzykenneth

Thanks for the reply. What I'm looking to do is allow a user to write something akin to following code (in the browser):

from p5 import *

def setup():
  createCanvas(400, 400);

def draw():
  if mouseIsPressed:
    fill(0)
  else:
    fill(255)
  ellipse(mouseX, mouseY, 80, 80)

run()

This is like the p5 global mode - but the name space is not window. It's a different namespace but behaves almost the same as the global mode does. (the call to run is basically invoking the _start mechanism).

I have it working - but I had to hack around with some private methods in the p5 core to make it happen.

I don't know if that makes more or less sense...

s-cork avatar Mar 19 '21 14:03 s-cork

I think what I really wanted was the following sort of api:

const p = new p5({
    globalObject: __main__, // a namespace defaults to window
    onSetProperty(prop, val) {}, // hook function
}) 

And then main.js would look something like this

if (sketch == null) {
  // window global mode
  this._isGlobal = true;
  this._globalObject = window;
} else if (typeof sketch === 'function') {
  // instance mode
} else if (typeof sketch === 'object') {
  // global mode with hooks 
  this._isGlobal = true;
  this._globalObject = sketch.globalObject || window;
  this.run = this._start // allow the user to invoke _start via p5instance.run()
} else {
  throw new Error('...')
}

s-cork avatar Mar 19 '21 15:03 s-cork

I'm afraid I still don't understand this. Specifically I don't understand what skulpt is doing that means it cannot access window.

limzykenneth avatar Mar 19 '21 16:03 limzykenneth

How skulpt works: See trinket.io as an example in action.

When the code is compiled to javascript it executes inside a scope. The local python variables and functions are attached to a namespace inside that scope.

Those python variables and functions don't get attached to the window. In theory skulpt could do something to attach those functions to the window object but it also seems like a hack, and I'd prefer not to pollute the window namespace if I can avoid it. Particularly when skulpt's goal is not really to execute p5 but to allow others to create a python environment inside the browser.

I may be wrong but so far as I can tell the web editor for p5 works by executing the code inside an iframe. The code is scoped inside the iframe window and the global object is the iframe window.

zooming out - I'm trying to create an environment for p5 code to execute, where:

  • window is not the global namespace for the functions and variables
  • executing based on document.readyState is premature
  • Using the alternative p5 instance mode works but is also insufficient without hacking the p5 prototype

namespace - I see some code that must have been implemented for similar reasons. https://github.com/processing/p5.js/blob/68ac2be8f7c3887a0f2b68554437cc0e152813f1/src/core/main.js#L617-L622

hooks - I also noticed some hook functions. But I couldn't find current documentation on whether this was a supported api. https://github.com/processing/p5.js/blob/68ac2be8f7c3887a0f2b68554437cc0e152813f1/src/core/main.js#L731

Both those apis seem private to me and rely on overriding the p5.prototype. The namespace api doesn't go far enough for what I was looking for.

I am curious as to whether there is any potential support for an api that formalises either hooks or namespace with the passing a sketch object to the constructor which has options e.g.

new p5({
  pre: [() => {}], // an array of functions or a single function
  post: [() => {}], // an array of functions or a single function
  // etc
})

s-cork avatar Mar 20 '21 02:03 s-cork

The _createFriendlyGlobalFunctionBinder function that you found is a Friendly Error System function that detects if a global p5 created variable is overwritten by the user, the possibility to bind global is only limited to this function to account for tests running in node.js which has global instead of window.

The way p5 interacts with window is in three ways:

  1. It uses global variable and functions attached to window by the runtime (console and document for example)
  2. It attaches library functions and variables onto window.
  3. The user define functions attached to window that is called by the library runtime.

With what you described is it accurate to say only 3 is not possible with the basic global mode syntax? ie. what is attached to window explicitly should still be available regardless of whether it is enclosed in another scope or not.

If what you need is to be able to define where functions and variables from 2 and 3 is attached to you should be able to use instance mode to achieve what you need. When defining the function for instance mode, you will also define a parameter that is used to attached 2 and 3. You can simply assign that parameter to the global object you have in mind (__main__ for example):

let sketch = function(p){
  __main__ = p;
};

new p5(sketch)

Then 2 and 3 should be attached to __main__ instead of window.

limzykenneth avatar Mar 20 '21 12:03 limzykenneth

Yes 2/3 is basically exactly what I need except that the __main__ namespace already exists and so I can't override it but need to work with it.

s-cork avatar Mar 20 '21 12:03 s-cork

You can define p above as usual in instance mode and the use Object.assign to attach what's in p to __main__.

limzykenneth avatar Mar 20 '21 12:03 limzykenneth

Sure but then on each draw I'd also need things like __main__.frameCount to have updated.

s-cork avatar Mar 20 '21 12:03 s-cork

@s-cork @limzykenneth thanks for this detailed write-up and discussion. I see what you're saying, but I think there are some practical limits we have to set on use cases we support. with global and instance mode we've tried to capture two of the most common ones. since we are a volunteer team, there's just not the capacity to support all different use cases within the library. in this case, using p5 in python feels a bit niche. there is a python mode for processing, which might be more useful to your case.

lmccart avatar Mar 28 '21 14:03 lmccart

It's very niche. Thanks for the link - though JavaScript is definitely the best implementation for skulpt (it's python in the browser compiled to JavaScript - even more niche). p5 has been requested several times recently by those using skulpt for education.

I'd be happy to work on relevant prs. But I understand hesitancy surrounding extending the api.

s-cork avatar Mar 28 '21 15:03 s-cork