idyll icon indicating copy to clipboard operation
idyll copied to clipboard

P5JS Support

Open mathisonian opened this issue 6 years ago • 14 comments
trafficstars

Is your feature request related to a problem? Please describe. I'd like to support a streamlined workflow for embedding Processing/P5 sketches in idyll documents.

Describe the solution you'd like Ideally it would look something like this:

Normal text

```exec:p5
function setup() {

}

function draw() {
  ellipse(50, 50, 80, 80);
}
```

... more normal text. 

There is some initial support for this type of workflow in this plugin: https://github.com/idyll-lang/idyll-plugin-runscripts/, but it currently just supports R code and is at the "proof of concept" level. We can extend or fork that to build a similar Processing workflow.

This plugin will need to do some code generation to spit out a component that automatically imports the P5 library, enables reactivity via idyll variables, etc, which I can discuss in more detail, but I think the first step is to get the code sample described above working.

See also https://github.com/idyll-lang/idyll/issues/117 for some early work on this front. At the time that component was made the idyll plugin system didn't exist, so hopefully we have all the tools needed to make a really streamlined P5 experience.

/cc @amitbadala

mathisonian avatar Jul 01 '19 02:07 mathisonian

So I have created a custom script component that adds the given script to document. Then I am adding the p5js scripts from the idyll-plugin-runscript using this custom script component. And this seems to get the script to work on ssr and csr.

Could you explain the approach you mentioned before a bit more?

Also how can I work on idyll-plugin-observable? Unable to link it.

rollaball avatar Aug 08 '19 18:08 rollaball

It sounds like what you have is in the right direction. What are the specific issues that you are running into? The setup that I was imagining is:

  1. there is a plugin which reads in code snippets like the above during compile time, and serializes to disk an idyll component which includes all of the boilerplate for embedding P5.js, along with the custom code that the user provided; This functionality could either be added to the existing runscripts plugin, or put in its own plugin a la idyll-plugin-observable.

  2. once (1) works as expected, the plugin should be updated to take into account idyll reactive variables, meaning that if the P5 code references an Idyll variable, it should always receive the latest value of that variable. This logic should be added to the serialized output that the plugin produces

Here are the steps to develop idyll-plugin-observable:

  • clone locally
  • link the component (cd idyll-plugin-observable/component; npm link .)
  • link the plugin (cd idyll-plugin-observable/plugin; npm link .)
  • make these availabe in your test project (cd my-idyll-project; npm link idyll-observable-component idyll-plugin-observable)

Depending on your setup, you may see errors relating to babel coming from the component. In this case cd idyll-plugin-observable/component; npm i babel-preset-env babel-preset-react babel-preset-stage-2.

mathisonian avatar Aug 12 '19 23:08 mathisonian

These seem to answer most of my questions. You mentioned something about adding this flow to the compile pipeline. Is that the same thing explained here?

rollaball avatar Aug 12 '19 23:08 rollaball

Ah yeah, so as it stands, if a component is created/serialized during the compile process it won't be available until the next time idyll runs because of the way that component loading happens now.

This is because the component resolver builds a map of available components when it is instantiated (https://github.com/idyll-lang/idyll/blob/master/packages/idyll-cli/src/index.js#L89) but when trying to look up components after the compile has happened (https://github.com/idyll-lang/idyll/blob/master/packages/idyll-cli/src/pipeline/index.js#L33-L51) it doesn't reload to check for new files that have appeared on disk.

For this to be really streamlined we would need to update that so it can resolve components that have been generated during compile.

mathisonian avatar Aug 13 '19 00:08 mathisonian

It would be better to use instance mode instead of global mode as we will be having multiple p5 scripts so the syntax would look something like this

```exec:p5
const sketch = ( p ) => {
  p.setup = function() {
  
  };

  p.draw = function() {
  
  };
};

or

```exec:p5
p.setup = function() {
  
};

p.draw = function() {
  
};

https://github.com/processing/p5.js/wiki/p5.js-overview#instantiation--namespace

rollaball avatar Aug 19 '19 17:08 rollaball

@mathisonian let me know if I should go ahead with this.

rollaball avatar Aug 21 '19 05:08 rollaball

@rollaball - is it possible to wrap this logic for users? E.g. - they write something like --

function setup() {
   ...  
}

function draw() {
  
};

and behind the scenes we translate that into:

const sketch = ( p ) => {

  // user code is inserted here

  p.setup = setup;
  p.draw = draw;
};

mathisonian avatar Aug 22 '19 20:08 mathisonian

If it was just setup and draw it would have been fine but all the p5 functions and variables also need to have its instance object which would turn functions like createCanvas to p.createCanvas. We could deconstruct the object but we don't know which functions will be used.

const sketch = ( p ) => {
  const {createCanvas, elipses, ...} = p;

  // user code is inserted here

  p.setup = setup;
  p.draw = draw;
};

rollaball avatar Aug 22 '19 22:08 rollaball

I think there may be a way to use the approach that is used in the runtime to solve this issue, namely using eval() to force a particular context.

Then we end up with something like:

const sketch = ( p ) => {

  (function() {
    eval(`
     // user code is inserted here
     
     p.setup = setup
     p.draw = draw // need to test to make sure this is the right spot for these assignments
   `)
    
  }).call(p);
};

I don't think there are any security issues in this case since this is user provided code that is expected to be executed anyways.

mathisonian avatar Aug 22 '19 23:08 mathisonian

More info on this technique to inject a context here: https://stackoverflow.com/questions/8403108/calling-eval-in-particular-context

mathisonian avatar Aug 22 '19 23:08 mathisonian

But its possible that this is just over complicating the issue. In that case I think

```exec:p5
p.setup = function() {
  
};

p.draw = function() {
  
};
```

is a good fallback

mathisonian avatar Aug 22 '19 23:08 mathisonian

Hey @mathisonian, I have made a separate plugin for P5 support. https://github.com/RakeshUP/idyll-plugin-p5 I am still working on it. As of now, it would serialize a new component for each block of p5js code in idyll and it's rendering p5 sketch. It supports the following syntax

```exec:p5
let x = 0;

function setup() {
  p.background(100);  
}

function draw() {
  p.ellipse(x, p.height/2, 20, 20);
  x = x + 1;
}
```

But I am not sure how to proceed with Idyll reactive variables. Like how would the syntax look like for idyll variables in P5 code?

RakeshUP avatar Jan 10 '20 12:01 RakeshUP

Since Idyll accepts nearly every React component, why not just use https://github.com/Gherciu/react-p5 ?

Once you have npm i react-p5 (I personally had to use the --legacy-peer-deps option), you can just try it in an Idyll post (note that you need to launch Idyll with the --no-ssr option as that component uses window) :

[var name:"theX" value:0 /]
[var name:"theY" value:0 /]

X: [Range value:theX min:0 max:500 step:0.01  /]
Y: [Range value:theY min:0 max:500 step:0.01  /]

[ReactP5
  theX:theX,
  theY:theY,
  setup:` (p5, canvasParentRef) => {
    p5.createCanvas(500, 500).parent(canvasParentRef);
  };`
  draw:`(p5) => {
    p5.background(0);
    p5.ellipse(theX, theY, 70, 70);
  };`
/]

If we had an option to disable ssr for selected components, we could just use that one by default.

Otherwise I could just use react-p5 as a base for an idyll-p5 component that I could publish on npm, to replace https://github.com/JobLeonard/idyll-p5 which is not up-to-date (that was my plan initially but I'd rather have the opinion of more active Idyll devs first, to know in the case this not a clean enough solution).

Also, could it be added on https://idyll-lang.org/docs/components/npm ?

c4ffein avatar Nov 05 '21 01:11 c4ffein

This seems reasonable to me @c4ffein. If this solution is fairly ergonomic (which it seems like it is, except for the peer-deps/ssr issues) then it is probably more sustainable to rely on a well-maintained third party component.

It could be nice to add a tutorial or other docs walking through this usage, and maybe add a link to the "suggested components" here https://idyll-lang.org/docs/components/npm

mathisonian avatar Nov 05 '21 17:11 mathisonian