mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
std::is_pod is deprecated in C++20 and as such triggers -Wdeprecated-declarations when built with C++20, "is_standard_layout && is_trivial" is the equivalent, so migrate to that. While at it replace runtime dispatch with compile time and reduce the required trait for memcpy to what is really needed. Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
412 lines
8.9 KiB
C++
412 lines
8.9 KiB
C++
/* Copyright (C) 2025 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifndef INCLUDED_GRID
|
|
#define INCLUDED_GRID
|
|
|
|
#include "lib/code_annotation.h"
|
|
#include "lib/code_generation.h"
|
|
#include "lib/debug.h"
|
|
#include "lib/types.h"
|
|
#include "simulation2/serialization/SerializeTemplates.h"
|
|
#include "simulation2/system/Component.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <new>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#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<typename T>
|
|
class Grid
|
|
{
|
|
friend struct SerializeHelper<Grid<T>>;
|
|
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.
|
|
Grid& operator=(const Grid& g)
|
|
{
|
|
if (this == &g)
|
|
return *this;
|
|
|
|
if (m_W == g.m_W && m_H == g.m_H)
|
|
{
|
|
if constexpr (std::is_trivially_copyable_v<T>)
|
|
memcpy(m_Data, g.m_Data, m_W*m_H*sizeof(T));
|
|
else
|
|
std::copy(g.m_Data, g.m_Data + m_H*m_W, &m_Data[0]);
|
|
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];
|
|
if constexpr (std::is_trivially_copyable_v<T>)
|
|
memcpy(m_Data, g.m_Data, m_W*m_H*sizeof(T));
|
|
else
|
|
std::copy(g.m_Data, g.m_Data + m_H*m_W, &m_Data[0]);
|
|
}
|
|
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 operator==(const Grid& g) const
|
|
{
|
|
if (!compare_sizes(&g))
|
|
return false;
|
|
|
|
if constexpr (std::is_standard_layout_v<T> && std::is_trivial_v<T>)
|
|
return memcmp(m_Data, g.m_Data, m_W*m_H*sizeof(T)) == 0;
|
|
else
|
|
return std::equal(&m_Data[0], &m_Data[m_W*m_H], g.m_Data);
|
|
}
|
|
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 i0, int j0, int i1, int j1) const
|
|
{
|
|
if constexpr (std::is_standard_layout_v<T> && std::is_trivial_v<T>)
|
|
{
|
|
#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;
|
|
}
|
|
else
|
|
{
|
|
static_assert(!std::is_same<T, T>::value, "Not implemented.");
|
|
return false; // Fix warnings.
|
|
}
|
|
}
|
|
|
|
// Reset the data to its default-constructed value (usually 0), not changing size.
|
|
void reset()
|
|
{
|
|
if (m_Data) {
|
|
if constexpr (std::is_standard_layout_v<T> && std::is_trivial_v<T>)
|
|
memset(m_Data, 0, m_W*m_H*sizeof(T));
|
|
else
|
|
std::fill(&m_Data[0], &m_Data[m_H*m_W], T{});
|
|
}
|
|
}
|
|
|
|
// 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<u16, u16> coords) { return get(coords.first, coords.second); }
|
|
T& get(std::pair<u16, u16> coords) { return get(coords.first, coords.second); }
|
|
|
|
T& operator[](std::pair<u16, u16> coords) const { return get(coords.first, coords.second); }
|
|
T& get(std::pair<u16, u16> 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<typename U>
|
|
bool compare_sizes(const Grid<U>* 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<typename T>
|
|
struct SerializeHelper<Grid<T>>
|
|
{
|
|
void operator()(ISerializer& serialize, const char* name, Grid<T>& 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<T>& 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<typename T>
|
|
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<u8> 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
|