2
0
Эх сурвалжийг харах

Optimized network server operation by using HashMap instead of Map where applicable and by querying Serializable attributes once per network frame, instead of per user.

Lasse Öörni 13 жил өмнө
parent
commit
1cf5d99d44

+ 22 - 20
Engine/Container/Map.h

@@ -331,37 +331,39 @@ private:
     /// Find the node with smallest key.
     /// Find the node with smallest key.
     Node* FindFirst() const
     Node* FindFirst() const
     {
     {
-        if (!head_)
+        Node* node = Root();
+        if (!node)
             return 0;
             return 0;
         
         
-        // Check cached node first
-        if (head_->link_[0])
-            return reinterpret_cast<Node*>(head_->link_[0]);
-        
-        Node* node = reinterpret_cast<Node*>(head_->parent_);
-        while (node && node->link_[0])
-            node = node->Child(0);
+        // Search if not cached
+        Node*& first = reinterpret_cast<Node*&>(head_->link_[0]);
+        if (!first)
+        {
+            while (node && node->link_[0])
+                node = node->Child(0);
+            first = node;
+        }
         
         
-        head_->link_[0] = node;
-        return node;
+        return first;
     }
     }
     
     
     /// Find the node with largest key.
     /// Find the node with largest key.
     Node* FindLast() const
     Node* FindLast() const
     {
     {
-        if (!head_)
+        Node* node = Root();
+        if (!node)
             return 0;
             return 0;
         
         
-        // Check cached node first
-        if (head_->link_[1])
-            return reinterpret_cast<Node*>(head_->link_[1]);
-        
-        Node* node = reinterpret_cast<Node*>(head_->parent_);
-        while (node && node->link_[1])
-            node = node->Child(1);
+        // Search if not cached
+        Node*& last = reinterpret_cast<Node*&>(head_->link_[1]);
+        if (!last)
+        {
+            while (node && node->link_[1])
+                node = node->Child(1);
+            last = node;
+        }
         
         
-        head_->link_[1] = node;
-        return node;
+        return last;
     }
     }
     
     
     /// Find a node with key. Return null if not found.
     /// Find a node with key. Return null if not found.

+ 22 - 20
Engine/Container/Set.h

@@ -282,37 +282,39 @@ private:
     /// Find the node with smallest key.
     /// Find the node with smallest key.
     Node* FindFirst() const
     Node* FindFirst() const
     {
     {
-        if (!head_)
+        Node* node = Root();
+        if (!node)
             return 0;
             return 0;
         
         
-        // Check cached node first
-        if (head_->link_[0])
-            return reinterpret_cast<Node*>(head_->link_[0]);
-        
-        Node* node = reinterpret_cast<Node*>(head_->parent_);
-        while (node && node->link_[0])
-            node = node->Child(0);
+        // Search if not cached
+        Node*& first = reinterpret_cast<Node*&>(head_->link_[0]);
+        if (!first)
+        {
+            while (node && node->link_[0])
+                node = node->Child(0);
+            first = node;
+        }
         
         
-        head_->link_[0] = node;
-        return node;
+        return first;
     }
     }
     
     
     /// Find the node with largest key.
     /// Find the node with largest key.
     Node* FindLast() const
     Node* FindLast() const
     {
     {
-        if (!head_)
+        Node* node = Root();
+        if (!node)
             return 0;
             return 0;
         
         
-        // Check cached node first
-        if (head_->link_[1])
-            return reinterpret_cast<Node*>(head_->link_[1]);
-        
-        Node* node = reinterpret_cast<Node*>(head_->parent_);
-        while (node && node->link_[1])
-            node = node->Child(1);
+        // Search if not cached
+        Node*& last = reinterpret_cast<Node*&>(head_->link_[1]);
+        if (!last)
+        {
+            while (node && node->link_[1])
+                node = node->Child(1);
+            last = node;
+        }
         
         
-        head_->link_[1] = node;
-        return node;
+        return last;
     }
     }
     
     
     /// Find a node with key. Return null if not found.
     /// Find a node with key. Return null if not found.

+ 1 - 1
Engine/Graphics/AnimatedModel.cpp

@@ -564,7 +564,7 @@ void AnimatedModel::SetSkeleton(const Skeleton& skeleton, bool createBones)
 {
 {
     if (!node_ && createBones)
     if (!node_ && createBones)
     {
     {
-        LOGWARNING("AnimatedModel not attached to a scene node, can not create bone nodes");
+        LOGERROR("AnimatedModel not attached to a scene node, can not create bone nodes");
         return;
         return;
     }
     }
     
     

+ 24 - 25
Engine/Network/Connection.cpp

@@ -216,7 +216,7 @@ void Connection::Disconnect(int waitMSec)
     connection_->Disconnect(waitMSec);
     connection_->Disconnect(waitMSec);
 }
 }
 
 
-void Connection::SendServerUpdate()
+void Connection::SendServerUpdate(unsigned serverFrameNumber)
 {
 {
     if (!scene_ || !sceneLoaded_)
     if (!scene_ || !sceneLoaded_)
         return;
         return;
@@ -228,16 +228,16 @@ void Connection::SendServerUpdate()
     // Check for new or changed nodes
     // Check for new or changed nodes
     // Start from the root node (scene) so that the scene-wide components get sent first
     // Start from the root node (scene) so that the scene-wide components get sent first
     processedNodes_.Clear();
     processedNodes_.Clear();
-    ProcessNode(scene_);
+    ProcessNode(serverFrameNumber, scene_);
     
     
     // Then go through the rest of the nodes
     // Then go through the rest of the nodes
     for (Map<unsigned, Node*>::ConstIterator i = nodes.Begin(); i != nodes.End() && i->first_ < FIRST_LOCAL_ID; ++i)
     for (Map<unsigned, Node*>::ConstIterator i = nodes.Begin(); i != nodes.End() && i->first_ < FIRST_LOCAL_ID; ++i)
-        ProcessNode(i->second_);
+        ProcessNode(serverFrameNumber, i->second_);
     
     
     // Check for removed nodes
     // Check for removed nodes
-    for (Map<unsigned, NodeReplicationState>::Iterator i = sceneState_.Begin(); i != sceneState_.End();)
+    for (HashMap<unsigned, NodeReplicationState>::Iterator i = sceneState_.Begin(); i != sceneState_.End();)
     {
     {
-        Map<unsigned, NodeReplicationState>::Iterator current = i++;
+        HashMap<unsigned, NodeReplicationState>::Iterator current = i++;
         if (current->second_.frameNumber_ != frameNumber_)
         if (current->second_.frameNumber_ != frameNumber_)
         {
         {
             msg_.Clear();
             msg_.Clear();
@@ -311,9 +311,9 @@ void Connection::SendPackages()
     {
     {
         unsigned char buffer[PACKAGE_FRAGMENT_SIZE];
         unsigned char buffer[PACKAGE_FRAGMENT_SIZE];
         
         
-        for (Map<StringHash, PackageUpload>::Iterator i = uploads_.Begin(); i != uploads_.End();)
+        for (HashMap<StringHash, PackageUpload>::Iterator i = uploads_.Begin(); i != uploads_.End();)
         {
         {
-            Map<StringHash, PackageUpload>::Iterator current = i++;
+            HashMap<StringHash, PackageUpload>::Iterator current = i++;
             PackageUpload& upload = current->second_;
             PackageUpload& upload = current->second_;
             unsigned fragmentSize = Min((int)(upload.file_->GetSize() - upload.file_->GetPosition()), (int)PACKAGE_FRAGMENT_SIZE);
             unsigned fragmentSize = Min((int)(upload.file_->GetSize() - upload.file_->GetPosition()), (int)PACKAGE_FRAGMENT_SIZE);
             upload.file_->Read(buffer, fragmentSize);
             upload.file_->Read(buffer, fragmentSize);
@@ -758,7 +758,7 @@ void Connection::ProcessPackageDownload(int msgID, MemoryBuffer& msg)
         {
         {
             StringHash nameHash = msg.ReadStringHash();
             StringHash nameHash = msg.ReadStringHash();
             
             
-            Map<StringHash, PackageDownload>::Iterator i = downloads_.Find(nameHash);
+            HashMap<StringHash, PackageDownload>::Iterator i = downloads_.Find(nameHash);
             // In case of being unable to create the package file into the cache, we will still receive all data from the server.
             // In case of being unable to create the package file into the cache, we will still receive all data from the server.
             // Simply disregard it
             // Simply disregard it
             if (i == downloads_.End())
             if (i == downloads_.End())
@@ -979,7 +979,7 @@ unsigned Connection::GetNumDownloads() const
 
 
 const String& Connection::GetDownloadName() const
 const String& Connection::GetDownloadName() const
 {
 {
-    for (Map<StringHash, PackageDownload>::ConstIterator i = downloads_.Begin(); i != downloads_.End(); ++i)
+    for (HashMap<StringHash, PackageDownload>::ConstIterator i = downloads_.Begin(); i != downloads_.End(); ++i)
     {
     {
         if (i->second_.initiated_)
         if (i->second_.initiated_)
             return i->second_.name_;
             return i->second_.name_;
@@ -989,7 +989,7 @@ const String& Connection::GetDownloadName() const
 
 
 float Connection::GetDownloadProgress() const
 float Connection::GetDownloadProgress() const
 {
 {
-    for (Map<StringHash, PackageDownload>::ConstIterator i = downloads_.Begin(); i != downloads_.End(); ++i)
+    for (HashMap<StringHash, PackageDownload>::ConstIterator i = downloads_.Begin(); i != downloads_.End(); ++i)
     {
     {
         if (i->second_.initiated_)
         if (i->second_.initiated_)
             return (float)i->second_.receivedFragments_.Size() / (float)i->second_.totalFragments_;
             return (float)i->second_.receivedFragments_.Size() / (float)i->second_.totalFragments_;
@@ -1006,7 +1006,7 @@ void Connection::HandleAsyncLoadFinished(StringHash eventType, VariantMap& event
     SendMessage(MSG_SCENELOADED, true, true, msg_, NET_HIGH_PRIORITY);
     SendMessage(MSG_SCENELOADED, true, true, msg_, NET_HIGH_PRIORITY);
 }
 }
 
 
-void Connection::ProcessNode(Node* node)
+void Connection::ProcessNode(unsigned serverFrameNumber, Node* node)
 {
 {
     if (!node || processedNodes_.Contains(node))
     if (!node || processedNodes_.Contains(node))
         return;
         return;
@@ -1017,16 +1017,16 @@ void Connection::ProcessNode(Node* node)
     PODVector<Node*> dependencyNodes;
     PODVector<Node*> dependencyNodes;
     node->GetDependencyNodes(dependencyNodes);
     node->GetDependencyNodes(dependencyNodes);
     for (PODVector<Node*>::ConstIterator i = dependencyNodes.Begin(); i != dependencyNodes.End(); ++i)
     for (PODVector<Node*>::ConstIterator i = dependencyNodes.Begin(); i != dependencyNodes.End(); ++i)
-        ProcessNode(*i);
+        ProcessNode(serverFrameNumber, *i);
     
     
     // Check if the client's replication state already has this node
     // Check if the client's replication state already has this node
     if (sceneState_.Contains(node->GetID()))
     if (sceneState_.Contains(node->GetID()))
-        ProcessExistingNode(node);
+        ProcessExistingNode(serverFrameNumber, node);
     else
     else
-        ProcessNewNode(node);
+        ProcessNewNode(serverFrameNumber, node);
 }
 }
 
 
-void Connection::ProcessNewNode(Node* node)
+void Connection::ProcessNewNode(unsigned serverFrameNumber, Node* node)
 {
 {
     msg_.Clear();
     msg_.Clear();
     msg_.WriteNetID(node->GetID());
     msg_.WriteNetID(node->GetID());
@@ -1036,7 +1036,7 @@ void Connection::ProcessNewNode(Node* node)
     nodeState.frameNumber_ = frameNumber_;
     nodeState.frameNumber_ = frameNumber_;
     
     
     // Write node's attributes
     // Write node's attributes
-    node->WriteInitialDeltaUpdate(msg_, deltaUpdateBits_, nodeState.attributes_);
+    node->WriteInitialDeltaUpdate(serverFrameNumber, msg_, deltaUpdateBits_, nodeState.attributes_);
     
     
     // Write node's user variables
     // Write node's user variables
     const VariantMap& vars = node->GetVars();
     const VariantMap& vars = node->GetVars();
@@ -1064,13 +1064,13 @@ void Connection::ProcessNewNode(Node* node)
         
         
         msg_.WriteShortStringHash(component->GetType());
         msg_.WriteShortStringHash(component->GetType());
         msg_.WriteNetID(component->GetID());
         msg_.WriteNetID(component->GetID());
-        component->WriteInitialDeltaUpdate(msg_, deltaUpdateBits_, componentState.attributes_);
+        component->WriteInitialDeltaUpdate(serverFrameNumber, msg_, deltaUpdateBits_, componentState.attributes_);
     }
     }
     
     
     SendMessage(MSG_CREATENODE, true, true, msg_, NET_HIGH_PRIORITY);
     SendMessage(MSG_CREATENODE, true, true, msg_, NET_HIGH_PRIORITY);
 }
 }
 
 
-void Connection::ProcessExistingNode(Node* node)
+void Connection::ProcessExistingNode(unsigned serverFrameNumber, Node* node)
 {
 {
     NodeReplicationState& nodeState = sceneState_[node->GetID()];
     NodeReplicationState& nodeState = sceneState_[node->GetID()];
     nodeState.frameNumber_ = frameNumber_;
     nodeState.frameNumber_ = frameNumber_;
@@ -1086,7 +1086,7 @@ void Connection::ProcessExistingNode(Node* node)
     
     
     // Check if attributes have changed
     // Check if attributes have changed
     bool deltaUpdate, latestData;
     bool deltaUpdate, latestData;
-    node->PrepareUpdates(deltaUpdateBits_, classCurrentState_[node->GetType()], nodeState.attributes_, deltaUpdate, latestData);
+    node->PrepareUpdates(serverFrameNumber, deltaUpdateBits_, nodeState.attributes_, deltaUpdate, latestData);
     
     
     // Check if user variables have changed. Note: variable removal is not supported
     // Check if user variables have changed. Note: variable removal is not supported
     changedVars_.Clear();
     changedVars_.Clear();
@@ -1141,7 +1141,7 @@ void Connection::ProcessExistingNode(Node* node)
         if (component->GetID() >= FIRST_LOCAL_ID)
         if (component->GetID() >= FIRST_LOCAL_ID)
             continue;
             continue;
         
         
-        Map<unsigned, ComponentReplicationState>::Iterator j = nodeState.components_.Find(component->GetID());
+        HashMap<unsigned, ComponentReplicationState>::Iterator j = nodeState.components_.Find(component->GetID());
         if (j == nodeState.components_.End())
         if (j == nodeState.components_.End())
         {
         {
             // New component
             // New component
@@ -1153,7 +1153,7 @@ void Connection::ProcessExistingNode(Node* node)
             msg_.WriteNetID(node->GetID());
             msg_.WriteNetID(node->GetID());
             msg_.WriteShortStringHash(component->GetType());
             msg_.WriteShortStringHash(component->GetType());
             msg_.WriteNetID(component->GetID());
             msg_.WriteNetID(component->GetID());
-            component->WriteInitialDeltaUpdate(msg_, deltaUpdateBits_, componentState.attributes_);
+            component->WriteInitialDeltaUpdate(serverFrameNumber, msg_, deltaUpdateBits_, componentState.attributes_);
             
             
             SendMessage(MSG_CREATECOMPONENT, true, true, msg_, NET_HIGH_PRIORITY);
             SendMessage(MSG_CREATECOMPONENT, true, true, msg_, NET_HIGH_PRIORITY);
         }
         }
@@ -1163,8 +1163,7 @@ void Connection::ProcessExistingNode(Node* node)
             ComponentReplicationState& componentState = j->second_;
             ComponentReplicationState& componentState = j->second_;
             componentState.frameNumber_ = frameNumber_;
             componentState.frameNumber_ = frameNumber_;
             
             
-            component->PrepareUpdates(deltaUpdateBits_, classCurrentState_[component->GetType()], componentState.attributes_,
-                deltaUpdate, latestData);
+            component->PrepareUpdates(serverFrameNumber, deltaUpdateBits_, componentState.attributes_, deltaUpdate, latestData);
             
             
             // Send deltaupdate message if necessary
             // Send deltaupdate message if necessary
             if (deltaUpdate)
             if (deltaUpdate)
@@ -1190,9 +1189,9 @@ void Connection::ProcessExistingNode(Node* node)
     }
     }
     
     
     // Check for removed components
     // Check for removed components
-    for (Map<unsigned, ComponentReplicationState>::Iterator i = nodeState.components_.Begin(); i != nodeState.components_.End();)
+    for (HashMap<unsigned, ComponentReplicationState>::Iterator i = nodeState.components_.Begin(); i != nodeState.components_.End();)
     {
     {
-        Map<unsigned, ComponentReplicationState>::Iterator current = i++;
+        HashMap<unsigned, ComponentReplicationState>::Iterator current = i++;
         if (current->second_.frameNumber_ != frameNumber_)
         if (current->second_.frameNumber_ != frameNumber_)
         {
         {
             msg_.Clear();
             msg_.Clear();

+ 7 - 9
Engine/Network/Connection.h

@@ -124,7 +124,7 @@ public:
     /// Disconnect. If wait time is non-zero, will block while waiting for disconnect to finish.
     /// Disconnect. If wait time is non-zero, will block while waiting for disconnect to finish.
     void Disconnect(int waitMSec = 0);
     void Disconnect(int waitMSec = 0);
     /// Send scene update messages. Called by Network.
     /// Send scene update messages. Called by Network.
-    void SendServerUpdate();
+    void SendServerUpdate(unsigned serverFrameNumber);
     /// Send latest controls from the client. Called by Network.
     /// Send latest controls from the client. Called by Network.
     void SendClientUpdate();
     void SendClientUpdate();
     /// Send queued remote events. Called by Network.
     /// Send queued remote events. Called by Network.
@@ -194,11 +194,11 @@ private:
     /// Handle scene loaded event.
     /// Handle scene loaded event.
     void HandleAsyncLoadFinished(StringHash eventType, VariantMap& eventData);
     void HandleAsyncLoadFinished(StringHash eventType, VariantMap& eventData);
     /// Process a node for sending a network update. Recurses to process depended on node(s) first.
     /// Process a node for sending a network update. Recurses to process depended on node(s) first.
-    void ProcessNode(Node* node);
+    void ProcessNode(unsigned serverFrameNumber, Node* node);
     /// Process a node that the client had not yet received.
     /// Process a node that the client had not yet received.
-    void ProcessNewNode(Node* node);
+    void ProcessNewNode(unsigned serverFrameNumber, Node* node);
     /// Process a node that the client has already received.
     /// Process a node that the client has already received.
-    void ProcessExistingNode(Node* node);
+    void ProcessExistingNode(unsigned serverFrameNumber, Node* node);
     /// Initiate a package download.
     /// Initiate a package download.
     void RequestPackage(const String& name, unsigned fileSize, unsigned checksum);
     void RequestPackage(const String& name, unsigned fileSize, unsigned checksum);
     /// Send an error reply for a package download.
     /// Send an error reply for a package download.
@@ -215,13 +215,11 @@ private:
     /// Scene.
     /// Scene.
     WeakPtr<Scene> scene_;
     WeakPtr<Scene> scene_;
     /// Last sent state of the scene for network replication.
     /// Last sent state of the scene for network replication.
-    Map<unsigned, NodeReplicationState> sceneState_;
-    /// Preallocated attribute variants per networked object class for sending updates.
-    Map<ShortStringHash, Vector<Variant> > classCurrentState_;
+    HashMap<unsigned, NodeReplicationState> sceneState_;
     /// Waiting or ongoing package file receive transfers.
     /// Waiting or ongoing package file receive transfers.
-    Map<StringHash, PackageDownload> downloads_;
+    HashMap<StringHash, PackageDownload> downloads_;
     /// Ongoing package send transfers.
     /// Ongoing package send transfers.
-    Map<StringHash, PackageUpload> uploads_;
+    HashMap<StringHash, PackageUpload> uploads_;
     /// Pending latest data for not yet received nodes.
     /// Pending latest data for not yet received nodes.
     HashMap<unsigned, PODVector<unsigned char> > nodeLatestData_;
     HashMap<unsigned, PODVector<unsigned char> > nodeLatestData_;
     /// Pending latest data for not yet received components.
     /// Pending latest data for not yet received components.

+ 8 - 2
Engine/Network/Network.cpp

@@ -47,7 +47,8 @@ Network::Network(Context* context) :
     Object(context),
     Object(context),
     updateFps_(DEFAULT_UPDATE_FPS),
     updateFps_(DEFAULT_UPDATE_FPS),
     updateInterval_(1.0f / (float)DEFAULT_UPDATE_FPS),
     updateInterval_(1.0f / (float)DEFAULT_UPDATE_FPS),
-    updateAcc_(0.0f)
+    updateAcc_(0.0f),
+    frameNumber_(1)
 {
 {
     network_ = new kNet::Network();
     network_ = new kNet::Network();
     
     
@@ -427,7 +428,7 @@ void Network::PostUpdate(float timeStep)
             for (HashMap<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = clientConnections_.Begin();
             for (HashMap<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = clientConnections_.Begin();
                 i != clientConnections_.End(); ++i)
                 i != clientConnections_.End(); ++i)
             {
             {
-                i->second_->SendServerUpdate();
+                i->second_->SendServerUpdate(frameNumber_);
                 i->second_->SendRemoteEvents();
                 i->second_->SendRemoteEvents();
                 i->second_->SendPackages();
                 i->second_->SendPackages();
             }
             }
@@ -442,6 +443,11 @@ void Network::PostUpdate(float timeStep)
         
         
         // Notify that the update was sent
         // Notify that the update was sent
         SendEvent(E_NETWORKUPDATESENT);
         SendEvent(E_NETWORKUPDATESENT);
+        
+        // Increment server frame number. Wrap to 1 as 0 means "update never sent" for Serializables
+        ++frameNumber_;
+        if (!frameNumber_)
+            ++frameNumber_;
     }
     }
 }
 }
 
 

+ 2 - 0
Engine/Network/Network.h

@@ -132,6 +132,8 @@ private:
     float updateInterval_;
     float updateInterval_;
     /// Update time accumulator.
     /// Update time accumulator.
     float updateAcc_;
     float updateAcc_;
+    /// Server frame number.
+    unsigned frameNumber_;
     /// Package cache directory.
     /// Package cache directory.
     String packageCacheDir_;
     String packageCacheDir_;
 };
 };

+ 1 - 1
Engine/Network/ReplicationState.h

@@ -44,7 +44,7 @@ struct NodeReplicationState
     /// User variables.
     /// User variables.
     VariantMap vars_;
     VariantMap vars_;
     /// Components by ID.
     /// Components by ID.
-    Map<unsigned, ComponentReplicationState> components_;
+    HashMap<unsigned, ComponentReplicationState> components_;
     /// Interest management priority accumulator.
     /// Interest management priority accumulator.
     float priorityAcc_;
     float priorityAcc_;
     /// Update frame number.
     /// Update frame number.

+ 31 - 17
Engine/Scene/Serializable.cpp

@@ -35,6 +35,7 @@ OBJECTTYPESTATIC(Serializable);
 
 
 Serializable::Serializable(Context* context) :
 Serializable::Serializable(Context* context) :
     Object(context),
     Object(context),
+    lastServerFrameNumber_(0),
     loading_(false)
     loading_(false)
 {
 {
 }
 }
@@ -419,7 +420,7 @@ bool Serializable::SetAttribute(const String& name, const Variant& value)
     return false;
     return false;
 }
 }
 
 
-void Serializable::WriteInitialDeltaUpdate(Serializer& dest, PODVector<unsigned char>& deltaUpdateBits,
+void Serializable::WriteInitialDeltaUpdate(unsigned serverFrameNumber, Serializer& dest, PODVector<unsigned char>& deltaUpdateBits,
     Vector<Variant>& replicationState)
     Vector<Variant>& replicationState)
 {
 {
     const Vector<AttributeInfo>* attributes = GetNetworkAttributes();
     const Vector<AttributeInfo>* attributes = GetNetworkAttributes();
@@ -427,17 +428,28 @@ void Serializable::WriteInitialDeltaUpdate(Serializer& dest, PODVector<unsigned
         return;
         return;
     unsigned numAttributes = attributes->Size();
     unsigned numAttributes = attributes->Size();
     
     
+    // Get current attribute values from the component if necessary
+    if (serverFrameNumber != lastServerFrameNumber_ || serverAttributes_.Empty())
+    {
+        serverAttributes_.Resize(numAttributes);
+        for (unsigned i = 0; i < numAttributes; ++i)
+        {
+            const AttributeInfo& attr = attributes->At(i);
+            OnGetAttribute(attr, serverAttributes_[i]);
+        }
+        lastServerFrameNumber_ = serverFrameNumber;
+    }
+    
     replicationState.Resize(numAttributes);
     replicationState.Resize(numAttributes);
     deltaUpdateBits.Resize((numAttributes + 7) >> 3);
     deltaUpdateBits.Resize((numAttributes + 7) >> 3);
     for (unsigned i = 0; i < deltaUpdateBits.Size(); ++i)
     for (unsigned i = 0; i < deltaUpdateBits.Size(); ++i)
         deltaUpdateBits[i] = 0;
         deltaUpdateBits[i] = 0;
     
     
-    // Set initial attribute values and compare against defaults
+    // Copy attributes to replication state and compare against defaults
     for (unsigned i = 0; i < numAttributes; ++i)
     for (unsigned i = 0; i < numAttributes; ++i)
     {
     {
         const AttributeInfo& attr = attributes->At(i);
         const AttributeInfo& attr = attributes->At(i);
-        
-        OnGetAttribute(attr, replicationState[i]);
+        replicationState[i] = serverAttributes_[i];
         if (replicationState[i] != attr.defaultValue_)
         if (replicationState[i] != attr.defaultValue_)
             deltaUpdateBits[i >> 3] |= (1 << (i & 7));
             deltaUpdateBits[i >> 3] |= (1 << (i & 7));
     }
     }
@@ -447,14 +459,12 @@ void Serializable::WriteInitialDeltaUpdate(Serializer& dest, PODVector<unsigned
     
     
     for (unsigned i = 0; i < numAttributes; ++i)
     for (unsigned i = 0; i < numAttributes; ++i)
     {
     {
-        const AttributeInfo& attr = attributes->At(i);
-        
         if (deltaUpdateBits[i >> 3] & (1 << (i & 7)))
         if (deltaUpdateBits[i >> 3] & (1 << (i & 7)))
             dest.WriteVariantData(replicationState[i]);
             dest.WriteVariantData(replicationState[i]);
     }
     }
 }
 }
 
 
-void Serializable::PrepareUpdates(PODVector<unsigned char>& deltaUpdateBits, Vector<Variant>& classCurrentState,
+void Serializable::PrepareUpdates(unsigned serverFrameNumber, PODVector<unsigned char>& deltaUpdateBits,
     Vector<Variant>& replicationState, bool& deltaUpdate, bool& latestData)
     Vector<Variant>& replicationState, bool& deltaUpdate, bool& latestData)
 {
 {
     deltaUpdate = false;
     deltaUpdate = false;
@@ -465,23 +475,27 @@ void Serializable::PrepareUpdates(PODVector<unsigned char>& deltaUpdateBits, Vec
         return;
         return;
     unsigned numAttributes = attributes->Size();
     unsigned numAttributes = attributes->Size();
     
     
+    // Get current attribute values from the component if necessary
+    if (serverFrameNumber != lastServerFrameNumber_)
+    {
+        for (unsigned i = 0; i < numAttributes; ++i)
+        {
+            const AttributeInfo& attr = attributes->At(i);
+            OnGetAttribute(attr, serverAttributes_[i]);
+        }
+        lastServerFrameNumber_ = serverFrameNumber;
+    }
+    
     deltaUpdateBits.Resize((numAttributes + 7) >> 3);
     deltaUpdateBits.Resize((numAttributes + 7) >> 3);
     for (unsigned i = 0; i < deltaUpdateBits.Size(); ++i)
     for (unsigned i = 0; i < deltaUpdateBits.Size(); ++i)
         deltaUpdateBits[i] = 0;
         deltaUpdateBits[i] = 0;
     
     
-    // If class-specific current state has not been previously used, resize it now
-    if (classCurrentState.Empty())
-        classCurrentState.Resize(numAttributes);
-    
     for (unsigned i = 0; i < numAttributes; ++i)
     for (unsigned i = 0; i < numAttributes; ++i)
     {
     {
-        const AttributeInfo& attr = attributes->At(i);
-        
-        // Check for attribute change
-        OnGetAttribute(attr, classCurrentState[i]);
-        if (classCurrentState[i] != replicationState[i])
+        if (serverAttributes_[i] != replicationState[i])
         {
         {
-            replicationState[i] = classCurrentState[i];
+            const AttributeInfo& attr = attributes->At(i);
+            replicationState[i] = serverAttributes_[i];
             if (attr.mode_ & AM_LATESTDATA)
             if (attr.mode_ & AM_LATESTDATA)
                 latestData = true;
                 latestData = true;
             else
             else

+ 6 - 2
Engine/Scene/Serializable.h

@@ -63,9 +63,9 @@ public:
     /// %Set attribute by name. Return true if successfully set.
     /// %Set attribute by name. Return true if successfully set.
     bool SetAttribute(const String& name, const Variant& value);
     bool SetAttribute(const String& name, const Variant& value);
     /// Write initial delta network update (compared to default attribute values) and prepare the last sent state.
     /// Write initial delta network update (compared to default attribute values) and prepare the last sent state.
-    void WriteInitialDeltaUpdate(Serializer& dest, PODVector<unsigned char>& deltaUpdateBits, Vector<Variant>& replicationState);
+    void WriteInitialDeltaUpdate(unsigned serverFrameNumber, Serializer& dest, PODVector<unsigned char>& deltaUpdateBits, Vector<Variant>& replicationState);
     /// Prepare delta and latest data network updates. Needs a previously prepared last sent state from WriteInitialDeltaUpdate().
     /// Prepare delta and latest data network updates. Needs a previously prepared last sent state from WriteInitialDeltaUpdate().
-    void PrepareUpdates(PODVector<unsigned char>& deltaUpdateBits, Vector<Variant>& classCurrentState, Vector<Variant>& replicationState, bool& deltaUpdate, bool& latestData);
+    void PrepareUpdates(unsigned serverFrameNumber, PODVector<unsigned char>& deltaUpdateBits, Vector<Variant>& replicationState, bool& deltaUpdate, bool& latestData);
     /// Write a delta network update prepared with PrepareUpdates().
     /// Write a delta network update prepared with PrepareUpdates().
     void WriteDeltaUpdate(Serializer& dest, PODVector<unsigned char>& deltaUpdateBits, Vector<Variant>& replicationState);
     void WriteDeltaUpdate(Serializer& dest, PODVector<unsigned char>& deltaUpdateBits, Vector<Variant>& replicationState);
     /// Write a latestdata network update prepared with PrepareUpdates().
     /// Write a latestdata network update prepared with PrepareUpdates().
@@ -91,6 +91,10 @@ public:
     bool IsLoading() const { return loading_; }
     bool IsLoading() const { return loading_; }
     
     
 protected:
 protected:
+    /// Server-side attributes for sending updates. Only updated once per network frame, not per user.
+    Vector<Variant> serverAttributes_;
+    /// Last network frame number.
+    unsigned lastServerFrameNumber_;
     /// Is loading flag.
     /// Is loading flag.
     bool loading_;
     bool loading_;
 };
 };