Skip to content

fix builtin global accessors for instance mode in strands shaders#8878

Merged
davepagurek merged 5 commits into
processing:mainfrom
aashu2006:fix/strands-instance-mode-globals
Jun 11, 2026
Merged

fix builtin global accessors for instance mode in strands shaders#8878
davepagurek merged 5 commits into
processing:mainfrom
aashu2006:fix/strands-instance-mode-globals

Conversation

@aashu2006

Copy link
Copy Markdown
Contributor

Resolves #8877

Changes:

Fixed installBuiltinGlobalAccessors in strands_api.js so that the builtin globals like mouseX and width work correctly in instance mode when used inside strands shader callbacks.

Previously only window.mouseX was intercepted, so in instance mode (p.mouseX), the values got hardcoded into the shader instead of becoming live uniforms.

Now the function also installs property interceptors on p5.prototype and p5.Graphics.prototype. It correctly handles both data properties (like mouseX) and getter based properties (like width, which delegates to this._renderer?.width) by saving the original property descriptor and delegating to it when strands isn't active.

Also I've added a unit test that verifies myp5.mouseX and myp5.width return strands nodes inside hooks and normal numbers outside.

PR Checklist

  • npm run lint passes
  • [Inline reference] is included / updated
  • [Unit tests] are included / updated

@p5-bot

p5-bot Bot commented Jun 8, 2026

Copy link
Copy Markdown

Continuous Release

CDN link

Published Packages

Commit hash: 33e1b43

Previous deployments

94fb7b6


This is an automated message.

Comment thread src/strands/strands_api.js Outdated
const sym = Symbol(`_strands_${name}`)

// Define on window for global mode
Object.defineProperty(window, name, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to check _isGlobal here, to avoid adding accessors to the global window object on instance mode.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for catching it @eupthere, I've added the _isGlobal check so we only define window accessors in global mode now. Also updated the tests to use instance access (myp5.width instead of window.width) since the test framework runs in instance mode anyway.

Comment thread src/strands/strands_api.js Outdated

// For data properties (like mouseX), save the initial value into Symbol-keyed store
if (originalProtoDesc && 'value' in originalProtoDesc) {
strandsContext.p5.prototype[sym] = originalProtoDesc.value;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for using a symbol here as opposed to a string?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a symbol here to avoid any chance of colliding with existing properties on the instance/prototype. A string like _strands_mouseX would also work, but symbols are guaranteed to be unique and stay out of things like Object.keys() and for...in, so the backing store remains hidden.

happy to switch to strings if you think that's simpler 🙂

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do they need to be stored on the prototype? Elsewhere we store this on strandsContext.fnOverrides, e.g.:

strandsContext.fnOverrides[name] = fn[name];

fn in addons is a shortcut to p5.prototype, so it seems like maybe this should be using that same system.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switched to storing the original descriptors on strandsContext.fnOverrides instead of the prototype, and also switched from Symbol to a string key for the per-instance backing store, it should be cleaner now.

//In instance mode, myp5.mouseX and myp5.width should return strands nodes
const mxInHook = myp5.mouseX;
const wInHook = myp5.width;
assert.isTrue(mxInHook.isStrandsNode);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

@aashu2006

aashu2006 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Hi @davepagurek, I added the _isGlobal check so the window accessors are only defined in global mode. Also updated the tests to use instance access (myp5.width) instead of window.width, since the test environment runs in instance mode anyway.

Also moved the original descriptor storage to strandsContext.fnOverrides and switched from Symbols to string keys for the backing store, like you suggested.

@aashu2006

Copy link
Copy Markdown
Contributor Author

Also, the earlier CI failure was in p5.Framebuffer.js (a createElement mock issue) unrelated to this PR's changes.

@davepagurek

Copy link
Copy Markdown
Contributor

One thing around fnOverrides is that those are automatically put back again when you init/deinit strands:

function initStrandsContext(
ctx,
backend,
{ active = false, renderer = null, baseShader = null } = {},
) {
ctx.dag = createDirectedAcyclicGraph();
ctx.cfg = createControlFlowGraph();
ctx.uniforms = [];
ctx.vertexDeclarations = new Set();
ctx.fragmentDeclarations = new Set();
ctx.computeDeclarations = new Set();
ctx.hooks = [];
ctx.backend = backend;
ctx.active = active;
ctx.renderer = renderer;
ctx.baseShader = baseShader;
ctx.previousFES = p5.disableFriendlyErrors;
ctx.windowOverrides = {};
ctx.fnOverrides = {};
ctx.graphicsOverrides = {};
ctx._randomSeed = null;
if (active) {
p5.disableFriendlyErrors = true;
}
ctx.p5 = p5;
}
function deinitStrandsContext(ctx) {
ctx.dag = createDirectedAcyclicGraph();
ctx.cfg = createControlFlowGraph();
ctx.uniforms = [];
ctx.vertexDeclarations = new Set();
ctx.fragmentDeclarations = new Set();
ctx.computeDeclarations = new Set();
ctx.hooks = [];
ctx.active = false;
ctx._randomSeed = null;
p5.disableFriendlyErrors = ctx.previousFES;
for (const key in ctx.windowOverrides) {
window[key] = ctx.windowOverrides[key];
}
for (const key in ctx.fnOverrides) {
fn[key] = ctx.fnOverrides[key];
}
// Clean up the hooks temporarily installed on p5.Graphics.prototype (#8549)
const GraphicsProto = p5.Graphics?.prototype;
if (GraphicsProto) {
for (const key in ctx.graphicsOverrides) {
if (ctx.graphicsOverrides[key] === undefined) {
delete GraphicsProto[key];
} else {
GraphicsProto[key] = ctx.graphicsOverrides[key];
}
}
}
}

Currently we use special keys, so it doesn't quite work as expected -- it'd be adding things to fn with those keys on deinit. So if we're opting into this system, we may have to update the logic a little bit to make sure the name we store it as is the original so that it works with this resetting logic, and possibly updating the resetting logic to handle descriptors. Possibly it makes sense to make a similar fnDescriptorOverrides if that logic is different enough. But then it also resets them after Do we also reset the flag saying whether we've installed globals, so that if they're correctly placed back, we also correctly replace them again the next time we make a strands shader?

@aashu2006

Copy link
Copy Markdown
Contributor Author

@davepagurek yes you're right fnOverrides gets reset by initStrandsContext() on every modify() call, which means the stored descriptors wouldn't survive. It can also lead to deinitStrandsContext() overwriting the getters during restore.

I've removed the fnOverrides storage entirely and now use the original descriptors captured in the closure directly.

@davepagurek davepagurek left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates, looks good!

@davepagurek davepagurek merged commit 8cc4593 into processing:main Jun 11, 2026
4 checks passed
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

Successfully merging this pull request may close these issues.

[p5.js 2.0+ Bug Report]: p5 globals don't update in instance mode

3 participants