Skip to content

Commit fec30e3

Browse files
authored
Fix AreaStore's IDs persistence (#8888)
Improve documentation Read old formats Fix free ID function. Return first gap in map
1 parent 5fa614d commit fec30e3

File tree

6 files changed

+90
-42
lines changed

6 files changed

+90
-42
lines changed

builtin/game/features.lua

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ core.features = {
1414
object_independent_selectionbox = true,
1515
httpfetch_binary_data = true,
1616
formspec_version_element = true,
17+
area_store_persistent_ids = true,
1718
}
1819

1920
function core.has_feature(arg)

doc/lua_api.txt

+28-23
Original file line numberDiff line numberDiff line change
@@ -3804,6 +3804,8 @@ Utilities
38043804
httpfetch_binary_data = true,
38053805
-- Whether formspec_version[<version>] may be used (5.1.0)
38063806
formspec_version_element = true,
3807+
-- Whether AreaStore's IDs are kept on save/load (5.1.0)
3808+
area_store_persistent_ids = true,
38073809
}
38083810

38093811
* `minetest.has_feature(arg)`: returns `boolean, missing_features`
@@ -5197,35 +5199,38 @@ A fast access data structure to store areas, and find areas near a given
51975199
position or area.
51985200
Every area has a `data` string attribute to store additional information.
51995201
You can create an empty `AreaStore` by calling `AreaStore()`, or
5200-
`AreaStore(type_name)`.
5202+
`AreaStore(type_name)`. The mod decides where to save and load AreaStore.
52015203
If you chose the parameter-less constructor, a fast implementation will be
52025204
automatically chosen for you.
52035205

52045206
### Methods
52055207

5206-
* `get_area(id, include_borders, include_data)`: returns the area with the id
5207-
`id`.
5208-
(optional) Boolean values `include_borders` and `include_data` control what's
5209-
copied.
5210-
Returns nil if specified area id does not exist.
5211-
* `get_areas_for_pos(pos, include_borders, include_data)`: returns all areas
5212-
that contain the position `pos`.
5213-
(optional) Boolean values `include_borders` and `include_data` control what's
5214-
copied.
5215-
* `get_areas_in_area(edge1, edge2, accept_overlap, include_borders, include_data)`:
5216-
returns all areas that contain all nodes inside the area specified by `edge1`
5217-
and `edge2` (inclusive).
5218-
If `accept_overlap` is true, also areas are returned that have nodes in
5219-
common with the specified area.
5220-
(optional) Boolean values `include_borders` and `include_data` control what's
5221-
copied.
5208+
* `get_area(id, include_borders, include_data)`
5209+
* Returns the area information about the specified ID.
5210+
* Returned values are either of these:
5211+
5212+
nil -- Area not found
5213+
true -- Without `include_borders` and `include_data`
5214+
{
5215+
min = pos, max = pos -- `include_borders == true`
5216+
data = string -- `include_data == true`
5217+
}
5218+
5219+
* `get_areas_for_pos(pos, include_borders, include_data)`
5220+
* Returns all areas as table, indexed by the area ID.
5221+
* Table values: see `get_area`.
5222+
* `get_areas_in_area(edge1, edge2, accept_overlap, include_borders, include_data)`
5223+
* Returns all areas that contain all nodes inside the area specified by `edge1`
5224+
and `edge2` (inclusive).
5225+
* `accept_overlap`: if `true`, areas are returned that have nodes in
5226+
common (intersect) with the specified area.
5227+
* Returns the same values as `get_areas_for_pos`.
52225228
* `insert_area(edge1, edge2, data, [id])`: inserts an area into the store.
5223-
Returns the new area's ID, or nil if the insertion failed.
5224-
The (inclusive) positions `edge1` and `edge2` describe the area.
5225-
`data` is a string stored with the area. If passed, `id` will be used as the
5226-
internal area ID, it must be a unique number between 0 and 2^32-2. If you use
5227-
the `id` parameter you must always use it, or insertions are likely to fail
5228-
due to conflicts.
5229+
* Returns the new area's ID, or nil if the insertion failed.
5230+
* The (inclusive) positions `edge1` and `edge2` describe the area.
5231+
* `data` is a string stored with the area.
5232+
* `id` (optional): will be used as the internal area ID if it is an unique
5233+
number between 0 and 2^32-2.
52295234
* `reserve(count)`: reserves resources for at most `count` many contained
52305235
areas.
52315236
Only needed for efficiency, and only some implementations profit.

src/script/lua_api/l_areastore.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ int LuaAreaStore::l_insert_area(lua_State *L)
185185
if (lua_isnumber(L, 5))
186186
a.id = lua_tonumber(L, 5);
187187

188+
// Insert & assign a new ID if necessary
188189
if (!ast->insertArea(&a))
189190
return 0;
190191

src/unittest/test_areastore.cpp

+20-9
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,11 @@ void TestAreaStore::testSerialization()
128128
VectorAreaStore store;
129129

130130
Area a(v3s16(-1, 0, 1), v3s16(0, 1, 2));
131-
a.data = "Area A";
131+
a.data = "Area AA";
132132
store.insertArea(&a);
133133

134134
Area b(v3s16(123, 456, 789), v3s16(32000, 100, 10));
135-
b.data = "Area B";
135+
b.data = "Area BB";
136136
store.insertArea(&b);
137137

138138
std::ostringstream os;
@@ -143,20 +143,31 @@ void TestAreaStore::testSerialization()
143143
"\x00\x02" // Count
144144
"\xFF\xFF\x00\x00\x00\x01" // Area A min edge
145145
"\x00\x00\x00\x01\x00\x02" // Area A max edge
146-
"\x00\x06" // Area A data length
147-
"Area A" // Area A data
146+
"\x00\x07" // Area A data length
147+
"Area AA" // Area A data
148148
"\x00\x7B\x00\x64\x00\x0A" // Area B min edge (last two swapped with max edge for sorting)
149149
"\x7D\x00\x01\xC8\x03\x15" // Area B max edge (^)
150-
"\x00\x06" // Area B data length
151-
"Area B", // Area B data
150+
"\x00\x07" // Area B data length
151+
"Area BB" // Area B data
152+
"\x00\x00\x00\x00" // ID A = 0
153+
"\x00\x00\x00\x01", // ID B = 1
152154
1 + 2 +
153-
6 + 6 + 2 + 6 +
154-
6 + 6 + 2 + 6);
155+
(6 + 6 + 2 + 7) * 2 + // min/max edge, length, data
156+
2 * 4); // Area IDs
157+
155158
UASSERTEQ(const std::string &, str, str_wanted);
156159

157160
std::istringstream is(str);
158161
store.deserialize(is);
159162

160-
UASSERTEQ(size_t, store.size(), 4); // deserialize() doesn't clear the store
163+
// deserialize() doesn't clear the store
164+
// But existing IDs are overridden
165+
UASSERTEQ(size_t, store.size(), 2);
166+
167+
Area c(v3s16(33, -2, -6), v3s16(4, 77, -76));
168+
c.data = "Area CC";
169+
store.insertArea(&c);
170+
171+
UASSERTEQ(u32, c.id, 2);
161172
}
162173

src/util/areastore.cpp

+35-3
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ const Area *AreaStore::getArea(u32 id) const
6464

6565
void AreaStore::serialize(std::ostream &os) const
6666
{
67+
// WARNING:
68+
// Before 5.1.0-dev: version != 0 throws SerializationError
69+
// After 5.1.0-dev: version >= 5 throws SerializationError
70+
// Forwards-compatibility is assumed before version 5.
71+
6772
writeU8(os, 0); // Serialisation version
6873

6974
// TODO: Compression?
@@ -75,27 +80,41 @@ void AreaStore::serialize(std::ostream &os) const
7580
writeU16(os, a.data.size());
7681
os.write(a.data.data(), a.data.size());
7782
}
83+
84+
// Serialize IDs
85+
for (const auto &it : areas_map)
86+
writeU32(os, it.second.id);
7887
}
7988

8089
void AreaStore::deserialize(std::istream &is)
8190
{
8291
u8 ver = readU8(is);
83-
if (ver != 0)
92+
// Assume forwards-compatibility before version 5
93+
if (ver >= 5)
8494
throw SerializationError("Unknown AreaStore "
8595
"serialization version!");
8696

8797
u16 num_areas = readU16(is);
98+
std::vector<Area> areas;
8899
for (u32 i = 0; i < num_areas; ++i) {
89-
Area a;
100+
Area a(U32_MAX);
90101
a.minedge = readV3S16(is);
91102
a.maxedge = readV3S16(is);
92103
u16 data_len = readU16(is);
93104
char *data = new char[data_len];
94105
is.read(data, data_len);
95106
a.data = std::string(data, data_len);
96-
insertArea(&a);
107+
areas.emplace_back(a);
97108
delete [] data;
98109
}
110+
111+
bool read_ids = is.good(); // EOF for old formats
112+
113+
for (auto &area : areas) {
114+
if (read_ids)
115+
area.id = readU32(is);
116+
insertArea(&area);
117+
}
99118
}
100119

101120
void AreaStore::invalidateCache()
@@ -105,6 +124,19 @@ void AreaStore::invalidateCache()
105124
}
106125
}
107126

127+
u32 AreaStore::getNextId() const
128+
{
129+
u32 free_id = 0;
130+
for (const auto &area : areas_map) {
131+
if (area.first > free_id)
132+
return free_id; // Found gap
133+
134+
free_id = area.first + 1;
135+
}
136+
// End of map
137+
return free_id;
138+
}
139+
108140
void AreaStore::setCacheParams(bool enabled, u8 block_radius, size_t limit)
109141
{
110142
m_cache_enabled = enabled;

src/util/areastore.h

+5-7
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
3737

3838

3939
struct Area {
40-
Area() = default;
40+
Area(u32 area_id) : id(area_id) {}
4141

42-
Area(const v3s16 &mine, const v3s16 &maxe) :
43-
minedge(mine), maxedge(maxe)
42+
Area(const v3s16 &mine, const v3s16 &maxe, u32 area_id = U32_MAX) :
43+
id(area_id), minedge(mine), maxedge(maxe)
4444
{
4545
sortBoxVerticies(minedge, maxedge);
4646
}
4747

48-
u32 id = U32_MAX;
48+
u32 id;
4949
v3s16 minedge, maxedge;
5050
std::string data;
5151
};
@@ -109,7 +109,7 @@ class AreaStore {
109109
virtual void getAreasForPosImpl(std::vector<Area *> *result, v3s16 pos) = 0;
110110

111111
/// Returns the next area ID and increments it.
112-
u32 getNextId() { return m_next_id++; }
112+
u32 getNextId() const;
113113

114114
// Note: This can't be an unordered_map, since all
115115
// references would be invalidated on rehash.
@@ -125,8 +125,6 @@ class AreaStore {
125125
/// If you modify this, call invalidateCache()
126126
u8 m_cacheblock_radius = 64;
127127
LRUCache<v3s16, std::vector<Area *> > m_res_cache;
128-
129-
u32 m_next_id = 0;
130128
};
131129

132130

0 commit comments

Comments
 (0)