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

Sketch instantiation proposal #113

Closed
evhan55 opened this issue Feb 11, 2014 · 82 comments
Closed

Sketch instantiation proposal #113

evhan55 opened this issue Feb 11, 2014 · 82 comments

Comments

@evhan55
Copy link
Contributor

evhan55 commented Feb 11, 2014

Hi Everyone,

For some background, here is a document that we created during a big hands-on meeting last Friday during discussions: https://docs.google.com/document/d/1CAp4n3YtW6a6KSRosEcGf3T2ph_Id_7WlrkOXyDCShQ/edit

Below, please review a proposal that Lauren and I put together of the API for instantiating sketches in the base cases. Let us know what you think!
@bobholt @brysonian @kadamwhite, etc.

// API
preload(): runs once, first
setup(): runs once, second
draw(): loops, indefinitely
createCanvas(w, h): creates a canvas element at the 0,0 with input size

// NEXT QUESTIONS TO ANSWER
// 
// draw() vs. loop()?
// What did we decide about createHTMLElement() and the other DOM calls?
// What are the different mouse accessors?

// CASE 0
// No setup() and draw().
// createCanvas() gets called automatically behind the scenes and creates a default
// canvas at 0,0 with a default size and background color.
fill(255, 0, 0);
ellipse(10, 10, 50, 50);

// CASE 1
// Only setup().
// setup() runs once and createCanvas() gets called automatically with defaults.
function setup() {
  background(255, 0, 0);
  noStroke();
  ellipse(0, 0, 50, 50);
}

// CASE 2
// Only setup() and createCanvas().
// setup() runs once and createCanvas() returns a pointer to the canvas created
// with the input size, at 0,0.  Holding the pointer is optional.
function setup() {
  createCanvas(400, 400); 
  background(255, 0, 0);
  noStroke();
  ellipse(0, 0, 50, 50);
}

// CASE 3
// Only draw().
// createCanvas() is called automatically with defaults.
function draw() {
  ellipse(random(0, 400), random(0, 400), 50, 50);
}

// CASE 4
// setup() and draw() without createCanvas().
// createCanvas() is called automatically with defaults.
function setup() {
  background(255, 0, 0);
}
function draw() {
  ellipse(random(0, 400), random(0, 400), 50, 50);
}

// CASE 5
// setup() and draw() with createCanvas().
function setup() {
  createCanvas(400, 400);
  background(255, 255, 0);
}
function draw() {
  ellipse(random(0, 400), random(0, 400), 50, 50);
}


// CASE 6
// setup() and draw() with createCanvas(), holding pointer
var canvas;
function setup() {
  canvas = createCanvas(400, 400);
  canvas.position(100, 50); // allows you to set position, id, etc
  background(255, 255, 0);
}
function draw() {
  ellipse(random(0, 400), random(0, 400), 50, 50);
}
@kadamwhite
Copy link
Contributor

To clarify case 2, I believe this would be supported as well:

function setup() {
  var cnv = createCanvas(400, 400); 
  cnv.background(255, 0, 0);
  cnv.noStroke();
  cnv.ellipse(0, 0, 50, 50);
}

This is a variant of Case 6.

I recall us being strongly in favor of re-naming draw() to loop(), to make it more accurately describe what it does. If we need to maintain reverse-compatibility, I would err towards making loop() the canonical P5.js drawing loop method, and aliasing draw to loop for back-compat.

@evhan55
Copy link
Contributor Author

evhan55 commented Feb 11, 2014

+1 for case 2 clarification, I think that is still consistent with the proposal, and we left it out to reduce noise. Thanks for clarifying.

My take on loop() vs. draw() is that it will be easier to answer once we have the DOM API clarified, which won't happen until we have this instantiation API clarified.

@kadamwhite
Copy link
Contributor

Additionally, we looked at a few ways to instantiate P5 without the global window context:

var sketch = P5({
  setup: function() {
    this.rect( 0, 0, 100, 100 );
  }
});

and we also proposed a way to gain access to the implicitly-created canvas in CASE 1 by passing it in as an argument to setup:

function setup( canvas ) {
  canvas.rect( 0, 0, 100, 100 );
}

@kadamwhite
Copy link
Contributor

The canvas-as-setup-method-argument case outlined above could serve as an intermediate step between "you're calling everything globally" and "you're working entirely within an instantiated instance and have to know how this works"

@lmccart
Copy link
Member

lmccart commented Feb 11, 2014

@kadamwhite yes, all great points.
@evhan55 can we include these cases in the code above for clarity?

also with the instantiation example, there is the option to pass in an id.

var sketch = P5({
  setup: function() {
    this.background( 0, 100, 100 );
  },
  draw: function() {
    this.rect( 0, 0, 100, 100 );
  },
 id: 'canvas0'
});

I think like that?
The idea is you can either:

  1. pass in no id (creates a canvas and appends it to body, not recommended if there's other stuff on the page)
  2. pass in a canvas id (attached p5 instance to that canvas), throws error if canvas is already in use by another p5 instance
  3. pass in any other id (creates canvas and appends it to the element with specified id)

@kadamwhite
Copy link
Contributor

We also discussed the ability to pass in not only an ID, but potentially an actual DOM node (either a canvas or a container) as the first argument for the constructor. I'm ready to write tests to verify whatever behavior we settle on for these constructors, as soon as we get to agreement in this thread.

@lmccart
Copy link
Member

lmccart commented Feb 11, 2014

cool, I'm into flexibility, but can you explain the use case for this?
is it something like if you were using jquery to select an element?

@kadamwhite
Copy link
Contributor

You could get access to the DOM in any number of ways, including jQuery and document.querySelectorAll. The main way I would use this is if I was writing a script to both generate the HTML and also instantiate a sketch. This would let me do things like dynamically generate sketches on a page from scratch based on user input, and clean them up afterwards.

@kadamwhite
Copy link
Contributor

As a correlary: If I was writing an IDE for P5, the IDE would have a canvas or preview pane of some sort. I'd be maintaining a reference to that pane's DOM container already, so it would be expedient to pass that in directly rather than passing in an ID and having to re-select an element for which I already have a reference.

@lmccart
Copy link
Member

lmccart commented Feb 11, 2014

ah, all makes sense, sounds good!

@brysonian
Copy link
Contributor

This looks great.

As for loop vs draw. I agree that loop is better, in theory. In practice however, if the goal is to have this eventually be a replacement for processing (java) then switching to loop will make thousands of tutorials and sample code just that much more confusing. It'll be hard enough for a newcomer to port something, i don't think we should make it more difficult just for the sake of pedantry.

@kadamwhite
Copy link
Contributor

@brysonian "Replacement" is tricky. Java is an awesome environment, but it brings a lot of its own preconceptions to the table, and those impacted the design of Processing (classes, etc). @lmccart described P5.js as being an attempt to take the principles of Processing and apply them to the web, which by virtue of being a different environment with its own structures and patterns puts it at odds with maintaining a 1:1 API under the hood.

To me, the idea of a draw loop is a principle; having it called draw is a legacy attribute of the original implementation. That's why I was proposing to implement P5.js with the most semantically accurate language possible, and to have a compatibility layer that would shim in the specific methods Processing users expect. I feel like it's stronger to call a loop a loop, and have the loop be run through an abstraction where we can plug in "if draw exists and loop does not, use that for loop".

@brysonian
Copy link
Contributor

@kadamwhite I'm not arguing for a 1:1 implementation of the API, not in the slightest. By replacement, i mean a pedagogical replacement, since education has always been a goal of Processing, and is a goal of this project. The idea is to replace many of our classes that have used processing for years with p5.js.

The initial versions of processing used loop instead of draw, and @benfry and @REAS made a decision to use draw, for better or worse (it was quite a heated change); but there are many good reasons to keep it:

  • First, as i mentioned, the enormous number of existing tutorials and code that uses the setup/draw configuration.
  • When talking to a beginning student it very quickly can become unclear what you mean by "loop" the loop function? a for loop? a while loop? "No not that loop the other loop."
  • Hopefully many students will decide to continue learning by looking into things like OpenFrameworks or Cinder, both of which have draw(), or maybe three.js, which has render(), etc. So why not set them up for an easy transition to other platforms?
  • Processing, and i think by extension p5.js, is about drawing, interaction, and animation. In years of teaching i've had very few students confused about what happens in draw(), and once that is in place, those who need it quickly abstract it into a "slow loop" they can use for other purposes.
  • Also for beginners, having a place called draw() self-documents where you should put your draw commands, it isn't even a question. But that doesn't prevent anyone from learning they can "draw" from other parts of the program. The semantic weight of draw melts away pretty quickly.
  • and of course draw() does use a mechanism called requestAnimationFrame, so even there is the suggestion that this is for drawing/animation.

FWIW, libcinder has an update() method which is called before draw to try and separate out code that is updating state from code that is actually drawing, perhaps something like that is a good middle ground?

@lmccart
Copy link
Member

lmccart commented Feb 17, 2014

@brysonian thank you for such a thoughtful breakdown of the thought process and decisions behind draw vs loop. I think the point about "No not that loop the other loop" is a really convincing one. as programmers, it's easy to take for granted some things that seem intuitive to us might not seem intuitive to a beginner.

I also think it could be confusing to have a function called noLoop() that causes loop() to be called once. I would expect in this case that loop() not be called at all.

@tafsiri also made a good point that it's sort of more of a pseudo loop than a loop and could be confusing then to name the function loop.

@kadamwhite
Copy link
Contributor

+1 for avoiding the name conflict with other types of loop, I hadn't considered that angle at all. Thanks very much for the detailed response, I'm more than convinced! :)

@tafsiri
Copy link
Contributor

tafsiri commented Feb 17, 2014

I wouldn't even say that draw() is a pseudo-loop, it is just a function that gets called in a loop (most of the time); which pedagogically is an important distinction for me (as learners will eventually learn to call their own functions in various loops). I think @brysonian articulated the issues I had with loop() (and the contextual advantages of draw()) pretty well.

@evhan55
Copy link
Contributor Author

evhan55 commented Feb 17, 2014

Thanks @brysonian , makes so much sense.

I am curious what p5.js sketches that don't draw to the canvas (and instead work mainly with DOM elements) will look like.

@evhan55
Copy link
Contributor Author

evhan55 commented Feb 17, 2014

I will be implementing the API outlined above this week and will leave this issue open as a place to come raise questions as I come across them.

@evhan55
Copy link
Contributor Author

evhan55 commented Feb 18, 2014

@lmccart @kadamwhite Is there a consensus on the different ways to instantiate a P5 instance without attaching to the global context? (i.e. within the P5 namespace, to allow for multiple instances, etc).

If so, can someone summarize the different ways to instantiate here with all the different options, etc?

If not, let's try to finalize that! 💇 (first icon I found, it's a girl getting a haircut)

@lmccart
Copy link
Member

lmccart commented Feb 18, 2014

From above, incorporating @kadamwhite's option to pass in a DOM element:

var sketch = P5({
  setup: function() {
    this.background( 0, 100, 100 );
  },
  draw: function() {
    this.rect( 0, 0, 100, 100 );
  },
 id: 'canvas0'
});

The idea is you can either:

  1. pass in no id or elt (creates a canvas and appends it to body, not recommended if there's other stuff on the page)
  2. pass in a canvas id or elt (attached p5 instance to that canvas), throws error if canvas is already in use by another p5 instance
  3. pass in any other id or elt (creates canvas and appends it to the element with specified id)

I think the variations of setup + no draw, no setup + draw, etc as outlined in the base cases hold true for this as well? Though you would not have the case where you pass in code (case 0) without setup or draw, right?

Question: what should this optional argument be called? id, elt, target, something else?

@kadamwhite
Copy link
Contributor

If we hold a reference to the element I'd recommend calling that 'el', a widespread convention in backbone and jQuery docs. That doesn't answer the question of what to call the parameter―I have thoughts, will post them when I'm not on my phone.

@lmccart
Copy link
Member

lmccart commented Feb 18, 2014

also, while we're changing it up, should it be createCanvas() or just canvas()?

@REAS
Copy link
Member

REAS commented Feb 18, 2014

createCanvas() is the way to do it consistently with Processing. createShape(), createGraphics(), etc.

@tafsiri
Copy link
Contributor

tafsiri commented Feb 18, 2014

I'd go for createCanvas(), since it actually makes a new thing. To me canvas() is more ambiguous as to whether it operates on an existing thing or makes a new thing.

@evhan55
Copy link
Contributor Author

evhan55 commented Feb 18, 2014

👍 on 'it actually makes a new thing'

@kadamwhite
Copy link
Contributor

I'm not a big fan of P5({ id: 'canvas0' });—to me, having this be on the same object as the other methods conflates two different questions.

One question is "do I want this P5 instance to be global, or non-global." We've come to a general consensus that the way to do this is to pass a parameter to the P5 constructor that defines a setup or draw method.

The other question is "do I want to attach this P5 instance to an existing canvas, or create a new one." To me, that's separate from the global/non-global question; I would vote to make the DOM reference a separate (optional) argument.

I'm going to run through all the examples we've been over so far, for discussion: Which of these would we want to have be valid?

// Global / Implicit
// Create a globally-bound P5 instance, with an implicitly-created canvas element
P5();

// Global / Explicit, passing canvas element ID
P5( 'canvas-id' );

// Global / Explicit, passing container ID
// If container has a canvas, that is used; if not, a canvas is created within it
P5( 'div-id' );

// Global / Explicit, passing canvas node
var canvasNode = document.getElementById( 'canvas-id' );
P5( canvasNode );

// Global / Explicit, passing container node
var divNode = document.getElementById( 'div-id' );
P5( divNode );

// Local / Implicit, passing object
P5({
  setup: function( canvas ) {
    canvas.rect( 0, 0, 100, 100 );
    // calls to `this` would map through to the implicit canvas
    this.rect( 0, 0, 100, 100 ); // equivalent to the canvas.rect call
  }
});

// Local / Explicit, passing a node (or an ID, or... etc)
P5( canvasNode, { setup: function() { /* ... */ });

// Local / Explicit, passing a function (à la Processing.js)
P5( canvasNode, function( pInstance ) {
  pInstance.setup = function( canvas ) {
    // I would assume all the following would be equivalent:
    pInstance.rect( 0, 0, 100, 100 );
    this.rect( 0, 0, 100, 100 );
    canvas.rect( 0, 0, 100, 100 );
  };
});

// Local / Implicit, passing a function
P5(function( pInstance ) {
  pInstance.draw = function() { /* ... */ };
});

// Define an instance, do other stuff later?
var instance = P5({});
instance.draw = function() { /* ... */ };

I would 100% support the pass-a-context-object variant, as well as (naturally) the global variants. I'd also like to make a case for either supporting the Processing.js format, or else making the instantiation method pluggable so that I can write in support for it... I spent some time the other week playing with the object and function models in my head, and came to the conclusion that having the processing instance passed in to the function is actually a pretty powerful model for encapsulating more complex behavior that might not fit into the methods we could define on a context object.

@brysonian
Copy link
Contributor

+1 for something along the lines of that method of initialization in processing.js. I also like that Processing.js allows you to have a Sketch (Processing.Sketch) object that might not currently be active. So for example you do:

var sketch      = new Processing.Sketch()
sketch.attachFunction = function(p) {
    p.setup = function() {
        // code using p as Processing namespace...  
    }

    p.draw = function() {
        // code using p as Processing namespace...  
    }
}

to create a sketch, and then attach it to a canvas and run it with:

var pro = new Processing(canvas, sketch);

I'm not particularly fond of the name "attachFunction" but like the gist of it.

As for sketches that deal mostly with the DOM and don't draw. Maybe this is where having an "update" function that is called before draw could be handy. Though I also imagine a lot of those cases will be more about responding to user events?

@lmccart
Copy link
Member

lmccart commented Feb 19, 2014

I'm also in favor of the the pass-a-context-object variant, just wondering if it's a bit much for someone making the transition from global context to wrap their head around? Maybe at this point we assume the user knows js reasonably well and it's not a big deal.

And having the node element be outside of the object definitely makes sense, I wasn't thinking before!

@evhan55
Copy link
Contributor Author

evhan55 commented Feb 19, 2014

Can you all expand on the argument for passing the Processing instance to the function? I am not opposed, but would like to understand the use cases and what makes it powerful.

@evhan55
Copy link
Contributor Author

evhan55 commented Feb 19, 2014

👍 to the transition aspect, it would be nice if it were a clear transition from global to instance use case. I am working towards coming up with a set of instantiation examples , global and non-global, for you all to look at and verify before I actually implement anything

@kadamwhite
Copy link
Contributor

@lmccart awesome! I'd like to help with the testing component, I'll lurk in #p5js in case I can be of assistance :)

@evhan55
Copy link
Contributor Author

evhan55 commented Mar 10, 2014

@kadamwhite That sounds great. I am beginning to do those. We switched over from a BDD to TDD style:
https://github.com/lmccart/p5.js/issues/131

For starters, it'd be great to hear if you have opinions on the difference between those. I would like to settle on TDD and start writing core.js tests (and converting the current ones).

@kadamwhite
Copy link
Contributor

@evhan55 thanks; I didn't chime in on BDD vs TDD because I'm of the opinion it doesn't matter so long as we're consistent :) I assume you will be creating another issue for converting/adding tests, I'll jump in on that thread as soon as it exists

Final thought: Could you or @lmccart definitively recap the decision that was made? Did we go with the alternative to v4 as Lauren implied above, or did we pick another one?

@lmccart
Copy link
Member

lmccart commented Mar 10, 2014

For global mode, these are the supported variations:

// API
preload(): runs once, first
setup(): runs once, second
draw(): loops, indefinitely
createCanvas(w, h): creates a canvas element at the 0,0 with input size

// CASE 0
// No setup() and draw().
// createCanvas() gets called automatically behind the scenes and creates a default
// canvas at 0,0 with a default size and background color.
fill(255, 0, 0);
ellipse(10, 10, 50, 50);

// CASE 1
// Only setup().
// setup() runs once and createCanvas() gets called automatically with defaults.
function setup() {
  background(255, 0, 0);
  noStroke();
  ellipse(0, 0, 50, 50);
}

// CASE 2
// Only setup() and createCanvas().
// setup() runs once and createCanvas() returns a pointer to the canvas created
// with the input size, at 0,0.  Holding the pointer is optional.
function setup() {
  createCanvas(400, 400); 
  background(255, 0, 0);
  noStroke();
  ellipse(0, 0, 50, 50);
}

// CASE 3
// Only draw().
// createCanvas() is called automatically with defaults.
function draw() {
  ellipse(random(0, 400), random(0, 400), 50, 50);
}

// CASE 4
// setup() and draw() without createCanvas().
// createCanvas() is called automatically with defaults.
function setup() {
  background(255, 0, 0);
}
function draw() {
  ellipse(random(0, 400), random(0, 400), 50, 50);
}

// CASE 5
// setup() and draw() with createCanvas().
function setup() {
  createCanvas(400, 400);
  background(255, 255, 0);
}
function draw() {
  ellipse(random(0, 400), random(0, 400), 50, 50);
}

// CASE 6
// setup() and draw() with createCanvas(), holding pointer
var canvas;
function setup() {
  canvas = createCanvas(400, 400);
  canvas.position(100, 50); // allows you to set position, id, etc
  background(255, 255, 0);
}
function draw() {
  ellipse(random(0, 400), random(0, 400), 50, 50);
}

// CASE 7
// holding pointer, calling methods explicitly on that object
function setup() {
  var cnv = createCanvas(400, 400); 
  cnv.background(255, 0, 0);
  cnv.noStroke();
  cnv.ellipse(0, 0, 50, 50);
}

For instance mode, this is the template we will teach and support:

// CASE 0: no node specified
// Canvas is auto-generated and appended to body.
var s = function( sketch ) {

  var gray = 0; 

  sketch.setup = function() {
    sketch.createCanvas(600, 400);
    sketch.background(gray);
  };

  sketch.draw = function() {
    sketch.rect(sketch.width/2, sketch.height/2, 200, 200);
  }

  sketch.mousePressed = function() {
    gray += 10;
  }

  return sketch;
};

P5(s);

// CASE 1: node specified
// Node is either a canvas element or any generic element. if it is a canvas, p5 will attach to it. if it is another type of element, a canvas with p5 attached will be inserted inside of it.
// Note that "sketch" is arbitrary and a user may replace it any variable name.

var s = function( sketch ) {

  var gray = 0; 

  sketch.setup = function() {
    sketch.createCanvas(600, 400);
    sketch.background(gray);
  };

  sketch.draw = function() {
    sketch.rect(sketch.width/2, sketch.height/2, 200, 200);
  }

  sketch.mousePressed = function() {
    gray += 10;
  }

  return sketch;
};

P5(s, node);

Note that the above is functionally equivalent to below, either may be used, but the above will be the recommended syntax for beginners as we feel it's clearer.

P5(function( sketch ) {

  var gray = 0; 

  sketch.setup = function() {
    sketch.createCanvas(600, 400);
    sketch.background(gray);
  };

  sketch.draw = function() {
    sketch.rect(sketch.width/2, sketch.height/2, 200, 200);
  }

  sketch.mousePressed = function() {
    gray += 10;
  }

});

@brysonian
Copy link
Contributor

should a ref to the node be passed to the sketch initialization function?

var s = function( sketch, node ) {

  var gray = 0; 

  sketch.setup = function() {
    sketch.createCanvas(600, 400);
    sketch.background(gray);
  };

  sketch.draw = function() {
    sketch.rect(sketch.width/2, sketch.height/2, 200, 200);
  }

  sketch.mousePressed = function() {
    gray += 10;
  }

  return sketch;
};

P5(s, node);

or is there another way to get to that?

@kadamwhite
Copy link
Contributor

The way Processing.js does it is to have an explicit DOM canvas context as an optional first argument to the constructor function, which I think would still be my recommendation. We got sidetracked on that by the discussion of whether that node had to be a canvas, or whether it could be an arbitrary DOM container inside which you manipulate canvas or non-canvas elements.

var canvas = document.getElementById( 'my-canvas' );
P5( canvas, function sketch( p ) { /* ... */ });

I'd like to make the assumption that there will be a method on p that could be used to get to an automatically-created DOM context if you didn't choose to bind to one explicitly.

@lmccart
Copy link
Member

lmccart commented Mar 10, 2014

yes, I agree, this was the thinking behind the optional node argument above (case 1)

@kadamwhite
Copy link
Contributor

@lmccart I saw that, but I wanted to clarify whether we wanted to support DOM-node-first for compat with existing Processing.js sketches—It's not a lot of extra code to take it as first or second, it's just something that should be hammered out before we begin writing the test specs

@lmccart
Copy link
Member

lmccart commented Mar 10, 2014

ah I see. my inclination was to have it second so it could be optional. but maybe at the point where you're using instance mode it's reasonable to require the user creates their own element to attach to?

@kadamwhite
Copy link
Contributor

It's possible to make an optional first argument, or intermediate argument—jQuery does this in many places, e.g. .on()/.off() where you can pass optional selectors and data in between the event to be bound and the callback. (See the .on() API documentation for function signature). It's slightly more of a logical leap, but I like the semantics of "In this container, do this set of instructions".

@evhan55
Copy link
Contributor Author

evhan55 commented Mar 11, 2014

'In this container, do this set of instructions' has a nice ring to it!

@evhan55
Copy link
Contributor Author

evhan55 commented Mar 11, 2014

I have moved over @lmccart 's summary of the cases to a wiki page here:
https://github.com/lmccart/p5.js/wiki/Instantiation-Cases

I have also changed the 'node' parameter to come first instead of second, to try it on for size.

Is there any case missing from that document?
Any further thoughts on whether node should be the first or second argument in the instance case?

@evhan55
Copy link
Contributor Author

evhan55 commented Mar 11, 2014

Reopening this issue until we pin down the Instantiation Cases document for good

@evhan55 evhan55 reopened this Mar 11, 2014
@evhan55
Copy link
Contributor Author

evhan55 commented Mar 11, 2014

New question!

For the instance cases, there are yet more options to think about:

// all of these examples could alternatively not include the node, but I wanted
// to show the most verbose example possible
// 'node' and 'sketch' could also be 'n' and 's'
// 'myInstance' could be simply 'p1'

// CASE 0: using new, capitalized
var myInstance = new P5(node, sketch);

// CASE 1: no new, capitalized
var myInstance = P5(node, sketch);

// CASE 2: using new, lowercase
var myInstance = new p5(node, sketch);

// CASE 3: no new, lowercase
var myInstance = p5(node, sketch);

Any preferences, @lmccart ? @kadamwhite @bobholt @tafsiri others?
Did I miss other options or get something wrong?

@kadamwhite
Copy link
Contributor

My only preference is that we do an instanceof check within the constructor so that this would be valid (assuming CONSTRUCTOR_NAME is either p5 or P5):

// Called without storing the instance in a variable and without "new"
CONSTRUCTOR_NAME( context, sketchFn );

I think supporting the above is a significant benefit in terms of learnability—it's a lot less to memorize and you can leave teaching "new" and the idea of instances until you need to make use of them within a sketch; then later expand to show how the same approach can be applied to keep references to (and interact between) multiple sketches

@lmccart
Copy link
Member

lmccart commented Mar 11, 2014

I think I'd agree, and I vote for lowercase p5, for consistency across naming, documentation, code.

@evhan55
Copy link
Contributor Author

evhan55 commented Mar 11, 2014

Yup @kadamwhite that makes sense to me, I was writing the verbose version for completeness.

Ok, lowercase!
I've updated the document to show this decision: https://github.com/lmccart/p5.js/wiki/Instantiation-Cases

With the understanding that p5(s) returns a pointer to the instance.

@brysonian
Copy link
Contributor

I'm not overly fond of switching around the parameters, but mostly because it is a jQuery specific idiom that has caught on in other js frameworks, but which isn't common in other languages. As @lmccart mentioned above, it is more common to have optional parameters appear at the end of the arg list. I'm not convinced 'In this container, do this set of instructions' is clearer than 'Do this set of instructions in this container.' But i'll roll with whatever.

@evhan55
Copy link
Contributor Author

evhan55 commented Mar 11, 2014

I am not tied to either option or parameter order, @lmccart , do you say p5(s, node) ?
If you and @brysonian really prefer it, let's stick to it!
I'd like to stick to one, at least, for starters.

@kadamwhite
Copy link
Contributor

+1 on sticking to one... and I suppose nothing would prevent us from

// Processing.js compatibility
function Processing( a, b ) {
  if ( a instanceof HTMLElement || typeof a === 'string' ) {
    return p5( b, a );
  }
  return p5( a, b);
}

So, in that regard I say 🚢

@evhan55
Copy link
Contributor Author

evhan55 commented Mar 11, 2014

Excellent!

@lmccart
Copy link
Member

lmccart commented Mar 12, 2014

I think p5(sketchFn, context) makes more intuitive sense if context is optional. I don't think the fact that processingjs has the opposite is a strong enough reason to switch it up, and as @kadamwhite points out we can write in hidden support for both to allow processingjs fxns to run more easily.

@lmccart
Copy link
Member

lmccart commented Mar 21, 2014

currently, global cases 0 and 7 don't work yet.
this is case 0:

// CASE 0
// No setup() and draw().
// createCanvas() gets called automatically behind the scenes and creates a default
// canvas at 0,0 with a default size and background color.
fill(255, 0, 0);
ellipse(10, 10, 50, 50);

https://github.com/lmccart/p5.js/blob/master/examples/instantiation-global/case0/sketch.js

I remember we talked about this a long time ago and didn't have a good way to support a no setup or draw sketch without adding some trickery on the backend. does anyone have thoughts on how to do this in a way that would be straightforward and not add too much complexity? or is it a better option to just not support this case?

@REAS
Copy link
Member

REAS commented Mar 21, 2014

We've made a number of decisions based on something like this logic: it's important to keep p5.js like Processing, but when JavaScript works in a fundamentally different way, let's consider a change to make it more like JavaScript.

With this question, having case 0 is very important to Processing. It's the first step for the beginner. Explaining how to "ellipse(10, 10, 50, 50)" is much easier than starting with a block with stuff in it. In practice, this first step doesn't last very long, but it's a logical progression. And at this point, there are so many example and books that have code without a setup() and draw() we need to keep supporting it.

With p5.js, is it impossible to support, or just difficult? I think it's possible to make the decision to not support case 0 because you're starting documentation, examples from scratch, etc. but it would be ideal to support it if the benefits don't outweigh the consequences.

@kadamwhite
Copy link
Contributor

The issue as I understand it is related to instantiation order. The way the constructor works currently is that p5 is initialized automatically when the document is fully loaded -- that is, after the user's defined a sketch method, etc -- and if they haven't, only then we go into Global mode. In order to support the behavior above without breaking the JS interpreter (i.e., you can't call ellipse before the method has been defined, and it's meaningless before the drawing context has loaded) we'd need to initialize p5 and default over to Global mode up front, rather than waiting until everything's loaded. This way the drawing context and instance would be ready and waiting when ellipse was called.

This is definitely possible, but also challenging. It would be easy to support this as part of an IDE, for example, because we'd have full control over the order of instantiation—we could make an instance in the background and any commands in a text area would be evaluated against the pre-loaded context, as an example. Alternatively, the core library could be modified to look for a special tag -- p5.js commands within a <script type="text/p5"></script> or something that wouldn't be automatically parsed by the browser -- and then on init (iff global mode) we could grab the contents of those script blocks and manually evaluate whatever's there. This introduces challenges for debugging and it's kind of ugly from a JS developer context, but hopefully people would graduate from case 0 fairly quickly so perhaps it's feasible.

I'll keep thinking about alternatives.

@evhan55
Copy link
Contributor Author

evhan55 commented Mar 27, 2014

@kadamwhite Thank you for the ideas. I think we will move that topic to a new issue, copy your comment over, and close this one. Instantiation for global/instance cases is starting to be fleshed out, thanks to all the great discussion on this thread.

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

No branches or pull requests

10 participants