I’m concerned about the use of dynamic allocation and virtual functions.
Those things both chew up RAM and progmem in large quantities.
I speak from experience. I created a similar system for Minesweeper.
How/why they eat memory
Dynamic allocation adds a speed cost, a RAM cost and a progmem cost:
Allocating memory can be slow depending on the implementation. Often it involves stepping through unallocated blocks to find a block large enough
There’s an overhead for every allocated memory block. Typically that consists of at least the size of the allocated block and possibly an offset to another block (to allow walking the block list like a linked list)
new and delete are backed by malloc and free, meaning those functions have to be compiled and included in the output. On top of which, every time new and delete are called, that call itself requires extra calling code.
Virtual functions also add overhead:
They add an implicit pointer to every instance of a class that has a virtual function
The implicit pointer must be initialised every time an object of the class is constructed
To work, everay function needs a virtual function table, which eats quite a bit of progmem
Every time a virtual function is called, an indirect call must be used, which is more expensive
There’s also the fact your ArduText class is using a 64 character buffer instead of just storing a pointer to the text and the size of the text.
If you just stored the pointer and the size, it would cost 4 bytes instead of 64 bytes.
Though it would make it difficult to store integers.
You could also make a version of ArduText that accepts pointers to text in progmem rather than storing the text in RAM.
Don’t get me wrong, this system would be fine on a desktop,
but on the Arduboy it’s going to eat a lot of memory,
and thus reduce the size of the game you can make with it.
It will probably be alright for small games, but not large games.
Couldn’t you just cast the pointer to an int directly if say the first bit was high/low…? So then you could store strings pointers OR raw ints up to 3.9 bytes long.
It’s possible, but you’d have to put up a big warning sign.
Pointer tagging can be dangerous,
and it automatically makes the code dependent on details of the underlying system.
Also you’d have to make sure to use intptr_t or uintptr_t,
because those are the proper types for converting a pointer to an integer.
It would be safer and more portable (though certainly a little bit more expensive) to use a union and a scoped enumeration to manually implement a tagged union (a.k.a. a ‘sum type’).
Well, obviously you’d wrap that up nicely in the library’s API. The end-user would just be calling functions and getting back whatever - the underlying implementation could change over time.
And that’s why you’d have portable and non-portable parts of the code.
This is all I was commenting on. Now we’re just talking around each other lol. I was pointing out that there ARE easy ways to do both here without incurring a cost of 64 bytes. My implementation suggestion was just the first thing I thought of - not a recommendation from the almighty - or even a strong suggestion. Just “heres one way you could do it”.