diff options
Diffstat (limited to 'source/core/StarNetElementContainers.hpp')
-rw-r--r-- | source/core/StarNetElementContainers.hpp | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/source/core/StarNetElementContainers.hpp b/source/core/StarNetElementContainers.hpp new file mode 100644 index 0000000..5c2fd71 --- /dev/null +++ b/source/core/StarNetElementContainers.hpp @@ -0,0 +1,452 @@ +#ifndef STAR_NET_ELEMENT_CONTAINERS_HPP +#define STAR_NET_ELEMENT_CONTAINERS_HPP + +#include "StarMap.hpp" +#include "StarDataStreamExtra.hpp" +#include "StarNetElement.hpp" +#include "StarStrongTypedef.hpp" + +namespace Star { + +// NetElement map container that is more efficient than the naive serialization +// of an entire Map, because it delta encodes changes to save networking +// traffic. +template <typename BaseMap> +class NetElementMapWrapper : public NetElement, private BaseMap { +public: + typedef typename BaseMap::iterator iterator; + typedef typename BaseMap::const_iterator const_iterator; + + typedef typename BaseMap::key_type key_type; + typedef typename BaseMap::mapped_type mapped_type; + typedef typename BaseMap::value_type value_type; + + void initNetVersion(NetElementVersion const* version = nullptr) override; + + void enableNetInterpolation(float extrapolationHint = 0.0f) override; + void disableNetInterpolation() override; + void tickNetInterpolation(float dt) override; + + void netStore(DataStream& ds) const override; + void netLoad(DataStream& ds) override; + + bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override; + void readNetDelta(DataStream& ds, float interpolationTime = 0.0f) override; + + mapped_type const& get(key_type const& key) const; + mapped_type const* ptr(key_type const& key) const; + + const_iterator begin() const; + const_iterator end() const; + + using BaseMap::keys; + using BaseMap::values; + using BaseMap::pairs; + using BaseMap::contains; + using BaseMap::size; + using BaseMap::empty; + using BaseMap::maybe; + using BaseMap::value; + + pair<const_iterator, bool> insert(value_type v); + pair<const_iterator, bool> insert(key_type k, mapped_type v); + + void add(key_type k, mapped_type v); + // Calling set with a matching key and value does not cause a delta to be + // produced + void set(key_type k, mapped_type v); + // set requires that mapped_type implement operator==, push always generates + // a delta and does not require mapped_type operator== + void push(key_type k, mapped_type v); + + bool remove(key_type const& k); + + const_iterator erase(const_iterator i); + + mapped_type take(key_type const& k); + Maybe<mapped_type> maybeTake(key_type const& k); + + void clear(); + + BaseMap const& baseMap() const; + void reset(BaseMap values); + bool pullUpdated(); + + // Sets this map to contain the same keys / values as the given map. All + // values in this map not found in the given map are removed. (Same as + // reset, but with arbitrary map type). + template <typename MapType> + void setContents(MapType const& values); + +private: + // If a delta is written from further back than this many steps, the delta + // will fall back to a full serialization of the entire state. + static int64_t const MaxChangeDataVersions = 100; + + struct SetChange { + key_type key; + mapped_type value; + }; + struct RemoveChange { + key_type key; + }; + struct ClearChange {}; + + typedef Variant<SetChange, RemoveChange, ClearChange> ElementChange; + + static void writeChange(DataStream& ds, ElementChange const& change); + static ElementChange readChange(DataStream& ds); + + void addChangeData(ElementChange change); + + void addPendingChangeData(ElementChange change, float interpolationTime); + void applyChange(ElementChange change); + + Deque<pair<uint64_t, ElementChange>> m_changeData; + Deque<pair<float, ElementChange>> m_pendingChangeData; + NetElementVersion const* m_netVersion = nullptr; + uint64_t m_changeDataLastVersion = 0; + bool m_updated = false; + bool m_interpolationEnabled = false; +}; + +template <typename Key, typename Value> +using NetElementMap = NetElementMapWrapper<Map<Key, Value>>; + +template <typename Key, typename Value> +using NetElementHashMap = NetElementMapWrapper<HashMap<Key, Value>>; + +template <typename BaseMap> +void NetElementMapWrapper<BaseMap>::initNetVersion(NetElementVersion const* version) { + m_netVersion = version; + + m_changeData.clear(); + m_changeDataLastVersion = 0; + + for (auto& change : Star::take(m_pendingChangeData)) + applyChange(move(change.second)); + + addChangeData(ClearChange()); + for (auto const& p : *this) + addChangeData(SetChange{p.first, p.second}); +} + +template <typename BaseMap> +void NetElementMapWrapper<BaseMap>::enableNetInterpolation(float) { + m_interpolationEnabled = true; +} + +template <typename BaseMap> +void NetElementMapWrapper<BaseMap>::disableNetInterpolation() { + m_interpolationEnabled = false; + for (auto& change : Star::take(m_pendingChangeData)) + applyChange(move(change.second)); +} + +template <typename BaseMap> +void NetElementMapWrapper<BaseMap>::tickNetInterpolation(float dt) { + for (auto& p : m_pendingChangeData) + p.first -= dt; + + while (!m_pendingChangeData.empty() && m_pendingChangeData.first().first <= 0.0f) + applyChange(m_pendingChangeData.takeFirst().second); +} + +template <typename BaseMap> +void NetElementMapWrapper<BaseMap>::netStore(DataStream& ds) const { + ds.writeVlqU(BaseMap::size() + m_pendingChangeData.size()); + for (auto const& pair : *this) + writeChange(ds, SetChange{pair.first, pair.second}); + + for (auto const& p : m_pendingChangeData) + writeChange(ds, p.second); +} + +template <typename BaseMap> +void NetElementMapWrapper<BaseMap>::netLoad(DataStream& ds) { + m_changeData.clear(); + m_changeDataLastVersion = m_netVersion ? m_netVersion->current() : 0; + m_pendingChangeData.clear(); + BaseMap::clear(); + + addChangeData(ClearChange()); + + uint64_t count = ds.readVlqU(); + for (uint64_t i = 0; i < count; ++i) { + auto change = readChange(ds); + addChangeData(change); + applyChange(move(change)); + } + + m_updated = true; +} + +template <typename BaseMap> +bool NetElementMapWrapper<BaseMap>::writeNetDelta(DataStream& ds, uint64_t fromVersion) const { + bool deltaWritten = false; + + if (fromVersion < m_changeDataLastVersion) { + deltaWritten = true; + ds.writeVlqU(1); + netStore(ds); + + } else { + for (auto const& p : m_changeData) { + if (p.first >= fromVersion) { + deltaWritten = true; + ds.writeVlqU(2); + writeChange(ds, p.second); + } + } + } + + if (deltaWritten) + ds.writeVlqU(0); + + return deltaWritten; +} + +template <typename BaseMap> +void NetElementMapWrapper<BaseMap>::readNetDelta(DataStream& ds, float interpolationTime) { + while (true) { + uint64_t code = ds.readVlqU(); + if (code == 0) { + break; + } else if (code == 1) { + netLoad(ds); + } else if (code == 2) { + auto change = readChange(ds); + addChangeData(change); + + if (m_interpolationEnabled && interpolationTime > 0.0f) + addPendingChangeData(move(change), interpolationTime); + else + applyChange(move(change)); + } else { + throw IOException("Improper delta code received in NetElementMapWrapper::readNetDelta"); + } + } +} + +template <typename BaseMap> +auto NetElementMapWrapper<BaseMap>::get(key_type const& key) const -> mapped_type const & { + return BaseMap::get(key); +} + +template <typename BaseMap> +auto NetElementMapWrapper<BaseMap>::ptr(key_type const& key) const -> mapped_type const * { + return BaseMap::ptr(key); +} + +template <typename BaseMap> +auto NetElementMapWrapper<BaseMap>::begin() const -> const_iterator { + return BaseMap::begin(); +} + +template <typename BaseMap> +auto NetElementMapWrapper<BaseMap>::end() const -> const_iterator { + return BaseMap::end(); +} + +template <typename BaseMap> +auto NetElementMapWrapper<BaseMap>::insert(value_type v) -> pair<const_iterator, bool> { + auto res = BaseMap::insert(v); + if (res.second) { + addChangeData(SetChange{move(v.first), move(v.second)}); + m_updated = true; + } + return res; +} + +template <typename BaseMap> +auto NetElementMapWrapper<BaseMap>::insert(key_type k, mapped_type v) -> pair<const_iterator, bool> { + return insert(value_type(move(k), move(v))); +} + +template <typename BaseMap> +void NetElementMapWrapper<BaseMap>::add(key_type k, mapped_type v) { + if (!insert(value_type(move(k), move(v))).second) + throw MapException::format("Entry with key '%s' already present.", outputAny(k)); +} + +template <typename BaseMap> +void NetElementMapWrapper<BaseMap>::set(key_type k, mapped_type v) { + auto i = BaseMap::find(k); + if (i != BaseMap::end()) { + if (!(i->second == v)) { + addChangeData(SetChange{move(k), v}); + i->second = move(v); + m_updated = true; + } + } else { + addChangeData(SetChange{k, v}); + BaseMap::insert(value_type(move(k), move(v))); + m_updated = true; + } +} + +template <typename BaseMap> +void NetElementMapWrapper<BaseMap>::push(key_type k, mapped_type v) { + auto i = BaseMap::find(k); + if (i != BaseMap::end()) { + addChangeData(SetChange(move(k), v)); + i->second = move(v); + } else { + addChangeData(SetChange(k, v)); + BaseMap::insert(value_type(move(k), move(v))); + } + m_updated = true; +} + +template <typename BaseMap> +bool NetElementMapWrapper<BaseMap>::remove(key_type const& k) { + auto i = BaseMap::find(k); + if (i != BaseMap::end()) { + BaseMap::erase(i); + addChangeData(RemoveChange{k}); + m_updated = true; + return true; + } + return false; +} + +template <typename BaseMap> +auto NetElementMapWrapper<BaseMap>::erase(const_iterator i) -> const_iterator { + addChangeData(RemoveChange(i->first)); + m_updated = true; + return BaseMap::erase(i); +} + +template <typename BaseMap> +auto NetElementMapWrapper<BaseMap>::take(key_type const& k) -> mapped_type { + auto i = BaseMap::find(k); + if (i == BaseMap::end()) + throw MapException::format("Key '%s' not found in Map::take()", outputAny(k)); + auto m = move(i->second); + erase(i); + return m; +} + +template <typename BaseMap> +auto NetElementMapWrapper<BaseMap>::maybeTake(key_type const& k) -> Maybe<mapped_type> { + auto i = BaseMap::find(k); + if (i == BaseMap::end()) + return {}; + auto m = move(i->second); + erase(i); + return Maybe<mapped_type>(move(m)); +} + +template <typename BaseMap> +void NetElementMapWrapper<BaseMap>::clear() { + if (!empty()) { + addChangeData(ClearChange()); + m_updated = true; + BaseMap::clear(); + } +} + +template <typename BaseMap> +BaseMap const& NetElementMapWrapper<BaseMap>::baseMap() const { + return *this; +} + +template <typename BaseMap> +void NetElementMapWrapper<BaseMap>::reset(BaseMap values) { + for (auto const& p : *this) { + if (!values.contains(p.first)) { + addChangeData(RemoveChange{p.first}); + m_updated = true; + } + } + + for (auto const& p : values) { + auto v = ptr(p.first); + if (!v || !(*v == p.second)) { + addChangeData(SetChange{p.first, p.second}); + m_updated = true; + } + } + + BaseMap::operator=(move(values)); +} + +template <typename BaseMap> +bool NetElementMapWrapper<BaseMap>::pullUpdated() { + return Star::take(m_updated); +} + +template <typename BaseMap> +template <typename MapType> +void NetElementMapWrapper<BaseMap>::setContents(MapType const& values) { + reset(BaseMap::from(values)); +} + +template <typename BaseMap> +void NetElementMapWrapper<BaseMap>::writeChange(DataStream& ds, ElementChange const& change) { + if (auto sc = change.template ptr<SetChange>()) { + ds.write<uint8_t>(0); + ds.write(sc->key); + ds.write(sc->value); + } else if (auto rc = change.template ptr<RemoveChange>()) { + ds.write<uint8_t>(1); + ds.write(rc->key); + } else { + ds.write<uint8_t>(2); + } +} + +template <typename BaseMap> +auto NetElementMapWrapper<BaseMap>::readChange(DataStream& ds) -> ElementChange { + uint8_t t = ds.read<uint8_t>(); + if (t == 0) { + SetChange sc; + ds.read(sc.key); + ds.read(sc.value); + return sc; + } else if (t == 1) { + RemoveChange rc; + ds.read(rc.key); + return rc; + } else if (t == 2) { + return ClearChange(); + } else { + throw IOException("Improper type code received in NetElementMapWrapper::readChange"); + } +} + +template <typename BaseMap> +void NetElementMapWrapper<BaseMap>::addChangeData(ElementChange change) { + uint64_t currentVersion = m_netVersion ? m_netVersion->current() : 0; + starAssert(m_changeData.empty() || m_changeData.last().first <= currentVersion); + + m_changeData.append({currentVersion, move(change)}); + + m_changeDataLastVersion = max<int64_t>((int64_t)currentVersion - MaxChangeDataVersions, 0); + while (!m_changeData.empty() && m_changeData.first().first < m_changeDataLastVersion) + m_changeData.removeFirst(); +} + +template <typename BaseMap> +void NetElementMapWrapper<BaseMap>::addPendingChangeData(ElementChange change, float interpolationTime) { + if (!m_pendingChangeData.empty() && interpolationTime < m_pendingChangeData.last().first) { + for (auto& change : Star::take(m_pendingChangeData)) + applyChange(move(change.second)); + } + m_pendingChangeData.append({interpolationTime, move(change)}); +} + +template <typename BaseMap> +void NetElementMapWrapper<BaseMap>::applyChange(ElementChange change) { + if (auto set = change.template ptr<SetChange>()) + BaseMap::set(move(set->key), move(set->value)); + else if (auto remove = change.template ptr<RemoveChange>()) + BaseMap::remove(move(remove->key)); + else + BaseMap::clear(); + m_updated = true; +} + +} + +#endif |