Skip to content
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

Proof of concept of pyp5js running on top of pyodide #123

Merged
merged 1 commit into from
Jun 14, 2020

Conversation

berinhard
Copy link
Owner

@berinhard berinhard commented Jun 14, 2020

Fixes #73

This PR adds the file draft/index.html as a working proof of concept of pyp5js running on top of pyodide instead of using transpiled javascript code by transcrypt. I'll be a little verbose in this PR because there's a lot of knowledge and assumptions on it. So, I want do document my approach with care so people can help on a future real integration between pyp5js + pyodide.

The original code

As I said in the issue, I based this PR on @Luxapodular code (supported by COSA (The Clinic for Open Source Arts) at the University of Denver College of Arts and Sciences) where he was able to integrate p5.js with pyodide. His code was also a proof of concept and, because of that, it only supported a few methods from p5.js API, such as square, ellipse etc and that's done via a wrapper variable, a string to hold the code to integrate p5.js + pyodide.

On this code, he defined 2 functions to inject the python code into the p5 instance which holds the sketch. The first one, responsible to update p5.js variables, is called updateVariable and, the second one is the sketch.

This second function has 2 responsibilities: to inject python implementations of p5.js functions (setup, draw and event handlers such as keyPressed) and to expose the p5.js API as Python callables (arc, rect, ellipse etc)

The wrapper ends with the initialization of a p5 sketch which is stored in the global JS window. The initialization gets the sketch function the same way as the docs does with const s.

Finally, the sketch is executed in the runCode function where the final code is a combination from the real sketch code plus the wrapper one and it is passed to a pyodide.runPython call.

The pyp5js wrapper

This PR is strongly based on @Luxapodular high-level approach of concatenating the sketch code with the wrapper code and then passing it to be executed by pyodide. The differences relies on how pyp5js implements the wrapper and how it initializes the sketch and they exist because of how pyp5js was initially designed to be integrated with Transcrypt. All of the pyp5js' wrapper logic is implemented in pyp5js/pyp5js.py.

The most important thing to notice is that pyp5js holds a global variable for the current p5 sketch instance and, due to complexities on how Transcrypt deals with global variables, the code is a little bit confusing, I know. But, the entrypoint for pyp5js is the start_p5 mehotd which receives 3 parameters: a setup implementation, a draw implementation and a dictionary with implementation for the event functions.

This function implements a inner method called sketch_setup which has the same purpose of enabling the Python code from accessing the sketch's variables (more precisely via the pre_draw method). The p5.js API is already enabled because pyp5js implements proxy methods to the global p5 instance variables.

So the sketch_setup uses Transcrytp's __new__ function to initialize a new p5.js Sjetch and then it iterates through the event functions dictionary to inject the respective implementations as the function handlers.

Integrating pyp5js with pyodide

My main goal was to, without introducing to many changes to this architecture, use the same code with pyodide. For the proof of concept, I've used the same code from pyp5js' example 001:

t = 0

def setup():
    createCanvas(600, 600)
    stroke(250)
    strokeWeight(3)
    fill(40, 200, 40)


def draw():
    global t
    background(10, 10)

    xAngle = map(mouseX, 0, width, -4 * PI, 4 * PI, True)
    yAngle = map(mouseY, 0, height, -4 * PI, 4 * PI, True)
    for x in range(0, width, 30):
        for y in range(0, height, 30):

            angle = xAngle * (x / width) + yAngle * (y / height)

            myX = x + 20 * cos(2 * PI * t + angle)
            myY = y + 20 * sin(2 * TWO_PI * t + angle)

            ellipse(myX, myY, 10)

    t = t + 0.01

    console.log(frameRate())

And I was able to do so with the following steps:

  1. I've update the wrapper variable to hold the sketch code + all the code from pyp5js/pyp5js.py;
  2. I changed how the sketch was being initialized to stop using the __new__ method from Transcrypt and, instead, directly call the p5();
  3. The final code was the wrapper code plus a manual call of start_p5(setup, draw, {});

Running the example

You'll have to follow this:

  1. cd draft;
  2. python -m http.sever
  3. Open http://localhost:8000 on your browser and open the console;
  4. Wait for pyodide to be initialized (Python initialization complete be printed on your console output);
  5. From your browser console, execute runCode()
  6. The sketch will be displayed =)

Results

The sketch worked from the very first try and this proves that the way pyp5js wraps p5.js to Python with Transcrypt will also work with pyodide with minor changes.

The downside comes to the performance. While the same sketch with the Transcrypt version was running on an average of 60 fps on my computer, the pyodide version was on 35~30fps.

Although this is a performance issue, I don't think it's a blocker for integrating pyodide with pyp5js because of the many issues Transcrypt also have when it comes to fully supporting a Python syntax.

So the next step is to open an issue proposing the integration of pyodide with pyp5js =)

@berinhard berinhard merged commit 5d90a15 into develop Jun 14, 2020
@berinhard berinhard deleted the feature/pyodide-proof-of-concept branch June 14, 2020 20:08
@jeremydouglass
Copy link

Just adding a note here for anyone unclear on the core concept:

"Transcrypt" vs. "pyodide"

  1. Transcrypt transpiles Python into JavaScript, and runs as JavaScript.
  2. Pyodide runs python on a cpython-for-browser compiled to WebAssembly. It comes with some python scientific stack built-in: NumPy, Pandas, Matplotlib, parts of SciPy, and NetworkX.

@GoToLoop
Copy link
Contributor

GoToLoop commented Jun 15, 2020

The downside comes to the performance. While the same sketch with the Transcrypt version was running on an average of 60 fps on my computer, the pyodide version was on 35~30fps.

So let's hope the faster Transcrypt would still be kept as a compile option for pyp5js, leaving pyodide as Python's full syntax alternative. 🙏

@berinhard
Copy link
Owner Author

+1 @GoToLoop! My idea is to add pyodide as an extra module and the user will be able to select on top of which "compiler" the code will be executed, Transcrypt or Pyodide

@jtpio jtpio mentioned this pull request Nov 2, 2020
@berinhard
Copy link
Owner Author

I know there are some people in this issue interested in this discussion, so I invite you to take a loot in this demo I've released today:

https://berinhard.github.io/pyp5js/pyodide/

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

Successfully merging this pull request may close these issues.

Check viability of using Luca Damasco proof of concept with pyodide
3 participants