p5.js icon indicating copy to clipboard operation
p5.js copied to clipboard

[2.0] Add a way to resolve addon ordering dependencies

Open davepagurek opened this issue 7 months ago • 5 comments

Increasing access

Sometimes addons build off of other addons, or want to be compatible with other addons. Currently, every addon lifecycle stage will run addons' callbacks in the order that the callbacks were registered. This means that users might run into problems if they accidentally include the script tags for those addons in the wrong order. If addons have a way of describing their dependencies as they are registered, then there are less potential gotchas for users of those addons.

Most appropriate sub-area of p5.js?

  • [ ] Accessibility
  • [ ] Color
  • [x] Core/Environment/Rendering
  • [ ] Data
  • [ ] DOM
  • [ ] Events
  • [ ] Image
  • [ ] IO
  • [ ] Math
  • [ ] Typography
  • [ ] Utilities
  • [ ] WebGL
  • [ ] Build process
  • [ ] Unit testing
  • [ ] Internationalization
  • [ ] Friendly errors
  • [ ] Other (specify if possible)

Feature request details

An example of a situation where this might occur: addon A creates a new object in presetup, and addon B wants to do something with that object also in presetup. Addon B would need a way to always have its presetup run after addon A's presetup.

To make this work, ideally, addons have a way to name themselves so that other developers can describe dependencies relative to them. For example, imagine you have a physics engine that updates the positions of all objects predraw. It could register itself with a name:

p5.registerAddon(preloadAddon, { name: 'physicsEngine' });

Then, let's say you make an addon that adds some extra gravity forces to the physics system. You also want it to run before the user's code in predraw, but it needs to run before the physics engine applies the updates. You could then register this ordering dependency with before or after:

p5.registerAddon(gravityAddon, { name: 'gravity', before: 'physicsEngine' });

So the new API for registerAddon would be something like:

function registerAddon(
  callback: Function
  options?: {
    name?: string
    // These could be a single string or an array if there are multiple dependencies
    before?: string | string[]
    after?: string | string[]
  }
)

Internally, when initializing p5, we would then:

  • build a graph of the dependencies (each before/after dependency adds an edge)
  • come up with an ordering for all addons (via e.g. depth-first traversal of the graph)
  • sort all lifecycle hook callbacks based on that ordering

This before/after syntax isn't the only approach to getting an ordering, but I like that you only need to know the name of the other addon. I'm open to other ideas though!

davepagurek avatar May 07 '25 18:05 davepagurek

This idea is open for discussion - other technical approaches (for example, alternative ideas on syntax) are welcome!

ksen0 avatar May 07 '25 19:05 ksen0

I think this would be nice to have, however I think one of the challenges would be in the dependency graph building, specifically in the case of a cyclic dependency. It is probably rare and we can possibly just throw an error in that case perhaps.

limzykenneth avatar May 08 '25 10:05 limzykenneth

It might be worth to try using the internal modules, which are all written with the addon syntax and sufficiently complex, to figure out the technical details.

limzykenneth avatar May 08 '25 11:05 limzykenneth

I think this would be nice to have, however I think one of the challenges would be in the dependency graph building, specifically in the case of a cyclic dependency. It is probably rare and we can possibly just throw an error in that case perhaps.

Makes sense! At work we do something similar to manage our z indices across frontend components to avoid juggling lots of arbitrary numbers, and in the case of a circular dependency, we log an error mentioning what the cycle is, and then omit adding the edge that would create the cycle and continue on. I'm not sure whether or not treating it more like a warning or a full error is better, I could see either potentially being ok.

davepagurek avatar May 08 '25 12:05 davepagurek

After thinking a bit, I think in terms of API design, I would like it to be defined in the addon callback function instead of p5.registerAddon(), perhaps through a new p5.addonMeta() function that can be used in the function body. This is because the call to p5.registerAddon() is not always in the control of the addon author I think they would want that control over the name and dependency.

The extra option object that can be passed to p5.registerAddon() possibly can still be there for users to overwrite the meta but I'm not 100% sure if that's desired.

limzykenneth avatar May 09 '25 10:05 limzykenneth