Browse Source

Added sending of scene replication messages.

Lasse Öörni 14 years ago
parent
commit
bd35b5a0e0

+ 3 - 1
Engine/Core/Attribute.h

@@ -28,10 +28,12 @@
 
 /// Attribute used only for disk serialization
 static const unsigned AM_SERIALIZATION = 0x1;
-/// Attribute used only for network replication
+/// Attribute used for network replication
 static const unsigned AM_NETWORK = 0x2;
 /// Attribute used for both (default)
 static const unsigned AM_BOTH = 0x3;
+/// Attribute should use latest data grouping instead of delta update in network replication
+static const unsigned AM_LATESTDATA = 0x4;
 
 class Serializable;
 

+ 390 - 12
Engine/Network/Connection.cpp

@@ -22,6 +22,7 @@
 //
 
 #include "Precompiled.h"
+#include "Component.h"
 #include "Connection.h"
 #include "Log.h"
 #include "Protocol.h"
@@ -36,6 +37,7 @@ OBJECTTYPESTATIC(Connection);
 Connection::Connection(Context* context, bool isClient, kNet::SharedPtr<kNet::MessageConnection> connection) :
     Object(context),
     connection_(connection),
+    frameNumber_(0),
     isClient_(isClient),
     connectPending_(false),
     sceneLoaded_(false)
@@ -85,10 +87,10 @@ void Connection::SendMessage(int msgID, unsigned contentID, bool reliable, bool
 
 void Connection::SendRemoteEvent(StringHash eventType, bool inOrder, const VariantMap& eventData)
 {
-    VectorBuffer msg;
-    msg.WriteStringHash(eventType);
-    msg.WriteVariantMap(eventData);
-    SendMessage(MSG_REMOTEEVENT, true, inOrder, msg);
+    msg_.Clear();
+    msg_.WriteStringHash(eventType);
+    msg_.WriteVariantMap(eventData);
+    SendMessage(MSG_REMOTEEVENT, true, inOrder, msg_);
 }
 
 void Connection::SendRemoteEvent(Node* receiver, StringHash eventType, bool inOrder, const VariantMap& eventData)
@@ -109,11 +111,11 @@ void Connection::SendRemoteEvent(Node* receiver, StringHash eventType, bool inOr
         return;
     }
     
-    VectorBuffer msg;
-    msg.WriteVLE(receiver->GetID());
-    msg.WriteStringHash(eventType);
-    msg.WriteVariantMap(eventData);
-    SendMessage(MSG_REMOTENODEEVENT, true, inOrder, msg);
+    msg_.Clear();
+    msg_.WriteVLE(receiver->GetID());
+    msg_.WriteStringHash(eventType);
+    msg_.WriteVariantMap(eventData);
+    SendMessage(MSG_REMOTENODEEVENT, true, inOrder, msg_);
 }
 
 void Connection::SetScene(Scene* newScene)
@@ -130,11 +132,13 @@ void Connection::SetScene(Scene* newScene)
     
     if (isClient_ && scene_)
     {
+        sceneState_.Clear();
+        
         // When scene is assigned on the server, instruct the client to load it
         /// \todo Download package(s) needed for the scene, if they do not exist already on the client
-        VectorBuffer msg;
-        msg.WriteString(scene_->GetFileName());
-        SendMessage(MSG_LOADSCENE, true, true, msg);
+        msg_.Clear();
+        msg_.WriteString(scene_->GetFileName());
+        SendMessage(MSG_LOADSCENE, true, true, msg_);
     }
 }
 
@@ -159,6 +163,44 @@ void Connection::Disconnect(int waitMSec)
     connection_->Disconnect(waitMSec);
 }
 
+void Connection::ProcessReplication()
+{
+    if (!isClient_ || !scene_ || !sceneLoaded_)
+        return;
+    
+    const Map<unsigned, Node*>& nodes = scene_->GetAllNodes();
+    
+    // Check for new or modified nodes
+    /// \todo Send in correct order that takes node parenting into account
+    for (Map<unsigned, Node*>::ConstIterator i = nodes.Begin(); i != nodes.End(); ++i)
+    {
+        // Break when we reach local node IDs
+        if (i->first_ >= FIRST_LOCAL_ID)
+            break;
+        
+        if (sceneState_.Find(i->first_) == sceneState_.End())
+            ProcessNewNode(i->second_);
+        else
+            ProcessExistingNode(i->second_);
+    }
+    
+    // Check for removed nodes
+    for (Map<unsigned, NodeReplicationState>::Iterator i = sceneState_.Begin(); i != sceneState_.End();)
+    {
+        Map<unsigned, NodeReplicationState>::Iterator current = i++;
+        if (current->second_.frameNumber_ != frameNumber_)
+        {
+            msg_.Clear();
+            msg_.WriteVLE(current->first_);
+            
+            SendMessage(MSG_REMOVENODE, true, true, msg_);
+            sceneState_.Erase(current);
+        }
+    }
+    
+    ++frameNumber_;
+}
+
 kNet::MessageConnection* Connection::GetMessageConnection() const
 {
     return const_cast<kNet::MessageConnection*>(connection_.ptr());
@@ -191,3 +233,339 @@ String Connection::ToString() const
 {
     return GetAddress() + ":" + String(GetPort());
 }
+
+void Connection::ProcessNewNode(Node* node)
+{
+    msg_.Clear();
+    msg_.WriteVLE(node->GetID());
+    
+    NodeReplicationState newNodeState;
+    newNodeState.frameNumber_ = frameNumber_;
+    
+    // Write node's attributes
+    msg_.WriteVLE(node->GetNumNetworkAttributes());
+    const Vector<AttributeInfo>* attributes = node->GetAttributes();
+    for (unsigned i = 0; i < attributes->Size(); ++i)
+    {
+        const AttributeInfo& attr = attributes->At(i);
+        if (!(attr.mode_ & AM_NETWORK))
+            continue;
+        
+        Variant value = node->GetAttribute(i);
+        msg_.WriteVariantData(value);
+        newNodeState.attributes_.Push(value);
+    }
+    
+    // Write node's variable map
+    const VariantMap& vars = node->GetVars();
+    msg_.WriteVLE(vars.Size());
+    for (VariantMap::ConstIterator i = vars.Begin(); i != vars.End(); ++i)
+    {
+        msg_.WriteShortStringHash(i->first_);
+        msg_.WriteVariant(i->second_);
+        newNodeState.vars_[i->first_] = i->second_;
+    }
+    
+    // Write node's components
+    msg_.WriteVLE(node->GetNumNetworkComponents());
+    const Vector<SharedPtr<Component> >& components = node->GetComponents();
+    for (unsigned i = 0; i < components.Size(); ++i)
+    {
+        Component* component = components[i];
+        if (component->GetID() >= FIRST_LOCAL_ID)
+            continue;
+        
+        msg_.WriteShortStringHash(component->GetType());
+        msg_.WriteVLE(component->GetID());
+        
+        ComponentReplicationState newComponentState;
+        newComponentState.frameNumber_ = frameNumber_;
+        newComponentState.type_ = component->GetType();
+        
+        // Write component's attributes
+        msg_.WriteVLE(component->GetNumNetworkAttributes());
+        const Vector<AttributeInfo>* attributes = component->GetAttributes();
+        if (attributes)
+        {
+            for (unsigned j = 0; j < attributes->Size(); ++j)
+            {
+                const AttributeInfo& attr = attributes->At(j);
+                if (!(attr.mode_ & AM_NETWORK))
+                    continue;
+                
+                Variant value = component->GetAttribute(j);
+                msg_.WriteVariantData(value);
+                newComponentState.attributes_.Push(value);
+            }
+        }
+        
+        newNodeState.components_[component->GetID()] = newComponentState;
+    }
+    
+    SendMessage(MSG_CREATENODE, true, true, msg_);
+    sceneState_[node->GetID()] = newNodeState;
+}
+
+void Connection::ProcessExistingNode(Node* node)
+{
+    NodeReplicationState& nodeState = sceneState_[node->GetID()];
+    nodeState.frameNumber_ = frameNumber_;
+    
+    // Check if attributes have changed
+    bool deltaUpdate = false;
+    bool latestData = false;
+    const Vector<AttributeInfo>* attributes = node->GetAttributes();
+    deltaUpdateBits_.Resize((node->GetNumNetworkAttributes() + 7) >> 3);
+    for (unsigned i = 0; i < deltaUpdateBits_.Size(); ++i)
+        deltaUpdateBits_[i] = 0;
+    unsigned index = 0;
+    
+    // Make sure the current attributes vector is large enough
+    if (currentAttributes_.Size() < nodeState.attributes_.Size())
+        currentAttributes_.Resize(nodeState.attributes_.Size());
+    
+    for (unsigned i = 0; i < attributes->Size(); ++i)
+    {
+        const AttributeInfo& attr = attributes->At(i);
+        if (!(attr.mode_ & AM_NETWORK))
+            continue;
+        
+        // Check for attribute change
+        currentAttributes_[index] = node->GetAttribute(i);
+        if (currentAttributes_[index] != nodeState.attributes_[index])
+        {
+            if (attr.mode_ & AM_LATESTDATA)
+                latestData = true;
+            else
+            {
+                deltaUpdate = true;
+                deltaUpdateBits_[index >> 3] |= 1 << (index & 7);
+            }
+        }
+        
+        ++index;
+    }
+    
+    // Check if variable map has changed. Note: variable removal is not supported
+    changedVars_.Clear();
+    const VariantMap& vars = node->GetVars();
+    for (VariantMap::ConstIterator i = vars.Begin(); i != vars.End(); ++i)
+    {
+        VariantMap::ConstIterator j = nodeState.vars_.Find(i->first_);
+        if (j == nodeState.vars_.End() || i->second_ != j->second_)
+        {
+            changedVars_.Insert(i->first_);
+            deltaUpdate = true;
+        }
+    }
+    
+    // Send deltaupdate message if necessary
+    if (deltaUpdate)
+    {
+        msg_.Clear();
+        msg_.WriteVLE(node->GetID());
+        
+        // Write changed attributes
+        msg_.Write(&deltaUpdateBits_[0], deltaUpdateBits_.Size());
+        
+        index = 0;
+        for (unsigned i = 0; i < attributes->Size(); ++i)
+        {
+            const AttributeInfo& attr = attributes->At(i);
+            if (!(attr.mode_ & AM_NETWORK))
+                continue;
+            
+            if (deltaUpdateBits_[index << 3] & (1 << (index & 7)))
+            {
+                msg_.WriteVariantData(currentAttributes_[index]);
+                nodeState.attributes_[index] = currentAttributes_[index];
+            }
+            
+            ++index;
+        }
+        
+        // Write changed variables
+        msg_.WriteVLE(changedVars_.Size());
+        for (HashSet<ShortStringHash>::ConstIterator i = changedVars_.Begin(); i != changedVars_.End(); ++i)
+        {
+            VariantMap::ConstIterator j = vars.Find(*i);
+            msg_.WriteShortStringHash(j->first_);
+            msg_.WriteVariant(j->second_);
+            nodeState.vars_[j->first_] = j->second_;
+        }
+        
+        SendMessage(MSG_NODEDELTAUPDATE, true, true, msg_);
+    }
+    
+    // Send latestdata message if necessary
+    if (latestData)
+    {
+        // If at least one latest data attribute changes, send all of them
+        msg_.Clear();
+        msg_.WriteVLE(node->GetID());
+        
+        index = 0;
+        for (unsigned i = 0; i < attributes->Size(); ++i)
+        {
+            const AttributeInfo& attr = attributes->At(i);
+            if ((attr.mode_ & (AM_NETWORK | AM_LATESTDATA)) != (AM_NETWORK | AM_LATESTDATA))
+                continue;
+            
+            msg_.WriteVariantData(currentAttributes_[index]);
+            nodeState.attributes_[index] = currentAttributes_[index];
+            
+            ++index;
+        }
+        
+        SendMessage(MSG_NODELATESTDATA, node->GetID(), true, false, msg_);
+    }
+    
+    // Check for new/updated components
+    const Vector<SharedPtr<Component> >& components = node->GetComponents();
+    for (unsigned i = 0; i < components.Size(); ++i)
+    {
+        Component* component = components[i];
+        if (component->GetID() >= FIRST_LOCAL_ID)
+            continue;
+        
+        Map<unsigned, ComponentReplicationState>::Iterator j = nodeState.components_.Find(component->GetID());
+        if (j == nodeState.components_.End())
+        {
+            // New component
+            msg_.Clear();
+            msg_.WriteVLE(node->GetID());
+            msg_.WriteShortStringHash(component->GetType());
+            msg_.WriteVLE(component->GetID());
+            
+            ComponentReplicationState newComponentState;
+            newComponentState.frameNumber_ = frameNumber_;
+            newComponentState.type_ = component->GetType();
+            
+            // Write component's attributes
+            msg_.WriteVLE(component->GetNumNetworkAttributes());
+            const Vector<AttributeInfo>* attributes = component->GetAttributes();
+            if (attributes)
+            {
+                for (unsigned k = 0; k < attributes->Size(); ++k)
+                {
+                    const AttributeInfo& attr = attributes->At(k);
+                    if (!(attr.mode_ & AM_NETWORK))
+                        continue;
+                    
+                    Variant value = component->GetAttribute(k);
+                    msg_.WriteVariantData(value);
+                    newComponentState.attributes_.Push(value);
+                }
+            }
+            
+            SendMessage(MSG_CREATECOMPONENT, true, true, msg_);
+            nodeState.components_[component->GetID()] = newComponentState;
+        }
+        else
+        {
+            // Existing component
+            ComponentReplicationState& componentState = j->second_;
+            componentState.frameNumber_ = frameNumber_;
+            
+            deltaUpdate = false;
+            latestData = false;
+            const Vector<AttributeInfo>* attributes = component->GetAttributes();
+            deltaUpdateBits_.Resize((component->GetNumNetworkAttributes() + 7) >> 3);
+            for (unsigned k = 0; k < deltaUpdateBits_.Size(); ++k)
+                deltaUpdateBits_[k] = 0;
+            index = 0;
+            
+            // Make sure the current attributes vector is large enough
+            if (currentAttributes_.Size() < componentState.attributes_.Size())
+                currentAttributes_.Resize(componentState.attributes_.Size());
+            
+            for (unsigned k = 0; k < attributes->Size(); ++k)
+            {
+                const AttributeInfo& attr = attributes->At(k);
+                if (!(attr.mode_ & AM_NETWORK))
+                    continue;
+                
+                // Check for attribute change
+                currentAttributes_[index] = component->GetAttribute(k);
+                if (currentAttributes_[index] != nodeState.attributes_[index])
+                {
+                    if (attr.mode_ & AM_LATESTDATA)
+                        latestData = true;
+                    else
+                    {
+                        deltaUpdate = true;
+                        deltaUpdateBits_[index >> 3] |= 1 << (index & 7);
+                    }
+                }
+                
+                ++index;
+            }
+            
+            // Send deltaupdate message if necessary
+            if (deltaUpdate)
+            {
+                msg_.Clear();
+                msg_.WriteVLE(component->GetID());
+                
+                // Write changed attributes
+                msg_.Write(&deltaUpdateBits_[0], deltaUpdateBits_.Size());
+                
+                index = 0;
+                for (unsigned k = 0; k < attributes->Size(); ++k)
+                {
+                    const AttributeInfo& attr = attributes->At(k);
+                    if (!(attr.mode_ & AM_NETWORK))
+                        continue;
+                    
+                    if (deltaUpdateBits_[index << 3] & (1 << (index & 7)))
+                    {
+                        msg_.WriteVariantData(currentAttributes_[index]);
+                        componentState.attributes_[index] = currentAttributes_[index];
+                    }
+                    
+                    ++index;
+                }
+                
+                SendMessage(MSG_COMPONENTDELTAUPDATE, true, true, msg_);
+            }
+            
+            // Send latestdata message if necessary
+            if (latestData)
+            {
+                // If at least one latest data attribute changes, send all of them
+                msg_.Clear();
+                msg_.WriteVLE(component->GetID());
+                
+                index = 0;
+                for (unsigned k = 0; k < attributes->Size(); ++k)
+                {
+                    const AttributeInfo& attr = attributes->At(k);
+                    if ((attr.mode_ & (AM_NETWORK | AM_LATESTDATA)) != (AM_NETWORK | AM_LATESTDATA))
+                        continue;
+                    
+                    msg_.WriteVariantData(currentAttributes_[index]);
+                    componentState.attributes_[index] = currentAttributes_[index];
+                    
+                    ++index;
+                }
+                
+                SendMessage(MSG_COMPONENTLATESTDATA, component->GetID(), true, false, msg_);
+            }
+        }
+    }
+    
+    // Check for removed components
+    for (Map<unsigned, ComponentReplicationState>::Iterator i = nodeState.components_.Begin(); i != nodeState.components_.End();)
+    {
+        Map<unsigned, ComponentReplicationState>::Iterator current = i++;
+        if (current->second_.frameNumber_ != frameNumber_)
+        {
+            msg_.Clear();
+            msg_.WriteVLE(node->GetID());
+            msg_.WriteVLE(current->first_);
+            
+            SendMessage(MSG_REMOVECOMPONENT, true, true, msg_);
+            nodeState.components_.Erase(current);
+        }
+    }
+}

+ 25 - 0
Engine/Network/Connection.h

@@ -24,7 +24,9 @@
 #pragma once
 
 #include "Controls.h"
+#include "HashSet.h"
 #include "Object.h"
+#include "ReplicationState.h"
 #include "VectorBuffer.h"
 
 #include <kNetFwd.h>
@@ -70,6 +72,8 @@ public:
     void SetConnectPending(bool connectPending);
     /// Disconnect. If wait time is non-zero, will block while waiting for disconnect to finish
     void Disconnect(int waitMSec = 0);
+    /// Process scene replication state and send scene update messages. Called by Network on the server
+    void ProcessReplication();
     
     /// Return the kNet message connection
     kNet::MessageConnection* GetMessageConnection() const;
@@ -97,16 +101,37 @@ public:
     String ToString() const;
     
 private:
+    /// Process a node that the client had not yet received
+    void ProcessNewNode(Node* node);
+    /// Process a node that the client has already received
+    void ProcessExistingNode(Node* node);
+    
     /// kNet message connection
     kNet::SharedPtr<kNet::MessageConnection> connection_;
     /// Identity map
     VariantMap identity_;
     /// Scene
     WeakPtr<Scene> scene_;
+    /// Scene replication state (as last sent to the client)
+    Map<unsigned, NodeReplicationState> sceneState_;
+    /// Pending latest data for not yet received nodes
+    Map<unsigned, Vector<Variant> > nodeLatestData_;
+    /// Pending latest data for not yet received components
+    Map<unsigned, Vector<Variant> > componentLatestData_;
+    /// Internal vector for delta update
+    PODVector<unsigned char> deltaUpdateBits_;
+    /// Internal vector for comparing attributes
+    Vector<Variant> currentAttributes_;
+    /// Internal set for node's variable map changes
+    HashSet<ShortStringHash> changedVars_;
+    /// Reused message buffer
+    VectorBuffer msg_;
     /// Current controls
     Controls controls_;
     /// Previous controls
     Controls previousControls_;
+    /// Update frame number
+    unsigned frameNumber_;
     /// Client flag
     bool isClient_;
     /// Connection pending flag

+ 4 - 5
Engine/Network/Network.cpp

@@ -333,8 +333,8 @@ void Network::Update(float timeStep)
         else if (state == kNet::ConnectionClosed)
             OnServerDisconnected();
         
-        // Send the controls packet on update
-        if (updateNow)
+        // If scene has been assigned and loaded, send the controls packet on update
+        if (updateNow && serverConnection_->GetScene() && serverConnection_->IsSceneLoaded())
         {
             const Controls& controls = serverConnection_->GetControls();
             
@@ -355,11 +355,10 @@ void Network::Update(float timeStep)
         
         if (updateNow)
         {
+            // Process scene replication for each client connection
             for (Map<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = clientConnections_.Begin();
                 i != clientConnections_.End(); ++i)
-            {
-                // Process the scene synchronization of each client connection
-            }
+                i->second_->ProcessReplication();
         }
     }
 }

+ 48 - 0
Engine/Network/ReplicationState.h

@@ -0,0 +1,48 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 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"
+
+struct ComponentReplicationState
+{
+    /// Component type
+    ShortStringHash type_;
+    /// Attribute values
+    Vector<Variant> attributes_;
+    /// Update frame number
+    unsigned frameNumber_;
+};
+
+struct NodeReplicationState
+{
+    /// Attribute values
+    Vector<Variant> attributes_;
+    /// Node variables
+    VariantMap vars_;
+    /// Components by ID
+    Map<unsigned, ComponentReplicationState> components_;
+    /// Update frame number
+    unsigned frameNumber_;
+};

+ 2 - 2
Engine/Physics/RigidBody.cpp

@@ -70,11 +70,11 @@ void RigidBody::RegisterObject(Context* context)
     ATTRIBUTE(RigidBody, VAR_FLOAT, "Mass", mass_, DEFAULT_MASS);
     ACCESSOR_ATTRIBUTE_MODE(RigidBody, VAR_VECTOR3, "Physics Position", GetPosition, SetPosition, Vector3, Vector3::ZERO, AM_SERIALIZATION);
     ACCESSOR_ATTRIBUTE_MODE(RigidBody, VAR_QUATERNION, "Physics Rotation", GetRotation, SetRotation, Quaternion, Quaternion::IDENTITY, AM_SERIALIZATION);
-    ACCESSOR_ATTRIBUTE(RigidBody, VAR_VECTOR3, "Linear Velocity", GetLinearVelocity, SetLinearVelocity, Vector3, Vector3::ZERO);
+    ACCESSOR_ATTRIBUTE_MODE(RigidBody, VAR_VECTOR3, "Linear Velocity", GetLinearVelocity, SetLinearVelocity, Vector3, Vector3::ZERO, AM_BOTH | AM_LATESTDATA);
     ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Linear Rest Threshold", GetLinearRestThreshold, SetLinearRestThreshold, float, 0.01f);
     ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Linear Damping Threshold", GetLinearDampingThreshold, SetLinearDampingThreshold, float, 0.01f);
     ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Linear Damping Scale", GetLinearDampingScale, SetLinearDampingScale, float, 0.0f);
-    ACCESSOR_ATTRIBUTE(RigidBody, VAR_VECTOR3, "Angular Velocity", GetAngularVelocity, SetAngularVelocity, Vector3, Vector3::ZERO);
+    ACCESSOR_ATTRIBUTE_MODE(RigidBody, VAR_VECTOR3, "Angular Velocity", GetAngularVelocity, SetAngularVelocity, Vector3, Vector3::ZERO, AM_BOTH | AM_LATESTDATA);
     ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Angular Rest Threshold", GetAngularRestThreshold, SetAngularRestThreshold, float, 0.01f);
     ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Angular Damping Threshold", GetAngularDampingThreshold, SetAngularDampingThreshold, float, 0.01f);
     ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Angular Damping Scale", GetAngularDampingScale, SetAngularDampingScale, float, 0.0f);

+ 14 - 2
Engine/Scene/Node.cpp

@@ -66,8 +66,8 @@ void Node::RegisterObject(Context* context)
     context->RegisterFactory<Node>();
     
     ATTRIBUTE(Node, VAR_STRING, "Name", name_, String());
-    ATTRIBUTE(Node, VAR_VECTOR3, "Position", position_, Vector3::ZERO);
-    ATTRIBUTE(Node, VAR_QUATERNION, "Rotation", rotation_, Quaternion::IDENTITY);
+    ATTRIBUTE_MODE(Node, VAR_VECTOR3, "Position", position_, Vector3::ZERO, AM_BOTH | AM_LATESTDATA);
+    ATTRIBUTE_MODE(Node, VAR_QUATERNION, "Rotation", rotation_, Quaternion::IDENTITY, AM_BOTH | AM_LATESTDATA);
     ATTRIBUTE(Node, VAR_VECTOR3, "Scale", scale_, Vector3::UNITY);
     ATTRIBUTE_MODE(Node, VAR_VARIANTMAP, "Variables", vars_, VariantMap(), AM_SERIALIZATION);
 }
@@ -558,6 +558,18 @@ Node* Node::GetChild(StringHash nameHash, bool recursive) const
     return 0;
 }
 
+unsigned Node::GetNumNetworkComponents() const
+{
+    unsigned num = 0;
+    for (Vector<SharedPtr<Component> >::ConstIterator i = components_.Begin(); i != components_.End(); ++i)
+    {
+        if ((*i)->GetID() < FIRST_LOCAL_ID)
+            ++num;
+    }
+    
+    return num;
+}
+
 void Node::GetComponents(PODVector<Component*>& dest, ShortStringHash type) const
 {
     dest.Clear();

+ 2 - 0
Engine/Scene/Node.h

@@ -209,6 +209,8 @@ public:
     Node* GetChild(StringHash nameHash, bool recursive = false) const;
     /// Return number of components
     unsigned GetNumComponents() const { return components_.Size(); }
+    /// Return number of non-local components
+    unsigned GetNumNetworkComponents() const;
     /// Return all components
     const Vector<SharedPtr<Component> >& GetComponents() const { return components_; }
     /// Return all components of type

+ 17 - 0
Engine/Scene/Serializable.cpp

@@ -436,6 +436,23 @@ unsigned Serializable::GetNumAttributes() const
     return attributes ? attributes->Size() : 0;
 }
 
+unsigned Serializable::GetNumNetworkAttributes() const
+{
+    const Vector<AttributeInfo>* attributes = context_->GetAttributes(GetType());
+    if (!attributes)
+        return 0;
+    
+    unsigned num = 0;
+    for (unsigned i = 0; i < attributes->Size(); ++i)
+    {
+        const AttributeInfo& attr = attributes->At(i);
+        if (attr.mode_ & AM_NETWORK)
+            ++num;
+    }
+    
+    return num;
+}
+
 const Vector<AttributeInfo>* Serializable::GetAttributes() const
 {
     return context_->GetAttributes(GetType());

+ 2 - 0
Engine/Scene/Serializable.h

@@ -67,6 +67,8 @@ public:
     Variant GetAttribute(const String& name);
     /// Return number of attributes
     unsigned GetNumAttributes() const;
+    /// Return number of networked attributes
+    unsigned GetNumNetworkAttributes() const;
     /// Return attribute descriptions, or null if none defined
     const Vector<AttributeInfo>* GetAttributes() const;