mirror of https://github.com/OpenTTD/OpenTTD
Change: Don't store procedural trees counter parity in the map array
parent
17114369c0
commit
f91d632a62
|
@ -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()
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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 ),
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue