1
0
Fork 0

Change: Don't store procedural trees counter parity in the map array

pull/11955/head
dP 2024-02-02 18:53:25 +05:30
parent 17114369c0
commit f91d632a62
8 changed files with 77 additions and 61 deletions

View File

@ -759,49 +759,78 @@ std::tuple<CommandCost, Money> 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);
/** static const uint TILE_LOOP_PATTERN_SIZE = MIN_MAP_SIZE * MIN_MAP_SIZE; // Size of the pattern
* Gradually iterate over all tiles on the map, calling their TileLoopProcs once every TILE_UPDATE_FREQUENCY ticks. 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 /* The pseudorandom sequence of tiles is generated using a Galois linear feedback
* shift register (LFSR). This allows a deterministic pseudorandom ordering, but * shift register (LFSR). This allows a deterministic pseudorandom ordering, but
* still with minimal state and fast iteration. */ * still with minimal state and fast iteration. */
/* Maximal length LFSR feedback terms, from 12-bit (for 64x64 maps) to 24-bit (for 4096x4096 maps). static int16_t lfsr_current = _cur_tileloop_seed % TILE_LOOP_PATTERN_SIZE;
* Extracted from http://www.ece.cmu.edu/~koopman/lfsr/ */ const uint32_t LFSR_FEEDBACK = 0xD8F; // 12-bit feedback term extracted from http://www.ece.cmu.edu/~koopman/lfsr/
static const uint32_t feedbacks[] = { static_assert(TILE_LOOP_PATTERN_SIZE == 4096); // If we ever change pattern size, we need a different feedback term.
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];
/* We update every tile every TILE_UPDATE_FREQUENCY ticks, so divide the map size by 2^TILE_UPDATE_FREQUENCY_LOG = TILE_UPDATE_FREQUENCY */ _cur_tile_loop_pattern[0] = 0; // LFSR doesn't iterate 0 so add it separately
static_assert(2 * MIN_MAP_SIZE_BITS >= TILE_UPDATE_FREQUENCY_LOG); for (uint i = 1; i < TILE_LOOP_PATTERN_SIZE; i++) {
uint count = 1 << (Map::LogX() + Map::LogY() - TILE_UPDATE_FREQUENCY_LOG); 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; void RunTileLoop()
/* The LFSR cannot have a zeroed state. */ {
assert(tile != 0); 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) { if (TimerGameTick::counter % TILE_UPDATE_FREQUENCY == 0) {
_tile_type_procs[GetTileType(0)]->tile_loop_proc(0); /* Initialize starting value for LFSR from tick counter so we iterate all possible patterns. */
count--; _cur_tileloop_seed = (TimerGameTick::counter >> 8) % (TILE_LOOP_PATTERN_SIZE - 1) + 1;
GenerateTileLoopPattern();
} }
while (count--) { /* 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);
/* 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); _tile_type_procs[GetTileType(tile)]->tile_loop_proc(tile);
/* Get the next tile in sequence using a Galois LFSR. */
tile = (tile.base() >> 1) ^ (-(int32_t)(tile.base() & 1) & feedback);
} }
}
_cur_tileloop_tile = tile; }
} }
void InitializeLandscape() void InitializeLandscape()

View File

@ -133,6 +133,8 @@ bool HasFoundationNW(TileIndex tile, Slope slope_here, uint z_here);
bool HasFoundationNE(TileIndex tile, Slope slope_here, uint z_here); bool HasFoundationNE(TileIndex tile, Slope slope_here, uint z_here);
void DoClearSquare(TileIndex tile); void DoClearSquare(TileIndex tile);
bool WasTileIteratedThisCycle(TileIndex tile);
void GenerateTileLoopPattern();
void RunTileLoop(); void RunTileLoop();
void InitializeLandscape(); void InitializeLandscape();

View File

@ -36,7 +36,7 @@
#include "safeguards.h" #include "safeguards.h"
extern TileIndex _cur_tileloop_tile; extern uint32_t _cur_tileloop_seed;
extern void MakeNewgameSettingsLive(); extern void MakeNewgameSettingsLive();
void InitializeSound(); void InitializeSound();
@ -100,7 +100,7 @@ void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settin
_pause_mode = PM_UNPAUSED; _pause_mode = PM_UNPAUSED;
_game_speed = 100; _game_speed = 100;
TimerGameTick::counter = 0; TimerGameTick::counter = 0;
_cur_tileloop_tile = 1; _cur_tileloop_seed = 1;
_thd.redsq = INVALID_TILE; _thd.redsq = INVALID_TILE;
if (reset_settings) MakeNewgameSettingsLive(); if (reset_settings) MakeNewgameSettingsLive();

View File

@ -565,9 +565,10 @@ bool AfterLoadGame()
{ {
SetSignalHandlers(); 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. */ /* 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(); if (IsSavegameVersionBefore(SLV_98)) _gamelog.Oldver();

View File

@ -26,7 +26,7 @@
#include "../safeguards.h" #include "../safeguards.h"
extern TileIndex _cur_tileloop_tile; extern uint32_t _cur_tileloop_seed;
extern uint16_t _disaster_delay; extern uint16_t _disaster_delay;
extern uint8_t _trees_tick_ctr; 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("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("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("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_seed, 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_UINT32, SLV_6, SL_MAX_VERSION),
SLEG_VAR("next_disaster_start", _disaster_delay, SLE_UINT16), SLEG_VAR("next_disaster_start", _disaster_delay, SLE_UINT16),
SLEG_VAR("random_state[0]", _random.state[0], SLE_UINT32), SLEG_VAR("random_state[0]", _random.state[0], SLE_UINT32),
SLEG_VAR("random_state[1]", _random.state[1], SLE_UINT32), SLEG_VAR("random_state[1]", _random.state[1], SLE_UINT32),

View File

@ -1592,7 +1592,7 @@ static bool LoadTTDPatchExtraChunks(LoadgameState *ls, int)
return true; return true;
} }
extern TileIndex _cur_tileloop_tile; extern uint32_t _cur_tileloop_seed;
extern uint16_t _disaster_delay; extern uint16_t _disaster_delay;
extern uint8_t _trees_tick_ctr; extern uint8_t _trees_tick_ctr;
extern uint8_t _age_cargo_skip_counter; // From misc_sl.cpp 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_U8, 1, &_age_cargo_skip_counter ),
OCL_VAR ( OC_FILE_U16 | OC_VAR_U64, 1, &TimerGameTick::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 ), OCL_ASSERT( OC_TTO, 0x3A2E ),

View File

@ -613,10 +613,13 @@ static void DrawTile_Trees(TileInfo *ti)
/* Use a fixed value for the counter (0) when trees aren't growing. */ /* 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) { 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. */ /* It may happen that counter has not increased yet but the tile was already
if (counter % 2 != trees - 1) counter++; * 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); 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. * Also, we use a simple hash to spread the updates evenly over the map.
*/ */
uint32_t tile_hash = SimpleHash32(tile.base()); 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. */ /* 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) { 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 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; 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); bool can_plant_extra = CanPlantExtraTrees(tile);
uint8_t value = PickRandomGrowth(tile_hash, can_plant_extra, counter); 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) { if (growth == proc_growth && count == proc_count) {
growth = TreeGrowthStage::Procedural; growth = TreeGrowthStage::Procedural;
SetTreeGrowth(tile, growth); 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) { 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. */ /* Don't mark dirty if trees didn't change. */
if (HasBit(value, 5)) return; if (HasBit(value, 5)) return;

View File

@ -193,20 +193,6 @@ inline void AddTreeCount(Tile t, int c)
t.m5() += c << 6; 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. * Returns the tree growth stage.
* *