Integration Question - namespace other than window
- [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
_startfunction (hacking the prototype and preventingthis._startfrom being set) - I added a
runmethod which would call the_startmethod after attaching the relevant p5 functions to the p5 instance. - I overrode the
_setPropertymethod 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).
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.
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.
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...
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('...')
}
I'm afraid I still don't understand this. Specifically I don't understand what skulpt is doing that means it cannot access window.
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.readyStateis 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
})
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:
- It uses global variable and functions attached to
windowby the runtime (consoleanddocumentfor example) - It attaches library functions and variables onto
window. - The user define functions attached to
windowthat 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.
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.
You can define p above as usual in instance mode and the use Object.assign to attach what's in p to __main__.
Sure but then on each draw I'd also need things like __main__.frameCount to have updated.
@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.
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.