Changeset View
Standalone View
source/simulation2/serialization/SerializeTemplates.h
/* Copyright (C) 2016 Wildfire Games. | /* Copyright (C) 2019 Wildfire Games. | ||||
* This file is part of 0 A.D. | * This file is part of 0 A.D. | ||||
* | * | ||||
* 0 A.D. is free software: you can redistribute it and/or modify | * 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 | * it under the terms of the GNU General Public License as published by | ||||
* the Free Software Foundation, either version 2 of the License, or | * the Free Software Foundation, either version 2 of the License, or | ||||
* (at your option) any later version. | * (at your option) any later version. | ||||
* | * | ||||
* 0 A.D. is distributed in the hope that it will be useful, | * 0 A.D. is distributed in the hope that it will be useful, | ||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
* GNU General Public License for more details. | * GNU General Public License for more details. | ||||
* | * | ||||
* You should have received a copy of the GNU General Public License | * 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/>. | * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | ||||
*/ | */ | ||||
#ifndef INCLUDED_SERIALIZETEMPLATES | #ifndef INCLUDED_SERIALIZETEMPLATES | ||||
#define INCLUDED_SERIALIZETEMPLATES | #define INCLUDED_SERIALIZETEMPLATES | ||||
/** | /** | ||||
* @file | * @file | ||||
* Helper templates for serializing/deserializing common objects. | * Helper templates for serializing/deserializing common objects. | ||||
*/ | */ | ||||
#include <utility> | |||||
#include "simulation2/components/ICmpPathfinder.h" | #include "simulation2/components/ICmpPathfinder.h" | ||||
template<typename ELEM> | template<typename ELEM> | ||||
struct SerializeVector | struct SerializeVector | ||||
{ | { | ||||
template<typename T> | template<typename T> | ||||
void operator()(ISerializer& serialize, const char* name, std::vector<T>& value) | void operator()(ISerializer& serialize, const char* name, std::vector<T>& value) | ||||
{ | { | ||||
size_t len = value.size(); | size_t len = value.size(); | ||||
serialize.NumberU32_Unbounded("length", (u32)len); | serialize.NumberU32_Unbounded("length", (u32)len); | ||||
for (size_t i = 0; i < len; ++i) | for (size_t i = 0; i < len; ++i) | ||||
ELEM()(serialize, name, value[i]); | ELEM()(serialize, name, value[i]); | ||||
} | } | ||||
template<typename T> | template<typename T> | ||||
void operator()(IDeserializer& deserialize, const char* name, std::vector<T>& value) | void operator()(IDeserializer& deserialize, const char* name, std::vector<T>& value) | ||||
{ | { | ||||
value.clear(); | |||||
u32 len; | u32 len; | ||||
deserialize.NumberU32_Unbounded("length", len); | deserialize.NumberU32_Unbounded("length", len); | ||||
value.reserve(len); // TODO: watch out for out-of-memory | value.reserve(len); // TODO: watch out for out-of-memory | ||||
elexis: group value.clear + value.resize
perhaps add a comment that resize was faster in a benchmark | |||||
for (size_t i = 0; i < len; ++i) | for (size_t i = 0; i < len; ++i) | ||||
{ | { | ||||
T el; | T el; | ||||
ELEM()(deserialize, name, el); | ELEM()(deserialize, name, el); | ||||
value.push_back(el); | // Currently we don't use resize + direct assignment here, | ||||
// because we have a usage of std::vector<bool>. | |||||
value.emplace_back(std::move(el)); | |||||
Not Done Inline ActionsI always wonder about this pattern whether one shouldn't push_back() and then operate on that reference instead of copy/move assigning after processing the constructed value. Like value.emplace_back(); ELEM()(deserialize, name, value.back()); That's faster than moving and shared_ptr and stricter than raw ptr. If there was a hypothetical error during the process, one can still erase the value (which should never happen, and if it happens, its probably unrecoverable) elexis: I always wonder about this pattern whether one shouldn't push_back() and then operate on that… | |||||
Done Inline ActionsYes, it'd be good, but it's not possible yet. Because we have std::vector<bool>, that's a "special" vector. I'll refactor it later. vladislavbelov: Yes, it'd be good, but it's not possible yet. Because we have `std::vector<bool>`, that's a… | |||||
Not Done Inline ActionsI suppose > 90% of the serialized vector instances are not bool, so we shouldnt make > 90% of the calls slower for no reason when we can have it faster by specializing bool. If Im not mistaken this code is also called for the hash serializer, which means it's called every N turns during an MP game or replay thereof. (And perhaps we enable hashing for SP replays too) elexis: I suppose > 90% of the serialized vector instances are not bool, so we shouldnt make > 90% of… | |||||
Not Done Inline Actions// Cant use references nor move semantics due to std::vector<bool> elexis: // Cant use references nor move semantics due to std::vector<bool>
| |||||
} | } | ||||
} | } | ||||
}; | }; | ||||
Done Inline Actions(same) elexis: (same) | |||||
template<typename ELEM> | template<typename ELEM> | ||||
struct SerializeRepetitiveVector | struct SerializeRepetitiveVector | ||||
{ | { | ||||
template<typename T> | template<typename T> | ||||
void operator()(ISerializer& serialize, const char* name, std::vector<T>& value) | void operator()(ISerializer& serialize, const char* name, std::vector<T>& value) | ||||
{ | { | ||||
size_t len = value.size(); | size_t len = value.size(); | ||||
serialize.NumberU32_Unbounded("length", (u32)len); | serialize.NumberU32_Unbounded("length", (u32)len); | ||||
Show All 31 Lines | for (size_t i = 0; i < len;) | ||||
T el; | T el; | ||||
ELEM()(deserialize, name, el); | ELEM()(deserialize, name, el); | ||||
i += count; | i += count; | ||||
value.insert(value.end(), count, el); | value.insert(value.end(), count, el); | ||||
} | } | ||||
} | } | ||||
}; | }; | ||||
template<typename ELEM> | |||||
struct SerializeSet | |||||
{ | |||||
template<typename T> | |||||
void operator()(ISerializer& serialize, const char* name, const std::set<T>& value) | |||||
{ | |||||
serialize.NumberU32_Unbounded("size", static_cast<u32>(value.size())); | |||||
Done Inline Actionscan be inlined elexis: can be inlined | |||||
for (const T& elem : value) | |||||
ELEM()(serialize, name, elem); | |||||
} | |||||
template<typename T> | |||||
void operator()(IDeserializer& deserialize, const char* name, std::set<T>& value) | |||||
{ | |||||
value.clear(); | |||||
Not Done Inline Actions(if you want consistency you can move the clear below the size getter here too; or whatever) elexis: (if you want consistency you can move the clear below the size getter here too; or whatever) | |||||
u32 size; | |||||
deserialize.NumberU32_Unbounded("size", size); | |||||
Not Done Inline Actionsreserve value.clear + reserve after obtaining size, for grouping elexis: reserve
(I guess shrinking wont be needed)
value.clear + reserve after obtaining size, for… | |||||
for (size_t i = 0; i < size; ++i) | |||||
{ | |||||
T el; | |||||
ELEM()(deserialize, name, el); | |||||
value.emplace(std::move(el)); | |||||
} | |||||
} | |||||
}; | |||||
template<typename KS, typename VS> | template<typename KS, typename VS> | ||||
struct SerializeMap | struct SerializeMap | ||||
{ | { | ||||
template<typename K, typename V> | template<typename K, typename V> | ||||
void operator()(ISerializer& serialize, const char* UNUSED(name), std::map<K, V>& value) | void operator()(ISerializer& serialize, const char* UNUSED(name), std::map<K, V>& value) | ||||
{ | { | ||||
size_t len = value.size(); | size_t len = value.size(); | ||||
serialize.NumberU32_Unbounded("length", (u32)len); | serialize.NumberU32_Unbounded("length", (u32)len); | ||||
Show All 25 Lines | void operator()(IDeserializer& deserialize, const char* UNUSED(name), M& value) | ||||
u32 len; | u32 len; | ||||
deserialize.NumberU32_Unbounded("length", len); | deserialize.NumberU32_Unbounded("length", len); | ||||
for (size_t i = 0; i < len; ++i) | for (size_t i = 0; i < len; ++i) | ||||
{ | { | ||||
K k; | K k; | ||||
V v; | V v; | ||||
KS()(deserialize, "key", k); | KS()(deserialize, "key", k); | ||||
VS()(deserialize, "value", v); | VS()(deserialize, "value", v); | ||||
value.insert(std::make_pair(k, v)); | value.emplace(std::move(k), std::move(v)); | ||||
} | } | ||||
} | } | ||||
template<typename M, typename C> | template<typename M, typename C> | ||||
void operator()(IDeserializer& deserialize, const char* UNUSED(name), M& value, C& context) | void operator()(IDeserializer& deserialize, const char* UNUSED(name), M& value, C& context) | ||||
{ | { | ||||
typedef typename M::key_type K; | typedef typename M::key_type K; | ||||
typedef typename M::value_type::second_type V; // M::data_type gives errors with gcc | typedef typename M::value_type::second_type V; // M::data_type gives errors with gcc | ||||
value.clear(); | value.clear(); | ||||
u32 len; | u32 len; | ||||
deserialize.NumberU32_Unbounded("length", len); | deserialize.NumberU32_Unbounded("length", len); | ||||
for (size_t i = 0; i < len; ++i) | for (size_t i = 0; i < len; ++i) | ||||
{ | { | ||||
K k; | K k; | ||||
V v; | V v; | ||||
KS()(deserialize, "key", k); | KS()(deserialize, "key", k); | ||||
VS()(deserialize, "value", v, context); | VS()(deserialize, "value", v, context); | ||||
value.insert(std::make_pair(k, v)); | value.emplace(std::move(k), std::move(v)); | ||||
} | } | ||||
} | } | ||||
}; | }; | ||||
// We have to order the map before serializing to make things consistent | // We have to order the map before serializing to make things consistent | ||||
template<typename KS, typename VS> | template<typename KS, typename VS> | ||||
struct SerializeUnorderedMap | struct SerializeUnorderedMap | ||||
{ | { | ||||
Show All 18 Lines | struct SerializeU8_Enum | ||||
{ | { | ||||
serialize.NumberU8(name, value, 0, max); | serialize.NumberU8(name, value, 0, max); | ||||
} | } | ||||
void operator()(IDeserializer& deserialize, const char* name, T& value) | void operator()(IDeserializer& deserialize, const char* name, T& value) | ||||
{ | { | ||||
u8 val; | u8 val; | ||||
deserialize.NumberU8(name, val, 0, max); | deserialize.NumberU8(name, val, 0, max); | ||||
value = (T)val; | value = static_cast<T>(val); | ||||
} | } | ||||
}; | }; | ||||
struct SerializeU8_Unbounded | struct SerializeU8_Unbounded | ||||
{ | { | ||||
void operator()(ISerializer& serialize, const char* name, u8 value) | void operator()(ISerializer& serialize, const char* name, u8 value) | ||||
{ | { | ||||
serialize.NumberU8_Unbounded(name, value); | serialize.NumberU8_Unbounded(name, value); | ||||
▲ Show 20 Lines • Show All 107 Lines • Show Last 20 Lines |
group value.clear + value.resize
perhaps add a comment that resize was faster in a benchmark