State Pattern Paradigm?

You can see me attempt to use this pattern in New Game?: Dungeon Crawl

I am thinking of expanding this with a State abstract class that has two functions:

  • entry()
  • loop()

In a second phase, I would like to replace loop with several event callback functions like singleClick, doubleClick, attach, detach, faceValueChanged, etc…

And then have a separate animationLoop function intended for animating the blink for it’s current state.

I think if done right and with preventing dynamic allocation, this could help make blinks programming even more accessible.

Preventing dynamic allocation:

The intent is for all State class subclasses to be singletons that are only allocated statically.

I also need to learn more about the PROGMEM keyword.

Debouncing the attach, detach, and faceValueChange callbacks might be tricky, though there is already debouncing happening around the click count flag functions.

This is all assuming that this is no worse performance wise to the current loop and flag check paradigm of the current SDK.

I think Blinks lend themselves very well to the State Pattern as there are so few possible events to be handled. And almost all current games involve various blinks going through various “state changes”.

It of course remains to be seen how this compares in terms of learning curve to the current API.

You have picked up on a very fundamental blinks vibe! The original blinks API was elegantly state based with async callbacks - just like you are envisioning. Check this out, I think it will look familiar!.. :slight_smile:

So what happened? How did we end up with the crude and brutish polling API we all know today?

For all its beauty, the state pattern is anti-idiomatic Arduino. After hours and hours of heart wrenching and soul searching, we decided it would be better to follow the Arduino pattern that millions of programmers are already familiar with. We also knew that it would always be possible to someday build a sync model API on top of the current polling model (the inverse is not true), so that helped us feel better about the decision.

I personally would love to see this finally happen, so please let us know if there is anything we can do to support and encourage your noble efforts! Thanks!

1 Like

Of course it was already considered :grin:. Nothing new under the sun.

I do have a few ideas on how to implement the State Pattern as a “user space” template. This would make for better backwards and forwards compatibility. Devs could choose whichever version of the template suits their needs. Eg allNeighborChange, vs specific neighbor face change.

We’ll see how far I get.

Next choice is should I use a struct of static functions or a base class?

I come from a Java JVM background. I’m having to relearn all my C++ and C concepts. The thing I think I know is that we want to avoid heap allocation at all costs.

I think we want the state objects/structs and their functions to be statically allocated as opposed to stack allocated.

Any additional user functions should also be statically allocated as well.

If any one wants to do higher order functions they are on their own I guess.

I know how to do static function definitions in C++. As does every C programmer whether they know it by that name or not.

I need to figure out how to do a static object instance definition. Ideally as an anonymous subclass of a State base class.

I ended up throwing a state machine at my blinks project as well. Also went with static allocation. I originally had a separate enter function like you suggest, but ended up removing it since it saved a ton of program space to just make it a param in the event loop. The farther I got into the project, the more I came to appreciate how limited space really was.

Static allocation did lead to some funky dependency requirements thou when it came to keeping the state code independent of the game

1 Like

Phase 1 complete:

I ended up going with a macro approach as lambdas were incurring a significant memory penalty. I looked high and low for a way to inline a function definition at the function pointer assignment site. No luck.

Example comparison:

Sketch uses 3182 bytes (54%) of program storage space. Maximum is 5888 bytes.
Global variables use 696 bytes (67%) of dynamic memory, leaving 328 bytes for local variables. Maximum is 1024 bytes.


Sketch uses 3422 bytes (58%) of program storage space. Maximum is 5888 bytes.
Global variables use 700 bytes (68%) of dynamic memory, leaving 324 bytes for local variables. Maximum is 1024 bytes.

This DSL(domain specific language) added 260 bytes of program storage space and 4 bytes of dynamic memory space.

I think that is tolerable for now. Maybe other optimizations can be found in the future.

The need to forward declare all of your states is kind of annoying but Arduino’s magic auto declare logic only goes so far.