-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
[p5.js 2.0 Beta Bug Report]: p5.registerAddon instance access in global mode (init lifecycle support) #7742
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
Comments
Is the function myAddon(p5, fn, lifecycles) {
lifecycles.presetup = function() {
const instance = this;
instance.classObjects = [];
p5.MyClass = class {
constructor() {
instance.classObjects.push(this);
}
}
}
fn.logClassObjects = function() {
console.log(this.classObjects);
}
} user code: let myObj1;
let myObj2;
function setup() {
createCanvas(200, 200);
myObj1 = new p5.MyClass();
myObj2 = new p5.MyClass();
logClassObjects(); // logs myObj1 and myObj2
} The main thing I can see that would be a little finicky is that the preload compatibility addon also uses a |
If we want to make sure there's a way to run before everything without worrying too much about preloads, I think just adding another |
No, you can run the examples I made to verify that the desired behavior can not be achieved with presetup currently. The init (or presetup) hooks should ideally be run after all the included p5.js bundle addons are added but before instance props and functions are added to window in global mode. Same as init in p5 v1. |
@limzykenneth if the intention is to use |
Just a status update: we'll discuss some more to iron out exactly what the API for this will be. It likely won't be in 2.0.0 because we're locking in the feature set to be able to do a release shortly, but we're planning on doing a follow-up release fairly soon after, so we'll still get to this! |
After looking more at this with @limzykenneth , here's an example that shows global state management: p5.registerAddon(function (p5, fn, lifecycles) {
lifecycles.presetup = function () {
this.globalVar += "B";
};
});
p5.registerAddon(function (p5, fn, lifecycles) {
fn.globalVar = "A"; // fn is an alias for p5.prototype. This will be exported to global mode
lifecycles.presetup = function () {
this.globalVar += "C";
};
});
async function setup() {
console.log(this.globalVar); // This will be ABC
} To better show the order of execution, I've included 2 hypothetical addons that both access 1 global var. So, first everything in "registerAddon" functions is run in order (setting "A"), then the presetup code is run in order (appending "B" and "C"). @quinton-ashley would this support your intended usage? If so we can convert this issue to a documentation task, eg on the contributor documentation for library creation or perhaps combining with some of the other global mode usages, since I'm sure state management in global mode is something others would really like to have a clearer overview of. |
@ksen0 Hmm I guess that technically works (EDIT: I was wrong, it'd break preload support) but it's cumbersome and unintuitive compared to the old way of attaching to |
@quinton-ashley ok let's keep this open for technical ideas, also please comment if the below example is not quite reflecting the intended use. @davepagurek do you think there's a technically plausible (and maintainable) way to do the equivalent of p5.registerAddon(function (p5, fn, lifecycles) {
// fn.globalVar = ""; <-- could this be implicit?
lifecycles.presetup = function () {
this.globalVar = "B";
};
});
p5.registerAddon(function (p5, fn, lifecycles) {
lifecycles.presetup = function () {
this.globalVar = "C";
};
});
async function setup() {
console.log(this.globalVar); // This would be C in this hypothetical scenario
} |
@ksen0 you can't add to a string that doesn't exist. There's no way for p5 to anticipate what addon devs are going to name their variables or what type of variables they should be. |
Removed append in the ex. |
@ksen0 Okay yes that's the desired behavior. There's no easier way to implement it though. |
I also don't see any way to do implicit global state management. Because the behavior is already possible (if a bit awkward), then I would argue it's best not to add another hook (also awkward in other places/ways). So either (1) someone has an idea about a technical solution that doesn't require init hook, or (2) there's other needs for init hook. I propose keeping this open for a while to see if there is any further input on either (1) or (2)! |
I think that would mean adding something like Lines 136 to 148 in 55ed78e
At this point I believe addons have been registered and the current instance exists so I think that it has everything it needs? |
@ksen0 The other reason to have a separate "init" hook is for it to run before preload. Consider that the two biggest addon library developers, Dan Shiffman and myself, still want to offer preload system support when p5 v2 is used with ml5 and p5play respectively. ml5js/ml5-next-gen#244 (comment) Also I don't want to do state management in such an awkward way. You don't mean to suggest that the addon system in p5 v2 should offer less functionality than the addon system of p5 v1 right? It seems removing "init" was simply an oversight and the sooner we can fix that mistake the better. |
@davepagurek Yup! @ksen0 Should I make a PR for this? |
Thanks for this idea @davepagurek, I think it's a better solution than adding a lifecycle stage, yes @quinton-ashley please feel free to file the PR, thanks!
|
A good start would be to upload a build with the new behaviour to e.g. the p5 web editor and just manually try registering a dummy addon that sets a property on the instance in preload, and use that property globally in a test sketch. We aren't quite sure what our current test suite's capacity to test global mode is, so something manual is good for now. |
That's totally reasonable, I've edited my comment. I do still feel like global mode behaviors could be a bit better documented and tested, but you're right. It's challenging enough to automate right now that it doesn't need to stop this issue from being resolved. Thanks! |
@davepagurek Can't use await there because the constructor can't be async. @ksen0 Making a separate "init" hook will ensure existing behavior of "presetup" hooks, which can be async functions. Will demo this in a PR. |
We can also move the global binding to be in here after Line 229 in 55ed78e
|
@davepagurek That'd make it impossible to implement top level global mode in the future. It'd also break preload compatibility to have global binding be done there, since preload is called inside a presetup hook. I was wrong, this issue can't be fixed by changing "presetup" and the workaround suggested by @ksen0 is not valid either, my bad. There needs to be a separate "init" hook. |
It's true we probably need globals initially bound before |
Ok thanks for looking into it, both. I think it would be very helpful for users to be able to declare global vars easily, but I think it is also important to recognize that, to the best of my understanding, the introduction of Can you explain what you mean by the workaround not being valid, which workaround, the |
@ksen0 This is the output I get:
If the workaround solved the issue, then "ABC" would be printed from preload. The issue is that p5 instance props need to be added to the global scope before preload runs, which also means before presetup hooks run. I understand removing "init" was intentional but the intent was not to remove the functionality it offered, rather to replace it with equivalent functionality, which I've demonstrated and you've demonstrated is not the case. @davepagurek That would prevent addons from overriding p5 functions. I do that in p5play to disable image loading/displaying as a debug feature. Also p5play's image caching and color palette system depend on it. Sometimes the most obvious solution to a problem ("init" hooks in this case) is also the cleanest and simplest right? Just two lines of code to implement. 😅 |
The workaround provides a way to have a global variable usable in global mode, so that part does work. I wasn't trying to get "ABC" printed in preload, but that's because I am not understanding something really important here I think, but I would really like to understand. If you need something in
Those 2 particular lines also introduce additional bugs (or at minimum questions), so while I'm not opposed to |
I think that output is the expected behaviour -- if we used p5.registerAddon(function (p5, fn, lifecycles) {
lifecycles.presetup = function () {
this.globalVar += "B";
};
});
p5.registerAddon(function (p5, fn, lifecycles) {
lifecycles.init = function() {
this.globalVar = "A";
};
lifecycles.presetup = function () {
this.globalVar += "C";
};
}); ...then I would expect the output to be the same. So in @ksen0's example, just the assignment to
In another conversation we were talking about capturing the current p5 instance, is that what you're considering? something like: function myAddon(p5, fn, lifecycles) {
lifecycles.init = function() {
fn.globalVar = this.someFunctionThatNeedsTheInstance()
}
} For context for how we're currently thinking about function overriding, in the preload addon, we also override p5 default functions, using a pattern like this: function preloadAddon(p5, fn, lifecycles) {
// ...
const prevLoadBytes = fn.loadBytes;
fn.loadBytes = function(...args) {
if (!this._isInPreload) return prevLoadBytes.apply(this, args);
const obj = {};
const promise = prevLoadBytes.apply(this, args).then((result) => {
obj.bytes = result;
});
promises.push(promise);
return obj;
};
// ...
} |
Sorry I'm getting a bit frustrated here. I understand this is a complicated issue but I feel that I already explained it in depth, as best I can, and offered the simplest valid solution with a PR. |
@ksen0 @davepagurek ChatGPT 4.1 offers a better explanation than I could. Here's what it says: The alternative suggestion of using
Demonstration: If you try to mutate or initialize a global variable in Conclusion: The removal of the |
@ksen0 Since p5.js v2 was released before this issue was solved, it made it impossible for me to add support in p5play. Now games made with p5play aren't working if they were set to load the latest version of p5.js. https://p5play.org/play Ideally this issue should've been fixed before v2 was released. |
Adding the init hook does not do what you are expecting it to, assigning to If anything needs to be exposed in global mode, it must be attached to To achieve what you need here, you can do this instead: p5.registerAddon(function(p5, fn, lifecycles){
// This attach the variable `tester` to `window`
fn.tester = null;
lifecycles.presetup = function(){
// This initializes the value of `tester`
this.tester = "hi";
}
}); There is no practical difference between the |
@davepagurek @ksen0 @limzykenneth I was not aware of those changes and my assumption was that p5.js v2 still propagated props on the instance to the global scope. Although I don't like having to declare props on Nevertheless an "init" hook is still needed for all the reasons I mentioned previously. I do not see the intended results with Limzy's example code. https://editor.p5js.org/quinton-ashley/sketches/X6ONeT29r Here is a demonstration with my PR that shows that https://editor.p5js.org/quinton-ashley/sketches/hO-XUOV9K I hope that this clearly shows the practical difference between the "init" and "presetup" hooks. I seem to be facing a greater than expected resistance and stubbornness towards adding "init" hooks back to p5 v2. I don't understand why. Please have some respect for what I'm saying p5play needs and stop attempting to figure out some other way to solve this issue. "init" hooks are a familiar API for p5 v1 addon developers and "init" hooks are needed for preload compatibility, since I'm operating in good faith here under the assumption that you all want to do what's necessary for p5 v2 and its new addon system to be able to support p5play and ml5.js at a technical level. I'm not trying to be rude or disrespectful in any way but I simply have no interest in belaboring this issue further. Please take some accountability and merge my PR so that I can begin to work on p5 v2 support in p5play. Many students and teachers will greatly appreciate it. 🙏🏻 |
Unless there is clearer evidence around what the init hook adds in terms of feature for p5.js instead of to patch what p5play is not willing implement in terms of new API, I'm inclined to close this issue as this is not productive. |
Here's a quote from Daniel Shiffman @shiffman in regards to the issue of preload support:
I fully agree. To which @limzykenneth, you replied:
So has something changed regarding support for preload when p5.js is used with addons since then?
The source code of p5play is publicly available, it is not closed source.This is the code for the entire library: https://p5play.org/v3/p5play.js The Learn p5play interactive textbook I wrote is also free for personal use. Recently I made commercial use of p5play free too. Selling education licenses for p5play is not a big money maker, but it puts food on the table for myself, my wife, and two year old daughter. I also support free use of p5play by volunteer efforts such as this: https://substack.com/home/post/p-159621537 I'm very sorry that we've had some frivolous arguments in the past but please try to take this issue more seriously.
Of course you're not responsible for maintenance work on p5play. I'm not asking for your help on it. It's not that I'm unwilling to put in the effort to make p5play work with p5 v2. Clearly I am willing to do that! But I can't change games students have made in the past to not use preload. |
Ah! Now I've figured it out. Instead of including the preload compatibility addon in p5play.js verbatim, I put p5play's init method inside the presetup hook, then add p5play's stuff to the global scope before preload runs! |
Hey @quinton-ashley glad to hear you found a solution! @davepagurek has made a new issue here #7797 to discuss possible syntax for making the ordering a bit more intuitive and not needing a workaround; as well as this issue #7798 for documenting the workaround in the meantime. I'll be closing this issue (and the associated PR) because based on this discussion, the problem is more general than this issue; that PR does not fully resolve the more general problem. The proposed new init lifecycle hook introduces a relatively major change (the existence of a lifecycle hook that cannot be async). Since there do exist solutions that do not require this, I will close this for now. |
@quinton-ashley For ml5, the comment is in the context of ml5 having one version to support both 1.x and 2.x where 1.x uses preload and 2.x uses async/await, their API already fully support promises and preload is an extra for them to work with p5.js 1.x. It was never about preload in 2.x. If the source code of p5play is not an open source license and restrict the rights of the user in their usage, including to fork the project, it is by definition not open source. I'm not interested in discussing the license of p5play as it is besides the point and you are free to decide what works for you. The main thing is that I don't see a good reason for p5.js to support p5play specifically, considering the many barriers presented by the license, if you wish to suggest any future enhancement to p5.js I can only suggest presenting additional context beyond p5play itself. |
@limzykenneth His clear preference would be having support for preload out of the box when ml5 v1 is used with p5 v2 with the compatibility addon, but he's also fine with just having people use ml5 v1 with p5 v1 instead.
I got the feeling from the way that you replied in that thread that that's how you misread his comment. Thinking that way is going to limit the adoption of p5 v2 among ml5 and p5play users. You've received transparent feedback on that, yet seem fine with us not supporting p5 v2, but also you recognize that my workaround is hacky and far from ideal. Here's the definition of closed source:
I didn't say p5play was Open Source™ but it's not closed source either. I will file a separate issue, and hopefully a more robust solution to this problem can be developed. |
Most appropriate sub-area of p5.js?
p5.js version
2.0-beta8
Web browser and version
Chrome
Operating system
macOS
Steps to reproduce this
First I'd like to thank @limzykenneth for the extensive documentation on the new
p5.registerAddon
function.https://dev.to/limzykenneth/designing-an-addon-library-system-for-p5js-20-3d4p
But this bit is only partially true.
I understand that the standard way to add functions to p5 would be to add them to
fn
. But it's also important for addon developers to be able to add to each p5 instance, so that each instance can have its own data with its own state. For example in p5play, a separate physics simulation is created for each p5 instance.The "init" hook in p5 v1 enabled access to the p5 instance but
p5.registerAddon
, since it's a static method, does not.I got this to work when using p5 in instance mode but not in global mode.
To fix the issue I suggest adding back the ability to define "init" hooks that run before p5 adds stuff to the global scope in global mode.
The text was updated successfully, but these errors were encountered: