/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_GRID #define INCLUDED_GRID #include "simulation2/serialization/SerializeTemplates.h" #include #ifdef NDEBUG #define GRID_BOUNDS_DEBUG 0 #else #define GRID_BOUNDS_DEBUG 1 #endif /** * Basic 2D array, intended for storing tile data, plus support for lazy updates * by ICmpObstructionManager. * @c T must be a POD type that can be initialised with 0s. */ template class Grid { friend struct SerializeHelper>; protected: // Tag-dispatching internal utilities for convenience. struct default_type{}; struct is_pod { operator default_type() { return default_type{}; }}; struct is_container { operator default_type() { return default_type{}; }}; // helper to detect value_type template struct has_value_type : std::false_type { }; template struct has_value_type (), 0)> : std::true_type { }; template using if_ = typename std::conditional::type; template using dispatch = if_< std::is_pod, is_pod, if_, is_container, default_type>>; public: Grid() : m_W(0), m_H(0), m_Data(NULL) { } Grid(u16 w, u16 h) : m_W(w), m_H(h), m_Data(NULL) { resize(w, h); } Grid(const Grid& g) : m_W(0), m_H(0), m_Data(NULL) { *this = g; } using value_type = T; public: // Ensure that o and this are the same size before calling. void copy_data(T* o, default_type) { std::copy(o, o + m_H*m_W, &m_Data[0]); } void copy_data(T* o, is_pod) { memcpy(m_Data, o, m_W*m_H*sizeof(T)); } Grid& operator=(const Grid& g) { if (this == &g) return *this; if (m_W == g.m_W && m_H == g.m_H) { copy_data(g.m_Data, dispatch{}); return *this; } m_W = g.m_W; m_H = g.m_H; SAFE_ARRAY_DELETE(m_Data); if (g.m_Data) { m_Data = new T[m_W * m_H]; copy_data(g.m_Data, dispatch{}); } return *this; } void swap(Grid& g) { std::swap(m_Data, g.m_Data); std::swap(m_H, g.m_H); std::swap(m_W, g.m_W); } ~Grid() { SAFE_ARRAY_DELETE(m_Data); } // Ensure that o and this are the same size before calling. bool compare_data(T* o, default_type) const { return std::equal(&m_Data[0], &m_Data[m_W*m_H], o); } bool compare_data(T* o, is_pod) const { return memcmp(m_Data, o, m_W*m_H*sizeof(T)) == 0; } bool operator==(const Grid& g) const { if (!compare_sizes(&g)) return false; return compare_data(g.m_Data, dispatch{}); } bool operator!=(const Grid& g) const { return !(*this==g); } bool blank() const { return m_W == 0 && m_H == 0; } u16 width() const { return m_W; }; u16 height() const { return m_H; }; bool _any_set_in_square(int, int, int, int, default_type) const { static_assert(!std::is_same::value, "Not implemented."); return false; // Fix warnings. } bool _any_set_in_square(int i0, int j0, int i1, int j1, is_pod) const { #if GRID_BOUNDS_DEBUG ENSURE(i0 >= 0 && j0 >= 0 && i1 <= m_W && j1 <= m_H); #endif for (int j = j0; j < j1; ++j) { int sum = 0; for (int i = i0; i < i1; ++i) sum += m_Data[j*m_W + i]; if (sum > 0) return true; } return false; } bool any_set_in_square(int i0, int j0, int i1, int j1) const { return _any_set_in_square(i0, j0, i1, j1, dispatch{}); } void reset_data(default_type) { std::fill(&m_Data[0], &m_Data[m_H*m_W], T{}); } void reset_data(is_pod) { memset(m_Data, 0, m_W*m_H*sizeof(T)); } // Reset the data to its default-constructed value (usually 0), not changing size. void reset() { if (m_Data) reset_data(dispatch{}); } // Clear the grid setting the size to 0 and freeing any data. void clear() { resize(0, 0); } void resize(u16 w, u16 h) { SAFE_ARRAY_DELETE(m_Data); m_W = w; m_H = h; if (!m_W && !m_H) return; m_Data = new T[m_W * m_H]; ENSURE(m_Data); reset(); } // Add two grids of the same size void add(const Grid& g) { #if GRID_BOUNDS_DEBUG ENSURE(g.m_W == m_W && g.m_H == m_H); #endif for (int i=0; i < m_H*m_W; ++i) m_Data[i] += g.m_Data[i]; } void bitwise_or(const Grid& g) { if (this == &g) return; #if GRID_BOUNDS_DEBUG ENSURE(g.m_W == m_W && g.m_H == m_H); #endif for (int i = 0; i < m_H*m_W; ++i) m_Data[i] |= g.m_Data[i]; } void set(int i, int j, const T& value) { #if GRID_BOUNDS_DEBUG ENSURE(0 <= i && i < m_W && 0 <= j && j < m_H); #endif m_Data[j*m_W + i] = value; } T& operator[](std::pair coords) { return get(coords.first, coords.second); } T& get(std::pair coords) { return get(coords.first, coords.second); } T& operator[](std::pair coords) const { return get(coords.first, coords.second); } T& get(std::pair coords) const { return get(coords.first, coords.second); } T& get(int i, int j) { #if GRID_BOUNDS_DEBUG ENSURE(0 <= i && i < m_W && 0 <= j && j < m_H); #endif return m_Data[j*m_W + i]; } T& get(int i, int j) const { #if GRID_BOUNDS_DEBUG ENSURE(0 <= i && i < m_W && 0 <= j && j < m_H); #endif return m_Data[j*m_W + i]; } template bool compare_sizes(const Grid* g) const { return g && m_W == g->m_W && m_H == g->m_H; } u16 m_W, m_H; T* m_Data; }; /** * Serialize a grid, applying a simple RLE compression that is assumed efficient. */ template struct SerializeHelper> { void operator()(ISerializer& serialize, const char* name, Grid& value) { size_t len = value.m_H * value.m_W; serialize.NumberU16_Unbounded("width", value.m_W); serialize.NumberU16_Unbounded("height", value.m_H); if (len == 0) return; u32 count = 1; T prevVal = value.m_Data[0]; for (size_t i = 1; i < len; ++i) { if (prevVal == value.m_Data[i]) { count++; continue; } serialize.NumberU32_Unbounded("#", count); Serializer(serialize, name, prevVal); count = 1; prevVal = value.m_Data[i]; } serialize.NumberU32_Unbounded("#", count); Serializer(serialize, name, prevVal); } void operator()(IDeserializer& deserialize, const char* name, Grid& value) { u16 w, h; deserialize.NumberU16_Unbounded("width", w); deserialize.NumberU16_Unbounded("height", h); u32 len = h * w; value.resize(w, h); for (size_t i = 0; i < len;) { u32 count; deserialize.NumberU32_Unbounded("#", count); T el; Serializer(deserialize, name, el); std::fill(&value.m_Data[i], &value.m_Data[i+count], el); i += count; } } }; /** * Similar to Grid, except optimised for sparse usage (the grid is subdivided into * buckets whose contents are only initialised on demand, to save on memset cost). */ template class SparseGrid { NONCOPYABLE(SparseGrid); enum { BucketBits = 4, BucketSize = 1 << BucketBits }; T* GetBucket(int i, int j) { size_t b = (j >> BucketBits) * m_BW + (i >> BucketBits); if (!m_Data[b]) { m_Data[b] = new T[BucketSize*BucketSize](); } return m_Data[b]; } public: SparseGrid(u16 w, u16 h) : m_W(w), m_H(h), m_DirtyID(0) { ENSURE(m_W && m_H); m_BW = (u16)((m_W + BucketSize-1) >> BucketBits); m_BH = (u16)((m_H + BucketSize-1) >> BucketBits); m_Data = new T*[m_BW*m_BH](); } ~SparseGrid() { reset(); SAFE_ARRAY_DELETE(m_Data); } void reset() { for (size_t i = 0; i < (size_t)(m_BW*m_BH); ++i) SAFE_ARRAY_DELETE(m_Data[i]); // Reset m_Data by value-constructing in place with placement new. m_Data = new (m_Data) T*[m_BW*m_BH](); } void set(int i, int j, const T& value) { #if GRID_BOUNDS_DEBUG ENSURE(0 <= i && i < m_W && 0 <= j && j < m_H); #endif GetBucket(i, j)[(j % BucketSize)*BucketSize + (i % BucketSize)] = value; } T& get(int i, int j) { #if GRID_BOUNDS_DEBUG ENSURE(0 <= i && i < m_W && 0 <= j && j < m_H); #endif return GetBucket(i, j)[(j % BucketSize)*BucketSize + (i % BucketSize)]; } u16 m_W, m_H; u16 m_BW, m_BH; T** m_Data; size_t m_DirtyID; // if this is < the id maintained by ICmpObstructionManager then it needs to be updated }; /** * Structure holding grid dirtiness informations, for clever updates. */ struct GridUpdateInformation { bool dirty; bool globallyDirty; Grid dirtinessGrid; /** * Update the information with additionnal needed updates, then erase the source of additions. * This can usually be optimized through a careful memory management. */ void MergeAndClear(GridUpdateInformation& b) { ENSURE(dirtinessGrid.compare_sizes(&b.dirtinessGrid)); bool wasDirty = dirty; dirty |= b.dirty; globallyDirty |= b.globallyDirty; // If the current grid is useless, swap it if (!wasDirty) dirtinessGrid.swap(b.dirtinessGrid); // If the new grid isn't used, don't bother updating it else if (dirty && !globallyDirty) dirtinessGrid.bitwise_or(b.dirtinessGrid); b.Clean(); } /** * Mark everything as clean */ void Clean() { dirty = false; globallyDirty = false; dirtinessGrid.reset(); } }; #endif // INCLUDED_GRID