From 6352e8e3196f78388b6c771073f9e03eaa612673 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Tue, 20 Jun 2023 14:33:09 +1000 Subject: everything everywhere all at once --- source/core/StarNetElementContainers.hpp | 452 +++++++++++++++++++++++++++++++ 1 file changed, 452 insertions(+) create mode 100644 source/core/StarNetElementContainers.hpp (limited to 'source/core/StarNetElementContainers.hpp') 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 +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 insert(value_type v); + pair 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 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 + 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 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> m_changeData; + Deque> m_pendingChangeData; + NetElementVersion const* m_netVersion = nullptr; + uint64_t m_changeDataLastVersion = 0; + bool m_updated = false; + bool m_interpolationEnabled = false; +}; + +template +using NetElementMap = NetElementMapWrapper>; + +template +using NetElementHashMap = NetElementMapWrapper>; + +template +void NetElementMapWrapper::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 +void NetElementMapWrapper::enableNetInterpolation(float) { + m_interpolationEnabled = true; +} + +template +void NetElementMapWrapper::disableNetInterpolation() { + m_interpolationEnabled = false; + for (auto& change : Star::take(m_pendingChangeData)) + applyChange(move(change.second)); +} + +template +void NetElementMapWrapper::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 +void NetElementMapWrapper::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 +void NetElementMapWrapper::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 +bool NetElementMapWrapper::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 +void NetElementMapWrapper::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 +auto NetElementMapWrapper::get(key_type const& key) const -> mapped_type const & { + return BaseMap::get(key); +} + +template +auto NetElementMapWrapper::ptr(key_type const& key) const -> mapped_type const * { + return BaseMap::ptr(key); +} + +template +auto NetElementMapWrapper::begin() const -> const_iterator { + return BaseMap::begin(); +} + +template +auto NetElementMapWrapper::end() const -> const_iterator { + return BaseMap::end(); +} + +template +auto NetElementMapWrapper::insert(value_type v) -> pair { + auto res = BaseMap::insert(v); + if (res.second) { + addChangeData(SetChange{move(v.first), move(v.second)}); + m_updated = true; + } + return res; +} + +template +auto NetElementMapWrapper::insert(key_type k, mapped_type v) -> pair { + return insert(value_type(move(k), move(v))); +} + +template +void NetElementMapWrapper::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 +void NetElementMapWrapper::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 +void NetElementMapWrapper::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 +bool NetElementMapWrapper::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 +auto NetElementMapWrapper::erase(const_iterator i) -> const_iterator { + addChangeData(RemoveChange(i->first)); + m_updated = true; + return BaseMap::erase(i); +} + +template +auto NetElementMapWrapper::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 +auto NetElementMapWrapper::maybeTake(key_type const& k) -> Maybe { + auto i = BaseMap::find(k); + if (i == BaseMap::end()) + return {}; + auto m = move(i->second); + erase(i); + return Maybe(move(m)); +} + +template +void NetElementMapWrapper::clear() { + if (!empty()) { + addChangeData(ClearChange()); + m_updated = true; + BaseMap::clear(); + } +} + +template +BaseMap const& NetElementMapWrapper::baseMap() const { + return *this; +} + +template +void NetElementMapWrapper::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 +bool NetElementMapWrapper::pullUpdated() { + return Star::take(m_updated); +} + +template +template +void NetElementMapWrapper::setContents(MapType const& values) { + reset(BaseMap::from(values)); +} + +template +void NetElementMapWrapper::writeChange(DataStream& ds, ElementChange const& change) { + if (auto sc = change.template ptr()) { + ds.write(0); + ds.write(sc->key); + ds.write(sc->value); + } else if (auto rc = change.template ptr()) { + ds.write(1); + ds.write(rc->key); + } else { + ds.write(2); + } +} + +template +auto NetElementMapWrapper::readChange(DataStream& ds) -> ElementChange { + uint8_t t = ds.read(); + 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 +void NetElementMapWrapper::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)currentVersion - MaxChangeDataVersions, 0); + while (!m_changeData.empty() && m_changeData.first().first < m_changeDataLastVersion) + m_changeData.removeFirst(); +} + +template +void NetElementMapWrapper::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 +void NetElementMapWrapper::applyChange(ElementChange change) { + if (auto set = change.template ptr()) + BaseMap::set(move(set->key), move(set->value)); + else if (auto remove = change.template ptr()) + BaseMap::remove(move(remove->key)); + else + BaseMap::clear(); + m_updated = true; +} + +} + +#endif -- cgit v1.2.3