How to adapt a library for Instance Mode (P5JS)

Hi there,

I have already written the p5.3D library, for adding 3D text and images into WebGL, but currently this only works for Global Mode.

I’m not sure exactly what needs to be done, or how it needs to be reformatted, to make it work for Instance Mode. Has anyone else on here already converted a library to do this, or understands what is required?

1 Like

Not some library, but separate class files, which request the sketch’s p5 instance reference as their 1st argument:

Both classes Ball & Chamber store the sketch’s reference in their property p:


And then, access p5js’ API via that property p:


See that instance mode sketch in action at the link below:
GoSubRoutine.GitHub.io/Ball-in-the-Chamber/instance/

1 Like

Right okay, but that means that in the instance mode, you’re calling the function and passing in the instance. In other libraries, the function can be called by the instance i.e.

var sketch = p5(function(p){p.setup(){...}; p.draw(){p.function(){}}})

as opposed to

var sketch = p5(function(p){p.setup(){...}; p.draw(){function(p){}}})

  • Not sure if I understood your example above. :confused:
  • Anyways, a p5js library needs to work whether a sketch is using global or instance mode. :construction_worker_man:
  • And a sure way to accomplish such flexibility is for the library to request the sketch’s p5 reference from its user. :books:
  • As an example, take a look at how I instantiate classes Ball & Chamber: :see_no_evil:


  • Even though the “sketch.js” above is using instance mode, if I decide to convert it to global mode, the 2 code blocks above would be exactly the same! :flushed:
  • That is, classes Ball & Chamber work regardless the current mode a sketch is using, b/c they request the sketch’s reference as their 1st parameter! :partying_face:
1 Like

I understand how you achieved that, though thank you for explaining, my previous comment was just saying that I don’t want to have to pass in the sketch’s references, and I know there are other ways to do it from what I have seen other libraries use. It is these methods that I want to implement.

1 Like
  • Yes, there is a way to do it. But not when using the keyword new.
  • And guess what: p5js keeps a secret property when global mode is active -> p5.instance.
  • You can write your classes’ constructor to request a p5 object, but fallback to p5.instance in case your library’s user decided not to pass it.
  • Something like this: this.p = p instanceof p5 && p || p5.instance;
  • I’ve refactored your classes Object3D & Word3D’s constructors to do just that:
"use strict";

class Object3D {
  constructor({ p, depth=1, size=1, resolution=1,
                bevelled=false, threshold=60 }) {
    this.p = p instanceof p5 && p || p5.instance;

    this.depth = +depth;
    this.size = +size;
    this.resX = this.resY = +resolution;
    this.bevelled = bevelled;
    this.threshold = +threshold;

    this.edges = Int32Array.of(resolution, 0);
  }
}

class Word3D extends Object3D {
  constructor({ p, string='', depth, size, resolution, bevelled=true, 
                font='Times New Roman', style='bold' }) {
    super({ p, depth, size, resolution, bevelled, threshold: 160 });

    this.string = string.split('').join(String.fromCharCode(8202));
    this.stringLength = string.length;

    this.font = font;
    this.style = style;
  }
}
  • Another trick is that some p5js classes secretly store the sketch’s p5 object!
  • For example: class p5.Element has hidden property _pInst.
  • While class p5.Vector has hidden property p5.
  • Possibly, other p5js classes may do the same, but I didn’t fully research them all yet.
  • Your class Canvas3D happens to require a p5.Graphics object as parameter canvas.
  • The p5.Graphics is a subclass of p5.Element.
  • Therefore, it’s got the hidden property p5.Element::_pInst too!
  • So we can rewrite Canvas3D::constructor() to grab a p5 via a p5.Graphics object like this:
class Canvas3D extends Object3D {
  constructor({ canvas, depth, size, resolution, bevelled }) {
    super({ p: canvas._pInst, depth, size, resolution, bevelled });
    this.canvas = canvas;
  }
}
1 Like
  • Now the solution you’re looking for: Not request the p5 object from your library’s user!
  • You already know by now that directly instantiating classes using the keyword new can’t consistently get a sketch’s p5 object, unless they require it as their constructor()'s parameter.
  • There are some workarounds though like p5.instance, p5.Element::pInst, p5.Vector::p5, etc.
  • However, they’re all circumstantial and don’t work for all cases.
  • What you need to do instead is adding an instantiator method in the class p5’s prototype{} object.
  • B/c it’s a p5 method, it automatically receives the p5’s object via the keyword this every time it’s invoked!
  • That’s exactly how p5 methods like createVector(), createCanvas(), createGraphics(), createElement(), etc. work!
  • They all instantiate a class in our place passing the p5’s this as 1 of the arguments behind-the-scenes.
  • You’d only need to advice your library’s users to prefer those createXXX() methods rather than directly instantiating a class via new:
p5.prototype.createWord3D(string, depth, size, resolution, bevelled, font, style) {
  return new Word3D({ p: this, string, depth, size, resolution, bevelled, font, style });
}
1 Like

That’s awesome, thank you very much for your help on this. It now works exactly as intended, and the “create” syntax is nicer anyway since it’s closer to what already exists.

It can be found here: https://github.com/FreddieRa/p5.3D

1 Like