Browse Source

Refactoring of how network updates to clients are collected. This removes costly, redundant Variant compares that were performed per-user.

Lasse Öörni 13 years ago
parent
commit
1c2cc43f14

+ 2 - 2
Engine/Container/Hash.h

@@ -26,13 +26,13 @@
 /// Pointer hash function.
 template <class T> unsigned MakeHash(T* value)
 {
-    return (unsigned)value;
+    return (unsigned)value / sizeof(T);
 }
 
 /// Const pointer hash function.
 template <class T> unsigned MakeHash(const T* value)
 {
-    return (unsigned)value;
+    return (unsigned)value / sizeof(T);
 }
 
 /// Generic hash function.

+ 2 - 2
Engine/Engine/NetworkAPI.cpp

@@ -126,7 +126,7 @@ static Network* GetNetwork()
 
 static CScriptArray* NetworkGetClientConnections(Network* ptr)
 {
-    const HashMap<kNet::MessageConnection*, SharedPtr<Connection> >& connections = ptr->GetClientConnections();
+    const Map<kNet::MessageConnection*, SharedPtr<Connection> >& connections = ptr->GetClientConnections();
     
     asIScriptContext *context = asGetActiveContext();
     if (context)
@@ -135,7 +135,7 @@ static CScriptArray* NetworkGetClientConnections(Network* ptr)
         CScriptArray* arr = new CScriptArray(connections.Size(), type);
         
         unsigned index = 0;
-        for (HashMap<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = connections.Begin();
+        for (Map<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = connections.Begin();
             i != connections.End(); ++i)
         {
             // Increment reference count for storing in the array

+ 165 - 117
Engine/Network/Connection.cpp

@@ -69,6 +69,7 @@ Connection::Connection(Context* context, bool isClient, kNet::SharedPtr<kNet::Me
     sceneLoaded_(false),
     logStatistics_(false)
 {
+    sceneState_.connection_ = this;
 }
 
 Connection::~Connection()
@@ -147,8 +148,8 @@ void Connection::SetScene(Scene* newScene)
 {
     if (scene_)
     {
-        // Reset the owner reference from the previous scene's nodes
-        scene_->ResetOwner(this);
+        // Remove replication states and owner references from the previous scene
+        scene_->CleanupConnection(this);
     }
     
     scene_ = newScene;
@@ -215,38 +216,45 @@ void Connection::Disconnect(int waitMSec)
     connection_->Disconnect(waitMSec);
 }
 
-void Connection::SendServerUpdate(unsigned frameNumber)
+void Connection::SendServerUpdate()
 {
     if (!scene_ || !sceneLoaded_)
         return;
     
     PROFILE(SendServerUpdate);
     
-    const HashMap<unsigned, Node*>& nodes = scene_->GetReplicatedNodes();
+    // Always check the root node (scene) first so that the scene-wide components get sent first
+    nodesToProcess_.Clear();
+    nodesToProcess_.Insert(scene_->GetID());
+    ProcessNode(scene_);
     
-    // Check for new or changed nodes
-    // Start from the root node (scene) so that the scene-wide components get sent first
-    processedNodes_.Clear();
-    ProcessNode(frameNumber, scene_);
+    // Then go through all dirtied nodes
+    nodesToProcess_.Insert(sceneState_.dirtyNodes_);
+    nodesToProcess_.Erase(scene_->GetID()); // Do not process the root node twice
     
-    // Then go through the rest of the nodes
-    for (HashMap<unsigned, Node*>::ConstIterator i = nodes.Begin(); i != nodes.End() && i->first_ < FIRST_LOCAL_ID; ++i)
-        ProcessNode(frameNumber, i->second_);
-    
-    // Check for removed nodes
-    for (HashMap<unsigned, NodeReplicationState>::Iterator i = sceneState_.Begin(); i != sceneState_.End();)
+    while (nodesToProcess_.Size())
     {
-        HashMap<unsigned, NodeReplicationState>::Iterator current = i++;
-        if (current->second_.frameNumber_ != frameNumber)
+        unsigned id = nodesToProcess_.Front();
+        Node* node = scene_->GetNode(id);
+        if (node)
+            ProcessNode(node);
+        else
         {
-            msg_.Clear();
-            msg_.WriteNetID(current->first_);
+            nodesToProcess_.Erase(id);
             
-            // Note: we will send MSG_REMOVENODE redundantly for each node in the hierarchy, even if removing the root node
-            // would be enough. However, this may be better due to the client not possibly having updated parenting information
-            // at the time of receiving this message
-            SendMessage(MSG_REMOVENODE, true, true, msg_, NET_HIGH_PRIORITY);
-            sceneState_.Erase(current);
+            // If node is dirty, but is no longer found, it has been removed
+            HashMap<unsigned, NodeReplicationState>::Iterator j = sceneState_.nodeStates_.Find(id);
+            if (j != sceneState_.nodeStates_.End())
+            {
+                msg_.Clear();
+                msg_.WriteNetID(id);
+                
+                // Note: we will send MSG_REMOVENODE redundantly for each node in the hierarchy, even if removing the root node
+                // would be enough. However, this may be better due to the client not possibly having updated parenting information
+                // at the time of receiving this message
+                SendMessage(MSG_REMOVENODE, true, true, msg_, NET_HIGH_PRIORITY);
+                sceneState_.nodeStates_.Erase(id);
+            }
         }
     }
 }
@@ -502,7 +510,7 @@ void Connection::ProcessSceneUpdate(int msgID, MemoryBuffer& msg)
             }
             
             // Read initial attributes, then snap the motion smoothing immediately to the end
-            node->ReadDeltaUpdate(msg, deltaUpdateBits_);
+            node->ReadDeltaUpdate(msg);
             SmoothedTransform* transform = node->GetComponent<SmoothedTransform>();
             if (transform)
                 transform->Update(1.0f, 0.0f);
@@ -544,7 +552,7 @@ void Connection::ProcessSceneUpdate(int msgID, MemoryBuffer& msg)
                 }
                 
                 // Read initial attributes and apply
-                component->ReadDeltaUpdate(msg, deltaUpdateBits_);
+                component->ReadDeltaUpdate(msg);
                 component->ApplyAttributes();
             }
         }
@@ -556,7 +564,7 @@ void Connection::ProcessSceneUpdate(int msgID, MemoryBuffer& msg)
             Node* node = scene_->GetNode(nodeID);
             if (node)
             {
-                node->ReadDeltaUpdate(msg, deltaUpdateBits_);
+                node->ReadDeltaUpdate(msg);
                 // ApplyAttributes() is deliberately skipped, as Node has no attributes that require late applying.
                 // Furthermore it would propagate to components and child nodes, which is not desired in this case
                 unsigned changedVars = msg.ReadVLE();
@@ -630,7 +638,7 @@ void Connection::ProcessSceneUpdate(int msgID, MemoryBuffer& msg)
                 }
                 
                 // Read initial attributes and apply
-                component->ReadDeltaUpdate(msg, deltaUpdateBits_);
+                component->ReadDeltaUpdate(msg);
                 component->ApplyAttributes();
             }
             else
@@ -644,7 +652,7 @@ void Connection::ProcessSceneUpdate(int msgID, MemoryBuffer& msg)
             Component* component = scene_->GetComponent(componentID);
             if (component)
             {
-                component->ReadDeltaUpdate(msg, deltaUpdateBits_);
+                component->ReadDeltaUpdate(msg);
                 component->ApplyAttributes();
             }
             else
@@ -1003,37 +1011,42 @@ void Connection::HandleAsyncLoadFinished(StringHash eventType, VariantMap& event
     SendMessage(MSG_SCENELOADED, true, true, msg_, NET_HIGH_PRIORITY);
 }
 
-void Connection::ProcessNode(unsigned frameNumber, Node* node)
+void Connection::ProcessNode(Node* node)
 {
-    if (!node || processedNodes_.Contains(node))
+    if (!node || !nodesToProcess_.Contains(node->GetID()))
         return;
     
-    processedNodes_.Insert(node);
+    nodesToProcess_.Erase(node->GetID());
     
-    // Process depended upon nodes first
-    const PODVector<Node*>& dependencyNodes = node->GetDependencyNodes(frameNumber);
+    // Process depended upon nodes first, if they are dirty
+    const PODVector<Node*>& dependencyNodes = node->GetDependencyNodes();
     for (PODVector<Node*>::ConstIterator i = dependencyNodes.Begin(); i != dependencyNodes.End(); ++i)
-        ProcessNode(frameNumber, *i);
+    {
+        if (sceneState_.dirtyNodes_.Contains((*i)->GetID()))
+            ProcessNode(*i);
+    }
     
     // Check if the client's replication state already has this node
-    HashMap<unsigned, NodeReplicationState>::Iterator i = sceneState_.Find(node->GetID());
-    if (i != sceneState_.End())
-        ProcessExistingNode(frameNumber, node, i->second_);
+    HashMap<unsigned, NodeReplicationState>::Iterator i = sceneState_.nodeStates_.Find(node->GetID());
+    if (i != sceneState_.nodeStates_.End())
+        ProcessExistingNode(node, i->second_);
     else
-        ProcessNewNode(frameNumber, node);
+        ProcessNewNode(node);
 }
 
-void Connection::ProcessNewNode(unsigned frameNumber, Node* node)
+void Connection::ProcessNewNode(Node* node)
 {
     msg_.Clear();
     msg_.WriteNetID(node->GetID());
     
-    NodeReplicationState& nodeState = sceneState_[node->GetID()];
-    nodeState.priorityAcc_ = 0.0f;
-    nodeState.frameNumber_ = frameNumber;
+    NodeReplicationState& nodeState = sceneState_.nodeStates_[node->GetID()];
+    nodeState.connection_ = this;
+    nodeState.sceneState_ = &sceneState_;
+    nodeState.node_ = node;
+    node->AddReplicationState(&nodeState);
     
     // Write node's attributes
-    node->WriteInitialDeltaUpdate(frameNumber, msg_, deltaUpdateBits_, nodeState.attributes_);
+    node->WriteInitialDeltaUpdate(msg_);
     
     // Write node's user variables
     const VariantMap& vars = node->GetVars();
@@ -1042,7 +1055,6 @@ void Connection::ProcessNewNode(unsigned frameNumber, Node* node)
     {
         msg_.WriteShortStringHash(i->first_);
         msg_.WriteVariant(i->second_);
-        nodeState.vars_[i->first_] = i->second_;
     }
     
     // Write node's components
@@ -1055,22 +1067,25 @@ void Connection::ProcessNewNode(unsigned frameNumber, Node* node)
         if (component->GetID() >= FIRST_LOCAL_ID)
             continue;
         
-        ComponentReplicationState& componentState = nodeState.components_[component->GetID()];
-        componentState.frameNumber_ = frameNumber;
-        componentState.type_ = component->GetType();
+        ComponentReplicationState& componentState = nodeState.componentStates_[component->GetID()];
+        componentState.connection_ = this;
+        componentState.nodeState_ = &nodeState;
+        componentState.component_ = component;
+        component->AddReplicationState(&componentState);
         
         msg_.WriteShortStringHash(component->GetType());
         msg_.WriteNetID(component->GetID());
-        component->WriteInitialDeltaUpdate(frameNumber, msg_, deltaUpdateBits_, componentState.attributes_);
+        component->WriteInitialDeltaUpdate(msg_);
     }
     
     SendMessage(MSG_CREATENODE, true, true, msg_, NET_HIGH_PRIORITY);
+    
+    nodeState.markedDirty_ = false;
+    sceneState_.dirtyNodes_.Erase(node->GetID());
 }
 
-void Connection::ProcessExistingNode(unsigned frameNumber, Node* node, NodeReplicationState& nodeState)
+void Connection::ProcessExistingNode(Node* node, NodeReplicationState& nodeState)
 {
-    nodeState.frameNumber_ = frameNumber;
-    
     // Check from the interest management component, if exists, whether should update
     NetworkPriority* priority = node->GetComponent<NetworkPriority>();
     if (priority && (!priority->GetAlwaysUpdateOwner() || node->GetOwner() != this))
@@ -1081,51 +1096,63 @@ void Connection::ProcessExistingNode(unsigned frameNumber, Node* node, NodeRepli
     }
     
     // Check if attributes have changed
-    bool deltaUpdate, latestData;
-    node->PrepareUpdates(frameNumber, deltaUpdateBits_, nodeState.attributes_, deltaUpdate, latestData);
-    
-    // Check if user variables have changed. Note: variable removal is not supported
-    changedVars_.Clear();
-    const VariantMap& vars = node->GetVars();
-    for (VariantMap::ConstIterator i = vars.Begin(); i != vars.End(); ++i)
+    if (nodeState.dirtyAttributes_.Count())
     {
-        VariantMap::Iterator j = nodeState.vars_.Find(i->first_);
-        if (j == nodeState.vars_.End() || i->second_ != j->second_)
+        const Vector<AttributeInfo>* attributes = node->GetNetworkAttributes();
+        unsigned numAttributes = attributes->Size();
+        bool hasLatestData = false;
+        
+        for (unsigned i = 0; i < numAttributes; ++i)
         {
-            nodeState.vars_[i->first_] = i->second_;
-            changedVars_.Insert(i->first_);
-            deltaUpdate = true;
+            if (nodeState.dirtyAttributes_.IsSet(i) && (attributes->At(i).mode_ & AM_LATESTDATA))
+            {
+                hasLatestData = true;
+                nodeState.dirtyAttributes_.Clear(i);
+            }
         }
-    }
-    
-    // Send deltaupdate message if necessary
-    if (deltaUpdate)
-    {
-        msg_.Clear();
-        msg_.WriteNetID(node->GetID());
-        node->WriteDeltaUpdate(msg_, deltaUpdateBits_, nodeState.attributes_);
         
-        // Write changed variables
-        msg_.WriteVLE(changedVars_.Size());
-        for (HashSet<ShortStringHash>::ConstIterator i = changedVars_.Begin(); i != changedVars_.End(); ++i)
+        // Send latestdata message if necessary
+        if (hasLatestData)
         {
-            VariantMap::ConstIterator j = vars.Find(*i);
-            msg_.WriteShortStringHash(j->first_);
-            msg_.WriteVariant(j->second_);
+            msg_.Clear();
+            msg_.WriteNetID(node->GetID());
+            node->WriteLatestDataUpdate(msg_);
+            
+            SendMessage(MSG_NODELATESTDATA, true, false, msg_, NET_HIGH_PRIORITY, node->GetID());
         }
         
-        SendMessage(MSG_NODEDELTAUPDATE, true, true, msg_, NET_HIGH_PRIORITY);
-    }
-    
-    // Send latestdata message if necessary
-    if (latestData)
-    {
-        // If at least one latest data attribute changes, send all of them
-        msg_.Clear();
-        msg_.WriteNetID(node->GetID());
-        node->WriteLatestDataUpdate(msg_, nodeState.attributes_);
-        
-        SendMessage(MSG_NODELATESTDATA, true, false, msg_, NET_HIGH_PRIORITY, node->GetID());
+        // Send deltaupdate if remaining dirty bits, or vars have changed
+        if (nodeState.dirtyAttributes_.Count() || nodeState.dirtyVars_.Size())
+        {
+            msg_.Clear();
+            msg_.WriteNetID(node->GetID());
+            node->WriteDeltaUpdate(msg_, nodeState.dirtyAttributes_);
+            
+            // Write changed variables
+            msg_.WriteVLE(nodeState.dirtyVars_.Size());
+            const VariantMap& vars = node->GetVars();
+            for (HashSet<ShortStringHash>::ConstIterator i = nodeState.dirtyVars_.Begin(); i != nodeState.dirtyVars_.End(); ++i)
+            {
+                VariantMap::ConstIterator j = vars.Find(*i);
+                if (j != vars.End())
+                {
+                    msg_.WriteShortStringHash(j->first_);
+                    msg_.WriteVariant(j->second_);
+                }
+                else
+                {
+                    // Variable has been marked dirty, but is removed (which is unsupported): send a dummy variable in place
+                    LOGWARNING("Sending dummy user variable as original value was removed");
+                    msg_.WriteShortStringHash(ShortStringHash());
+                    msg_.WriteVariant(Variant());
+                }
+            }
+            
+            SendMessage(MSG_NODEDELTAUPDATE, true, true, msg_, NET_HIGH_PRIORITY);
+            
+            nodeState.dirtyAttributes_.ClearAll();
+            nodeState.dirtyVars_.Clear();
+        }
     }
     
     // Check for new or changed components
@@ -1137,19 +1164,21 @@ void Connection::ProcessExistingNode(unsigned frameNumber, Node* node, NodeRepli
         if (component->GetID() >= FIRST_LOCAL_ID)
             continue;
         
-        HashMap<unsigned, ComponentReplicationState>::Iterator j = nodeState.components_.Find(component->GetID());
-        if (j == nodeState.components_.End())
+        HashMap<unsigned, ComponentReplicationState>::Iterator j = nodeState.componentStates_.Find(component->GetID());
+        if (j == nodeState.componentStates_.End())
         {
             // New component
-            ComponentReplicationState& componentState = nodeState.components_[component->GetID()];
-            componentState.frameNumber_ = frameNumber;
-            componentState.type_ = component->GetType();
+            ComponentReplicationState& componentState = nodeState.componentStates_[component->GetID()];
+            componentState.connection_ = this;
+            componentState.nodeState_ = &nodeState;
+            componentState.component_ = component;
+            component->AddReplicationState(&componentState);
             
             msg_.Clear();
             msg_.WriteNetID(node->GetID());
             msg_.WriteShortStringHash(component->GetType());
             msg_.WriteNetID(component->GetID());
-            component->WriteInitialDeltaUpdate(frameNumber, msg_, deltaUpdateBits_, componentState.attributes_);
+            component->WriteInitialDeltaUpdate(msg_);
             
             SendMessage(MSG_CREATECOMPONENT, true, true, msg_, NET_HIGH_PRIORITY);
         }
@@ -1157,46 +1186,65 @@ void Connection::ProcessExistingNode(unsigned frameNumber, Node* node, NodeRepli
         {
             // Existing component
             ComponentReplicationState& componentState = j->second_;
-            componentState.frameNumber_ = frameNumber;
-            
-            component->PrepareUpdates(frameNumber, deltaUpdateBits_, componentState.attributes_, deltaUpdate, latestData);
             
-            // Send deltaupdate message if necessary
-            if (deltaUpdate)
+            // Check if attributes have changed
+            if (componentState.dirtyAttributes_.Count())
             {
-                msg_.Clear();
-                msg_.WriteNetID(component->GetID());
-                component->WriteDeltaUpdate(msg_, deltaUpdateBits_, componentState.attributes_);
+                const Vector<AttributeInfo>* attributes = component->GetNetworkAttributes();
+                unsigned numAttributes = attributes->Size();
+                bool hasLatestData = false;
                 
-                SendMessage(MSG_COMPONENTDELTAUPDATE, true, true, msg_, NET_HIGH_PRIORITY);
-            }
-            
-            // Send latestdata message if necessary
-            if (latestData)
-            {
-                // If at least one latest data attribute changes, send all of them
-                msg_.Clear();
-                msg_.WriteNetID(component->GetID());
-                component->WriteLatestDataUpdate(msg_, componentState.attributes_);
+                for (unsigned i = 0; i < numAttributes; ++i)
+                {
+                    if (componentState.dirtyAttributes_.IsSet(i) && (attributes->At(i).mode_ & AM_LATESTDATA))
+                    {
+                        hasLatestData = true;
+                        componentState.dirtyAttributes_.Clear(i);
+                    }
+                }
+                
+                // Send latestdata message if necessary
+                if (hasLatestData)
+                {
+                    msg_.Clear();
+                    msg_.WriteNetID(component->GetID());
+                    component->WriteLatestDataUpdate(msg_);
+                    
+                    SendMessage(MSG_COMPONENTLATESTDATA, true, false, msg_, NET_HIGH_PRIORITY, component->GetID());
+                }
                 
-                SendMessage(MSG_COMPONENTLATESTDATA, true, false, msg_, NET_HIGH_PRIORITY, component->GetID());
+                // Send deltaupdate if remaining dirty bits, or vars have changed
+                if (componentState.dirtyAttributes_.Count())
+                {
+                    msg_.Clear();
+                    msg_.WriteNetID(component->GetID());
+                    component->WriteDeltaUpdate(msg_, componentState.dirtyAttributes_);
+                    
+                    SendMessage(MSG_COMPONENTDELTAUPDATE, true, true, msg_, NET_HIGH_PRIORITY);
+                    
+                    componentState.dirtyAttributes_.ClearAll();
+                }
             }
         }
     }
     
     // Check for removed components
-    for (HashMap<unsigned, ComponentReplicationState>::Iterator i = nodeState.components_.Begin(); i != nodeState.components_.End();)
+    for (HashMap<unsigned, ComponentReplicationState>::Iterator i = nodeState.componentStates_.Begin();
+        i != nodeState.componentStates_.End();)
     {
         HashMap<unsigned, ComponentReplicationState>::Iterator current = i++;
-        if (current->second_.frameNumber_ != frameNumber)
+        if (!current->second_.component_)
         {
             msg_.Clear();
             msg_.WriteNetID(current->first_);
             
             SendMessage(MSG_REMOVECOMPONENT, true, true, msg_, NET_HIGH_PRIORITY);
-            nodeState.components_.Erase(current);
+            nodeState.componentStates_.Erase(current);
         }
     }
+    
+    nodeState.markedDirty_ = false;
+    sceneState_.dirtyNodes_.Erase(node->GetID());
 }
 
 void Connection::RequestPackage(const String& name, unsigned fileSize, unsigned checksum)

+ 9 - 13
Engine/Network/Connection.h

@@ -124,7 +124,7 @@ public:
     /// Disconnect. If wait time is non-zero, will block while waiting for disconnect to finish.
     void Disconnect(int waitMSec = 0);
     /// Send scene update messages. Called by Network.
-    void SendServerUpdate(unsigned frameNumber);
+    void SendServerUpdate();
     /// Send latest controls from the client. Called by Network.
     void SendClientUpdate();
     /// Send queued remote events. Called by Network.
@@ -194,11 +194,11 @@ private:
     /// Handle scene loaded event.
     void HandleAsyncLoadFinished(StringHash eventType, VariantMap& eventData);
     /// Process a node for sending a network update. Recurses to process depended on node(s) first.
-    void ProcessNode(unsigned frameNumber, Node* node);
-    /// Process a node that the client had not yet received.
-    void ProcessNewNode(unsigned frameNumber, Node* node);
+    void ProcessNode(Node* node);
+    /// Process a node that the client has not yet received.
+    void ProcessNewNode(Node* node);
     /// Process a node that the client has already received.
-    void ProcessExistingNode(unsigned frameNumber, Node* node, NodeReplicationState& nodeState);
+    void ProcessExistingNode(Node* node, NodeReplicationState& nodeState);
     /// Initiate a package download.
     void RequestPackage(const String& name, unsigned fileSize, unsigned checksum);
     /// Send an error reply for a package download.
@@ -214,8 +214,8 @@ private:
     kNet::SharedPtr<kNet::MessageConnection> connection_;
     /// Scene.
     WeakPtr<Scene> scene_;
-    /// Last sent state of the scene for network replication.
-    HashMap<unsigned, NodeReplicationState> sceneState_;
+    /// Network replication state of the scene.
+    SceneReplicationState sceneState_;
     /// Waiting or ongoing package file receive transfers.
     HashMap<StringHash, PackageDownload> downloads_;
     /// Ongoing package send transfers.
@@ -224,14 +224,10 @@ private:
     HashMap<unsigned, PODVector<unsigned char> > nodeLatestData_;
     /// Pending latest data for not yet received components.
     HashMap<unsigned, PODVector<unsigned char> > componentLatestData_;
-    /// Node's changed user variables.
-    HashSet<ShortStringHash> changedVars_;
-    /// Already processed nodes during a replication update.
-    HashSet<Node*> processedNodes_;
+    /// Node ID's to process during a replication update.
+    HashSet<unsigned> nodesToProcess_;
     /// Reusable message buffer.
     VectorBuffer msg_;
-    /// Reusable delta update bits.
-    PODVector<unsigned char> deltaUpdateBits_;
     /// Queued remote events.
     Vector<RemoteEvent> remoteEvents_;
     /// Scene file to load once all packages (if any) have been downloaded.

+ 23 - 14
Engine/Network/Network.cpp

@@ -47,8 +47,7 @@ Network::Network(Context* context) :
     Object(context),
     updateFps_(DEFAULT_UPDATE_FPS),
     updateInterval_(1.0f / (float)DEFAULT_UPDATE_FPS),
-    updateAcc_(0.0f),
-    frameNumber_(1)
+    updateAcc_(0.0f)
 {
     network_ = new kNet::Network();
     
@@ -176,7 +175,7 @@ void Network::ClientDisconnected(kNet::MessageConnection* connection)
     connection->Disconnect(0);
     
     // Remove the client connection that corresponds to this MessageConnection
-    HashMap<kNet::MessageConnection*, SharedPtr<Connection> >::Iterator i = clientConnections_.Find(connection);
+    Map<kNet::MessageConnection*, SharedPtr<Connection> >::Iterator i = clientConnections_.Find(connection);
     if (i != clientConnections_.End())
     {
         Connection* connection = i->second_;
@@ -287,14 +286,14 @@ void Network::BroadcastMessage(int msgID, bool reliable, bool inOrder, const uns
 
 void Network::BroadcastRemoteEvent(StringHash eventType, bool inOrder, const VariantMap& eventData)
 {
-    for (HashMap<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = clientConnections_.Begin();
+    for (Map<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = clientConnections_.Begin();
         i != clientConnections_.End(); ++i)
         i->second_->SendRemoteEvent(eventType, inOrder, eventData);
 }
 
 void Network::BroadcastRemoteEvent(Scene* scene, StringHash eventType, bool inOrder, const VariantMap& eventData)
 {
-    for (HashMap<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = clientConnections_.Begin();
+    for (Map<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = clientConnections_.Begin();
         i != clientConnections_.End(); ++i)
     {
         if (i->second_->GetScene() == scene)
@@ -316,7 +315,7 @@ void Network::BroadcastRemoteEvent(Node* receiver, StringHash eventType, bool in
     }
     
     Scene* scene = receiver->GetScene();
-    for (HashMap<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = clientConnections_.Begin();
+    for (Map<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = clientConnections_.Begin();
         i != clientConnections_.End(); ++i)
     {
         if (i->second_->GetScene() == scene)
@@ -353,7 +352,7 @@ void Network::SetPackageCacheDir(const String& path)
 
 Connection* Network::GetConnection(kNet::MessageConnection* connection) const
 {
-    HashMap<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = clientConnections_.Find(connection);
+    Map<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = clientConnections_.Find(connection);
     if (i != clientConnections_.End())
         return i->second_;
     else if (serverConnection_ && serverConnection_->GetMessageConnection() == connection)
@@ -424,11 +423,26 @@ void Network::PostUpdate(float timeStep)
         
         if (IsServerRunning())
         {
+            // Collect and update all networked scenes
+            networkScenes_.Clear();
+            for (Map<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = clientConnections_.Begin();
+                i != clientConnections_.End(); ++i)
+            {
+                Scene* scene = i->second_->GetScene();
+                if (scene)
+                    networkScenes_.Insert(scene);
+            }
+            for (HashSet<Scene*>::ConstIterator i = networkScenes_.Begin(); i != networkScenes_.End(); ++i)
+            {
+                PROFILE(PrepareServerUpdate);
+                (*i)->PrepareNetworkUpdate();
+            }
+            
             // Send server updates for each client connection
-            for (HashMap<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = clientConnections_.Begin();
+            for (Map<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = clientConnections_.Begin();
                 i != clientConnections_.End(); ++i)
             {
-                i->second_->SendServerUpdate(frameNumber_);
+                i->second_->SendServerUpdate();
                 i->second_->SendRemoteEvents();
                 i->second_->SendPackages();
             }
@@ -443,11 +457,6 @@ void Network::PostUpdate(float timeStep)
         
         // Notify that the update was sent
         SendEvent(E_NETWORKUPDATESENT);
-        
-        // Increment server frame number. Wrap to 1 as 0 means "update never sent" for Serializables
-        ++frameNumber_;
-        if (!frameNumber_)
-            ++frameNumber_;
     }
 }
 

+ 5 - 4
Engine/Network/Network.h

@@ -25,6 +25,7 @@
 
 #include "Connection.h"
 #include "HashSet.h"
+#include "Map.h"
 #include "Object.h"
 #include "VectorBuffer.h"
 
@@ -95,7 +96,7 @@ public:
     /// Return the connection to the server. Null if not connected.
     Connection* GetServerConnection() const;
     /// Return all client connections.
-    const HashMap<kNet::MessageConnection*, SharedPtr<Connection> > GetClientConnections() const { return clientConnections_; }
+    const Map<kNet::MessageConnection*, SharedPtr<Connection> > GetClientConnections() const { return clientConnections_; }
     /// Return whether the server is running.
     bool IsServerRunning() const;
     /// Return whether a remote event is allowed to be sent and received. If no events are registered, all are allowed.
@@ -123,17 +124,17 @@ private:
     /// Client's server connection.
     SharedPtr<Connection> serverConnection_;
     /// Server's client connections.
-    HashMap<kNet::MessageConnection*, SharedPtr<Connection> > clientConnections_;
+    Map<kNet::MessageConnection*, SharedPtr<Connection> > clientConnections_;
     /// Allowed remote events.
     HashSet<StringHash> allowedRemoteEvents_;
+    /// Networked scenes.
+    HashSet<Scene*> networkScenes_;
     /// Update FPS.
     int updateFps_;
     /// Update time interval.
     float updateInterval_;
     /// Update time accumulator.
     float updateAcc_;
-    /// Server frame number.
-    unsigned frameNumber_;
     /// Package cache directory.
     String packageCacheDir_;
 };

+ 0 - 52
Engine/Network/ReplicationState.h

@@ -1,52 +0,0 @@
-//
-// Urho3D Engine
-// Copyright (c) 2008-2012 Lasse Öörni
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
-
-#pragma once
-
-#include "Variant.h"
-
-/// Last sent state of a component for network replication.
-struct ComponentReplicationState
-{
-    /// Component type.
-    ShortStringHash type_;
-    /// Attribute values.
-    Vector<Variant> attributes_;
-    /// Update frame number.
-    unsigned frameNumber_;
-};
-
-/// Last sent state of a node for network replication.
-struct NodeReplicationState
-{
-    /// Attribute values.
-    Vector<Variant> attributes_;
-    /// User variables.
-    VariantMap vars_;
-    /// Components by ID.
-    HashMap<unsigned, ComponentReplicationState> components_;
-    /// Interest management priority accumulator.
-    float priorityAcc_;
-    /// Update frame number.
-    unsigned frameNumber_;
-};

+ 59 - 0
Engine/Scene/Component.cpp

@@ -25,6 +25,7 @@
 #include "Component.h"
 #include "Context.h"
 #include "Node.h"
+#include "ReplicationState.h"
 #include "XMLElement.h"
 
 #include "DebugNew.h"
@@ -77,6 +78,64 @@ const Matrix3x4& Component::GetWorldTransform() const
     return node_ ? node_->GetWorldTransform() : Matrix3x4::IDENTITY;
 }
 
+void Component::AddReplicationState(ComponentReplicationState* state)
+{
+    replicationStates_.Push(state);
+}
+
+void Component::PrepareNetworkUpdate()
+{
+    const Vector<AttributeInfo>* attributes = GetNetworkAttributes();
+    if (!attributes)
+        return;
+    
+    unsigned numAttributes = attributes->Size();
+    
+    if (currentState_.Size() != numAttributes)
+    {
+        currentState_.Resize(numAttributes);
+        previousState_.Resize(numAttributes);
+        
+        // Copy the default attribute values to the previous state as a starting point
+        for (unsigned i = 0; i < numAttributes; ++i)
+            previousState_[i] = attributes->At(i).defaultValue_;
+    }
+    
+    // Check for attribute changes
+    for (unsigned i = 0; i < numAttributes; ++i)
+    {
+        const AttributeInfo& attr = attributes->At(i);
+        OnGetAttribute(attr, currentState_[i]);
+        
+        if (currentState_[i] != previousState_[i])
+        {
+            previousState_[i] = currentState_[i];
+            
+            // Mark the attribute dirty in all replication states that are tracking this component
+            for (PODVector<ComponentReplicationState*>::Iterator j = replicationStates_.Begin(); j != replicationStates_.End(); ++j)
+            {
+                (*j)->dirtyAttributes_.Set(i);
+                
+                // Add component's parent node to the dirty set if not added yet
+                if (!(*j)->nodeState_->markedDirty_)
+                {
+                    (*j)->nodeState_->markedDirty_ = true;
+                    (*j)->nodeState_->sceneState_->dirtyNodes_.Insert(node_->GetID());
+                }
+            }
+        }
+    }
+}
+
+void Component::CleanupConnection(Connection* connection)
+{
+    for (unsigned i = replicationStates_.Size() - 1; i < replicationStates_.Size(); --i)
+    {
+        if (replicationStates_[i]->connection_ == connection)
+            replicationStates_.Erase(i);
+    }
+}
+
 void Component::SetID(unsigned id)
 {
     id_ = id;

+ 11 - 0
Engine/Scene/Component.h

@@ -28,6 +28,8 @@
 
 class Node;
 
+struct ComponentReplicationState;
+
 /// Base class for components. Components can be created to scene nodes.
 class Component : public Serializable
 {
@@ -75,6 +77,13 @@ public:
     /// Template version of returning components in the same scene node by type.
     template <class T> void GetComponents(PODVector<T*>& dest) const;
     
+    /// Add a replication state that is tracking this component.
+    void AddReplicationState(ComponentReplicationState* state);
+    /// Prepare network update by comparing attributes and marking replication states dirty as necessary.
+    void PrepareNetworkUpdate();
+    /// Clean up all references to a network connection that is about to be removed.
+    void CleanupConnection(Connection* connection);
+    
 protected:
     /// Handle scene node being assigned at creation.
     virtual void OnNodeSet(Node* node) {};
@@ -89,6 +98,8 @@ protected:
     unsigned id_;
     /// Scene node.
     Node* node_;
+    /// Per-user network replication states.
+    PODVector<ComponentReplicationState*> replicationStates_;
 };
 
 template <class T> T* Component::GetComponent() const { return static_cast<T*>(GetComponent(T::GetTypeStatic())); }

+ 118 - 17
Engine/Scene/Node.cpp

@@ -27,6 +27,7 @@
 #include "Log.h"
 #include "MemoryBuffer.h"
 #include "Profiler.h"
+#include "ReplicationState.h"
 #include "Scene.h"
 #include "SmoothedTransform.h"
 #include "XMLFile.h"
@@ -48,7 +49,6 @@ Node::Node(Context* context) :
     rotation_(Quaternion::IDENTITY),
     scale_(Vector3::ONE),
     worldTransform_(Matrix3x4::IDENTITY),
-    dependencyFrameNumber_(0),
     rotateCount_(0),
     dirty_(false)
 {
@@ -211,6 +211,11 @@ void Node::ApplyAttributes()
         children_[i]->ApplyAttributes();
 }
 
+void Node::AddReplicationState(NodeReplicationState* state)
+{
+    replicationStates_.Push(state);
+}
+
 bool Node::SaveXML(Serializer& dest)
 {
     SharedPtr<XMLFile> xml(new XMLFile(context_));
@@ -550,6 +555,9 @@ void Node::RemoveComponent(Component* component)
             // If the component is still referenced elsewhere, reset its node pointer now
             if (componentWeak)
                 componentWeak->SetNode(0);
+            
+            // Mark node dirty in all replication states
+            MarkReplicationDirty();
             return;
         }
     }
@@ -557,6 +565,9 @@ void Node::RemoveComponent(Component* component)
 
 void Node::RemoveAllComponents()
 {
+    if (components_.Empty())
+        return;
+    
     while (components_.Size())
     {
         Vector<SharedPtr<Component> >::Iterator i = components_.End() - 1;
@@ -571,6 +582,9 @@ void Node::RemoveAllComponents()
         if (componentWeak)
             componentWeak->SetNode(0);
     }
+    
+    // Mark node dirty in all replication states
+    MarkReplicationDirty();
 }
 
 Node* Node::Clone(CreateMode mode)
@@ -771,30 +785,113 @@ Component* Node::GetComponent(ShortStringHash type) const
     return 0;
 }
 
-const PODVector<Node*>& Node::GetDependencyNodes(unsigned frameNumber) const
+void Node::PrepareNetworkUpdate()
 {
-    if (frameNumber != dependencyFrameNumber_)
+    // Process dependencies first
+    dependencyNodes_.Clear();
+    
+    // Add the parent node, but if it is local, traverse to the first non-local node
+    if (parent_ && parent_ != scene_)
+    {
+        Node* current = parent_;
+        while (current->id_ >= FIRST_LOCAL_ID)
+            current = current->parent_;
+        if (current && current != scene_)
+            dependencyNodes_.Push(current);
+    }
+    
+    // Then let the components add their dependencies
+    for (Vector<SharedPtr<Component> >::ConstIterator i = components_.Begin(); i != components_.End(); ++i)
+        (*i)->GetDependencyNodes(dependencyNodes_);
+    
+    const Vector<AttributeInfo>* attributes = GetNetworkAttributes();
+    if (attributes)
     {
-        dependencyNodes_.Clear();
+        unsigned numAttributes = attributes->Size();
         
-        // Add the parent node, but if it is local, traverse to the first non-local node
-        if (parent_ && parent_ != scene_)
+        if (currentState_.Size() != numAttributes)
         {
-            Node* current = parent_;
-            while (current->id_ >= FIRST_LOCAL_ID)
-                current = current->parent_;
-            if (current && current != scene_)
-                dependencyNodes_.Push(current);
+            currentState_.Resize(numAttributes);
+            previousState_.Resize(numAttributes);
+            
+            // Copy the default attribute values to the previous state as a starting point
+            for (unsigned i = 0; i < numAttributes; ++i)
+                previousState_[i] = attributes->At(i).defaultValue_;
         }
         
-        // Then let the components add their dependencies
-        for (Vector<SharedPtr<Component> >::ConstIterator i = components_.Begin(); i != components_.End(); ++i)
-            (*i)->GetDependencyNodes(dependencyNodes_);
-        
-        dependencyFrameNumber_ = frameNumber;
+        // Check for attribute changes
+        for (unsigned i = 0; i < numAttributes; ++i)
+        {
+            const AttributeInfo& attr = attributes->At(i);
+            OnGetAttribute(attr, currentState_[i]);
+            
+            if (currentState_[i] != previousState_[i])
+            {
+                previousState_[i] = currentState_[i];
+                
+                // Mark the attribute dirty in all replication states that are tracking this node
+                for (PODVector<NodeReplicationState*>::Iterator j = replicationStates_.Begin(); j != replicationStates_.End();
+                    ++j)
+                {
+                    (*j)->dirtyAttributes_.Set(i);
+                    
+                    // Add node to the dirty set if not added yet
+                    if (!(*j)->markedDirty_)
+                    {
+                        (*j)->markedDirty_ = true;
+                        (*j)->sceneState_->dirtyNodes_.Insert(id_);
+                    }
+                }
+            }
+        }
     }
     
-    return dependencyNodes_;
+    // Check for user var changes
+    for (VariantMap::ConstIterator i = vars_.Begin(); i != vars_.End(); ++i)
+    {
+        VariantMap::ConstIterator j = previousVars_.Find(i->first_);
+        if (j == previousVars_.End() || j->second_ != i->second_)
+        {
+            previousVars_[i->first_] = i->second_;
+            
+            // Mark the var dirty in all replication states that are tracking this node
+            for (PODVector<NodeReplicationState*>::Iterator j = replicationStates_.Begin(); j != replicationStates_.End(); ++j)
+            {
+                (*j)->dirtyVars_.Insert(i->first_);
+                
+                if (!(*j)->markedDirty_)
+                {
+                    (*j)->markedDirty_ = true;
+                    (*j)->sceneState_->dirtyNodes_.Insert(id_);
+                }
+            }
+        }
+    }
+}
+
+void Node::CleanupConnection(Connection* connection)
+{
+    if (owner_ == connection)
+        owner_ = 0;
+    
+    for (unsigned i = replicationStates_.Size() - 1; i < replicationStates_.Size(); --i)
+    {
+        if (replicationStates_[i]->connection_ == connection)
+            replicationStates_.Erase(i);
+    }
+}
+
+void Node::MarkReplicationDirty()
+{
+    for (PODVector<NodeReplicationState*>::Iterator j = replicationStates_.Begin(); j != replicationStates_.End(); ++j)
+    {
+        // Add component's parent node to the dirty set if not added yet
+        if (!(*j)->markedDirty_)
+        {
+            (*j)->markedDirty_ = true;
+            (*j)->sceneState_->dirtyNodes_.Insert(id_);
+        }
+    }
 }
 
 void Node::SetID(unsigned id)
@@ -1015,6 +1112,10 @@ Component* Node::CreateComponent(ShortStringHash type, unsigned id, CreateMode m
     
     newComponent->SetNode(this);
     newComponent->OnMarkedDirty(this);
+    
+    // Mark node dirty in all replication states
+    MarkReplicationDirty();
+    
     return newComponent;
 }
 

+ 18 - 6
Engine/Scene/Node.h

@@ -32,6 +32,8 @@ class Connection;
 class Scene;
 class SceneResolver;
 
+struct NodeReplicationState;
+
 /// Component and child node creation mode for networking.
 enum CreateMode
 {
@@ -39,7 +41,6 @@ enum CreateMode
     LOCAL = 1
 };
 
-
 /// %Scene node that may contain components and child nodes.
 class Node : public Serializable
 {
@@ -67,6 +68,8 @@ public:
     virtual bool SaveXML(XMLElement& dest);
     /// Apply attribute changes that can not be applied immediately recursively to child nodes and components.
     virtual void ApplyAttributes();
+    /// Add a replication state that is tracking this node.
+    virtual void AddReplicationState(NodeReplicationState* state);
     
     /// Save to an XML file. Return true if successful.
     bool SaveXML(Serializer& dest);
@@ -265,8 +268,6 @@ public:
     const Vector<WeakPtr<Component> > GetListeners() const { return listeners_; }
     /// Return user variables.
     VariantMap& GetVars() { return vars_; }
-    /// Return the depended on nodes to order network updates.
-    const PODVector<Node*>& GetDependencyNodes(unsigned frameNumber) const;
     /// Return first component derived from class.
     template <class T> T* GetDerivedComponent() const;
     /// Return components derived from class.
@@ -300,6 +301,14 @@ public:
     bool Load(Deserializer& source, SceneResolver& resolver, bool loadChildren = true, bool rewriteIDs = false, CreateMode mode = REPLICATED);
     /// Load components from XML data and optionally load child nodes.
     bool LoadXML(const XMLElement& source, SceneResolver& resolver, bool loadChildren = true, bool rewriteIDs = false, CreateMode mode = REPLICATED);
+    /// Return the depended on nodes to order network updates.
+    const PODVector<Node*>& GetDependencyNodes() const { return dependencyNodes_; }
+    /// Prepare network update by comparing attributes and marking replication states dirty as necessary.
+    void PrepareNetworkUpdate();
+    /// Clean up all references to a network connection that is about to be removed.
+    void CleanupConnection(Connection* connection);
+    /// Mark node dirty in scene replication states.
+    void MarkReplicationDirty();
     
     /// User variables.
     VariantMap vars_;
@@ -310,6 +319,9 @@ protected:
     /// Create a child node with specific ID.
     Node* CreateChild(unsigned id, CreateMode mode);
     
+    /// Per-user network replication states.
+    PODVector<NodeReplicationState*> replicationStates_;
+    
 private:
     /// Recalculate the world transform.
     void UpdateWorldTransform() const;
@@ -349,11 +361,11 @@ private:
     /// Node listeners.
     Vector<WeakPtr<Component> > listeners_;
     /// Nodes this node depends on for network updates.
-    mutable PODVector<Node*> dependencyNodes_;
+    PODVector<Node*> dependencyNodes_;
+    /// Previous user variables for network updates.
+    VariantMap previousVars_;
     /// Attribute buffer for network updates.
     mutable VectorBuffer attrBuffer_;
-    /// Dependency nodes update framenumber.
-    mutable unsigned dependencyFrameNumber_;
     /// Consecutive rotation count for rotation renormalization.
     unsigned char rotateCount_;
     /// World transform needs update flag.

+ 176 - 0
Engine/Scene/ReplicationState.h

@@ -0,0 +1,176 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2012 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "HashMap.h"
+#include "HashSet.h"
+#include "Ptr.h"
+#include "StringHash.h"
+
+#include <cstring>
+
+static const unsigned MAX_NETWORK_ATTRIBUTES = 64;
+
+class Component;
+class Connection;
+class Node;
+class Scene;
+
+struct ComponentReplicationState;
+struct NodeReplicationState;
+struct SceneReplicationState;
+
+/// Dirty attribute bits structure for network replication.
+struct DirtyBits
+{
+    /// Construct empty.
+    DirtyBits() :
+        count_(0)
+    {
+        memset(data_, 0, MAX_NETWORK_ATTRIBUTES / 8);
+    }
+    
+    /// Copy-construct.
+    DirtyBits(const DirtyBits& bits) :
+        count_(bits.count_)
+    {
+        memcpy(data_, bits.data_, MAX_NETWORK_ATTRIBUTES / 8);
+    }
+    
+    /// Set a bit.
+    void Set(unsigned index)
+    {
+        if (index < MAX_NETWORK_ATTRIBUTES)
+        {
+            unsigned byteIndex = index >> 3;
+            unsigned bit = 1 << (index & 7);
+            if ((data_[byteIndex] & bit) == 0)
+            {
+                data_[byteIndex] |= bit;
+                ++count_;
+            }
+        }
+    }
+    
+    /// Clear a bit.
+    void Clear(unsigned index)
+    {
+        if (index < MAX_NETWORK_ATTRIBUTES)
+        {
+            unsigned byteIndex = index >> 3;
+            unsigned bit = 1 << (index & 7);
+            if ((data_[byteIndex] & bit) != 0)
+            {
+                data_[byteIndex] &= ~bit;
+                --count_;
+            }
+        }
+    }
+    
+    /// Clear all bits.
+    void ClearAll()
+    {
+        memset(data_, 0, MAX_NETWORK_ATTRIBUTES / 8);
+        count_ = 0;
+    }
+    
+    /// Return if bit is set.
+    bool IsSet(unsigned index) const
+    {
+        if (index < MAX_NETWORK_ATTRIBUTES)
+        {
+            unsigned byteIndex = index >> 3;
+            unsigned bit = 1 << (index & 7);
+            return (data_[byteIndex] & bit) != 0;
+        }
+        else
+            return false;
+    }
+    
+    /// Return number of set bits.
+    unsigned Count() const { return count_; }
+    
+    /// Bit data.
+    unsigned char data_[MAX_NETWORK_ATTRIBUTES / 8];
+    /// Number of set bits.
+    unsigned char count_;
+};
+
+/// Per-user component network replication state.
+struct ComponentReplicationState
+{
+    /// Parent network connection.
+    Connection* connection_;
+    /// Parent node replication state.
+    NodeReplicationState* nodeState_;
+    /// Link to the actual component.
+    WeakPtr<Component> component_;
+    /// Dirty attribute bits.
+    DirtyBits dirtyAttributes_;
+};
+
+/// Last sent state of a node for network replication.
+struct NodeReplicationState
+{
+    /// Construct.
+    NodeReplicationState() :
+        priorityAcc_(0.0f),
+        markedDirty_(false)
+    {
+    }
+    
+    /// Parent network connection.
+    Connection* connection_;
+    /// Parent scene replication state.
+    SceneReplicationState* sceneState_;
+    /// Link to the actual node.
+    WeakPtr<Node> node_;
+    /// Dirty attribute bits.
+    DirtyBits dirtyAttributes_;
+    /// Dirty user vars.
+    HashSet<ShortStringHash> dirtyVars_;
+    /// Components by ID.
+    HashMap<unsigned, ComponentReplicationState> componentStates_;
+    /// Interest management priority accumulator.
+    float priorityAcc_;
+    /// Whether exists in the SceneState's dirty set.
+    bool markedDirty_;
+};
+
+/// Per-user scene network replication state.
+struct SceneReplicationState
+{
+    /// Parent network connection.
+    Connection* connection_;
+    /// Nodes by ID.
+    HashMap<unsigned, NodeReplicationState> nodeStates_;
+    /// Dirty node IDs.
+    HashSet<unsigned> dirtyNodes_;
+    
+    void Clear()
+    {
+        nodeStates_.Clear();
+        dirtyNodes_.Clear();
+    }
+};

+ 40 - 14
Engine/Scene/Scene.cpp

@@ -29,6 +29,7 @@
 #include "Log.h"
 #include "PackageFile.h"
 #include "Profiler.h"
+#include "ReplicationState.h"
 #include "Scene.h"
 #include "SceneEvents.h"
 #include "SmoothedTransform.h"
@@ -157,6 +158,15 @@ bool Scene::LoadXML(const XMLElement& source)
         return false;
 }
 
+void Scene::AddReplicationState(NodeReplicationState* state)
+{
+    replicationStates_.Push(state);
+    
+    // This is the first update for a new connection. Mark all replicated nodes dirty
+    for (HashMap<unsigned, Node*>::ConstIterator i = replicatedNodes_.Begin(); i != replicatedNodes_.End(); ++i)
+        state->sceneState_->dirtyNodes_.Insert(i->first_);
+}
+
 bool Scene::LoadXML(Deserializer& source)
 {
     PROFILE(LoadSceneXML);
@@ -382,20 +392,6 @@ void Scene::ClearRequiredPackageFiles()
     requiredPackageFiles_.Clear();
 }
 
-void Scene::ResetOwner(Connection* owner)
-{
-    for (HashMap<unsigned, Node*>::Iterator i = replicatedNodes_.Begin(); i != replicatedNodes_.End(); ++i)
-    {
-        if (i->second_->GetOwner() == owner)
-            i->second_->SetOwner(0);
-    }
-    for (HashMap<unsigned, Node*>::Iterator i = localNodes_.Begin(); i != localNodes_.End(); ++i)
-    {
-        if (i->second_->GetOwner() == owner)
-            i->second_->SetOwner(0);
-    }
-}
-
 void Scene::RegisterVar(const String& name)
 {
     varNames_[ShortStringHash(name)] = name;
@@ -616,6 +612,9 @@ void Scene::NodeAdded(Node* node)
         }
         
         replicatedNodes_[id] = node;
+        
+        for (PODVector<NodeReplicationState*>::Iterator i = replicationStates_.Begin(); i != replicationStates_.End(); ++i)
+            (*i)->sceneState_->dirtyNodes_.Insert(id);
     }
     else
     {
@@ -638,7 +637,12 @@ void Scene::NodeRemoved(Node* node)
     
     unsigned id = node->GetID();
     if (id < FIRST_LOCAL_ID)
+    {
         replicatedNodes_.Erase(id);
+        
+        for (PODVector<NodeReplicationState*>::Iterator i = replicationStates_.Begin(); i != replicationStates_.End(); ++i)
+            (*i)->sceneState_->dirtyNodes_.Insert(id);
+    }
     else
         localNodes_.Erase(id);
     
@@ -707,6 +711,28 @@ String Scene::GetVarNamesAttr() const
     return ret;
 }
 
+void Scene::PrepareNetworkUpdate()
+{
+    Node::PrepareNetworkUpdate();
+    
+    for (HashMap<unsigned, Node*>::Iterator i = replicatedNodes_.Begin(); i != replicatedNodes_.End(); ++i)
+        i->second_->PrepareNetworkUpdate();
+    
+    for (HashMap<unsigned, Component*>::Iterator i = replicatedComponents_.Begin(); i != replicatedComponents_.End(); ++i)
+        i->second_->PrepareNetworkUpdate();
+}
+
+void Scene::CleanupConnection(Connection* connection)
+{
+    Node::CleanupConnection(connection);
+    
+    for (HashMap<unsigned, Node*>::Iterator i = replicatedNodes_.Begin(); i != replicatedNodes_.End(); ++i)
+        i->second_->CleanupConnection(connection);
+    
+    for (HashMap<unsigned, Component*>::Iterator i = replicatedComponents_.Begin(); i != replicatedComponents_.End(); ++i)
+        i->second_->CleanupConnection(connection);
+}
+
 void Scene::HandleUpdate(StringHash eventType, VariantMap& eventData)
 {
     using namespace Update;

+ 6 - 4
Engine/Scene/Scene.h

@@ -73,6 +73,8 @@ public:
     virtual bool Save(Serializer& dest);
     /// Load from XML data. Return true if successful.
     virtual bool LoadXML(const XMLElement& source);
+    /// Add a replication state that is tracking this scene.
+    virtual void AddReplicationState(NodeReplicationState* state);
     
     /// Load from an XML file. Return true if successful.
     bool LoadXML(Deserializer& source);
@@ -102,8 +104,6 @@ public:
     void AddRequiredPackageFile(PackageFile* package);
     /// Clear required package files.
     void ClearRequiredPackageFiles();
-    /// Reset specific owner reference from nodes on disconnect.
-    void ResetOwner(Connection* owner);
     /// Register a node user variable hash reverse mapping (for editing.)
     void RegisterVar(const String& name);
     /// Unregister a node user variable hash reverse mapping.
@@ -131,8 +131,6 @@ public:
     float GetSnapThreshold() const { return snapThreshold_; }
     /// Return required package files.
     const Vector<SharedPtr<PackageFile> >& GetRequiredPackageFiles() const { return requiredPackageFiles_; }
-    /// Return all replicated scene nodes.
-    const HashMap<unsigned, Node*>& GetReplicatedNodes() const { return replicatedNodes_; }
     /// Return a node user variable name, or empty if not registered.
     const String& GetVarName(ShortStringHash hash) const;
     
@@ -162,6 +160,10 @@ public:
     void SetVarNamesAttr(String value);
     /// Return node user variable reverse mappings.
     String GetVarNamesAttr() const;
+    /// Prepare network update by comparing attributes and marking replication states dirty as necessary.
+    void PrepareNetworkUpdate();
+    /// Clean up all references to a network connection that is about to be removed.
+    void CleanupConnection(Connection* connection);
     
 private:
     /// Handle the logic update event to update the scene, if active.

+ 36 - 93
Engine/Scene/Serializable.cpp

@@ -25,6 +25,7 @@
 #include "Context.h"
 #include "Deserializer.h"
 #include "Log.h"
+#include "ReplicationState.h"
 #include "Serializable.h"
 #include "Serializer.h"
 #include "XMLElement.h"
@@ -35,8 +36,6 @@ OBJECTTYPESTATIC(Serializable);
 
 Serializable::Serializable(Context* context) :
     Object(context),
-    serverFrameNumber_(0),
-    networkAttributes_(0),
     loading_(false)
 {
 }
@@ -421,141 +420,85 @@ bool Serializable::SetAttribute(const String& name, const Variant& value)
     return false;
 }
 
-void Serializable::WriteInitialDeltaUpdate(unsigned frameNumber, Serializer& dest, PODVector<unsigned char>& deltaUpdateBits,
-    Vector<Variant>& replicationState)
+void Serializable::WriteInitialDeltaUpdate(Serializer& dest)
 {
-    networkAttributes_ = GetNetworkAttributes();
-    if (!networkAttributes_)
+    const Vector<AttributeInfo>* attributes = GetNetworkAttributes();
+    if (!attributes)
         return;
     
-    unsigned numAttributes = networkAttributes_->Size();
-    
-    // Get current attribute values if necessary
-    if (frameNumber != serverFrameNumber_ || currentState_.Empty())
-    {
-        currentState_.Resize(numAttributes);
-        for (unsigned i = 0; i < numAttributes; ++i)
-        {
-            const AttributeInfo& attr = networkAttributes_->At(i);
-            OnGetAttribute(attr, currentState_[i]);
-        }
-        serverFrameNumber_ = frameNumber;
-    }
-    
-    replicationState.Resize(numAttributes);
-    deltaUpdateBits.Resize((numAttributes + 7) >> 3);
-    for (unsigned i = 0; i < deltaUpdateBits.Size(); ++i)
-        deltaUpdateBits[i] = 0;
+    unsigned numAttributes = attributes->Size();
+    DirtyBits attributeBits;
     
-    // Copy attributes to replication state and compare against defaults
+    // Compare against defaults
     for (unsigned i = 0; i < numAttributes; ++i)
     {
-        const AttributeInfo& attr = networkAttributes_->At(i);
-        replicationState[i] = currentState_[i];
-        if (replicationState[i] != attr.defaultValue_)
-            deltaUpdateBits[i >> 3] |= (1 << (i & 7));
+        const AttributeInfo& attr = attributes->At(i);
+        if (currentState_[i] != attr.defaultValue_)
+            attributeBits.Set(i);
     }
     
     // First write the change bitfield, then attribute data for non-default attributes
-    dest.Write(&deltaUpdateBits[0], deltaUpdateBits.Size());
+    dest.Write(attributeBits.data_, (numAttributes + 7) >> 3);
     
     for (unsigned i = 0; i < numAttributes; ++i)
     {
-        if (deltaUpdateBits[i >> 3] & (1 << (i & 7)))
-            dest.WriteVariantData(replicationState[i]);
+        if (attributeBits.IsSet(i))
+            dest.WriteVariantData(currentState_[i]);
     }
 }
 
-void Serializable::PrepareUpdates(unsigned frameNumber, PODVector<unsigned char>& deltaUpdateBits,
-    Vector<Variant>& replicationState, bool& deltaUpdate, bool& latestData)
+void Serializable::WriteDeltaUpdate(Serializer& dest, const DirtyBits& attributeBits)
 {
-    deltaUpdate = false;
-    latestData = false;
-    
-    if (!networkAttributes_ || currentState_.Empty())
-        return;
-    
-    unsigned numAttributes = networkAttributes_->Size();
-    
-    // Get current attribute values if necessary
-    if (frameNumber != serverFrameNumber_)
-    {
-        for (unsigned i = 0; i < numAttributes; ++i)
-        {
-            const AttributeInfo& attr = networkAttributes_->At(i);
-            OnGetAttribute(attr, currentState_[i]);
-        }
-        serverFrameNumber_ = frameNumber;
-    }
-    
-    deltaUpdateBits.Resize((numAttributes + 7) >> 3);
-    for (unsigned i = 0; i < deltaUpdateBits.Size(); ++i)
-        deltaUpdateBits[i] = 0;
-    
-    for (unsigned i = 0; i < numAttributes; ++i)
-    {
-        if (currentState_[i] != replicationState[i])
-        {
-            const AttributeInfo& attr = networkAttributes_->At(i);
-            replicationState[i] = currentState_[i];
-            if (attr.mode_ & AM_LATESTDATA)
-                latestData = true;
-            else
-            {
-                deltaUpdate = true;
-                deltaUpdateBits[i >> 3] |= 1 << (i & 7);
-            }
-        }
-    }
-}
-
-void Serializable::WriteDeltaUpdate(Serializer& dest, PODVector<unsigned char>& deltaUpdateBits, Vector<Variant>& replicationState)
-{
-    if (!networkAttributes_)
+    const Vector<AttributeInfo>* attributes = GetNetworkAttributes();
+    if (!attributes)
         return;
     
-    unsigned numAttributes = networkAttributes_->Size();
+    unsigned numAttributes = attributes->Size();
     
     // First write the change bitfield, then attribute data for changed attributes
-    dest.Write(&deltaUpdateBits[0], deltaUpdateBits.Size());
+    // Note: the attribute bits should not contain LATESTDATA attributes
+    dest.Write(attributeBits.data_, (numAttributes + 7) >> 3);
     
     for (unsigned i = 0; i < numAttributes; ++i)
     {
-        if (deltaUpdateBits[i >> 3] & (1 << (i & 7)))
-            dest.WriteVariantData(replicationState[i]);
+        if (attributeBits.IsSet(i))
+            dest.WriteVariantData(currentState_[i]);
     }
 }
 
-void Serializable::WriteLatestDataUpdate(Serializer& dest, Vector<Variant>& replicationState)
+void Serializable::WriteLatestDataUpdate(Serializer& dest)
 {
-    if (!networkAttributes_)
+    const Vector<AttributeInfo>* attributes = GetNetworkAttributes();
+    if (!attributes)
         return;
     
-    unsigned numAttributes = networkAttributes_->Size();
+    unsigned numAttributes = attributes->Size();
     
     for (unsigned i = 0; i < numAttributes; ++i)
     {
-        if (networkAttributes_->At(i).mode_ & AM_LATESTDATA)
-            dest.WriteVariantData(replicationState[i]);
+        if (attributes->At(i).mode_ & AM_LATESTDATA)
+            dest.WriteVariantData(currentState_[i]);
     }
 }
 
-void Serializable::ReadDeltaUpdate(Deserializer& source, PODVector<unsigned char>& deltaUpdateBits)
+void Serializable::ReadDeltaUpdate(Deserializer& source)
 {
     const Vector<AttributeInfo>* attributes = GetNetworkAttributes();
     if (!attributes)
         return;
+    
     unsigned numAttributes = attributes->Size();
+    DirtyBits attributeBits;
     
-    deltaUpdateBits.Resize((numAttributes + 7) >> 3);
-    source.Read(&deltaUpdateBits[0], deltaUpdateBits.Size());
+    source.Read(attributeBits.data_, (numAttributes + 7) >> 3);
     
     for (unsigned i = 0; i < numAttributes && !source.IsEof(); ++i)
     {
-        const AttributeInfo& attr = attributes->At(i);
-        
-        if (deltaUpdateBits[i >> 3] & (1 << (i & 7)))
+        if (attributeBits.IsSet(i))
+        {
+            const AttributeInfo& attr = attributes->At(i);
             OnSetAttribute(attr, source.ReadVariant(attr.type_));
+        }
     }
 }
 
@@ -564,12 +507,12 @@ void Serializable::ReadLatestDataUpdate(Deserializer& source)
     const Vector<AttributeInfo>* attributes = GetNetworkAttributes();
     if (!attributes)
         return;
+    
     unsigned numAttributes = attributes->Size();
     
     for (unsigned i = 0; i < numAttributes && !source.IsEof(); ++i)
     {
         const AttributeInfo& attr = attributes->At(i);
-        
         if (attr.mode_ & AM_LATESTDATA)
             OnSetAttribute(attr, source.ReadVariant(attr.type_));
     }

+ 14 - 15
Engine/Scene/Serializable.h

@@ -28,10 +28,13 @@
 
 #include <cstddef>
 
+class Connection;
 class Deserializer;
 class Serializer;
 class XMLElement;
 
+struct DirtyBits;
+
 /// Base class for objects with automatic serialization through attributes.
 class Serializable : public Object
 {
@@ -62,16 +65,14 @@ public:
     bool SetAttribute(unsigned index, const Variant& value);
     /// %Set attribute by name. Return true if successfully set.
     bool SetAttribute(const String& name, const Variant& value);
-    /// Write initial delta network update (compared to default attribute values) and prepare the last sent state.
-    void WriteInitialDeltaUpdate(unsigned frameNumber, 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().
-    void PrepareUpdates(unsigned frameNumber, PODVector<unsigned char>& deltaUpdateBits, Vector<Variant>& replicationState, bool& deltaUpdate, bool& latestData);
-    /// Write a delta network update prepared with PrepareUpdates().
-    void WriteDeltaUpdate(Serializer& dest, PODVector<unsigned char>& deltaUpdateBits, Vector<Variant>& replicationState);
-    /// Write a latestdata network update prepared with PrepareUpdates().
-    void WriteLatestDataUpdate(Serializer& dest, Vector<Variant>& replicationState);
+    /// Write initial delta network update.
+    void WriteInitialDeltaUpdate(Serializer& dest);
+    /// Write a delta network update according to dirty attribute bits.
+    void WriteDeltaUpdate(Serializer& dest, const DirtyBits& attributeBits);
+    /// Write a latest data network update.
+    void WriteLatestDataUpdate(Serializer& dest);
     /// Read and apply a network delta update.
-    void ReadDeltaUpdate(Deserializer& source, PODVector<unsigned char>& deltaUpdateBits);
+    void ReadDeltaUpdate(Deserializer& source);
     /// Read and apply a network latest data update.
     void ReadLatestDataUpdate(Deserializer& source);
     
@@ -90,13 +91,11 @@ public:
     /// Return whether is loading attributes from a file. Is false during network deserialization.
     bool IsLoading() const { return loading_; }
     
-private:
-    /// Current attributes for sending network updates. Updated only once per network frame, not per user.
+protected:
+    /// Current attributes for sending network updates.
     Vector<Variant> currentState_;
-    /// Cached network attribute infos.
-    mutable const Vector<AttributeInfo>* networkAttributes_;
-    /// Last server frame number.
-    unsigned serverFrameNumber_;
+    /// Previous attributes for sending network updates.
+    Vector<Variant> previousState_;
     /// Is loading flag.
     bool loading_;
 };