I'm having trouble understanding how specially defined p5 functions can reference globally defined variables. Functions like this one -- where a constant supplies the argument for a p5 function locally -- work just fine.
function setup() {
}
function draw() {
ellipse(50, 50, 80, 80);
}
But for some reason, I can't get something like this to draw an ellipse:
var CANVAS_HEIGHT = 1024;
var CANVAS_WIDTH = 768;
var RADIUS = 150;
var circleColor = 150;
var bgColor = 50;
function setup() {
backgroundColor = color(bgColor);
createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
}
function draw() {
fill(circleColor);
ellipse(CANVAS_WIDTH/2, CANVAS_HEIGHT/2, RADIUS*2, RADIUS*2);
}
Apparently the setup function does create a canvas referencing the global variables. The draw function, however, doesn't seem to reference them. What am I missing here? Thanks for any help.
You can't use variables like that in the setup() function.
You have to use the values directly, and you have to assign the variables inside the setup() function:
var CANVAS_HEIGHT;
var CANVAS_WIDTH;
var RADIUS;
var circleColor;
var bgColor;
function setup() {
backgroundColor = color(50);
createCanvas(1024, 768);
CANVAS_HEIGHT = 1024;
CANVAS_WIDTH = 768;
RADIUS = 150;
circleColor = 150;
bgColor = 50;
}
From the p5.js faq:
Why can't I assign variables using p5 functions and variables before setup()?
In global mode, p5 variable and function names are not available
outside setup(), draw(), mousePressed(), etc. (Except in the case
where they are placed inside functions that are called by one of these
methods.) What this means is that when declaring variables before
setup(), you will need to assign them values inside setup() if you
wish to use p5 functions. For example:
var n;
function setup() {
createCanvas(100, 100);
n = random(100);
}
The explanation for this is a little complicated, but it has to do
with the way the library is setup in order to support both global and
instance mode. To understand what's happening, let's first look at the
order things happen when a page with p5 is loaded (in global mode).
Scripts in are loaded. of HTML page loads (when this is complete, the
onload event fires, which then triggers step 3). p5 is started, all
functions are added to the global namespace. So the issue is that the
scripts are loaded and evaluated before p5 is started, when it's not
yet aware of the p5 variables. If we try to call them here, they will
cause an error. However, when we use p5 function calls inside setup()
and draw() this is ok, because the browser doesn't look inside
functions when the scripts are first loaded. This is because the
setup() and draw() functions are not called in the user code, they are
only defined, so the stuff inside of them isn't run or evaluated yet.
It's not until p5 is started up that the setup() function is actually
run (p5 calls it for you), and at this point, the p5 functions exist
in the global namespace.
Related
I'm trying to understand what I'm doing when I stick a pjs sketch to an html canvas in the following way.
<body>
<canvas id="mycanvas" style="border: 1px solid black;"></canvas>
</body>
<script src="https://cdn.jsdelivr.net/processing.js/1.4.8/processing.min.js">
</script>
<script>
var sketchProc = function(processingInstance) {
with (processingInstance) {
size(800, 400);
frameRate(30);
draw = function() {
background(235, 245, 255);
};
}};
var canvas = document.getElementById("mycanvas");
var processingInstance = new Processing(canvas, sketchProc);
</script>
When I began learning this stuff, I was eager to add my animations to an html doc. I learned the above way pretty much how I imagine a hamster can learn to push a button and receive a pellet; that is, I don't understand the mechanism well, but the result is positive.
Here is the above example in action:
https://jsfiddle.net/h1Lb91ux/
Here is my attempt at making sense of this.
The following code is defining a function called sketchProc.
var sketchProc = function(processingInstance) {
with (processingInstance) {
size(800, 400);
frameRate(30);
draw = function() {
background(235, 245, 255);
};
}};
This doesn't return anything because we're just defining sketchProc, not calling it. It's argument is processingInstance, which is being defined as a local variable just like if I did:
var functionX = function(x) {
return x;
};
But I do not understand the with statement. I know of with statements from excel-vba, which can be used to attach multiple methods to an object without having to repeatedly type out the object name. Is this the same?
The following statement makes perfect sense.
var canvas = document.getElementById("mycanvas");
But this next one is a source of confusion:
var processingInstance = new Processing(canvas, sketchProc);
To me, this looks like we're making a single instance or object from an object constructor function (in javascript) or a class (in processing), which takes 2 arguments: canvas and sketchproc. We're calling this specific instance processingInstance, which confusingly is the same name as the local variable used in the function sketchProc.
I hope this is not too rambling.
The following code is defining a function called sketchProc.
var sketchProc = function(processingInstance) {
with (processingInstance) {
size(800, 400);
frameRate(30);
draw = function() {
background(235, 245, 255);
};
}};
It's defining a function object that itself contains functions like draw(). You can read more about function objects here, but basically this is just an instance that contains functions that Processing can call.
But I do not understand the with statement.
Try googling something like "javascript with" for a ton of results, including this one. But my understanding is that it's a shortcut to avoid having to call processingInstance. before everything. You can try removing the with statement and putting processingInstance. before any Processing function to see the alternative. The with is optional, but makes you code slightly shorter. I personally don't like the use of with here, but that's just me.
But this next one is a source of confusion:
var processingInstance = new Processing(canvas, sketchProc);
To me, this looks like we're making a single instance or object from
an object constructor function (in javascript) or a class (in
processing), which takes 2 arguments: canvas and sketchproc. We're
calling this specific instance processingInstance, which confusingly
is the same name as the local variable used in the function
sketchProc.
Your understanding sounds about right. Creating an instance of Processing is what kicks off all of the Processing magic, calling the setup() function and the draw() function for you automatically. You can also rename the variable if it confuses you.
Taking a step back, this code uses Processing.js's instance mode, which is why you have all this extra code that you don't really understand. I would recommend you start with the more basic global mode, which would allow your code to look like this:
<script type="application/processing">
function setup(){
size(800, 400);
frameRate(30);
}
function draw() {
background(235, 245, 255);
};
</script>
<canvas> </canvas>
I'm also curious why you're using Processing.js. If you're coming from a JavaScript background, you might have more luck with P5.js instead.
function Childclass(){
this.printX = function(){
print(this.vector.y);
}
}
function Superclass(){
this.vector = createVector(1,2);
}
Childclass.prototype = new Superclass();
When running printX() I get the following error:
Uncaught ReferenceError: createVector is not defined
at new Superclass
Is this possible using createVector() in prototypes?
From the p5 FAQ:
Why can't I assign variables using p5 functions and variables before setup()?
Well, technically, you can by using on-demand 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, p5 variable and function names are not available outside setup(), draw(), mousePressed(), etc. (Except in the case where they are placed inside functions that are called by one of these methods.) What this means is that when declaring variables before setup(), you will need to assign them values inside setup() if you wish to use p5 functions.
The solution itself is also in the FAQ:
We mentioned on-demand 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. But another interesting use of on-demand global mode is the ability to call p5 explicitly and then use p5 functions outside of setup(). Here's an example:
new p5();
var boop = random(100);
function setup() {
createCanvas(100, 100);
}
function draw() {
background(255, 0, boop);
}
I want to have a file with global variables, for example:
function Globals() {
}
Globals.gravity = createVector(0, -9.81);
Unfortunately p5.js functions can only be used when they are declared inside setup() or draw() or are called from one of these functions.
My question is what would be the best approach to make globals easy to use?
My only idea is to make them functions, but that is not very pretty (you have to call function to get a value) and it is probably slow, because each access to a global variable requires making a call.
Globals.gravity = function() { return createVector(0, -9.81); }
Well, fortunately, you could use p5.js functions outside setup() and draw() function.
In order to use those functions, you need to call new p5() beforehand, like so ...
new p5(); //<-- call this
function Globals() {}
Globals.gravity = createVector(0, -9.81);
For more info, refer here
I'm trying to run trusted JS code in an "isolated" context.
Basically came up with this method:
function limitedEval(src, context) {
return (function() {
with(this) {
return eval(src)
}
}).call(context)
}
This works great, however when the script is using the var keyword it is stored in the execution context as opposed to the provided context in the with statement (which I understand is by design). So for example, the following code doesn't work:
var ctx = {};
limitedEval('var foo = "hello"', ctx);
limitedEval('alert(foo)', ctx); // error: foo is undefined
I'd like to be able to call limitedEval() multiple times and reuse the context. Is that possible?
This seems like a very interesting problem. The problem with your code is that you generate new function every time you execute limitedEval. This means that whatever variables you create using var keyword, will only exist within the context of that function. What you really need is to have 1 function per context and reuse that function's context. The most obvious way to do that is by using generators. Generators are essentially functions that can be paused and then restarted.
// IIFE to store gen_name
var limitedEval = function() {
// Symbol for generator property so we don't pollute `ctx` namespace
var gen_name = Symbol();
return function limitedEval(src, context) {
if(!(gen_name in context)) {
// Generator that will run eval and pause til getting the next source code
var generator = function * () {
with(this) {
while(true) {
yield eval( yield );
}
}
};
context[gen_name] = generator.call(context);
// Initially, we need to execute code until reaching the first `yield`
context[gen_name].next();
}
// First, send the source to the `eval`
context[gen_name].next( src );
// Second, get the `eval` result
return context[gen_name].next().value;
};
}();
And now you can call
var ctx = {};
limitedEval('var foo = "hello"', ctx);
limitedEval('alert(foo);', ctx);
Every limitedEval call will now reuse whatever generator function it will find on the provided ctx object. If the function doesn't exist, it will create it and put it on the ctx object. Because you now have only one function per ctx, it will reuse the function's context when creating variables with var keyword. Note: you will not be able to look up those variables via the ctx object, because they will only exist within the function's context.
I'm not entirely sure if you can achieve the same result without using generators.
Edit: others made great suggestions so I replaced randomly generated property with Symbol and did with(this) instead of with(context)
Here I have a p5 object that I am exporting to be bundled by browserify:
var p5 = require('p5')
var colorPicker = require('./color_picker.js')
module.exports = new p5(function () {
this.setup = function setup () {
this.createCanvas(700, 400)
this.background(205)
this.loadImage('/uploads/uploaded_image', function (img) {
image(img, 0, 0)
})
this.updatePixels()
}
this.clearCanvas = function redraw () {
this.background('black')
}
this.mouseDragged = function mouseDragged () {
var rgb = colorPicker.getRGB()
this.stroke(rgb.r, rgb.g, rgb.b)
this.strokeWeight(10)
this.line(this.pmouseX, this.pmouseY, this.mouseX, this.mouseY)
}
})
All of this works fine and I can access all built in p5 functions in other bundled scripts but not the clearCanvas function that I have defined. I also tried attaching it to the window object based on another SO post, like this:
window.this.clearCanvas = function redraw(){
//code
}
Everything so far has yielded Uncaught TypeError: Cannot set property 'clearCanvas' of undefined
Any idea what I'm doing wrong?
The modules build by browserify have their own scope, so nothing is exposed to the window object per default. You explicitly need to append your stuff to the window object to access it from a browser.
var p5 = require('p5')
var colorPicker = require('./color_picker.js')
module.exports = new p5(function () {
// ...
this.clearCanvas = function redraw () {
this.background('black')
}
// ...
window.clearCanvas = this.clearCanvas.bind(this);
});
First, for the section:
window.this.clearCanvas = function redraw(){
//code
}
To attach something to the window object do it directly,changing it to this:
window.clearCanvas = function redraw(){
//code
}
Worked, however I wanted to attach to the window object as infrequently as possible. For p5.js this section in the documentation is important:
By default, all p5.js functions are in the global namespace (i.e. bound to the window object), meaning you can call them simply ellipse(), fill(), etc. However, this might be inconvenient if you are mixing with other JS libraries or writing long programs of your own. To solve this problem, there is something we call "instance mode", where all p5 functions are bound up in a single variable instead of polluting your global namespace.
https://github.com/processing/p5.js/wiki/p5.js-overview
Running p5.js in instance mode allowed me to use the clearCanvas function without binding it to the window object.