Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion contributor_docs/creating_libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ You can access p5.js functions and variables such as `circle()` and `PI` in your

<details>
<summary>You should always use the “<code>function()</code>” keyword to attach methods to the <code>fn</code> argument object.</summary> Don’t use the arrow function syntax “<code>() =></code>” because the value of “<code>this</code>” when using the “<code>function()</code>” keyword is the created object (i.e., the p5 sketch), but with the arrow function syntax, the value of “<code>this</code>” is whatever the value of “<code>this</code>” is when the arrow function is defined. In the example below, “<code>this</code>” will refer to “<code>window</code>” instead of the p5 sketch, which is usually not what we want.
</details>

```js
function loadCSVAddon(p5, fn, lifecycles) {
Expand All @@ -139,6 +138,8 @@ function loadCSVAddon(p5, fn, lifecycles) {
}
```

</details>

```js
function loadCSVAddon(p5, fn, lifecycles) {
fn.loadCSV = function (filename) {
Expand Down Expand Up @@ -314,6 +315,54 @@ Please note that in the above example, if the user does not define `function myA

Overall, this custom actions approach supports accessing the custom action functions in both global mode and instance mode with the same code, simplifying your code from what it otherwise may need to be.

## Adding state to your addon

Most addons need to remember something between calls. p5.js keeps two kinds of state in two different places, and the difference that matters is whether `push()` and `pop()` should restore it.

**State that** `push()` **and** `pop()` **leave alone**

This is ordinary per-sketch state, such as a cache or a configuration flag. Give it a default on the `fn` argument, which is an alias for `p5.prototype`, then read and write it through `this` inside your methods and lifecycle hooks.

```js
function loadCSVAddon(p5, fn, lifecycles) {
fn.csvCache = null;

fn.loadCSV = async function (filename) {
this.csvCache = await fetch(filename).then((response) => response.text());
return this.csvCache;
};
}
```

The default on `fn` is shared, but the first write through `this` creates a property on that sketch instance which shadows it. Each sketch on the page therefore gets its own value from the moment it writes one.

It is worth being deliberate about this, because the shorter-looking alternative does not do the same thing. A variable declared in the addon function's own scope reads like addon state and is not:

```js
function loadCSVAddon(p5, fn, lifecycles) {
let csvCache = null; // shared by every sketch on the page

fn.loadCSV = async function (filename) {
csvCache = await fetch(filename).then((response) => response.text());
return csvCache;
};
}
```

The addon function runs once when the addon is registered, not once per sketch, so every p5 instance on the page reads and writes that same variable. With two sketches loaded, one will overwrite the other's cache.

**State that** `push()` **and** `pop()` **restore**

Drawing state belongs on the renderer, at `this._renderer.states`, alongside built-in state such as the current fill and stroke weight. Write it with `setValue()` rather than by assignment:

```js
this._renderer.states.setValue('myProperty', value);
```

Assigning directly, as in `this._renderer.states.myProperty = value`, appears to work and then quietly breaks `pop()`.

The reason is how the restore is implemented. A `States` object keeps a private record of which keys have changed since the last `push()`, and `setValue()` is what writes to that record, saving the previous value the first time a key changes. `push()` takes that record and puts it on a stack, and `pop()` takes it back off and reinstates the saved values. A direct assignment changes the value without ever telling the record, so when `pop()` runs there is nothing to restore and the change survives past the `pop()` that should have undone it.

## Next steps

Below are some extra tips about authoring your addon library.
Expand Down