summaryrefslogtreecommitdiff
path: root/src/newgrf_storage.h
blob: 819e8d9394a8195c323dc067c583419da3da99be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/*
 * This file is part of OpenTTD.
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 */

/** @file newgrf_storage.h Functionality related to the temporary and persistent storage arrays for NewGRFs. */

#ifndef NEWGRF_STORAGE_H
#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 temporary.
	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:
	/**
	 * Discard temporary changes.
	 */
	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.
 * @tparam SIZE the size of the array.
 */
template <typename TYPE, uint SIZE>
struct PersistentStorageArray : BasePersistentStorageArray {
	TYPE storage[SIZE]; ///< Memory to for the storage array
	TYPE *prev_storage; ///< Memory to store "old" states so we can revert them on the performance of test cases for commands etc.

	/** Simply construct the array */
	PersistentStorageArray() : prev_storage(nullptr)
	{
		memset(this->storage, 0, sizeof(this->storage));
	}

	/** And free all data related to it */
	~PersistentStorageArray()
	{
		free(this->prev_storage);
	}

	/** Resets all values to zero. */
	void ResetToZero()
	{
		memset(this->storage, 0, sizeof(this->storage));
	}

	/**
	 * Stores some value at a given position.
	 * If there is no backup of the data that backup is made and then
	 * we write the data.
	 * @param pos   the position to write at
	 * @param value the value to write
	 */
	void StoreValue(uint pos, int32 value)
	{
		/* Out of the scope of the array */
		if (pos >= SIZE) return;

		/* 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 (AreChangesPersistent()) {
			assert(this->prev_storage == nullptr);
		} else if (this->prev_storage == nullptr) {
			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);
		}

		this->storage[pos] = value;
	}

	/**
	 * Gets the value from a given position.
	 * @param pos the position to get the data from
	 * @return the data from that position
	 */
	TYPE GetValue(uint pos) const
	{
		/* Out of the scope of the array */
		if (pos >= SIZE) return 0;

		return this->storage[pos];
	}

	void ClearChanges()
	{
		if (this->prev_storage != nullptr) {
			memcpy(this->storage, this->prev_storage, sizeof(this->storage));
			free(this->prev_storage);
			this->prev_storage = nullptr;
		}
	}
};


/**
 * Class for temporary storage of data.
 * On #ClearChanges that data is always zero-ed.
 * @tparam TYPE the type of variable to store.
 * @tparam SIZE the size of the array.
 */
template <typename TYPE, uint SIZE>
struct TemporaryStorageArray {
	TYPE storage[SIZE]; ///< Memory to for the storage array
	uint16 init[SIZE];  ///< Storage has been assigned, if this equals 'init_key'.
	uint16 init_key;    ///< Magic key to 'init'.

	/** Simply construct the array */
	TemporaryStorageArray()
	{
		memset(this->storage, 0, sizeof(this->storage)); // not exactly needed, but makes code analysers happy
		memset(this->init, 0, sizeof(this->init));
		this->init_key = 1;
	}

	/**
	 * Stores some value at a given position.
	 * @param pos   the position to write at
	 * @param value the value to write
	 */
	void StoreValue(uint pos, int32 value)
	{
		/* Out of the scope of the array */
		if (pos >= SIZE) return;

		this->storage[pos] = value;
		this->init[pos] = this->init_key;
	}

	/**
	 * Gets the value from a given position.
	 * @param pos the position to get the data from
	 * @return the data from that position
	 */
	TYPE GetValue(uint pos) const
	{
		/* Out of the scope of the array */
		if (pos >= SIZE) return 0;

		if (this->init[pos] != this->init_key) {
			/* Unassigned since last call to ClearChanges */
			return 0;
		}

		return this->storage[pos];
	}

	void ClearChanges()
	{
		/* Increment init_key to invalidate all storage */
		this->init_key++;
		if (this->init_key == 0) {
			/* When init_key wraps around, we need to reset everything */
			memset(this->init, 0, sizeof(this->init));
			this->init_key = 1;
		}
	}
};

void AddChangedPersistentStorage(BasePersistentStorageArray *storage);

typedef PersistentStorageArray<int32, 16> OldPersistentStorage;

typedef uint32 PersistentStorageID;

struct PersistentStorage;
typedef Pool<PersistentStorage, PersistentStorageID, 1, 0xFF000> PersistentStoragePool;

extern PersistentStoragePool _persistent_storage_pool;

/**
 * Class for pooled persistent storage of data.
 */
struct PersistentStorage : PersistentStorageArray<int32, 256>, PersistentStoragePool::PoolItem<&_persistent_storage_pool> {
	/** We don't want GCC to zero our struct! It already is zeroed and has an index! */
	PersistentStorage(const uint32 new_grfid, byte feature, TileIndex tile)
	{
		this->grfid = new_grfid;
		this->feature = feature;
		this->tile = tile;
	}
};

static_assert(cpp_lengthof(OldPersistentStorage, storage) <= cpp_lengthof(PersistentStorage, storage));

#endif /* NEWGRF_STORAGE_H */