diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 6cb2919198..e9a82e3c9e 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -1855,6 +1855,9 @@ void LoadNewGRF(SpriteID load_index, uint num_baseset) /* Pseudo sprite processing is finished; free temporary stuff */ _cur_gps.ClearDataForNextFile(); + /* Make note of last sprite ID loaded for dynamic sprite management */ + ClearDynamicSprites(); + /* Call any functions that should be run after GRFs have been loaded. */ AfterLoadGRFs(); diff --git a/src/spritecache.cpp b/src/spritecache.cpp index 147e3b1bf8..7ff4efda6b 100644 --- a/src/spritecache.cpp +++ b/src/spritecache.cpp @@ -759,7 +759,7 @@ static void DeleteEntriesFromSpriteCache(size_t to_remove) SpriteID i = 0; for (; i != static_cast(_spritecache.size()) && candidate_bytes < to_remove; i++) { const SpriteCache *sc = GetSpriteCache(i); - if (sc->ptr != nullptr) { + if (sc->ptr != nullptr && sc->file != nullptr) { push({ sc->lru, i, sc->length }); if (candidate_bytes >= to_remove) break; } @@ -768,7 +768,7 @@ static void DeleteEntriesFromSpriteCache(size_t to_remove) * only sprites with LRU values <= the maximum (i.e. the top of the heap) need to be considered */ for (; i != static_cast(_spritecache.size()); i++) { const SpriteCache *sc = GetSpriteCache(i); - if (sc->ptr != nullptr && sc->lru <= candidates.front().lru) { + if (sc->ptr != nullptr && sc->file != nullptr && sc->lru <= candidates.front().lru) { push({ sc->lru, i, sc->length }); while (!candidates.empty() && candidate_bytes - candidates.front().size >= to_remove) { pop(); @@ -825,6 +825,32 @@ void *UniquePtrSpriteAllocator::AllocatePtr(size_t size) return this->data.get(); } +/** + * Allocate and inject memory for a memory-based sprite. + */ +std::span InjectSprite(SpriteType type, SpriteID load_index, size_t len) +{ + if (SpriteExists(load_index)) GetSpriteCache(load_index)->ClearSpriteData(); + + SpriteCache *sc = AllocateSpriteCache(load_index); + sc->file_pos = SIZE_MAX; + sc->file = nullptr; + sc->id = 0; + sc->lru = 0; + sc->type = type; + sc->warned = false; + sc->control_flags = {}; + + UniquePtrSpriteAllocator cache_allocator; + cache_allocator.Allocate(len); + + sc->ptr = std::move(cache_allocator.data); + sc->length = static_cast(cache_allocator.size); + _spritecache_bytes_used += sc->length; + + return {sc->ptr.get(), sc->length}; +} + /** * Handles the case when a sprite of different type is requested than is present in the SpriteCache. * For SpriteType::Font sprites, it is normal. In other cases, default sprite is loaded instead. @@ -958,3 +984,44 @@ void GfxClearFontSpriteCache() } /* static */ SpriteCollMap> SpriteLoader::Sprite::buffer; + +static SpriteID _sprites_end; ///< First usable free sprite ID. +static std::vector _dynamic_sprites; ///< List of used/free custom sprite slots. + +/** + * Clear custom sprites mapping and set first usable free sprite ID. + */ +void ClearDynamicSprites() +{ + _dynamic_sprites.clear(); + _sprites_end = _spritecache.size(); +} + +/** + * Allocate a custom sprite ID. + */ +SpriteID AllocateDynamicSprite() +{ + /* Find first unused slot, or make one. */ + auto it = std::ranges::find(_dynamic_sprites, 0); + if (it == std::end(_dynamic_sprites)) it = _dynamic_sprites.emplace(it, 0); + + (*it)++; + return _sprites_end + std::distance(std::begin(_dynamic_sprites), it); +} + +/** + * Mark a custom sprite ID as deallocated. + * The sprite slot is merely marked as reusable. + */ +void DeallocateDynamicSprite(SpriteID sprite) +{ + if (sprite >= _sprites_end && sprite < _sprites_end + _dynamic_sprites.size()) { + assert(_dynamic_sprites[sprite - _sprites_end] > 0); + --_dynamic_sprites[sprite - _sprites_end]; + + if (_dynamic_sprites[sprite - _sprites_end] == 0) { + GetSpriteCache(sprite)->ClearSpriteData(); + } + } +} diff --git a/src/spritecache.h b/src/spritecache.h index e91fa81eac..fbb220d127 100644 --- a/src/spritecache.h +++ b/src/spritecache.h @@ -59,5 +59,10 @@ size_t GetGRFSpriteOffset(uint32_t id); bool LoadNextSprite(SpriteID load_index, SpriteFile &file, uint file_sprite_id); bool SkipSpriteData(SpriteFile &file, uint8_t type, uint16_t num); void DupSprite(SpriteID old_spr, SpriteID new_spr); +std::span InjectSprite(SpriteType type, SpriteID load_index, size_t len); + +void ClearDynamicSprites(); +SpriteID AllocateDynamicSprite(); +void DeallocateDynamicSprite(SpriteID spite); #endif /* SPRITECACHE_H */