TLDR; New system brings massive performance gain when using lots of widgets…
One aspect that has really bugged me about the current design of Cabbage is how inefficient it is to send data from Csound to Cabbage. Cabbage to Csound is fine. We move a slider, and it updates Csound. The other way around is pretty awful because Cabbage has to poll every single widget for a channel change on each k-boundary1. The more widgets you have the more of a bottle neck you encounter. And it doesn’t even matter if you are updating things or not. And it’s worse for identifier channels, because they have to handle strings.
So in the past few days I’ve thrown together an experimental cabbageSet
opcode that will update a Cabbage widget directly. This removes the need for Cabbage to poll every widget’s identifier channel on each k-cycle in the hope that one has changed. The opcode syntax currently looks like this:
kSucces cabbageSet kTrig, SChannel, SIdentifer, xArg1, xArg2, etc…
Typical use of this new opcode would be something like this:
kSuccess cabbageSet metro(20), "image1", "bounds", random:k(0, 260), random:k(0, 290), random:k(0, 10), random:k(0, 40)
or
kSuccess cabbageSet metro(20), "button1", "text", "start", "stop"
This will cause the "image1"
widget to update 20 times a second. When I tested did, I created 1000 checkbox widgets and set their positions randomly on each k-cycle (don’t try this at home ). Needless to say it caused a large spike in my CPU, and when I tested with the current mechanism I saw only a small gain in performance. But here’s the rub. I tested the current identchannel()
system again, only this time I commented out the chnset
stuff. Although it wasn’t updating anything in Cabbage, it was still running at max CPU. In contrast, the new system uses no processing power UNLESS a widget is updated. To put it in perspective, my CPU goes from 114% to 6% when I swap to the new system.
So this got me thinking about regular channels too. They are also being polled on each k-cycle. It’s more efficient because there is less string allocation happening, but it still polls every widget with a channel assigned, on each k-cycle. So I added support for cabbageSet to set the value of a widget also:
kSuccess cabbageSet metro(20), "freqSlider", "value", random:k(0, 1000)
This again is much faster than the current system.
And finally. I’ve never been a fan of having two different channel identifiers. I always thought it would be better to have a single identifier that can be used for two-way communication. This is possible with this new system. Instead of declaring an identchannel()
, we can just use the channel()
identifier. I also plan to wrote a complimentary opcode called cababgeGet
, although chnget
will still work fine.
kValue cabbageGet SChannel, SIdentifier
And in use:
kFreq cabbageGet "slider1", "value"
The cabbageGet
opcode will work with the current system, but for cabbageSet to be most effective, we have to disable the old polling system. The only way to do this without breaking backwards compatibility is to add polling() identifier, that is set to true unless explicitly told otherwise. So all new Cabbage code should have polling(0) in the form declaration.
Negatives
One drawback to the new system is you can’t set a load of identifiers in one go:
SMessage sprintfk "pos(%d, %d), colour(%d,%d,%d)", 24, 234, 23, 23, 234
chnset SMessage, "image1"
Another drawback is you will need to use cabageSet
if you want to update the value of any widgets from your Csound code. Because the older polling methods will be disabled, the following simple line of code will no longer cause Cabbage to update the value of the slider “gain1”
chnset k(1), "gain1"
I’m curious to know your thoughts. Or if you have any suggestions. One added bonus to this system if we can potentially query the state of any widget identifier. For example, we can easily find it’s current position:
kPos[] cabbageGet "slider1", "pos"
Or bounds()
kBounds[] cabbageGet "slider1", "bounds"
etc. I know some of you will find that useful.
THE END
1 You can use the guirefresh()
identifier to increase the number of k-cycles between each update. This identifier was added because of the bottleneck in updates.