Library for streaming large maps from SD in TASMODE

I’ve put together a simple C++ class that allows for streaming large maps (theoretically up to ~130,000,000 tiles in both width and height). The idea is the maps need to be broken up into “chunks” of either 32x32 tiles (for 8x8 tiles) or 16x16 tiles (for 16x16 tiles).

Here’s the GitHub link: https://github.com/tuxinator2009/Pokitto_ChunkMap

Instructions are provided in the readme.

4 Likes

Just as I was trying to do this for my own project :stuck_out_tongue:

I saw your post on Discord asking about it so I thought I’d put the pieces into a little class library. My project uses a modified version of the same method (since I store extra info on each tile besides the pixel data).

This looks really useful!

1 Like

Good work @tuxinator2009! The code looks surprisingly simple.

1 Like

Are you open to suggestions on ways to improve the API?

1 Like

I think eventually there’ll be some improvements to the overall sysem. Possibly specifying the size of the chunks as a template parameter when constructing the ChunkMap object. Could also improve it to provide an easy way to add additional data to each tile (some way to specify the tile’s size in bytes and the offset to the image data), as my Prelude to a Dream stores 2 additional bytes per tile one for the battle background ID and one for the encounter rate.

For now I wanted to quickly make it available for others to use since my initial tests discovered that this is useful for literally any tilemap based game (platformer, rpg, etc.) and allows a game to have absolutely massive sized maps with minimal overhead.

1 Like

For now I was only going to raise three small points…

Firstly, I’d recommend using a reference for File so you don’t have to worry about anyone passing a nullptr. If someone passes nullptr as f and then you do file->tell() then you’re walking into nasal demon land, whereas if you use a reference you can be happily assured that you don’t need a nullptr check.
(You can also prevent tiledata being nullptr, but that requires templates and there may be situations where the user can only provide a pointer because they don’t have the full array for some bizzarre reason.)

Secondly I was going to point out that the standard library has a std::clamp function in <algorithm> as of C++17, so you might want to use that in the example to encourage people to get used to the standard functions, and to make the example a bit more self-explanatory.
(That’s not to say you should get rid of the comments, but rather that the code should more closely resemble what you want it to communicate to the reader, i.e. “I am clamping this value”.)

And thirdly, I was just wondering if there’s a particular reason why you chose to have a setup function rather than using a constructor.

If I use a reference for file can I do something like this:

class ChunkMap
{
    public:
        void setup(File &file) {m_file = &file;}
    private:
        File *m_file;
}

I wasn’t sure if I could take a reference as an argument and then store a pointer to the original.

It will need some asserts or other methods of error checking eventually. Wanted to quickly get the concept down to its most basic form with a working demo showcasing it. The reason this needs to be a pointer though is because the tile data is expected to be in a uint8_t array[numTiles * TILE_W * TILE_H] so to render a tile given it’s index number it can simply offset into the array and provide that to Pokitto::Display::drawTile.

Actually yes. The reason is so that you have a single ChunkMap object that can be passed different maps since games will only need to have one map loaded at a time. Realistically though I should probably change the function name to loadMap since setup indicates a function that should be called once in the beginning and that’s it (hence the confusion on why setup instead of a constructor).

However, the main goal of this library was mostly to demonstrate the methods for streaming a large map using TASMODE while providing a super simple class to get started. There’s potential for this to be expanded to be a great companion library to TASMODE, but it can also serve as a tutorial of sorts on how to do the streaming from SD. Still need to figure out exactly what direction I want to take it.

Certainly.

References typically are implemented as pointers ‘under the hood’, except in some cases where the compiler can figure out what the original object was and use that directly.
Either way, you can take the address as if they were the actual object.

(Although one thing you do have to be careful of is something trying to do map.setup(File("path"), /* et cetera*/);, I believe I know a way to prevent that though.)

Needing to use pointers internally doesn’t prevent you being able to check that the user is providing an actual array though.
(And if you know how big the array should be at compile time you can even prevent the code from compiling if the array is the wrong size.)

Using constructors doesn’t prevent that.
You can use map = ChunkMap(file, width, height, data); to have the map object overwritten.
(In case you’re wondering if that would be more expensive than a call to setup, it won’t be due to copy elision and move semantics.)
If you prefer you could have map = ChunkMap::load(file, width, height, data); instead.

Unless you meant loading a different part of the data with the same file, in which case that would be more akin to a seek function anyway, and hence completely independent to a setup/loadMap function or a constructor.

Whichever direction you choose, at least you’re aware of those possibilities now, and thus free to make use of them or not. (After all, when you do come to make your decisions I may not necessarily be around to make suggestions.)

1 Like