From f91d632a62c05631ec8e302e9279b59159603a15 Mon Sep 17 00:00:00 2001 From: dP Date: Fri, 2 Feb 2024 18:53:25 +0530 Subject: [PATCH] Change: Don't store procedural trees counter parity in the map array --- src/landscape.cpp | 83 +++++++++++++++++++++++------------ src/landscape.h | 2 + src/misc.cpp | 4 +- src/saveload/afterload.cpp | 5 ++- src/saveload/misc_sl.cpp | 6 +-- src/saveload/oldloader_sl.cpp | 4 +- src/tree_cmd.cpp | 20 ++++----- src/tree_map.h | 14 ------ 8 files changed, 77 insertions(+), 61 deletions(-) diff --git a/src/landscape.cpp b/src/landscape.cpp index 7964e3ba02..673983018e 100644 --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -759,49 +759,78 @@ std::tuple CmdClearArea(DoCommandFlag flags, TileIndex tile, } -TileIndex _cur_tileloop_tile; +/* Tile loop repeats pattern every MIN_MAP_SIZE tiles. Having it too small might + * be noticeable, And too big is a waste of resources. */ +static_assert(MIN_MAP_SIZE == 64); -/** - * Gradually iterate over all tiles on the map, calling their TileLoopProcs once every TILE_UPDATE_FREQUENCY ticks. +static const uint TILE_LOOP_PATTERN_SIZE = MIN_MAP_SIZE * MIN_MAP_SIZE; // Size of the pattern +static uint8_t _cur_tile_loop_pattern[TILE_LOOP_PATTERN_SIZE]; // Tile loop pattern, changes every TILE_UPDATE_FREQUENCY ticks + +uint32_t _cur_tileloop_seed; // Seed value that was used to generate current tile loop pattern + +/* + * Determines whether the tile loop was already called for the tile in the + * current cycle (TILE_UPDATE_FREQUENCY ticks). + * @param tile TileIndex of the tile + * @returns true iff tile was already iterated */ -void RunTileLoop() +bool WasTileIteratedThisCycle(TileIndex tile) { - PerformanceAccumulator framerate(PFE_GL_LANDSCAPE); + uint pattern_index = (TileY(tile) % MIN_MAP_SIZE) * MIN_MAP_SIZE + TileX(tile) % MIN_MAP_SIZE; + return (_cur_tile_loop_pattern[pattern_index] <= TimerGameTick::counter % TILE_UPDATE_FREQUENCY); +} +/* + * Generates a pattern to use for the tile loop seeded on the value of _cur_tileloop_seed + */ +void GenerateTileLoopPattern() { /* The pseudorandom sequence of tiles is generated using a Galois linear feedback * shift register (LFSR). This allows a deterministic pseudorandom ordering, but * still with minimal state and fast iteration. */ - /* Maximal length LFSR feedback terms, from 12-bit (for 64x64 maps) to 24-bit (for 4096x4096 maps). - * Extracted from http://www.ece.cmu.edu/~koopman/lfsr/ */ - static const uint32_t feedbacks[] = { - 0xD8F, 0x1296, 0x2496, 0x4357, 0x8679, 0x1030E, 0x206CD, 0x403FE, 0x807B8, 0x1004B2, 0x2006A8, 0x4004B2, 0x800B87 - }; - static_assert(lengthof(feedbacks) == 2 * MAX_MAP_SIZE_BITS - 2 * MIN_MAP_SIZE_BITS + 1); - const uint32_t feedback = feedbacks[Map::LogX() + Map::LogY() - 2 * MIN_MAP_SIZE_BITS]; + static int16_t lfsr_current = _cur_tileloop_seed % TILE_LOOP_PATTERN_SIZE; + const uint32_t LFSR_FEEDBACK = 0xD8F; // 12-bit feedback term extracted from http://www.ece.cmu.edu/~koopman/lfsr/ + static_assert(TILE_LOOP_PATTERN_SIZE == 4096); // If we ever change pattern size, we need a different feedback term. - /* We update every tile every TILE_UPDATE_FREQUENCY ticks, so divide the map size by 2^TILE_UPDATE_FREQUENCY_LOG = TILE_UPDATE_FREQUENCY */ - static_assert(2 * MIN_MAP_SIZE_BITS >= TILE_UPDATE_FREQUENCY_LOG); - uint count = 1 << (Map::LogX() + Map::LogY() - TILE_UPDATE_FREQUENCY_LOG); + _cur_tile_loop_pattern[0] = 0; // LFSR doesn't iterate 0 so add it separately + for (uint i = 1; i < TILE_LOOP_PATTERN_SIZE; i++) { + lfsr_current = (lfsr_current >> 1) ^ (-(lfsr_current & 1) & LFSR_FEEDBACK); + _cur_tile_loop_pattern[lfsr_current] = i / (TILE_LOOP_PATTERN_SIZE >> TILE_UPDATE_FREQUENCY_LOG); + } +} - TileIndex tile = _cur_tileloop_tile; - /* The LFSR cannot have a zeroed state. */ - assert(tile != 0); +void RunTileLoop() +{ + PerformanceAccumulator framerate(PFE_GL_LANDSCAPE); - /* Manually update tile 0 every TILE_UPDATE_FREQUENCY ticks - the LFSR never iterates over it itself. */ + /* Every TILE_UPDATE_FREQUENCY ticks generate a new tile pattern just to make it a bit more random. */ if (TimerGameTick::counter % TILE_UPDATE_FREQUENCY == 0) { - _tile_type_procs[GetTileType(0)]->tile_loop_proc(0); - count--; + /* Initialize starting value for LFSR from tick counter so we iterate all possible patterns. */ + _cur_tileloop_seed = (TimerGameTick::counter >> 8) % (TILE_LOOP_PATTERN_SIZE - 1) + 1; + GenerateTileLoopPattern(); } - while (count--) { - _tile_type_procs[GetTileType(tile)]->tile_loop_proc(tile); + /* Having sizes equally divisible helps to avoid boundary checks in the loop. */ + assert(Map::SizeX() % MIN_MAP_SIZE == 0); + assert(Map::SizeY() % MIN_MAP_SIZE == 0); - /* Get the next tile in sequence using a Galois LFSR. */ - tile = (tile.base() >> 1) ^ (-(int32_t)(tile.base() & 1) & feedback); + /* We update every tile every TILE_UPDATE_FREQUENCY ticks, */ + for (uint i = 0; i < TILE_LOOP_PATTERN_SIZE; i++) { + + /* Find tiles in a pattern that need to be updated on a current iteration, */ + if (_cur_tile_loop_pattern[i] != TimerGameTick::counter % TILE_UPDATE_FREQUENCY) continue; + uint dx = i % MIN_MAP_SIZE; + uint dy = i / MIN_MAP_SIZE; + + /* Update tiles all over the map that correspond to this position in the pattern. */ + for (uint y = 0; y < Map::SizeY(); y += MIN_MAP_SIZE) { + uint tile_y = y + dy; + for (uint x = 0; x < Map::SizeX(); x += MIN_MAP_SIZE) { + TileIndex tile = TileXY(x + dx, tile_y); + _tile_type_procs[GetTileType(tile)]->tile_loop_proc(tile); + } + } } - - _cur_tileloop_tile = tile; } void InitializeLandscape() diff --git a/src/landscape.h b/src/landscape.h index 98221c5019..dc9caa8e49 100644 --- a/src/landscape.h +++ b/src/landscape.h @@ -133,6 +133,8 @@ bool HasFoundationNW(TileIndex tile, Slope slope_here, uint z_here); bool HasFoundationNE(TileIndex tile, Slope slope_here, uint z_here); void DoClearSquare(TileIndex tile); +bool WasTileIteratedThisCycle(TileIndex tile); +void GenerateTileLoopPattern(); void RunTileLoop(); void InitializeLandscape(); diff --git a/src/misc.cpp b/src/misc.cpp index 72701ff23c..ae3ba5ff36 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -36,7 +36,7 @@ #include "safeguards.h" -extern TileIndex _cur_tileloop_tile; +extern uint32_t _cur_tileloop_seed; extern void MakeNewgameSettingsLive(); void InitializeSound(); @@ -100,7 +100,7 @@ void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settin _pause_mode = PM_UNPAUSED; _game_speed = 100; TimerGameTick::counter = 0; - _cur_tileloop_tile = 1; + _cur_tileloop_seed = 1; _thd.redsq = INVALID_TILE; if (reset_settings) MakeNewgameSettingsLive(); diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 06102930b7..c498614ab0 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -565,9 +565,10 @@ bool AfterLoadGame() { SetSignalHandlers(); - extern TileIndex _cur_tileloop_tile; // From landscape.cpp. + extern uint32_t _cur_tileloop_seed; // From landscape.cpp. /* The LFSR used in RunTileLoop iteration cannot have a zeroed state, make it non-zeroed. */ - if (_cur_tileloop_tile == 0) _cur_tileloop_tile = 1; + if (_cur_tileloop_seed == 0) _cur_tileloop_seed = 1; + GenerateTileLoopPattern(); if (IsSavegameVersionBefore(SLV_98)) _gamelog.Oldver(); diff --git a/src/saveload/misc_sl.cpp b/src/saveload/misc_sl.cpp index 77c6b8cc36..b3d364d2ba 100644 --- a/src/saveload/misc_sl.cpp +++ b/src/saveload/misc_sl.cpp @@ -26,7 +26,7 @@ #include "../safeguards.h" -extern TileIndex _cur_tileloop_tile; +extern uint32_t _cur_tileloop_seed; extern uint16_t _disaster_delay; extern uint8_t _trees_tick_ctr; @@ -89,8 +89,8 @@ static const SaveLoad _date_desc[] = { SLEG_CONDVAR("economy_date_fract", TimerGameEconomy::date_fract, SLE_UINT16, SLV_ECONOMY_DATE, SL_MAX_VERSION), SLEG_CONDVAR("calendar_sub_date_fract", TimerGameCalendar::sub_date_fract, SLE_UINT16, SLV_CALENDAR_SUB_DATE_FRACT, SL_MAX_VERSION), SLEG_CONDVAR("age_cargo_skip_counter", _age_cargo_skip_counter, SLE_UINT8, SL_MIN_VERSION, SLV_162), - SLEG_CONDVAR("cur_tileloop_tile", _cur_tileloop_tile, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_6), - SLEG_CONDVAR("cur_tileloop_tile", _cur_tileloop_tile, SLE_UINT32, SLV_6, SL_MAX_VERSION), + SLEG_CONDVAR("cur_tileloop_tile", _cur_tileloop_seed, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_6), + SLEG_CONDVAR("cur_tileloop_tile", _cur_tileloop_seed, SLE_UINT32, SLV_6, SL_MAX_VERSION), SLEG_VAR("next_disaster_start", _disaster_delay, SLE_UINT16), SLEG_VAR("random_state[0]", _random.state[0], SLE_UINT32), SLEG_VAR("random_state[1]", _random.state[1], SLE_UINT32), diff --git a/src/saveload/oldloader_sl.cpp b/src/saveload/oldloader_sl.cpp index 538c1336c3..c79c74129a 100644 --- a/src/saveload/oldloader_sl.cpp +++ b/src/saveload/oldloader_sl.cpp @@ -1592,7 +1592,7 @@ static bool LoadTTDPatchExtraChunks(LoadgameState *ls, int) return true; } -extern TileIndex _cur_tileloop_tile; +extern uint32_t _cur_tileloop_seed; extern uint16_t _disaster_delay; extern uint8_t _trees_tick_ctr; extern uint8_t _age_cargo_skip_counter; // From misc_sl.cpp @@ -1638,7 +1638,7 @@ static const OldChunks main_chunk[] = { OCL_VAR ( OC_FILE_U16 | OC_VAR_U8, 1, &_age_cargo_skip_counter ), OCL_VAR ( OC_FILE_U16 | OC_VAR_U64, 1, &TimerGameTick::counter ), - OCL_VAR ( OC_TILE, 1, &_cur_tileloop_tile ), + OCL_VAR ( OC_TILE, 1, &_cur_tileloop_seed ), OCL_ASSERT( OC_TTO, 0x3A2E ), diff --git a/src/tree_cmd.cpp b/src/tree_cmd.cpp index 30a6dc4f56..4fdf7d0954 100644 --- a/src/tree_cmd.cpp +++ b/src/tree_cmd.cpp @@ -613,10 +613,13 @@ static void DrawTile_Trees(TileInfo *ti) /* Use a fixed value for the counter (0) when trees aren't growing. */ if (_settings_game.construction.extra_tree_placement != ETP_NO_GROWTH_NO_SPREAD) { - counter = TimerGameTick::counter >> 12; + uint64_t cycle = tile_hash + (TimerGameTick::counter >> 8); + counter = cycle >> 4; - /* For procedural growth counter parity is stored instead of tree count. */ - if (counter % 2 != trees - 1) counter++; + /* It may happen that counter has not increased yet but the tile was already + * iterated with the new value so we need to adjust. + */ + if ((cycle & 15) == 15 && WasTileIteratedThisCycle(ti->tile)) counter++; } auto proc_value = PickRandomGrowth(tile_hash, CanPlantExtraTrees(ti->tile), counter); @@ -778,7 +781,7 @@ static void TileLoop_Trees(TileIndex tile) * Also, we use a simple hash to spread the updates evenly over the map. */ uint32_t tile_hash = SimpleHash32(tile.base()); - uint32_t cycle = tile_hash + (TimerGameTick::counter >> 8); + uint64_t cycle = tile_hash + (TimerGameTick::counter >> 8); /* Handle growth of grass (under trees/on MP_TREES tiles) at every 8th processings, like it's done for grass on MP_CLEAR tiles. */ if ((cycle & 7) == 7 && GetTreeGround(tile) == TREE_GROUND_GRASS) { @@ -794,7 +797,7 @@ static void TileLoop_Trees(TileIndex tile) static const uint32_t TREE_UPDATE_FREQUENCY = 16; // How many tile updates happen for one tree update if (cycle % TREE_UPDATE_FREQUENCY != TREE_UPDATE_FREQUENCY - 1) return; - uint64_t counter = (TimerGameTick::counter >> 12) + 1; + uint64_t counter = (cycle >> 4) + 1; bool can_plant_extra = CanPlantExtraTrees(tile); uint8_t value = PickRandomGrowth(tile_hash, can_plant_extra, counter); @@ -809,15 +812,10 @@ static void TileLoop_Trees(TileIndex tile) if (growth == proc_growth && count == proc_count) { growth = TreeGrowthStage::Procedural; SetTreeGrowth(tile, growth); - SetTreeCycle(tile, 0); // Tree cycle overwrites tree count + AddTreeCount(tile, -(int)count); // with procedural growth count is not used so just set it to 0 } if (growth == TreeGrowthStage::Procedural) { - /* Store the parity of this cycle so that drawing routine can detect - * whether the tile was alredy looped this cycle asd so chose appropriate state. - */ - SetTreeCycle(tile, counter % 2); - /* Don't mark dirty if trees didn't change. */ if (HasBit(value, 5)) return; diff --git a/src/tree_map.h b/src/tree_map.h index 5c222a93ae..0d0d4552c3 100644 --- a/src/tree_map.h +++ b/src/tree_map.h @@ -193,20 +193,6 @@ inline void AddTreeCount(Tile t, int c) t.m5() += c << 6; } -/** - * Returns the tree growth stage. - * Sets the tree cycle parity (for procedural growth). Uses the same bits as tree counter. - * - * @param t The tile to set the value on - * @param c The value to set - * @pre Tile must be of type MP_TREES - */ -static inline void SetTreeCycle(Tile t, int c) -{ - assert(IsTileType(t, MP_TREES)); - SB(t.m5(), 6, 2, c); -} - /** * Returns the tree growth stage. *