@@ -598,13 +598,13 @@ bool DoCommandP(TileIndex tile, uint32 p
/**
* Helper to deduplicate the code for returning.
* @param cmd the command cost to return.
* @param clear whether to keep the storage changes or not.
*/
#define return_dcpi(cmd, clear) { _docommand_recursive = 0; ClearPersistentStorageChanges(clear); return cmd; }
#define return_dcpi(cmd) { _docommand_recursive = 0; return cmd; }
/*!
* Helper function for the toplevel network safe docommand function for the current company.
*
* @param tile The tile to perform a command on (see #CommandProc)
* @param p1 Additional data for the command (see #CommandProc)
@@ -642,34 +642,35 @@ CommandCost DoCommandPInternal(TileIndex
#ifdef ENABLE_NETWORK
/* Make sure p2 is properly set to a ClientID. */
assert(!(cmd_flags & CMD_CLIENT_ID) || p2 != 0);
#endif
/* Do not even think about executing out-of-bounds tile-commands */
if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (cmd_flags & CMD_ALL_TILES) == 0))) return_dcpi(CMD_ERROR, false);
if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (cmd_flags & CMD_ALL_TILES) == 0))) return_dcpi(CMD_ERROR);
/* Always execute server and spectator commands as spectator */
bool exec_as_spectator = (cmd_flags & (CMD_SPECTATOR | CMD_SERVER)) != 0;
/* If the company isn't valid it may only do server command or start a new company!
* The server will ditch any server commands a client sends to it, so effectively
* this guards the server from executing functions for an invalid company. */
if (_game_mode == GM_NORMAL && !exec_as_spectator && !Company::IsValidID(_current_company) && !(_current_company == OWNER_DEITY && (cmd_flags & CMD_DEITY) != 0)) {
return_dcpi(CMD_ERROR, false);
return_dcpi(CMD_ERROR);
}
Backup<CompanyByte> cur_company(_current_company, FILE_LINE);
if (exec_as_spectator) cur_company.Change(COMPANY_SPECTATOR);
bool test_and_exec_can_differ = (cmd_flags & CMD_NO_TEST) != 0;
/* Test the command. */
_cleared_object_areas.Clear();
SetTownRatingTestMode(true);
ClearPersistentStorageChanges(false);
BasePersistentStorageArray::SwitchMode(PSM_ENTER_TESTMODE);
CommandCost res = proc(tile, flags, p1, p2, text);
BasePersistentStorageArray::SwitchMode(PSM_LEAVE_TESTMODE);
SetTownRatingTestMode(false);
/* Make sure we're not messing things up here. */
assert(exec_as_spectator ? _current_company == COMPANY_SPECTATOR : cur_company.Verify());
/* If the command fails, we're doing an estimate
@@ -682,13 +683,13 @@ CommandCost DoCommandPInternal(TileIndex
if (!_networking || _generating_world || (cmd & CMD_NETWORK_COMMAND) != 0) {
/* Log the failed command as well. Just to be able to be find
* causes of desyncs due to bad command test implementations. */
DEBUG(desync, 1, "cmdf: %08x; %02x; %02x; %06x; %08x; %08x; %08x; \"%s\" (%s)", _date, _date_fract, (int)_current_company, tile, p1, p2, cmd & ~CMD_NETWORK_COMMAND, text, GetCommandName(cmd));
cur_company.Restore();
return_dcpi(res, false);
return_dcpi(res);
/*
* If we are in network, and the command is not from the network
* send it to the command-queue and abort execution
@@ -698,22 +699,23 @@ CommandCost DoCommandPInternal(TileIndex
/* Don't return anything special here; no error, no costs.
* This way it's not handled by DoCommand and only the
* actual execution of the command causes messages. Also
* reset the storages as we've not executed the command. */
return_dcpi(CommandCost(), false);
return_dcpi(CommandCost());
#endif /* ENABLE_NETWORK */
DEBUG(desync, 1, "cmd: %08x; %02x; %02x; %06x; %08x; %08x; %08x; \"%s\" (%s)", _date, _date_fract, (int)_current_company, tile, p1, p2, cmd & ~CMD_NETWORK_COMMAND, text, GetCommandName(cmd));
/* Actually try and execute the command. If no cost-type is given
* use the construction one */
BasePersistentStorageArray::SwitchMode(PSM_ENTER_COMMAND);
CommandCost res2 = proc(tile, flags | DC_EXEC, p1, p2, text);
BasePersistentStorageArray::SwitchMode(PSM_LEAVE_COMMAND);
if (cmd_id == CMD_COMPANY_CTRL) {
cur_company.Trash();
/* We are a new company -> Switch to new local company.
* We were closed down -> Switch to spectator
* Some other company opened/closed down -> The outside function will switch back */
@@ -728,23 +730,23 @@ CommandCost DoCommandPInternal(TileIndex
* return of the command. Otherwise we can check whether the
* test and execution have yielded the same result,
* i.e. cost and error state are the same. */
if (!test_and_exec_can_differ) {
assert(res.GetCost() == res2.GetCost() && res.Failed() == res2.Failed()); // sanity check
} else if (res2.Failed()) {
return_dcpi(res2, false);
return_dcpi(res2);
/* If we're needing more money and we haven't done
* anything yet, ask for the money! */
if (_additional_cash_required != 0 && res2.GetCost() == 0) {
/* It could happen we removed rail, thus gained money, and deleted something else.
* So make sure the signal buffer is empty even in this case */
UpdateSignalsInBuffer();
SetDParam(0, _additional_cash_required);
return_dcpi(CommandCost(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY), false);
return_dcpi(CommandCost(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY));
/* update last build coordinate of company. */
if (tile != 0) {
Company *c = Company::GetIfValid(_current_company);
if (c != NULL) c->last_build_coordinate = tile;
@@ -752,13 +754,13 @@ CommandCost DoCommandPInternal(TileIndex
SubtractMoneyFromCompany(res2);
/* update signals if needed */
return_dcpi(res2, true);
#undef return_dcpi
* Adds the cost of the given command return value to this cost.
@@ -102,12 +102,14 @@ static void _GenerateWorld(void *)
/* Set the Random() seed to generation_seed so we produce the same map with the same seed */
if (_settings_game.game_creation.generation_seed == GENERATE_NEW_SEED) _settings_game.game_creation.generation_seed = _settings_newgame.game_creation.generation_seed = InteractiveRandom();
_random.SetSeed(_settings_game.game_creation.generation_seed);
SetGeneratingWorldProgress(GWP_MAP_INIT, 2);
SetObjectToPlace(SPR_CURSOR_ZZZ, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0);
BasePersistentStorageArray::SwitchMode(PSM_ENTER_GAMELOOP);
IncreaseGeneratingWorldProgress(GWP_MAP_INIT);
/* Must start economy early because of the costs. */
StartupEconomy();
/* Don't generate landscape items when in the scenario editor. */
if (_gw.mode == GWM_EMPTY) {
@@ -138,14 +140,12 @@ static void _GenerateWorld(void *)
GenerateIndustries();
GenerateObjects();
GenerateTrees();
ClearPersistentStorageChanges(true);
/* These are probably pointless when inside the scenario editor. */
SetGeneratingWorldProgress(GWP_GAME_INIT, 3);
StartupCompanies();
IncreaseGeneratingWorldProgress(GWP_GAME_INIT);
StartupEngines();
@@ -176,12 +176,14 @@ static void _GenerateWorld(void *)
_generating_world = false;
BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP);
ResetObjectToPlace();
_cur_company.Trash();
_current_company = _local_company = _gw.lc;
SetGeneratingWorldProgress(GWP_GAME_START, 1);
/* Call any callback */
@@ -199,12 +201,13 @@ static void _GenerateWorld(void *)
if (_debug_desync_level > 0) {
char name[MAX_PATH];
snprintf(name, lengthof(name), "dmp_cmds_%08x_%08x.sav", _settings_game.game_creation.generation_seed, _date);
SaveOrLoad(name, SL_SAVE, AUTOSAVE_DIR, false);
} catch (...) {
BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP, true);
if (_cur_company.IsValid()) _cur_company.Restore();
_modal_progress_work_mutex->EndCritical();
throw;
@@ -19,12 +19,16 @@
PersistentStoragePool _persistent_storage_pool("PersistentStorage");
INSTANTIATE_POOL_METHODS(PersistentStorage)
/** The changed storage arrays */
static std::set<BasePersistentStorageArray*> *_changed_storage_arrays = new std::set<BasePersistentStorageArray*>;
bool BasePersistentStorageArray::gameloop;
bool BasePersistentStorageArray::command;
bool BasePersistentStorageArray::testmode;
* Remove references to use.
BasePersistentStorageArray::~BasePersistentStorageArray()
{
_changed_storage_arrays->erase(this);
@@ -39,28 +43,57 @@ BasePersistentStorageArray::~BasePersist
void AddChangedPersistentStorage(BasePersistentStorageArray *storage)
_changed_storage_arrays->insert(storage);
* Clear the changes made since the last #ClearStorageChanges.
* This is done for *all* storages that have been registered to with
* #AddChangedStorage since the previous #ClearStorageChanges.
* Clear temporary changes made since the last call to SwitchMode, and
* set whether subsequent changes shall be persistent or temporary.
* This can be done in two ways:
* - saving the changes permanently
* - reverting to the previous version
* @param keep_changes do we save or revert the changes since the last #ClearChanges?
* @param mode Mode switch affecting temporary/persistent changes.
* @param ignore_prev_mode Disable some sanity checks for exceptional call circumstances.
void ClearPersistentStorageChanges(bool keep_changes)
/* static */ void BasePersistentStorageArray::SwitchMode(PersistentStorageMode mode, bool ignore_prev_mode)
/* Loop over all changes arrays */
for (std::set<BasePersistentStorageArray*>::iterator it = _changed_storage_arrays->begin(); it != _changed_storage_arrays->end(); it++) {
if (!keep_changes) {
DEBUG(desync, 1, "Discarding persistent storage changes: Feature %d, GrfID %08X, Tile %d", (*it)->feature, BSWAP32((*it)->grfid), (*it)->tile);
(*it)->ClearChanges(keep_changes);
switch (mode) {
case PSM_ENTER_GAMELOOP:
assert(ignore_prev_mode || !gameloop);
assert(!command && !testmode);
gameloop = true;
break;
case PSM_LEAVE_GAMELOOP:
assert(ignore_prev_mode || gameloop);
gameloop = false;
case PSM_ENTER_COMMAND:
assert((ignore_prev_mode || !command) && !testmode);
command = true;
case PSM_LEAVE_COMMAND:
assert(ignore_prev_mode || command);
command = false;
case PSM_ENTER_TESTMODE:
assert(!command && (ignore_prev_mode || !testmode));
testmode = true;
case PSM_LEAVE_TESTMODE:
assert(ignore_prev_mode || testmode);
testmode = false;
default: NOT_REACHED();
/* And then clear that array */
/* Discard all temporary changes */
(*it)->ClearChanges();
_changed_storage_arrays->clear();
@@ -13,30 +13,52 @@
#define NEWGRF_STORAGE_H
#include "core/pool_type.hpp"
#include "tile_type.h"
* Mode switches to the behaviour of persistent storage array.
enum PersistentStorageMode {
PSM_ENTER_GAMELOOP, ///< Enter the gameloop, changes will be permanent.
PSM_LEAVE_GAMELOOP, ///< Leave the gameloop, changes will be temporary.
PSM_ENTER_COMMAND, ///< Enter command scope, changes will be permanent.
PSM_LEAVE_COMMAND, ///< Leave command scope, revert to previous mode.
PSM_ENTER_TESTMODE, ///< Enter command test mode, changes will be tempoary.
PSM_LEAVE_TESTMODE, ///< Leave command test mode, revert to previous mode.
};
* Base class for all persistent NewGRF storage arrays. Nothing fancy, only here
* so we have a generalised access to the virtual methods.
struct BasePersistentStorageArray {
uint32 grfid; ///< GRFID associated to this persistent storage. A value of zero means "default".
byte feature; ///< NOSAVE: Used to identify in the owner of the array in debug output.
TileIndex tile; ///< NOSAVE: Used to identify in the owner of the array in debug output.
virtual ~BasePersistentStorageArray();
static void SwitchMode(PersistentStorageMode mode, bool ignore_prev_mode = false);
protected:
* Clear the changes made since the last #ClearChanges.
* Discard temporary changes.
virtual void ClearChanges(bool keep_changes) = 0;
virtual void ClearChanges() = 0;
* Check whether currently changes to the storage shall be persistent or
* temporary till the next call to ClearChanges().
static bool AreChangesPersistent() { return (gameloop || command) && !testmode; }
private:
static bool gameloop;
static bool command;
static bool testmode;
* Class for persistent storage of data.
* On #ClearChanges that data is either reverted or saved.
* @tparam TYPE the type of variable to store.
@@ -79,13 +101,15 @@ struct PersistentStorageArray : BasePers
/* The value hasn't changed, so we pretend nothing happened.
* Saves a few cycles and such and it's pretty easy to check. */
if (this->storage[pos] == value) return;
/* We do not have made a backup; lets do so */
if (this->prev_storage == NULL) {
if (AreChangesPersistent()) {
assert(this->prev_storage == NULL);
} else if (this->prev_storage == NULL) {
this->prev_storage = MallocT<TYPE>(SIZE);
memcpy(this->prev_storage, this->storage, sizeof(this->storage));
/* We only need to register ourselves when we made the backup
* as that is the only time something will have changed */
AddChangedPersistentStorage(this);
@@ -104,25 +128,19 @@ struct PersistentStorageArray : BasePers
/* Out of the scope of the array */
if (pos >= SIZE) return 0;
return this->storage[pos];
* Clear the changes, or assign them permanently to the storage.
* @param keep_changes Whether to assign or ditch the changes.
void ClearChanges(bool keep_changes)
void ClearChanges()
assert(this->prev_storage != NULL);
if (this->prev_storage != NULL) {
memcpy(this->storage, this->prev_storage, sizeof(this->storage));
free(this->prev_storage);
this->prev_storage = NULL;
* Class for temporary storage of data.
@@ -186,14 +204,12 @@ struct TemporaryStorageArray {
this->init_key = 1;
void AddChangedPersistentStorage(BasePersistentStorageArray *storage);
void ClearPersistentStorageChanges(bool keep_changes);
typedef PersistentStorageArray<int32, 16> OldPersistentStorage;
typedef uint32 PersistentStorageID;
struct PersistentStorage;
@@ -1352,21 +1352,20 @@ void StateGameLoop()
CallWindowTickEvent();
return;
if (HasModalProgress()) return;
Layouter::ReduceLineCache();
if (_game_mode == GM_EDITOR) {
RunTileLoop();
CallVehicleTicks();
CallLandscapeTick();
UpdateLandscapingLimits();
NewsLoop();
} else {
if (_debug_desync_level > 2 && _date_fract == 0 && (_date & 0x1F) == 0) {
@@ -1379,18 +1378,19 @@ void StateGameLoop()
CheckCaches();
/* All these actions has to be done from OWNER_NONE
* for multiplayer compatibility */
Backup<CompanyByte> cur_company(_current_company, OWNER_NONE, FILE_LINE);
AnimateAnimatedTiles();
IncreaseDate();
#ifndef DEBUG_DUMP_COMMANDS
AI::GameLoop();
Game::GameLoop();
@@ -36,12 +36,13 @@ static void Load_PSAC()
static void Save_PSAC()
PersistentStorage *ps;
/* Write the industries */
FOR_ALL_STORAGES(ps) {
ps->ClearChanges();
SlSetArrayIndex(ps->index);
SlObject(ps, _storage_desc);
/** Chunk handler for persistent storages. */
Status change: