Feature Request: Global Variables

Can we get global variables that are accessible and can also be set across patterns? Setting a global hsl that can be used across many patterns would be great, but also other variables as per individual user requirements. For example I have a single PB which controls 2 strips which need to be turned on and off independently - for which I use 2 external variables to turn on/off each strip. This works fine on a pattern by pattern basis but switching patterns requires me to set those states again. Having those variables available globally would be fantastic.

3 Likes

Even better: If we could import another file by name, and access its constants and functions:

import “defaultFile”

Which would basically work like composability (not inheritance, no need to get fancy). You’d get access to the functions in that file, you’d get the constants from that file, and it would run as if that file was inserted in the existing file at that line. If you have an identical function, the last one declared wins (so you can override functions or variables). Would really help if you want to have a variety of patterns use the same logic

1 Like

:notes: Hello complexity my old friend. I’ve come to talk with you again. :notes:

This is a big can of worms to open and I’m glad that @wizard has done other amazing things first. In particular, the leader/follower magic (which I have yet to play with except for testing that python-rs audio broadcasting example) … because don’t you want your global variables to be global across all your PixelBlazes? :smiley:

I like the sound of import “defaultFile” for re-using code … but in what filesystem does that exist? :thinking:

I’m not sure that I like the idea of a global lexical context coming along with import, though. Make it more like C’s #include which is basically “read the text of this file right here” which doesn’t add any compiler complexity and is (comparatively) easy to reason about.

Instead of a lexical context, I would prefer a persistent tag on globally-scoped variables, whether in the main pattern or an included file. These variables would be stored in a different heap which is not reset between pattern changes.

As for the distributed version, have a broadcast tag which makes a variable read-only to followers, but read-write to the leader, who broadcasts every change. Only allow changes inside beforeRender() to avoid a packet storm! I suppose a broadcast variable would also be implicitly persistent.

For extra points, make all of that work with dynamically-sized arrays! Wait no, don’t do that. :stuck_out_tongue:

Like I said … can of worms! :worm:

2 Likes

The filesystem question is a little thorny. It’s clearly solvable (the web view can show a list of named patterns, so just match names), but it’s not free out of the box, and it has to make assumptions (look through each pattern in order until you find the first one with the right name), and it would need to add a compiler error for “pattern not found”.

100% it should be c#’s “include”. Anything else is hard; include is easy, and the filesystem issue has a trivial workaround.

The “import/include” setup does solve the “I want to run variations on code on multiple different followers” and “I want to share default variables” problem (I’m trying to solve that for a complex leader/follower context).

But, it doesn’t solve “I want strip A to be off and B on when I switch between patterns”, which is a logical extension of OP’s request. So this is probably still two things.

Persistent and broadcast seem like great things to have. There’s some really fine grained nuance with both, though.

Persistent variables feel like they’re “system level” things, more than “pattern level” things. So an approach for them would be a table of persistent variables (and their defaults) to settings or similar.

Broadcast has lots of challenges: you’d need to not have the follower calculate it, and we don’t have separate code paths for leaders and followers (you can do it by setting the groupId of each). Overusing the radios will drain power fast. When a new follower joins, it needs to receive the latest broadcast state.

Data transfer speed is also an issue for broadcast - when you send the variable, the updated value wouldn’t be available for the followers in the same render loop. So “after beforeRender” is 100% the right place to send that, and “at the start of beforeRender” is the right place to receive it (receiving during the render loop would be a recipe for glitchiness).

1 Like

Not always no. In my previous example the purpose is to allow a single blaze turn on and off parts of a single strip so it functions like multiple discrete lights. So I wouldn’t want those syncing across devices.

So I like your persistent idea

idea.

For global variables, that is global state that is shared across patterns, I have in mind an extension / global module system. This would let you run, edit, and share these things much like you do patterns. They would run in some scope that continues to exist between patterns, and could export at the least variables, functions, or arrays that patterns could import and depend on.

The key word there is depend, because this opens up the can of worms of dependency management, where a pattern would either need to bundle these modules when being shared, or declare their dependency in some sane way such that the PB system could go fetch it, or leave the end user with a puzzling mess of sorting it out and installing all the right things to make a pattern work. It’s a tricky thing to get right.

Another way of handling code re-use would be to allow importing other patterns. That would let you re-use some bits from them, or compose one or more of them into new more complex patterns. Outside of the global module system that wouldn’t solve global/shared state, only code reuse. In addition to dependency management, since this would still run in the pattern lifecycle context, it would also add the step of recompiling/updating dependents when a shared bit of code changes. I think the most common reasons for composing patterns are better served with a UI tool, like playlists, or layer mixing system, rather than via code. For these reasons I don’t favor this approach.

FWIW, I intend to keep Pixelblaze as a subset of JavaScript for a handful of reasons, so that means sticking with the module systems possible in that language. There are a lot to chose from! Given that PB is based on ES6 syntax, we have the import/export keywords, and can work with the ES6 module system. PB already leverages this for platform hooks, so that you can expose certain functions and/or variables that do automagic things like detecting the appropriate render function, creating UI controls by conforming to a naming convention, and exposing any top level variable/array as a get/set API.

Shared state across a network has it’s own set of challenges. I plan to add ways to share data and/or send messages between nodes. Likely using some kind of eventually consistent model.

4 Likes

Eventually consistent sounds right for such a wildly asynchronous system!

It might be neat to have an array variable indexed by nodeId, which provides the latest received value from each node.

ga = broadcast Array()
ga[myNodeId] = calculate() // broadcast once per frame. Each node can only write to its own index (?)
nextvalue = ga[( myNodeId+1 ) % ga.length()] // get last value sent by "next" node

Sadly, it looks like WiFi broadcast packets are redistributed by the AP, even to non-PB devices, so it would be better (traffic-wise) for all followers to send their value to the leader. Once per frame the leader would send all of the values to each follower.