Changeset View
Changeset View
Standalone View
Standalone View
source/simulation2/helpers/EnumArray.h
- This file was added.
/* Copyright (C) 2020 Wildfire Games. | |||||
* This file is part of 0 A.D. | |||||
Stan: I wonder if something like boost doesn't provide such an array? | |||||
Done Inline ActionsProbably, but I'd rather not depend on boost. I think ICU actually has a rather similar "EnumSet" class. wraitii: Probably, but I'd rather not depend on boost.
I think ICU actually has a rather similar… | |||||
* | |||||
* 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_ENUMARRAY | |||||
#define INCLUDED_ENUMARRAY | |||||
#include "Player.h" | |||||
#include "simulation2/serialization/IDeserializer.h" | |||||
#include "simulation2/serialization/ISerializer.h" | |||||
#include "simulation2/serialization/SerializeTemplates.h" | |||||
/** | |||||
* This class keeps a packed representation of flag/bit values (from Enum) for SIZE members. | |||||
* A typical use-case is LOS storing a boolean or a 2-bit flag per player. | |||||
* A large part of the interface of this class only makes sense if Enum is bitfield-like. | |||||
*/ | |||||
template<typename Enum, size_t SIZE = 8> | |||||
class EnumArray | |||||
{ | |||||
friend class TestEnumArray; | |||||
friend struct SerializeEnumArray; | |||||
friend struct storage_wrapper; | |||||
////////////////////////////////////////////////////////////// | |||||
//// Size requirements & data storage | |||||
////////////////////////////////////////////////////////////// | |||||
public: | |||||
static constexpr u8 bits_per_enum = std::numeric_limits<Enum>::digits; | |||||
static_assert(bits_per_enum <= 8, "Data types bigger than 8 bits are not supported"); | |||||
static_assert(bits_per_enum > 0, "0-bit data need not be stored"); | |||||
static constexpr u16 total_bits = bits_per_enum * SIZE; | |||||
static constexpr u16 bytes = total_bits / CHAR_BIT + ((total_bits % CHAR_BIT) > 0); | |||||
// Determine storage type based on type. | |||||
static_assert(bytes <= 8, "Cannot store more data than 64 bits for now"); | |||||
using storage = \ | |||||
typename std::conditional<bytes <= 1, u8, | |||||
typename std::conditional<bytes <=2, u16, | |||||
typename std::conditional<bytes <= 4, u32, | |||||
u64 | |||||
>::type>::type>::type; | |||||
protected: | |||||
storage m_Storage; | |||||
////////////////////////////////////////////////////////////// | |||||
//// Convenience bit functions | |||||
//// TODO: C++14 these can be constexpr | |||||
////////////////////////////////////////////////////////////// | |||||
protected: | |||||
// Returns 1 for the first 'bits_per_enum' bits, then 0. | |||||
static storage first_bits_mask() | |||||
{ | |||||
static_assert(std::is_scalar<storage>::value, "Not implemented for non-scalar types"); | |||||
u8 r = 0; | |||||
for (u8 i = 0; i < bits_per_enum; i++) | |||||
r |= 1 << i; | |||||
return r; | |||||
} | |||||
// Returns val SIZE times. | |||||
static storage bitmask_all(Enum val) | |||||
{ | |||||
static_assert(std::is_scalar<storage>::value, "Not implemented for non-scalar types"); | |||||
storage r = 0; | |||||
for (u8 i = 0; i < SIZE; i++) | |||||
r |= static_cast<storage>(val) << (i * bits_per_enum); | |||||
return r; | |||||
} | |||||
////////////////////////////////////////////////////////////// | |||||
//// Returned by operator[] to provide write semantics. | |||||
////////////////////////////////////////////////////////////// | |||||
protected: | |||||
class storage_wrapper | |||||
{ | |||||
friend class EnumArray; | |||||
protected: | |||||
EnumArray& data; | |||||
u8 pos; | |||||
storage_wrapper(EnumArray& d, u8 p) : data(d), pos(p) {}; | |||||
public: | |||||
void operator |= (Enum val) { data.or_at(val, pos); } | |||||
void operator &= (Enum val) { data.and_at(val, pos); } | |||||
void unset_bit(Enum val) { data.and_at(Enum(~(u8)val), pos); } | |||||
bool operator=(Enum val) { return data.replace(val, pos); }; | |||||
}; | |||||
////////////////////////////////////////////////////////////// | |||||
//// Constructors & Conversions | |||||
////////////////////////////////////////////////////////////// | |||||
public: | |||||
EnumArray() : m_Storage(0) {} | |||||
EnumArray(Enum val, u8 pos) : m_Storage(0) { or_at(val, pos); } | |||||
protected: | |||||
explicit EnumArray(storage val) : m_Storage(val) {} | |||||
explicit operator storage() { return m_Storage; } | |||||
////////////////////////////////////////////////////////////// | |||||
//// Storage-wise operations | |||||
////////////////////////////////////////////////////////////// | |||||
public: | |||||
EnumArray operator ~() const { return EnumArray(~m_Storage); } | |||||
EnumArray operator |(const EnumArray& o) const { return EnumArray(m_Storage | o.m_Storage); } | |||||
EnumArray operator &(const EnumArray& o) const { return EnumArray(m_Storage & o.m_Storage); } | |||||
void operator &= (const EnumArray& o) { m_Storage &= o.m_Storage; } | |||||
void operator |= (const EnumArray& o) { m_Storage |= o.m_Storage; } | |||||
bool operator==(const EnumArray& o) const { return m_Storage == o.m_Storage; }; | |||||
bool operator!=(const EnumArray& o) const { return m_Storage != o.m_Storage; }; | |||||
bool any(Enum val) { return m_Storage & bitmask_all(val); } | |||||
////////////////////////////////////////////////////////////// | |||||
//// Member-wise operations | |||||
////////////////////////////////////////////////////////////// | |||||
public: | |||||
void or_at(Enum val, u8 pos) | |||||
{ | |||||
static_assert(std::is_scalar<storage>::value, "Not implemented for non-scalar types"); | |||||
if (pos >= SIZE) | |||||
return; | |||||
// We need to set all non-pos bits to 0 to avoid accidentally settings bits for other members. | |||||
// (the shift defaults to 0 so it's fine to do it in the left-op). | |||||
m_Storage |= ((u8)val & first_bits_mask()) << (pos * bits_per_enum); | |||||
} | |||||
void and_at(Enum val, u8 pos) | |||||
{ | |||||
static_assert(std::is_scalar<storage>::value, "Not implemented for non-scalar types"); | |||||
if (pos >= SIZE) | |||||
return; | |||||
// We need to be sure all non-pos bits are 1 so that &= doesn't reset bits for other members. | |||||
m_Storage &= ((u8)val << (pos * bits_per_enum)) | ~(first_bits_mask() << (pos * bits_per_enum)); | |||||
} | |||||
// Returns true if storage was modified. | |||||
bool replace(Enum val, u8 pos) | |||||
{ | |||||
static_assert(std::is_scalar<storage>::value, "Not implemented for non-scalar types"); | |||||
if (pos >= SIZE) | |||||
return false; | |||||
Enum oldval = at(pos); | |||||
// First clear whatever bits we had there | |||||
m_Storage &= ~(first_bits_mask() << (pos * bits_per_enum)); | |||||
// Then set whatever we have now. | |||||
m_Storage |= (u8)val << pos * bits_per_enum; | |||||
return oldval != at(pos); | |||||
} | |||||
Enum at(u8 pos) const | |||||
{ | |||||
static_assert(std::is_scalar<storage>::value, "Not implemented for non-scalar types"); | |||||
return Enum((m_Storage >> (pos * bits_per_enum)) & first_bits_mask()); | |||||
} | |||||
Enum operator[](u8 pos) const | |||||
{ | |||||
return at(pos); | |||||
} | |||||
storage_wrapper operator[](u8 pos) | |||||
{ | |||||
return { *this, pos }; | |||||
} | |||||
////////////////////////////////////////////////////////////// | |||||
//// Other | |||||
////////////////////////////////////////////////////////////// | |||||
public: | |||||
// NB -> not per member, so an EnumArray is truthy if any bit of any member is non-zero. | |||||
explicit operator bool() const { return m_Storage > 0; } | |||||
static EnumArray mask(u8 n) { return EnumArray(first_bits_mask() << (n * bits_per_enum)); } | |||||
static EnumArray all(Enum val) { return EnumArray(bitmask_all(val)); } | |||||
static constexpr size_t size() { return SIZE; } | |||||
}; | |||||
struct SerializeEnumArray | |||||
{ | |||||
template<typename T, size_t N> | |||||
void operator()(ISerializer& serialize, const char* name, const EnumArray<T, N> value) | |||||
{ | |||||
SerializeType(serialize, name, value.m_Storage); | |||||
} | |||||
template<typename T, size_t N> | |||||
void operator()(IDeserializer& deserialize, const char* name, EnumArray<T, N> value) | |||||
{ | |||||
SerializeType(deserialize, name, value.m_Storage); | |||||
} | |||||
}; | |||||
#endif // INCLUDED_ENUMARRAY |
Wildfire Games · Phabricator
I wonder if something like boost doesn't provide such an array?