Get actual filled tiles count

Hi, is there is a way to find out number of tiles that are actually filled with sprites?
For example I have a map grid 50x50 tiles, but only 28 are filled with sprites, so I need a custom property like ‘content_count’ with value 28. I’m using json map format and base64 & zlib compression for layer data, for educational purposes I’m writing parser by my self using C++, and that custom property will help me to create a good old C stack array with a size of 28 with positions and gids for each sprite instead of std::vector.
I can add a custom property, but I will need to edit it by hand each time when this number will change.
Or I can write a python plugin that will add this property or I can make a tiled fork and add this property in C++ code (which is easier for me than Python plugin scripting) somewhere in mapwriter.cpp.
Thank you.

This would be a neat addition, since it would allow easily calculating whether a given map layer is sparse or mostly filled, and use that information to optimize parsing/storage methods at runtime. I’d prefer it be an optional include though (perhaps an export option?), since it’s useless in most cases and would just be a waste of bytes.

In the interim, you could use a vector to temporarily store the tile data, and then create the array using the vector’s length. This way you’ll get the performance of an array while the map is in use, with only a temporary increase in RAM (for the vector and duplicated data) at load-time.

That said, wouldn’t it be more performant in most cases (anything but a very sparse map) to use an array based on the map’s dimensions? That way you would not need to store locations and deal with vectors at all, as a tile’s location in the array would correspond to its location in the map, so the array could just store the GIDs. This would likely make your code for generating the renderable object (likely a vertex array) simpler, as well.

Or, you can use scripting to automatically keep a custom property updated upon save:

function updateTileCounts(asset) {
    asset = asset || tiled.activeAsset

    if (asset && asset.isTileMap) {
        function countTiles(tileLayer) {
            let tileCount = 0

            for (let x = tileLayer.width - 1; x >= 0; --x) {
                for (let y = tileLayer.height - 1; y >= 0; --y) {
                    if (tileLayer.cellAt(x, y).tileId !== -1)
                        ++tileCount
                }
            }

            tileLayer.setProperty("tileCount", tileCount)
        }

        function processLayers(mapOrGroupLayer) {
            for (let i = mapOrGroupLayer.layerCount - 1; i >= 0; --i) {
                let layer = mapOrGroupLayer.layerAt(i)

                if (layer.isTileLayer) {
                    countTiles(layer)
                } else if (layer.isGroupLayer) {
                    processLayers(layer)
                }
            }
        }

        processLayers(asset)
    }
}

tiled.assetAboutToBeSaved.connect(updateTileCounts)

This script should work with a recent development snapshot.

Though of course, if your map is indeed that sparse, you might want to script a whole custom map format instead that exports to a more efficient format for such cases. It may not be actually smaller since the zlib compression also deals very well with sparse layers, but it could be more convenient to load.

Edit: I did just discover a strange bug when running this script repeatedly, which is that for some reason the layer script object can get deleted in between the call to layerAt and the isTileLayer check. Right now I have no idea why this happens, and since it’s a delayed deletion it may be hard to find out what’s triggering it. At least the script works sometimes… (edit: issue was fixed in 104540a4acd27d)