diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 9199130fe..dc1722bf4 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -2594,6 +2594,11 @@ invalidates the ``texpos`` value that used to point to that texture. The ``textures`` module solves this problem by providing a stable handle instead of a raw ``texpos``. When we need to draw a particular tile, we can look up the current ``texpos`` value via the handle. +Texture module can register textures in two ways: to reserved and dynamic ranges. +Reserved range is a limit buffer in a game texture vector, that will never be wiped. +It is good for static assets, which need to be loaded at the very beginning and will be used during the process running. +In other cases, it is better to use dynamic range. +If reserved range buffer limit has been reached, dynamic range will be used by default. * ``loadTileset(file, tile_px_w, tile_px_h, reserved?)`` diff --git a/library/modules/Textures.cpp b/library/modules/Textures.cpp index bdb1e815a..a2810dd20 100644 --- a/library/modules/Textures.cpp +++ b/library/modules/Textures.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -34,6 +35,7 @@ struct ReservedRange { this->start = start; this->end = start + ReservedRange::size; this->current = start; + this->is_installed = true; } long get_new_texpos() { if (this->current == this->end) @@ -47,6 +49,7 @@ struct ReservedRange { int32_t start = -1; int32_t end = -1; long current = -1; + bool is_installed = false; }; static ReservedRange reserved_range{}; @@ -54,6 +57,7 @@ static std::unordered_map g_handle_to_texpos; static std::unordered_map g_handle_to_reserved_texpos; static std::unordered_map g_handle_to_surface; static std::unordered_map> g_tileset_to_handles; +static std::vector g_delayed_regs; static std::mutex g_adding_mutex; static std::atomic loading_state = false; @@ -134,6 +138,12 @@ std::vector slice_tileset(SDL_Surface* surface, int tile_px_w, int int dimx = surface->w / tile_px_w; int dimy = surface->h / tile_px_h; + if (reserved && (dimx * dimy > reserved_range.end - reserved_range.current)) { + WARN(textures).print( + "there is not enough space in reserved range for whole tileset, using dynamic range\n"); + reserved = false; + } + for (int y = 0; y < dimy; y++) { for (int x = 0; x < dimx; x++) { SDL_Surface* tile = DFSDL_CreateRGBSurface( @@ -153,26 +163,35 @@ std::vector slice_tileset(SDL_Surface* surface, int tile_px_w, int TexposHandle Textures::loadTexture(SDL_Surface* surface, bool reserved) { if (!surface || !enabler) return 0; // should be some error, i guess - if (loading_state) { - ERR(textures).printerr("unable to load texture during game loading\n"); - return 0; - } + if (loading_state) + reserved = true; // use reserved range during loading for all textures auto handle = reinterpret_cast(surface); g_handle_to_surface.emplace(handle, surface); surface->refcount++; // prevent destruct on next FreeSurface by game - if (reserved) { + + if (reserved && reserved_range.is_installed) { auto texpos = reserved_range.get_new_texpos(); - if (texpos == -1) { + if (texpos != -1) { + insert_texture(surface, texpos); + g_handle_to_reserved_texpos.emplace(handle, texpos); + return handle; + } + + if (loading_state) { // if we in loading state and reserved range is full -> error ERR(textures).printerr("reserved range limit has been reached, use dynamic range\n"); return 0; } - insert_texture(surface, texpos); - g_handle_to_reserved_texpos.emplace(handle, texpos); + } + + // if we here in loading state = true, then it should be dynamic range -> delay reg + if (loading_state) { + g_delayed_regs.push_back(handle); } else { auto texpos = add_texture(surface); g_handle_to_texpos.emplace(handle, texpos); } + return handle; } @@ -192,10 +211,9 @@ std::vector Textures::loadTileset(const std::string& file, int til surface = canonicalize_format(surface); auto handles = slice_tileset(surface, tile_px_w, tile_px_h, reserved); - DEBUG(textures).print("loaded %zd textures from '%s' to %s range\n", handles.size(), - file.c_str(), reserved ? "reserved" : "dynamic"); - + DEBUG(textures).print("loaded %zd textures from '%s'\n", handles.size(), file.c_str()); g_tileset_to_handles[file] = handles; + return handles; } @@ -207,12 +225,14 @@ long Textures::getTexposByHandle(TexposHandle handle) { return g_handle_to_reserved_texpos[handle]; if (g_handle_to_texpos.contains(handle)) return g_handle_to_texpos[handle]; + if (std::find(g_delayed_regs.begin(), g_delayed_regs.end(), handle) != g_delayed_regs.end()) + return 0; if (g_handle_to_surface.contains(handle)) { - if (loading_state) { - ERR(textures).printerr("unable reinit texture from dynamic range during loading\n"); - return -1; - } g_handle_to_surface[handle]->refcount++; // prevent destruct on next FreeSurface by game + if (loading_state) { // reinit dor dynamic range during loading -> delayed + g_delayed_regs.push_back(handle); + return 0; + } auto texpos = add_texture(g_handle_to_surface[handle]); g_handle_to_texpos.emplace(handle, texpos); return texpos; @@ -284,6 +304,15 @@ static void reset_surface() { g_handle_to_surface.clear(); } +static void register_delayed_handles() { + DEBUG(textures).print("register delayed handles, size %zd\n", g_delayed_regs.size()); + for (auto& handle : g_delayed_regs) { + auto texpos = add_texture(g_handle_to_surface[handle]); + g_handle_to_texpos.emplace(handle, texpos); + } + g_delayed_regs.clear(); +} + // reset point on New Game struct tracking_stage_new_region : df::viewscreen_new_regionst { typedef df::viewscreen_new_regionst interpose_base; @@ -292,7 +321,10 @@ struct tracking_stage_new_region : df::viewscreen_new_regionst { if (this->m_raw_load_stage != this->raw_load_stage) { TRACE(textures).print("raw_load_stage %d -> %d\n", this->m_raw_load_stage, this->raw_load_stage); + bool tmp_state = loading_state; loading_state = this->raw_load_stage >= 0 && this->raw_load_stage < 3 ? true : false; + if (tmp_state != loading_state && !loading_state) + register_delayed_handles(); this->m_raw_load_stage = this->raw_load_stage; if (this->m_raw_load_stage == 1) reset_texpos(); @@ -312,7 +344,10 @@ struct tracking_stage_adopt_region : df::viewscreen_adopt_regionst { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { if (this->m_cur_step != this->cur_step) { TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step); + bool tmp_state = loading_state; loading_state = this->cur_step >= 0 && this->cur_step < 3 ? true : false; + if (tmp_state != loading_state && !loading_state) + register_delayed_handles(); this->m_cur_step = this->cur_step; if (this->m_cur_step == 1) reset_texpos(); @@ -332,7 +367,10 @@ struct tracking_stage_load_region : df::viewscreen_loadgamest { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { if (this->m_cur_step != this->cur_step) { TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step); + bool tmp_state = loading_state; loading_state = this->cur_step >= 0 && this->cur_step < 3 ? true : false; + if (tmp_state != loading_state && !loading_state) + register_delayed_handles(); this->m_cur_step = this->cur_step; if (this->m_cur_step == 1) reset_texpos(); @@ -352,7 +390,10 @@ struct tracking_stage_new_arena : df::viewscreen_new_arenast { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { if (this->m_cur_step != this->cur_step) { TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step); + bool tmp_state = loading_state; loading_state = this->cur_step >= 0 && this->cur_step < 3 ? true : false; + if (tmp_state != loading_state && !loading_state) + register_delayed_handles(); this->m_cur_step = this->cur_step; if (this->m_cur_step == 0) reset_texpos(); @@ -380,6 +421,11 @@ static void uninstall_reset_point() { } static void reserve_static_range() { + if (static_cast(enabler->textures.init_texture_size) != enabler->textures.raws.size()) { + WARN(textures).print( + "reserved range can't be installed! all textures will be loaded to dynamic range!"); + return; + } reserved_range.init(enabler->textures.init_texture_size); auto dummy_surface = DFSDL_CreateRGBSurfaceWithFormat(0, 0, 0, 32, SDL_PixelFormatEnum::SDL_PIXELFORMAT_RGBA32);