diff --git a/src/multiplayer.cpp b/src/multiplayer.cpp index b2d64173..ed90126f 100644 --- a/src/multiplayer.cpp +++ b/src/multiplayer.cpp @@ -4,6 +4,142 @@ #include "engine.h" #include "multiplayer_internal.h" +namespace replication +{ + Item::Item(ControlBlock& control, const Settings& settings) + : controller{ control } + { + controller.add(*this, settings); + } + + Item::~Item() = default; + + void Item::setId(Key, uint16_t id) + { + replication_id = id; + } + + ControlBlock& Item::getController(Key) const + { + return controller; + } + + uint16_t Item::getId(Key) const + { + return replication_id; + } + + void Item::setDirty() + { + controller.setDirty(*this); + } + + void ControlBlock::add(Item& item, const Item::Settings& settings) + { + assert(items.size() < static_cast(~controlled_flag)); + item.setId({}, static_cast(items.size())); + items.emplace_back(&item); + min_update_interval.emplace_back(settings.min_delay); + time_since_last_update.emplace_back(0.f); + if (dirty.size() * sizeof(size_t) < items.size()) + dirty.resize((items.size() + sizeof(size_t) - 1) / sizeof(size_t)); + +#ifdef DEBUG + names.emplace_back(settings.name); +#endif + } + + bool ControlBlock::isDirty(const Item& item) const + { + assert(&item.getController({}) == this); + return isDirty(item.getId({})); + } + + void ControlBlock::setDirty(const Item& item) + { + assert(&item.getController({}) == this); + setDirty(item.getId({})); + } + + size_t ControlBlock::send(sf::Packet& packet, bool everything, float delta /* = 0.f */) + { + size_t sent{}; + + // Update all deltas. + for (auto& value : time_since_last_update) + value += delta; + + if (!everything) + { + for (auto dirty_batch = 0; dirty_batch < dirty.size(); ++dirty_batch) + { + auto batch = dirty[dirty_batch]; + if (batch) // skip over entire batches with no change. + { + for (auto i = 0; i < sizeof(size_t); ++i) + { + auto bit = (size_t{ 1 } << i); + auto index = dirty_batch * sizeof(size_t) + i; + if ((batch & bit) != 0 && time_since_last_update[index] > min_update_interval[index]) + { + auto item = items[index]; + packet << int16_t(item->getId({}) | controlled_flag); + item->send(*this, packet); + time_since_last_update[index] = 0.f; + sent += 1; + batch &= ~bit; + } + } + + if (batch != dirty[dirty_batch]) + { + dirty[dirty_batch] = batch; + } + } + } + } + else + { + for (auto& item : items) + { + packet << int16_t(item->getId({}) | controlled_flag); + item->send(*this, packet); + } + sent = items.size(); + } + + return sent; + } + + bool ControlBlock::handles(uint16_t net_id) const + { + return (net_id & controlled_flag) && (net_id & ~controlled_flag) < items.size(); + } + + void ControlBlock::receive(uint16_t net_id, sf::Packet& packet) + { + auto element_key = net_id & ~controlled_flag; + items[element_key]->receive(*this, packet); + } + + + bool ControlBlock::isDirty(size_t id) const + { + return (dirty[id / sizeof(size_t)] & (size_t{ 1 } << (id % sizeof(size_t)))) != 0; + } + + void ControlBlock::setDirty(size_t id) + { + dirty[id / sizeof(size_t)] |= (size_t{ 1 } << (id % sizeof(size_t))); + } + + void ControlBlock::resetDirty() + { + memset(dirty.data(), 0, dirty.size() * sizeof(size_t)); + } +} // ns replication + + static PVector collisionable_significant; class CollisionableReplicationData { @@ -26,6 +162,7 @@ MultiplayerClassListItem* multiplayerClassListStart; MultiplayerObject::MultiplayerObject(string multiplayerClassIdentifier) : multiplayerClassIdentifier(multiplayerClassIdentifier) +, replicationControl{std::make_unique()} { multiplayerObjectId = noId; replicated = false; diff --git a/src/multiplayer.h b/src/multiplayer.h index ee50e169..e8adcc74 100644 --- a/src/multiplayer.h +++ b/src/multiplayer.h @@ -3,7 +3,7 @@ #include #include -#include +#include #include "Updatable.h" #include "stringImproved.h" @@ -125,6 +125,79 @@ bool multiplayerReplicationFunctions::isChanged(void* data, void* prev_data_p template <> bool multiplayerReplicationFunctions::isChanged(void* data, void* prev_data_ptr); +namespace replication +{ + class ControlBlock; + + /*! a replicated item - base class that may be anything. */ + class Item + { + public: + struct Settings + { + float min_delay{}; +#ifdef DEBUG + string name{}; +#endif + }; + /*! Allow fine-grained access control to parts of the internal. + * + * Used to grant access to some classes, while still being to guarantee class invariants. + */ + class Key final { + friend class ControlBlock; + constexpr Key() = default; + }; + + explicit Item(ControlBlock& controller, const Settings& settings); + virtual ~Item(); + + virtual void send(const ControlBlock&, sf::Packet&) = 0; + virtual void receive(const ControlBlock&, sf::Packet&) = 0; + + // + ControlBlock& getController(Key) const; + + uint16_t getId(Key) const; + void setId(Key, uint16_t id); + protected: + void setDirty(); + private: + ControlBlock& controller; + uint16_t replication_id{}; + // There's padding here, might be useful. + }; + + class ControlBlock + { + public: + class Key final { + friend class ReplicatedBase; + constexpr Key() = default; + }; + + void add(Item& item, const Item::Settings& settings); + bool isDirty(const Item& item) const; + void setDirty(const Item& item); + size_t send(sf::Packet&, bool everything, float delta = 0.f); + bool handles(uint16_t net_id) const; + void receive(uint16_t net_id, sf::Packet&); + private: + static constexpr uint16_t controlled_flag{ 0x8000 }; + + bool isDirty(size_t) const; + void setDirty(size_t); + void resetDirty(); + std::vector dirty; + std::vector time_since_last_update; + std::vector min_update_interval; + std::vector items; +#ifdef DEBUG + std::vector names; +#endif + }; +} // ns replication + //In between class that handles all the nasty synchronization of objects between server and client. //I'm assuming that it should be a pure virtual class though. class MultiplayerObject : public virtual PObject @@ -151,6 +224,7 @@ class MultiplayerObject : public virtual PObject void(*cleanupFunction)(void* prev_data_ptr); }; std::vector memberReplicationInfo; + std::unique_ptr replicationControl; public: MultiplayerObject(string multiplayerClassIdentifier); virtual ~MultiplayerObject(); @@ -248,9 +322,11 @@ class MultiplayerObject : public virtual PObject virtual void onReceiveClientCommand(int32_t client_id, sf::Packet& packet) {} //Got data from a client, handle it. virtual void onReceiveServerCommand(sf::Packet& packet) {} //Got data from a server, handle it. + replication::ControlBlock& getReplicationController() const { return *replicationControl; } private: friend class GameServer; friend class GameClient; + friend class MultiplayerStaticReplicationBase; template static inline @@ -267,6 +343,172 @@ class MultiplayerObject : public virtual PObject } }; +namespace replication +{ + template + class Field; + + template + sf::Packet& operator >> (sf::Packet& packet, Field& field); + + template + class Field : public Item + { + public: + class MutableKey final + { + friend sf::Packet& operator >> <>(sf::Packet& packet, Field& field); + constexpr MutableKey() = default; + }; + + template + Field(MultiplayerObject* parent, const Item::Settings& settings, Args&&... params) + : Item{ parent->getReplicationController(), settings } + , value(std::forward(params)...) + {} + + const T& get() const { return value; } + T& get(MutableKey) { setDirty(); return value; } + operator const T& () const { return get(); } + + Field& operator =(const Field& other) + { + return *this = other.get(); + } + + // RMW ops. + template + Field& operator=(const U& new_value) + { + if (value != new_value) + { + value = new_value; + setDirty(); + } + return *this; + } + + template + Field& operator-=(U&& update) + { + auto previous = value; + value -= std::forward(update); + if (previous != value) + setDirty(); + + return *this; + } + + template + Field& operator+=(U&& update) + { + auto previous = value; + value += std::forward(update); + if (previous != value) + setDirty(); + + return *this; + } + + template + Field& operator*=(U&& update) + { + auto previous = value; + value *= std::forward(update); + if (previous != value) + setDirty(); + + return *this; + } + + template + Field& operator/=(U&& update) + { + auto previous = value; + value /= std::forward(update); + if (previous != value) + setDirty(); + + return *this; + } + + void send(const ControlBlock&, sf::Packet& packet) override + { + packet << get(); + } + + void receive(const ControlBlock&, sf::Packet& packet) override + { + packet >> value; + } + private: + T value; + }; + + template<> + class Field final : Item + { + public: + Field(MultiplayerObject* parent, const Item::Settings& settings) + :Item{ parent->getReplicationController(), settings } + {} + + void send(const ControlBlock&, sf::Packet& packet) final + { + packet << get(); + } + + void receive(const ControlBlock&, sf::Packet& packet) final + { + packet >> value; + } + + const string& get() const { return value; } + operator const string& () const { return get(); } + + template + Field& operator=(U&& other) + { + value = std::forward(other); + setDirty(); + return *this; + } + private: + string value; + }; + + template + sf::Packet& operator >> (sf::Packet& packet, Field& field) + { + packet >> field.get({}); + return packet; + } +} // ns replication + +template +bool operator ==(U&& lhs, const replication::Field& rhs) +{ + return std::forward(lhs) == rhs.get(); +} + +template +bool operator ==(const replication::Field& lhs, U&& rhs) +{ + return std::forward(rhs) == rhs; +} + +template +bool operator !=(U&& lhs, const replication::Field& rhs) +{ + return std::forward(lhs) != rhs.get(); +} + +template +bool operator !=(const replication::Field& lhs, U&& rhs) +{ + return std::forward(rhs) != rhs; +} + typedef MultiplayerObject* (*CreateMultiplayerObjectFunction)(); class MultiplayerClassListItem; diff --git a/src/multiplayer_client.cpp b/src/multiplayer_client.cpp index 7556676b..70e4b617 100644 --- a/src/multiplayer_client.cpp +++ b/src/multiplayer_client.cpp @@ -126,10 +126,12 @@ void GameClient::update(float /*delta*/) obj->multiplayerObjectId = id; objectMap[id] = obj; - int16_t idx; + int16_t idx{}; while(packet >> idx) { - if (idx >= 0 && idx < int16_t(obj->memberReplicationInfo.size())) + if (obj->replicationControl->handles(idx)) + obj->replicationControl->receive(idx, packet); + else if (idx >= 0 && idx < uint16_t(obj->memberReplicationInfo.size())) (obj->memberReplicationInfo[idx].receiveFunction)(obj->memberReplicationInfo[idx].ptr, packet); else LOG(DEBUG) << "Odd index from server replication: " << idx; @@ -157,7 +159,9 @@ void GameClient::update(float /*delta*/) P obj = objectMap[id]; while(packet >> idx) { - if (idx < int32_t(obj->memberReplicationInfo.size())) + if (obj->replicationControl->handles(idx)) + obj->replicationControl->receive(idx, packet); + else if (idx < int32_t(obj->memberReplicationInfo.size())) (obj->memberReplicationInfo[idx].receiveFunction)(obj->memberReplicationInfo[idx].ptr, packet); } } diff --git a/src/multiplayer_server.cpp b/src/multiplayer_server.cpp index 9e68c8f1..a5bd8927 100644 --- a/src/multiplayer_server.cpp +++ b/src/multiplayer_server.cpp @@ -164,6 +164,8 @@ void GameServer::update(float /*gameDelta*/) } } } + + cnt += obj->replicationControl->send(packet, false, delta); if (cnt > 0) { sendAll(packet); @@ -529,6 +531,8 @@ void GameServer::generateCreatePacketFor(P obj, sf::Packet& p packet << int16_t(n); (obj->memberReplicationInfo[n].sendFunction)(obj->memberReplicationInfo[n].ptr, packet); } + + obj->replicationControl->send(packet, true); } void GameServer::generateDeletePacketFor(int32_t id, sf::Packet& packet)