The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
map.hpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2010 - 2025
3 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
4 Copyright (C) 2006 - 2009 by Rusty Russell <rusty@rustcorp.com.au>
5 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY.
13
14 See the COPYING file for more details.
15*/
16
17#pragma once
18
19#include "map/location.hpp"
20#include "units/ptr.hpp"
22
23#include <cassert>
24#include <map>
25#include <unordered_map>
26
27//#define DEBUG_UNIT_MAP
28
29/**
30 * Container associating units to locations.
31 *
32 * The indirection location -> underlying_id -> unit ensures that iterators stay valid even if WML
33 * modifies or moves units on the fly. They even stay valid if a unit is erased from the map and
34 * another unit with the same underlying id is inserted in the map. In other words, it is a doubly
35 * indexed ordered map with persistent iterators (that never invalidate).
36 *
37 * @note The unit_map is implemented as 2 maps: an unordered map that stores iterators from the
38 * ordered map of reference-counted pointers to units.
39 *
40 * The unordered map provides O(1) find times. The ordered map ensures ordering of units by
41 * underlying_id. The reference counting is what guarantees the persistent iterators. Storing an
42 * iterator only prevents that dead unit's id-map entry from being recovered.
43 *
44 * @note Preferred usages for tight loops follows:
45 *
46 * Use the std::pair<iterator, bool> format which checks the preconditions and returns false
47 * (second) to indicate failure and no change to the unit_map. True indicates success along with the
48 * new iterator (first).
49 *
50 * Storing the result iterator prevents the old iterator from entering the fallback recovery code.
51 * This is faster than the old methodology of find to check if empty, insert, and then find to check
52 * for success. It is the same method as std::map uses, the C++ way.
53 *
54 * ================================================================================================
55 *
56 * unit_iterator i; //results are stored here
57 *
58 * ADD:
59 * std::pair<unit_iterator, bool> try_add(units->add(loc, unit));
60 * if(try_add.second) { i = try_add.first; }
61 *
62 * MOVE:
63 * std::pair<unit_iterator, bool> try_add(units->move(src, dst));
64 * if(try_add.second) { i = try_add.first; }
65 *
66 * INSERT:
67 * std::pair<unit_iterator, bool> try_add(units->insert(unitp));
68 * if(try_add.second) { i = try_add.first; }
69 *
70 * REPLACE (preferred over erase and then add)
71 * std::pair<unit_iterator, bool> try_add(units->move(loc, unit));
72 * if(try_add.second){ i = try_add.first; }
73 *
74 * ================================================================================================
75 *
76 *
77 * @note The previous implementation was 2 binary tree based maps: a location map pointing to another.
78 * Lookups were O(2*log(N)) and O(log(N)). Order was implicit in the id map chosen as the base.
79 * Persistence was provided by reference counting all iterators collectively and only recovering space
80 * when there were no iterators outstanding. Even 1 iterator being stored caused a leak, because all
81 * space for dead units was not recovered.
82 *
83 * @note Units are owned by the container.
84 *
85 * @note The indirection does not involve map lookups whenever an iterator is dereferenced, it just
86 * causes a pointer indirection. The downside is that incrementing iterators is not O(1).
87 *
88 * @note The code does not involve any magic, so units moved while being iterated upon may be skipped
89 * or visited twice.
90 *
91 * @note Iterators prevent ghost units from being collected, so they should never be stored in data
92 * structures, as it will cause slowdowns!
93 *
94 * @note By popular demand iterators are effectively permanent. They are handles and not iterators.
95 * Any access might cause a full lookup. Keeping iterators around holds onto memory.
96 */
98{
99 /** The pointer to the unit and a reference counter to record the number of extant iteratorspointing to this unit. */
100 struct unit_pod
101 {
103 : unit()
104 , ref_count()
105 {
106 }
107
110 };
111
112 /*
113 * Map of underlying_id to unit and a reference counter. Dead units have a unit pointer equal to nullptr.
114 * The map entry is removed iff the reference counter equals zero and there are no more
115 * iterators pointing to this unit.
116 */
117 typedef std::map<std::size_t, unit_pod> umap;
118
119 /** Map of location to umap iterator. */
120 typedef std::unordered_map<map_location, umap::iterator> lmap;
121
122public:
123 // ~~~ Begin iterator code ~~~
124
126 {
128 typedef unit_map::umap::iterator iterator_type;
130 };
131
133 {
135 typedef unit_map::umap::iterator iterator_type;
136 typedef const unit value_type;
137 };
138
139 template<typename iter_types>
141 {
142 typedef std::forward_iterator_tag iterator_category;
143 typedef int difference_type;
144 typedef typename iter_types::value_type value_type;
145 typedef std::shared_ptr<value_type> pointer;
147 typedef typename iter_types::container_type container_type;
148 typedef typename iter_types::iterator_type iterator_type;
149
151 {
152 dec();
153 }
154
156 : i_()
157 , tank_(nullptr)
158 {
159 }
160
162 : i_(i)
163 , tank_(m)
164 {
165 inc();
166 valid_exit();
167 }
168
170 : i_(that.i_)
171 , tank_(that.tank_)
172 {
173 inc();
174 valid_exit();
175 }
176
178 {
179 if(*this != that) {
180 dec();
181 tank_ = that.tank_;
182 i_ = that.i_;
183 inc();
184 valid_exit();
185 }
186
187 return *this;
188 }
189
194
195 private:
196 /** Constructs an iterator from the location map. */
197 iterator_base(lmap::iterator ui, container_type* m)
198 : i_(ui->second)
199 , tank_(m)
200 {
201 inc();
202 valid_exit();
203 }
204
205 public:
207 {
208 assert(valid());
209 tank_->self_check();
210 return i_->second.unit;
211 }
212
213 /**
214 * This is exactly the same as operator-> but it's slightly more readable, and can replace
215 * &*iter syntax easily.
216 */
218 {
219 return operator->();
220 }
221
223 {
224 tank_->self_check();
225 assert(valid());
226 return *i_->second.unit;
227 }
228
230 {
231 assert(valid_entry());
232 tank_->self_check();
233 iterator_type new_i(i_);
234 do {
235 ++new_i;
236 } while((new_i != the_map().end()) && (!new_i->second.unit));
237 dec();
238 i_ = new_i;
239 inc();
240 valid_exit();
241 return *this;
242 }
243
245 {
246 iterator_base temp(*this);
247 operator++();
248 return temp;
249 }
250
252 {
253 assert(tank_ && i_ != the_map().begin());
254 tank_->self_check();
256 dec();
257 do {
258 --i_;
259 } while(i_ != begin && (!i_->second.unit));
260 inc();
261
262 valid_exit();
263 return *this;
264 }
265
267 {
268 iterator_base temp(*this);
269 operator--();
270 return temp;
271 }
272
273 bool valid() const
274 {
275 return (valid_for_dereference() && i_->second.unit);
276 }
277
278 explicit operator bool() const
279 {
280 return valid();
281 }
282
283 bool operator==(const iterator_base& rhs) const
284 {
285 return (tank_ == rhs.tank_) && (i_ == rhs.i_);
286 }
287
288 bool operator!=(const iterator_base& rhs) const
289 {
290 return !operator==(rhs);
291 }
292
293 template<typename Y>
294 friend struct iterator_base;
295
296 private:
298 {
299 return (tank_ != nullptr) && (i_ != the_map().end());
300 }
301
302 bool valid_entry() const
303 {
304 return ((tank_ != nullptr) && (i_ != the_map().end()));
305 }
306
307 void valid_exit() const
308 {
309 if((tank_ != nullptr) && i_ != the_map().end()) {
310 assert(i_->second.ref_count > 0);
311 }
312 }
313
314 bool valid_ref_count() const
315 {
316 return (tank_ != nullptr) && (i_ != the_map().end());
317 }
318
319 /** Increment the reference counter. */
320 void inc()
321 {
322 if(valid_ref_count()) {
323 ++(i_->second.ref_count);
324 }
325 }
326
327 /**
328 * Decrement the reference counter
329 * Delete the umap entry if the unit is gone and the reference counter is zero.
330 *
331 * @note this deletion will advance i_ to the next umap entry.
332 */
333 void dec()
334 {
335 if(valid_ref_count()) {
336 assert(i_->second.ref_count != 0);
337 if((--(i_->second.ref_count) == 0) && (!i_->second.unit)) {
338 iterator_type old = i_++;
339 tank_->umap_.erase(old);
340 }
341 }
342 }
343
345 {
346 return tank_->umap_;
347 }
348
349 friend class unit_map;
350
351 /** local iterator */
353 /** the unit_map for i_ */
355 };
356
357 // ~~~ End iterator code ~~~
358
359 unit_map();
360 unit_map(const unit_map& that);
361 unit_map& operator=(const unit_map& that);
362
363 ~unit_map();
364 void swap(unit_map& o) noexcept;
365
368
369 // Provided as a convenience, since unit_map used to be an std::map.
372
373 unit_iterator find(std::size_t id);
375
377 {
378 return const_cast<unit_map*>(this)->find(loc);
379 }
380
381 const_unit_iterator find(std::size_t id) const
382 {
383 return const_cast<unit_map*>(this)->find(id);
384 }
385
386 template<typename T>
388 {
389 auto res = find(val);
390 return res != end() ? unit_ptr(res.get_shared_ptr()) : unit_ptr();
391 }
392
393 template<typename T>
394 unit_const_ptr find_unit_ptr(const T& val) const
395 {
396 auto res = find(val);
397 return res != end() ? unit_const_ptr(res.get_shared_ptr()) : unit_const_ptr();
398 }
399
400 unit_iterator find_leader(int side);
401
403 {
404 return const_cast<unit_map*>(this)->find_leader(side);
405 }
406
408
409 std::vector<unit_iterator> find_leaders(int side);
410
411 std::vector<const_unit_iterator> find_leaders(int side) const;
412
413 std::size_t count(const map_location& loc) const
414 {
415 return lmap_.count(loc);
416 }
417
419 {
421 }
422
427
429 {
430 return make_unit_iterator(umap_.end());
431 }
432
434 {
435 return make_const_unit_iterator(umap_.end());
436 }
437
438 std::size_t size() const
439 {
440 return lmap_.size();
441 }
442
443 std::size_t num_iters() const;
444
445 bool empty() const
446 {
447 return lmap_.empty();
448 }
449
450 void clear(bool force = false);
451
452 using umap_retval_pair_t = std::pair<unit_iterator, bool>;
453
454 /**
455 * Adds a copy of unit @a u at location @a l of the map.
456 *
457 * @returns A pair consisting of an iterator pointing to the new unit (or the unit
458 * already occupying that location) and a bool indicating success.
459 *
460 * @note It is 3 times as fast to attempt to insert a unit at @a l and check for
461 * success than it is to verify that the location is empty, insert the unit,
462 * and then check the location for the unit.
463 */
464 umap_retval_pair_t add(const map_location& l, const unit& u);
465
466 /**
467 * Inserts the unit pointed to by @a p into the map.
468 *
469 * If insertion into either the unit or location map fails, all operations are reverted.
470 *
471 * The one oddity is that to facilitate non-invalidating iterators, the list sometimes has
472 * nullptr pointers which should be used when they correspond to uids previously used.
473 *
474 * @returns A pair consisting of an iterator pointing to the new unit (or the unit
475 * already occupying that location) and a bool indicating success.
476 *
477 * @note It is 3 times as fast to attempt to insert a unit at @a l and check for
478 * success than it is to verify that the location is empty, insert the unit,
479 * and then check the location for the unit.
480 *
481 * @note If the unit::underlying_id is already in use, a new one will be generated.
482 * @note The map takes ownership of the pointed object only if the operation succeeds.
483 */
485
486 /**
487 * Moves a unit from location @a src to location @a dst.
488 *
489 * @returns A pair consisting of an iterator pointing to the new unit (or the unit
490 * already occupying that location) and a bool indicating success.
491 *
492 * @note It is 3 times as fast to attempt to insert a unit at @a l and check for
493 * success than it is to verify that the location is empty, insert the unit,
494 * and then check the location for the unit.
495 */
497
498 /**
499 * Works like unit_map::add; but @a l is emptied first, if needed.
500 *
501 * @returns A pair consisting of an iterator pointing to the new unit (or the unit
502 * already occupying that location) and a bool indicating success.
503 */
505
506 /**
507 * Erases the unit at location @a l, if any.
508 * @returns the number of erased units (0 or 1).
509 */
510 std::size_t erase(const map_location& l);
511
512 /**
513 * Erases a unit given by a pointer or an iterator.
514 * @pre The unit is on the map.
515 */
516 template<typename T>
517 std::size_t erase(const T& iter);
518
519 /**
520 * Extracts a unit from the map.
521 * The unit is no longer owned by the map. It can be reinserted later, if needed.
522 */
524
525 /** Checks invariants. For debugging purposes only. Doesn't do anything in non-debug mode. */
526 bool self_check() const;
527
528 /**
529 * Is the unit in the map?
530 *
531 * @pre @p u != @c nullptr
532 *
533 * @param u Pointer to the unit to find.
534 *
535 * @returns True if found, false otherwise.
536 */
537 bool has_unit(const unit* const u) const;
538
539 /** Tests whether a unit exists at the given location. */
540 bool has_unit_at(const map_location& loc) const;
541
542private:
543 umap::iterator begin_core() const;
544
545 bool is_valid(const umap::const_iterator& i) const
546 {
547 return is_found(i) && (i->second.unit != nullptr);
548 }
549
550 bool is_valid(const lmap::const_iterator& i) const
551 {
552 return is_found(i) && (i->second->second.unit != nullptr);
553 }
554
555 bool is_found(const umap::const_iterator& i) const
556 {
557 return i != umap_.end();
558 }
559
560 bool is_found(const lmap::const_iterator& i) const
561 {
562 return i != lmap_.end();
563 }
564
565 template<typename X>
567 {
568 if(!is_found(i)) {
569 return unit_iterator(umap_.end(), this);
570 }
571
572 return unit_iterator(i, this);
573 }
574
575 template<typename X>
577 {
578 if(!is_found(i)) {
579 return const_unit_iterator(umap_.end(), this);
580 }
581
582 return const_unit_iterator(i, this);
583 }
584
585 /**
586 * underlying_id -> unit_pod.
587 * This requires that underlying_id be unique (which is enforced in unit_map::insert).
588 */
589 mutable umap umap_;
590
591 /**
592 * location -> umap::iterator.
593 */
595};
596
597/** Implement non-member swap function for std::swap (calls @ref unit_map::swap). */
598void swap(unit_map& lhs, unit_map& rhs) noexcept;
599
600template<typename T>
601std::size_t unit_map::erase(const T& iter)
602{
603 assert(iter.valid());
604
605 return erase(iter->get_location());
606}
map_location loc
Definition move.cpp:172
ref_counter is a reference counter.
Container associating units to locations.
Definition map.hpp:98
unit_const_ptr find_unit_ptr(const T &val) const
Definition map.hpp:394
umap::iterator begin_core() const
Definition map.cpp:64
std::vector< unit_iterator > find_leaders(int side)
Definition map.cpp:348
unit_iterator end()
Definition map.hpp:428
bool empty() const
Definition map.hpp:445
umap umap_
underlying_id -> unit_pod.
Definition map.hpp:589
bool has_unit_at(const map_location &loc) const
Tests whether a unit exists at the given location.
Definition map.cpp:435
std::pair< unit_iterator, bool > umap_retval_pair_t
Definition map.hpp:452
const_unit_iterator end() const
Definition map.hpp:433
bool self_check() const
Checks invariants.
Definition map.cpp:370
void swap(unit_map &o) noexcept
Definition map.cpp:51
unit_map()
Definition map.cpp:29
iterator_base< const_iter_types > const_unit_iterator
Definition map.hpp:367
const_unit_iterator find(std::size_t id) const
Definition map.hpp:381
void clear(bool force=false)
Definition map.cpp:244
unit_ptr extract(const map_location &loc)
Extracts a unit from the map.
Definition map.cpp:259
unit_iterator iterator
Definition map.hpp:370
std::size_t count(const map_location &loc) const
Definition map.hpp:413
const_unit_iterator const_iterator
Definition map.hpp:371
iterator_base< standard_iter_types > unit_iterator
Definition map.hpp:366
unit_ptr find_unit_ptr(const T &val)
Definition map.hpp:387
const_unit_iterator begin() const
Definition map.hpp:423
const_unit_iterator find(const map_location &loc) const
Definition map.hpp:376
unit_iterator find(std::size_t id)
Definition map.cpp:302
unit_map & operator=(const unit_map &that)
Definition map.cpp:44
umap_retval_pair_t replace(const map_location &l, const unit_ptr &p)
Works like unit_map::add; but l is emptied first, if needed.
Definition map.cpp:216
unit_map::unit_iterator make_unit_iterator(const X &i)
Definition map.hpp:566
unit_iterator begin()
Definition map.hpp:418
std::size_t num_iters() const
Definition map.cpp:224
unit_iterator find_first_leader(int side)
Definition map.cpp:332
lmap lmap_
location -> umap::iterator.
Definition map.hpp:594
bool is_found(const lmap::const_iterator &i) const
Definition map.hpp:560
bool has_unit(const unit *const u) const
Is the unit in the map?
Definition map.cpp:422
std::size_t erase(const map_location &l)
Erases the unit at location l, if any.
Definition map.cpp:289
umap_retval_pair_t add(const map_location &l, const unit &u)
Adds a copy of unit u at location l of the map.
Definition map.cpp:76
std::size_t size() const
Definition map.hpp:438
unit_iterator find_leader(int side)
Definition map.cpp:320
std::unordered_map< map_location, umap::iterator > lmap
Map of location to umap iterator.
Definition map.hpp:120
umap_retval_pair_t insert(const unit_ptr &p)
Inserts the unit pointed to by p into the map.
Definition map.cpp:135
const_unit_iterator find_leader(int side) const
Definition map.hpp:402
bool is_valid(const lmap::const_iterator &i) const
Definition map.hpp:550
bool is_found(const umap::const_iterator &i) const
Definition map.hpp:555
bool is_valid(const umap::const_iterator &i) const
Definition map.hpp:545
std::map< std::size_t, unit_pod > umap
Definition map.hpp:117
umap_retval_pair_t move(const map_location &src, const map_location &dst)
Moves a unit from location src to location dst.
Definition map.cpp:92
~unit_map()
Definition map.cpp:59
unit_map::const_unit_iterator make_const_unit_iterator(const X &i) const
Definition map.hpp:576
This class represents a single unit of a specific type.
Definition unit.hpp:39
std::shared_ptr< const unit > unit_const_ptr
Definition ptr.hpp:27
std::shared_ptr< unit > unit_ptr
Definition ptr.hpp:26
rect dst
Location on the final composed sheet.
rect src
Non-transparent portion of the surface to compose.
Encapsulates the map of the game.
Definition location.hpp:46
unit_map const container_type
Definition map.hpp:134
unit_map::umap::iterator iterator_type
Definition map.hpp:135
std::forward_iterator_tag iterator_category
Definition map.hpp:142
bool valid() const
Definition map.hpp:273
iter_types::value_type value_type
Definition map.hpp:144
iterator_base(lmap::iterator ui, container_type *m)
Constructs an iterator from the location map.
Definition map.hpp:197
iterator_base & operator=(const iterator_base &that)
Definition map.hpp:177
iterator_base operator--(int)
Definition map.hpp:266
unit_map::umap & the_map() const
Definition map.hpp:344
iterator_base(const iterator_base &that)
Definition map.hpp:169
bool valid_for_dereference() const
Definition map.hpp:297
std::shared_ptr< value_type > pointer
Definition map.hpp:145
reference operator*() const
Definition map.hpp:222
bool operator==(const iterator_base &rhs) const
Definition map.hpp:283
iterator_base(iterator_type i, container_type *m)
Definition map.hpp:161
iter_types::container_type container_type
Definition map.hpp:147
pointer operator->() const
Definition map.hpp:206
iterator_base & operator--()
Definition map.hpp:251
bool operator!=(const iterator_base &rhs) const
Definition map.hpp:288
iter_types::iterator_type iterator_type
Definition map.hpp:148
bool valid_entry() const
Definition map.hpp:302
iterator_base operator++(int)
Definition map.hpp:244
iterator_type i_
local iterator
Definition map.hpp:352
void valid_exit() const
Definition map.hpp:307
void dec()
Decrement the reference counter Delete the umap entry if the unit is gone and the reference counter i...
Definition map.hpp:333
value_type & reference
Definition map.hpp:146
iterator_base & operator++()
Definition map.hpp:229
container_type * tank_
the unit_map for i_
Definition map.hpp:354
pointer get_shared_ptr() const
This is exactly the same as operator-> but it's slightly more readable, and can replace &*iter syntax...
Definition map.hpp:217
void inc()
Increment the reference counter.
Definition map.hpp:320
bool valid_ref_count() const
Definition map.hpp:314
unit_map::umap::iterator iterator_type
Definition map.hpp:128
The pointer to the unit and a reference counter to record the number of extant iteratorspointing to t...
Definition map.hpp:101
n_ref_counter::ref_counter< signed int > ref_count
Definition map.hpp:109
unit_ptr unit
Definition map.hpp:108
mock_party p
void swap(unit_map &lhs, unit_map &rhs) noexcept
Implement non-member swap function for std::swap (calls unit_map::swap).
Definition map.cpp:440
#define i