Преглед изворни кода

Explicitly mark changed nodes to avoid going through the whole scene in PrepareNetworkUpdate().

Lasse Öörni пре 13 година
родитељ
комит
91f6f87472

+ 1 - 1
Bin/Data/Scripts/TestSceneOld.as

@@ -233,7 +233,7 @@ void InitScene()
         light.shadowNearFarRatio = 0.01;
 
         // Store the original rotation as a node property
-        newNode.vars["rotation"] = Variant(newNode.rotation);
+        newNode.vars["rotation"] = newNode.rotation;
 
         lights.Push(newNode);
     }

+ 2 - 0
Docs/Reference.dox

@@ -1153,6 +1153,8 @@ There are some things to watch out for:
 
 - Networked attributes can either be in delta update or latest data mode. Delta updates are small incremental changes and must be applied in order, which may cause increased latency if there is a stall in network message delivery eg. due to packet loss. High volume data such as position, rotation and velocities are transmitted as latest data, which does not need ordering, instead this mode simply discards any old data received out of order. Note that node and component creation (when initial attributes need to be sent) and removal can also be considered as delta updates and are therefore applied in order.
 
+- To avoid going through the whole scene when sending network updates, nodes and components explicitly mark themselves for update when necessary. When writing your own replicated C++ components, call \ref Component::MarkNetworkUpdate "MarkNetworkUpdate()" in member functions that modify any networked attribute.
+
 - The server update logic orders replication messages so that parent nodes are created and updated before their children. Remote events are queued and only sent after the replication update to ensure that if they target a newly created node, it will already exist on the receiving end. However, it is also possible to specify unordered transmission for a remote event, in which case that guarantee does not hold.
 
 - Nodes have the concept of the \ref Node::SetOwner "owner connection" (for example the player that is controlling a specific game object), which can be set in server code. This property is not replicated to the client. Messages or remote events can be used instead to tell the players what object they control.

+ 38 - 16
Docs/ScriptAPI.dox

@@ -1187,6 +1187,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 
 Properties:<br>
 - ShortStringHash type (readonly)
@@ -1276,10 +1277,10 @@ Properties:<br>
 - Component@[] components (readonly)
 - String& name
 - Node@ parent
+- VariantMap& vars (readonly)
 - Scene@ scene (readonly)
 - Connection@ owner
 - ScriptObject@ scriptObject (readonly)
-- VariantMap vars
 
 
 SmoothedTransform
@@ -1293,6 +1294,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void Update(float, float)
 
 Properties:<br>
@@ -1401,6 +1403,7 @@ Properties:<br>
 - Component@[] components (readonly)
 - String& name
 - Node@ parent
+- VariantMap& vars (readonly)
 - bool active
 - float smoothingConstant
 - float snapThreshold
@@ -1412,7 +1415,6 @@ Properties:<br>
 - DebugRenderer@ debugRenderer (readonly)
 - Octree@ octree (readonly)
 - PhysicsWorld@ physicsWorld (readonly)
-- VariantMap vars
 
 
 Camera
@@ -1426,6 +1428,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void SetOrthoSize(const Vector2&)
 - Frustum GetSplitFrustum(float, float)
 - Ray GetScreenRay(float, float)
@@ -1738,6 +1741,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void AddLine(const Vector3&, const Vector3&, const Color&, bool arg3 = true)
 - void AddNode(Node@, bool arg1 = true)
 - void AddBoundingBox(const BoundingBox&, const Color&, bool arg2 = true)
@@ -1767,6 +1771,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void DrawDebugGeometry(DebugRenderer@, bool)
 
 Properties:<br>
@@ -1831,6 +1836,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void DrawDebugGeometry(DebugRenderer@, bool)
 
 Properties:<br>
@@ -1886,6 +1892,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void DrawDebugGeometry(DebugRenderer@, bool)
 
 Properties:<br>
@@ -1933,6 +1940,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void DrawDebugGeometry(DebugRenderer@, bool)
 
 Properties:<br>
@@ -1976,6 +1984,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void DrawDebugGeometry(DebugRenderer@, bool)
 
 Properties:<br>
@@ -2036,6 +2045,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void DrawDebugGeometry(DebugRenderer@, bool)
 - AnimationState@ AddAnimationState(Animation@)
 - void RemoveAnimationState(Animation@)
@@ -2096,6 +2106,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - bool Play(const String&, uint8, bool, float arg3 = 0.0f)
 - bool PlayExclusive(const String&, uint8, bool, float arg3 = 0.0f)
 - void Stop(const String&, float arg1 = 0.0f)
@@ -2155,6 +2166,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void DrawDebugGeometry(DebugRenderer@, bool)
 - void Updated()
 
@@ -2201,6 +2213,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void SetActive(bool, bool)
 
 Properties:<br>
@@ -2242,6 +2255,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void Resize(const BoundingBox&, uint)
 - void DrawDebugGeometry(bool) const
 - void AddManualDrawable(Drawable@)
@@ -2388,6 +2402,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void Play(Sound@)
 - void Play(Sound@, float)
 - void Play(Sound@, float, float)
@@ -2424,6 +2439,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void Play(Sound@)
 - void Play(Sound@, float)
 - void Play(Sound@, float, float)
@@ -2564,7 +2580,7 @@ Properties:<br>
 - IntVector2 screenPosition (readonly)
 - float derivedOpacity (readonly)
 - IntRect combinedScreenRect (readonly)
-- VariantMap vars
+- VariantMap& vars (readonly)
 
 
 BorderImage
@@ -2644,11 +2660,11 @@ Properties:<br>
 - IntVector2 screenPosition (readonly)
 - float derivedOpacity (readonly)
 - IntRect combinedScreenRect (readonly)
+- VariantMap& vars (readonly)
 - Texture@ texture
 - IntRect& imageRect
 - IntRect& border
 - IntVector2& hoverOffset
-- VariantMap vars
 
 
 Button
@@ -2731,6 +2747,7 @@ Properties:<br>
 - IntVector2 screenPosition (readonly)
 - float derivedOpacity (readonly)
 - IntRect combinedScreenRect (readonly)
+- VariantMap& vars (readonly)
 - Texture@ texture
 - IntRect& imageRect
 - IntRect& border
@@ -2739,7 +2756,6 @@ Properties:<br>
 - IntVector2& labelOffset
 - float repeatDelay
 - float repeatRate
-- VariantMap vars
 
 
 CheckBox
@@ -2820,13 +2836,13 @@ Properties:<br>
 - IntVector2 screenPosition (readonly)
 - float derivedOpacity (readonly)
 - IntRect combinedScreenRect (readonly)
+- VariantMap& vars (readonly)
 - Texture@ texture
 - IntRect& imageRect
 - IntRect& border
 - IntVector2& hoverOffset
 - bool checked
 - IntVector2& checkedOffset
-- VariantMap vars
 
 
 Cursor
@@ -2907,12 +2923,12 @@ Properties:<br>
 - IntVector2 screenPosition (readonly)
 - float derivedOpacity (readonly)
 - IntRect combinedScreenRect (readonly)
+- VariantMap& vars (readonly)
 - Texture@ texture
 - IntRect& imageRect
 - IntRect& border
 - IntVector2& hoverOffset
 - CursorShape shape
-- VariantMap vars
 
 
 Slider
@@ -2993,6 +3009,7 @@ Properties:<br>
 - IntVector2 screenPosition (readonly)
 - float derivedOpacity (readonly)
 - IntRect combinedScreenRect (readonly)
+- VariantMap& vars (readonly)
 - Texture@ texture
 - IntRect& imageRect
 - IntRect& border
@@ -3001,7 +3018,6 @@ Properties:<br>
 - float range
 - float value
 - BorderImage@ knob (readonly)
-- VariantMap vars
 
 
 ScrollBar
@@ -3082,6 +3098,7 @@ Properties:<br>
 - IntVector2 screenPosition (readonly)
 - float derivedOpacity (readonly)
 - IntRect combinedScreenRect (readonly)
+- VariantMap& vars (readonly)
 - Orientation orientation
 - float range
 - float value
@@ -3091,7 +3108,6 @@ Properties:<br>
 - Button@ backButton (readonly)
 - Button@ forwardButton (readonly)
 - Slider@ slider (readonly)
-- VariantMap vars
 
 
 ScrollView
@@ -3171,6 +3187,7 @@ Properties:<br>
 - IntVector2 screenPosition (readonly)
 - float derivedOpacity (readonly)
 - IntRect combinedScreenRect (readonly)
+- VariantMap& vars (readonly)
 - UIElement@ contentElement
 - IntVector2& viewPosition
 - float scrollStep
@@ -3178,7 +3195,6 @@ Properties:<br>
 - ScrollBar@ horizontalScrollBar (readonly)
 - ScrollBar@ verticalScrollBar (readonly)
 - BorderImage@ scrollPanel (readonly)
-- VariantMap vars
 
 
 ListView
@@ -3273,6 +3289,7 @@ Properties:<br>
 - IntVector2 screenPosition (readonly)
 - float derivedOpacity (readonly)
 - IntRect combinedScreenRect (readonly)
+- VariantMap& vars (readonly)
 - IntVector2& viewPosition
 - UIElement@ contentElement (readonly)
 - ScrollBar@ horizontalScrollBar (readonly)
@@ -3291,7 +3308,6 @@ Properties:<br>
 - bool hierarchyMode
 - bool clearSelectionOnDefocus
 - float floatClickInterval
-- VariantMap vars
 
 
 Text
@@ -3373,6 +3389,7 @@ Properties:<br>
 - IntVector2 screenPosition (readonly)
 - float derivedOpacity (readonly)
 - IntRect combinedScreenRect (readonly)
+- VariantMap& vars (readonly)
 - Font@ font (readonly)
 - int fontSize (readonly)
 - String& text
@@ -3385,7 +3402,6 @@ Properties:<br>
 - Color& hoverColor
 - uint numRows (readonly)
 - int rowHeight (readonly)
-- VariantMap vars
 
 
 LineEdit
@@ -3465,6 +3481,7 @@ Properties:<br>
 - IntVector2 screenPosition (readonly)
 - float derivedOpacity (readonly)
 - IntRect combinedScreenRect (readonly)
+- VariantMap& vars (readonly)
 - Texture@ texture
 - IntRect& imageRect
 - IntRect& border
@@ -3479,7 +3496,6 @@ Properties:<br>
 - bool textCopyable
 - Text@ textElement (readonly)
 - BorderImage@ cursor (readonly)
-- VariantMap vars
 
 
 Menu
@@ -3564,6 +3580,7 @@ Properties:<br>
 - IntVector2 screenPosition (readonly)
 - float derivedOpacity (readonly)
 - IntRect combinedScreenRect (readonly)
+- VariantMap& vars (readonly)
 - Texture@ texture
 - IntRect& imageRect
 - IntRect& border
@@ -3577,7 +3594,6 @@ Properties:<br>
 - bool showPopup
 - int acceleratorKey (readonly)
 - int acceleratorQualifiers (readonly)
-- VariantMap vars
 
 
 DropDownList
@@ -3668,6 +3684,7 @@ Properties:<br>
 - IntVector2 screenPosition (readonly)
 - float derivedOpacity (readonly)
 - IntRect combinedScreenRect (readonly)
+- VariantMap& vars (readonly)
 - Texture@ texture
 - IntRect& imageRect
 - IntRect& border
@@ -3686,7 +3703,6 @@ Properties:<br>
 - UIElement@ selectedItem (readonly)
 - ListView@ listView (readonly)
 - UIElement@ placeholder (readonly)
-- VariantMap vars
 
 
 Window
@@ -3766,6 +3782,7 @@ Properties:<br>
 - IntVector2 screenPosition (readonly)
 - float derivedOpacity (readonly)
 - IntRect combinedScreenRect (readonly)
+- VariantMap& vars (readonly)
 - Texture@ texture
 - IntRect& imageRect
 - IntRect& border
@@ -3773,7 +3790,6 @@ Properties:<br>
 - bool movable
 - bool resizable
 - IntRect& resizeBorder
-- VariantMap vars
 
 
 FileSelector
@@ -3848,6 +3864,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 
 Properties:<br>
 - ShortStringHash type (readonly)
@@ -3928,6 +3945,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void SetSphere(float, const Vector3& arg1 = Vector3 ( ), const Quaternion& arg2 = Quaternion ( ))
 - void SetBox(const Vector3&, const Vector3& arg1 = Vector3 ( ), const Quaternion& arg2 = Quaternion ( ))
 - void SetCylinder(float, float, const Vector3& arg2 = Vector3 ( ), const Quaternion& arg3 = Quaternion ( ))
@@ -3966,6 +3984,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void SetTransform(const Vector3&, const Quaternion&)
 - void SetCollisionLayerAndMask(uint, uint)
 - void ApplyForce(const Vector3&)
@@ -4021,6 +4040,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void Clear()
 - void DrawDebugGeometry(DebugRenderer@, bool)
 
@@ -4059,6 +4079,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - void Update(float)
 - void UpdateCollisions()
 - PhysicsRaycastResult[]@ Raycast(const Ray&, float arg1 = M_INFINITY, uint arg2 = 0xffff)
@@ -4108,6 +4129,7 @@ Methods:<br>
 - bool SetAttribute(const String&, const Variant&)
 - Variant GetAttribute(const String&)
 - void Remove()
+- void MarkNetworkUpdate() const
 - bool CreateObject(ScriptFile@, const String&)
 - bool Execute(const String&, const Variant[]@)
 - bool Execute(const String&)

+ 9 - 0
Engine/Audio/SoundSource.cpp

@@ -161,6 +161,8 @@ void SoundSource::Play(Sound* sound)
     }
     else
         PlayLockless(sound);
+    
+    MarkNetworkUpdate();
 }
 
 void SoundSource::Play(Sound* sound, float frequency)
@@ -199,6 +201,8 @@ void SoundSource::Stop()
     // Free the compressed sound decoder now if any
     FreeDecoder();
     sound_.Reset();
+    
+    MarkNetworkUpdate();
 }
 
 void SoundSource::SetSoundType(SoundType type)
@@ -207,26 +211,31 @@ void SoundSource::SetSoundType(SoundType type)
         return;
     
     soundType_ = type;
+    MarkNetworkUpdate();
 }
 
 void SoundSource::SetFrequency(float frequency)
 {
     frequency_ = Clamp(frequency, 0.0f, 535232.0f);
+    MarkNetworkUpdate();
 }
 
 void SoundSource::SetGain(float gain)
 {
     gain_ = Max(gain, 0.0f);
+    MarkNetworkUpdate();
 }
 
 void SoundSource::SetAttenuation(float attenuation)
 {
     attenuation_ = Clamp(attenuation, 0.0f, 1.0f);
+    MarkNetworkUpdate();
 }
 
 void SoundSource::SetPanning(float panning)
 {
     panning_ = Clamp(panning, -1.0f, 1.0f);
+    MarkNetworkUpdate();
 }
 
 void SoundSource::SetAutoRemove(bool enable)

+ 4 - 0
Engine/Audio/SoundSource3D.cpp

@@ -68,21 +68,25 @@ void SoundSource3D::SetDistanceAttenuation(float nearDistance, float farDistance
     nearDistance_ = Max(nearDistance, 0.0f);
     farDistance_ = Max(farDistance, 0.0f);
     rolloffFactor_ = Max(rolloffFactor, MIN_ROLLOFF);
+    MarkNetworkUpdate();
 }
 
 void SoundSource3D::SetFarDistance(float distance)
 {
     farDistance_ = Max(distance, 0.0f);
+    MarkNetworkUpdate();
 }
 
 void SoundSource3D::SetNearDistance(float distance)
 {
     nearDistance_ = Max(distance, 0.0f);
+    MarkNetworkUpdate();
 }
 
 void SoundSource3D::SetRolloffFactor(float factor)
 {
     rolloffFactor_ = Max(factor, MIN_ROLLOFF);
+    MarkNetworkUpdate();
 }
 
 void SoundSource3D::CalculateAttenuation()

+ 15 - 2
Engine/Engine/APITemplates.h

@@ -372,6 +372,7 @@ template <class T> void RegisterComponent(asIScriptEngine* engine, const char* c
     RegisterSerializable<T>(engine, className);
     RegisterSubclass<Component, T>(engine, "Component", className);
     engine->RegisterObjectMethod(className, "void Remove()", asMETHODPR(T, Remove, (), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void MarkNetworkUpdate() const", asMETHODPR(T, MarkNetworkUpdate, (), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "uint get_id()", asMETHODPR(T, GetID, () const, unsigned), asCALL_THISCALL);
     if (nodeRegistered)
         engine->RegisterObjectMethod(className, "Node@+ get_node() const", asMETHODPR(T, GetNode, () const, Node*), asCALL_THISCALL);
@@ -488,6 +489,13 @@ static CScriptArray* NodeGetChildrenWithClassName(const String& className, bool
     return VectorToHandleArray<Node>(result, "Array<Node@>");
 }
 
+static VariantMap& NodeGetVars(Node* ptr)
+{
+    // Assume that the vars will be modified and queue a network update attribute check
+    ptr->MarkNetworkUpdate();
+    return const_cast<VariantMap&>(ptr->GetVars());
+}
+
 /// Template function for registering a class derived from Node.
 template <class T> void RegisterNode(asIScriptEngine* engine, const char* className)
 {
@@ -558,7 +566,7 @@ template <class T> void RegisterNode(asIScriptEngine* engine, const char* classN
     engine->RegisterObjectMethod(className, "const String& get_name() const", asMETHOD(T, GetName), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void set_parent(Node@+)", asMETHOD(T, SetParent), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "Node@+ get_parent() const", asMETHOD(T, GetParent), asCALL_THISCALL);
-    engine->RegisterObjectProperty(className, "VariantMap vars", offsetof(T, vars_));
+    engine->RegisterObjectMethod(className, "VariantMap& get_vars()", asFUNCTION(NodeGetVars), asCALL_CDECL_OBJLAST);
 }
 
 static bool ResourceLoad(File* file, XMLFile* ptr)
@@ -708,6 +716,11 @@ static unsigned UIElementGetNumChildrenRecursive(UIElement* ptr)
     return ptr->GetNumChildren(true);
 }
 
+static VariantMap& UIElementGetVars(UIElement* ptr)
+{
+    return const_cast<VariantMap&>(ptr->GetVars());
+}
+
 /// Template function for registering a class derived from UIElement.
 template <class T> void RegisterUIElement(asIScriptEngine* engine, const char* className)
 {
@@ -816,7 +829,7 @@ template <class T> void RegisterUIElement(asIScriptEngine* engine, const char* c
     engine->RegisterObjectMethod(className, "IntVector2 get_screenPosition()", asMETHOD(T, GetScreenPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "float get_derivedOpacity()", asMETHOD(T, GetDerivedOpacity), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "IntRect get_combinedScreenRect()", asMETHOD(T, GetCombinedScreenRect), asCALL_THISCALL);
-    engine->RegisterObjectProperty(className, "VariantMap vars", offsetof(T, vars_));
+    engine->RegisterObjectMethod(className, "VariantMap& get_vars()", asFUNCTION(UIElementGetVars), asCALL_CDECL_OBJLAST);
 }
 
 /// Template function for registering a class derived from BorderImage.

+ 12 - 3
Engine/Graphics/AnimatedModel.cpp

@@ -89,6 +89,7 @@ void AnimatedModel::RegisterObject(Context* context)
     ACCESSOR_ATTRIBUTE(AnimatedModel, VAR_FLOAT, "Shadow Distance", GetShadowDistance, SetShadowDistance, float, 0.0f, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE(AnimatedModel, VAR_FLOAT, "LOD Bias", GetLodBias, SetLodBias, float, 1.0f, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE(AnimatedModel, VAR_FLOAT, "Animation LOD Bias", GetAnimationLodBias, SetAnimationLodBias, float, 1.0f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(AnimatedModel, VAR_FLOAT, "Invisible Anim LOD Factor", GetInvisibleLodFactor, SetInvisibleLodFactor, float, 0.0f, AM_DEFAULT);
     COPY_BASE_ATTRIBUTES(AnimatedModel, Drawable);
     ATTRIBUTE(AnimatedModel, VAR_INT, "Ray/Occl. LOD Level", softwareLodLevel_, M_MAX_UNSIGNED, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE(AnimatedModel, VAR_VARIANTVECTOR, "Bone Animation Enabled", GetBonesEnabledAttr, SetBonesEnabledAttr, VariantVector, VariantVector(), AM_FILE | AM_NOEDIT);
@@ -338,6 +339,8 @@ void AnimatedModel::SetModel(Model* model, bool createBones)
     // Copy bounding box & skeleton
     SetBoundingBox(model->GetBoundingBox());
     SetSkeleton(model->GetSkeleton(), createBones);
+    
+    MarkNetworkUpdate();
 }
 
 AnimationState* AnimatedModel::AddAnimationState(Animation* animation)
@@ -410,6 +413,7 @@ void AnimatedModel::RemoveAllAnimationStates()
 void AnimatedModel::SetAnimationLodBias(float bias)
 {
     animationLodBias_ = Max(bias, 0.0f);
+    MarkNetworkUpdate();
 }
 
 void AnimatedModel::SetInvisibleLodFactor(float factor)
@@ -418,7 +422,9 @@ void AnimatedModel::SetInvisibleLodFactor(float factor)
         factor = 0.0f;
     else if (factor != 0.0f && factor < 1.0f)
         factor = 1.0f;
+    
     invisibleLodFactor_ = factor;
+    MarkNetworkUpdate();
 }
 
 void AnimatedModel::SetMorphWeight(unsigned index, float weight)
@@ -431,7 +437,6 @@ void AnimatedModel::SetMorphWeight(unsigned index, float weight)
     if (weight != morphs_[index].weight_)
     {
         morphs_[index].weight_ = weight;
-        MarkMorphsDirty();
         
         // For a master model, set the same morph weight on non-master models
         if (isMaster_)
@@ -443,6 +448,9 @@ void AnimatedModel::SetMorphWeight(unsigned index, float weight)
             for (unsigned i = 1; i < models.Size(); ++i)
                 models[i]->SetMorphWeight(morphs_[index].nameHash_, weight);
         }
+        
+        MarkMorphsDirty();
+        MarkNetworkUpdate();
     }
 }
 
@@ -475,8 +483,6 @@ void AnimatedModel::ResetMorphWeights()
     for (Vector<ModelMorph>::Iterator i = morphs_.Begin(); i != morphs_.End(); ++i)
         i->weight_ = 0.0f;
     
-    MarkMorphsDirty();
-
     // For a master model, reset weights on non-master models
     if (isMaster_)
     {
@@ -487,6 +493,9 @@ void AnimatedModel::ResetMorphWeights()
         for (unsigned i = 1; i < models.Size(); ++i)
             models[i]->ResetMorphWeights();
     }
+    
+    MarkMorphsDirty();
+    MarkNetworkUpdate();
 }
 
 float AnimatedModel::GetMorphWeight(unsigned index) const

+ 18 - 1
Engine/Graphics/AnimationController.cpp

@@ -127,6 +127,7 @@ void AnimationController::Update(float timeStep)
             if (state)
                 model->RemoveAnimationState(state);
             i = animations_.Erase(i);
+            MarkNetworkUpdate();
         }
         else
             ++i;
@@ -166,6 +167,7 @@ bool AnimationController::Play(const String& name, unsigned char layer, bool loo
     animations_[index].targetWeight_ = 1.0f;
     animations_[index].fadeTime_ = fadeInTime;
     
+    MarkNetworkUpdate();
     return true;
 }
 
@@ -188,6 +190,7 @@ bool AnimationController::Stop(const String& name, float fadeOutTime)
     {
         animations_[index].targetWeight_ = 0.0f;
         animations_[index].fadeTime_ = fadeOutTime;
+        MarkNetworkUpdate();
     }
     
     return index != M_MAX_UNSIGNED || state != 0;
@@ -208,6 +211,8 @@ void AnimationController::StopLayer(unsigned char layer, float fadeOutTime)
             i->fadeTime_ = fadeOutTime;
         }
     }
+    
+    MarkNetworkUpdate();
 }
 
 void AnimationController::StopAll(float fadeOutTime)
@@ -221,6 +226,8 @@ void AnimationController::StopAll(float fadeOutTime)
         i->targetWeight_ = 0.0f;
         i->fadeTime_ = fadeOutTime;
     }
+    
+    MarkNetworkUpdate();
 }
 
 bool AnimationController::Fade(const String& name, float targetWeight, float fadeTime)
@@ -233,6 +240,7 @@ bool AnimationController::Fade(const String& name, float targetWeight, float fad
     
     animations_[index].targetWeight_ = Clamp(targetWeight, 0.0f, 1.0f);
     animations_[index].fadeTime_ = fadeTime;
+    MarkNetworkUpdate();
     return true;
 }
 
@@ -260,6 +268,8 @@ bool AnimationController::FadeOthers(const String& name, float targetWeight, flo
             }
         }
     }
+    
+    MarkNetworkUpdate();
     return true;
 }
 
@@ -270,6 +280,7 @@ bool AnimationController::SetLayer(const String& name, unsigned char layer)
         return false;
     
     state->SetLayer(layer);
+    MarkNetworkUpdate();
     return true;
 }
 
@@ -282,6 +293,7 @@ bool AnimationController::SetStartBone(const String& name, const String& startBo
     AnimatedModel* model = GetComponent<AnimatedModel>();
     Bone* bone = model->GetSkeleton().GetBone(startBoneName);
     state->SetStartBone(bone);
+    MarkNetworkUpdate();
     return true;
 }
 
@@ -299,6 +311,7 @@ bool AnimationController::SetTime(const String& name, float time)
     animations_[index].setTime_ = (unsigned short)(time / state->GetLength() * 65535.0f);
     animations_[index].setTimeTtl_ = COMMAND_STAY_TIME;
     ++animations_[index].setTimeRev_;
+    MarkNetworkUpdate();
     return true;
 }
 
@@ -311,6 +324,7 @@ bool AnimationController::SetSpeed(const String& name, float speed)
         return false;
     
     animations_[index].speed_ = speed;
+    MarkNetworkUpdate();
     return true;
 }
 
@@ -328,6 +342,7 @@ bool AnimationController::SetWeight(const String& name, float weight)
     animations_[index].setWeight_ = (unsigned char)(weight * 255.0f);
     animations_[index].setWeightTtl_ = COMMAND_STAY_TIME;
     ++animations_[index].setWeightRev_;
+    MarkNetworkUpdate();
     return true;
 }
 
@@ -338,6 +353,7 @@ bool AnimationController::SetLooped(const String& name, bool enable)
         return false;
     
     state->SetLooped(enable);
+    MarkNetworkUpdate();
     return true;
 }
 
@@ -350,6 +366,7 @@ bool AnimationController::SetAutoFade(const String& name, float fadeOutTime)
         return false;
     
     animations_[index].autoFadeTime_ = Max(fadeOutTime, 0.0f);
+    MarkNetworkUpdate();
     return true;
 }
 
@@ -506,7 +523,7 @@ void AnimationController::SetNetAnimationsAttr(const PODVector<unsigned char>& v
         StringHash animHash = buf.ReadStringHash();
         processedAnimations.Insert(animHash);
         
-       // Check if the animation state exists. If not, add new
+        // Check if the animation state exists. If not, add new
         AnimationState* state = model->GetAnimationState(animHash);
         if (!state)
         {

+ 7 - 0
Engine/Graphics/BillboardSet.cpp

@@ -164,6 +164,7 @@ void BillboardSet::GetBatch(Batch& batch, const FrameInfo& frame, unsigned batch
 void BillboardSet::SetMaterial(Material* material)
 {
     material_ = material;
+    MarkNetworkUpdate();
 }
 
 void BillboardSet::SetNumBillboards(unsigned num)
@@ -185,34 +186,40 @@ void BillboardSet::SetNumBillboards(unsigned num)
     
     bufferSizeDirty_ = true;
     MarkPositionsDirty();
+    MarkNetworkUpdate();
 }
 
 void BillboardSet::SetRelative(bool enable)
 {
     relative_ = enable;
     MarkPositionsDirty();
+    MarkNetworkUpdate();
 }
 
 void BillboardSet::SetScaled(bool enable)
 {
     scaled_ = enable;
     MarkPositionsDirty();
+    MarkNetworkUpdate();
 }
 
 void BillboardSet::SetSorted(bool enable)
 {
     sorted_ = enable;
     MarkPositionsDirty();
+    MarkNetworkUpdate();
 }
 
 void BillboardSet::SetAnimationLodBias(float bias)
 {
     animationLodBias_ = Max(bias, 0.0f);
+    MarkNetworkUpdate();
 }
 
 void BillboardSet::Updated()
 {
     MarkPositionsDirty();
+    MarkNetworkUpdate();
 }
 
 Billboard* BillboardSet::GetBillboard(unsigned index)

+ 14 - 0
Engine/Graphics/Camera.cpp

@@ -89,6 +89,7 @@ void Camera::SetNearClip(float nearClip)
     nearClip_ = Max(nearClip, M_MIN_NEARCLIP);
     frustumDirty_ = true;
     projectionDirty_ = true;
+    MarkNetworkUpdate();
 }
 
 void Camera::SetFarClip(float farClip)
@@ -96,6 +97,7 @@ void Camera::SetFarClip(float farClip)
     farClip_ = Max(farClip, M_MIN_NEARCLIP);
     frustumDirty_ = true;
     projectionDirty_ = true;
+    MarkNetworkUpdate();
 }
 
 void Camera::SetFov(float fov)
@@ -103,6 +105,7 @@ void Camera::SetFov(float fov)
     fov_ = Clamp(fov, 0.0f, M_MAX_FOV);
     frustumDirty_ = true;
     projectionDirty_ = true;
+    MarkNetworkUpdate();
 }
 
 void Camera::SetOrthoSize(float orthoSize)
@@ -111,6 +114,7 @@ void Camera::SetOrthoSize(float orthoSize)
     aspectRatio_ = 1.0f;
     frustumDirty_ = true;
     projectionDirty_ = true;
+    MarkNetworkUpdate();
 }
 
 void Camera::SetOrthoSize(const Vector2& orthoSize)
@@ -119,6 +123,7 @@ void Camera::SetOrthoSize(const Vector2& orthoSize)
     aspectRatio_ = orthoSize.x_ / orthoSize.y_;
     frustumDirty_ = true;
     projectionDirty_ = true;
+    MarkNetworkUpdate();
 }
 
 void Camera::SetAspectRatio(float aspectRatio)
@@ -126,6 +131,7 @@ void Camera::SetAspectRatio(float aspectRatio)
     aspectRatio_ = aspectRatio;
     frustumDirty_ = true;
     projectionDirty_ = true;
+    MarkNetworkUpdate();
 }
 
 void Camera::SetZoom(float zoom)
@@ -133,21 +139,25 @@ void Camera::SetZoom(float zoom)
     zoom_ = Max(zoom, M_EPSILON);
     frustumDirty_ = true;
     projectionDirty_ = true;
+    MarkNetworkUpdate();
 }
 
 void Camera::SetLodBias(float bias)
 {
     lodBias_ = Max(bias, M_EPSILON);
+    MarkNetworkUpdate();
 }
 
 void Camera::SetViewMask(unsigned mask)
 {
     viewMask_ = mask;
+    MarkNetworkUpdate();
 }
 
 void Camera::SetViewOverrideFlags(unsigned flags)
 {
     viewOverrideFlags_ = flags;
+    MarkNetworkUpdate();
 }
 
 void Camera::SetOrthographic(bool enable)
@@ -155,23 +165,27 @@ void Camera::SetOrthographic(bool enable)
     orthographic_ = enable;
     frustumDirty_ = true;
     projectionDirty_ = true;
+    MarkNetworkUpdate();
 }
 
 void Camera::SetAutoAspectRatio(bool enable)
 {
     autoAspectRatio_ = enable;
+    MarkNetworkUpdate();
 }
 
 void Camera::SetProjectionOffset(const Vector2& offset)
 {
     projectionOffset_ = offset;
     projectionDirty_ = true;
+    MarkNetworkUpdate();
 }
 
 void Camera::SetFlipVertical(bool enable)
 {
     flipVertical_ = enable;
     projectionDirty_ = true;
+    MarkNetworkUpdate();
 }
 
 float Camera::GetNearClip() const

+ 12 - 0
Engine/Graphics/Drawable.cpp

@@ -115,31 +115,37 @@ void Drawable::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 void Drawable::SetDrawDistance(float distance)
 {
     drawDistance_ = distance;
+    MarkNetworkUpdate();
 }
 
 void Drawable::SetShadowDistance(float distance)
 {
     shadowDistance_ = distance;
+    MarkNetworkUpdate();
 }
 
 void Drawable::SetLodBias(float bias)
 {
     lodBias_ = Max(bias, M_EPSILON);
+    MarkNetworkUpdate();
 }
 
 void Drawable::SetViewMask(unsigned mask)
 {
     viewMask_ = mask;
+    MarkNetworkUpdate();
 }
 
 void Drawable::SetLightMask(unsigned mask)
 {
     lightMask_ = mask;
+    MarkNetworkUpdate();
 }
 
 void Drawable::SetShadowMask(unsigned mask)
 {
     shadowMask_ = mask;
+    MarkNetworkUpdate();
 }
 
 void Drawable::SetZoneMask(unsigned mask)
@@ -147,26 +153,31 @@ void Drawable::SetZoneMask(unsigned mask)
     zoneMask_ = mask;
     // Mark dirty to reset cached zone
     OnMarkedDirty(node_);
+    MarkNetworkUpdate();
 }
 
 void Drawable::SetMaxLights(unsigned num)
 {
     maxLights_ = num;
+    MarkNetworkUpdate();
 }
 
 void Drawable::SetVisible(bool enable)
 {
     visible_ = enable;
+    MarkNetworkUpdate();
 }
 
 void Drawable::SetCastShadows(bool enable)
 {
     castShadows_ = enable;
+    MarkNetworkUpdate();
 }
 
 void Drawable::SetOccluder(bool enable)
 {
     occluder_ = enable;
+    MarkNetworkUpdate();
 }
 
 void Drawable::SetOccludee(bool enable)
@@ -177,6 +188,7 @@ void Drawable::SetOccludee(bool enable)
         // Reinsert to octree to make sure octant occlusion does not erroneously hide this drawable
         if (octant_ && !reinsertionQueued_)
             octant_->GetRoot()->QueueReinsertion(this);
+        MarkNetworkUpdate();
     }
 }
 

+ 18 - 2
Engine/Graphics/Light.cpp

@@ -142,7 +142,7 @@ void Light::RegisterObject(Context* context)
 
 void Light::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
 {
-    Serializable::OnSetAttribute(attr, src);
+    Component::OnSetAttribute(attr, src);
     
     // Validate the bias, cascade & focus parameters
     switch (attr.offset_)
@@ -266,11 +266,13 @@ void Light::SetLightType(LightType type)
 {
     lightType_ = type;
     OnMarkedDirty(node_);
+    MarkNetworkUpdate();
 }
 
 void Light::SetPerVertex(bool enable)
 {
     perVertex_ = enable;
+    MarkNetworkUpdate();
 }
 
 void Light::SetColor(const Color& color)
@@ -278,82 +280,97 @@ void Light::SetColor(const Color& color)
     // Clamp RGB values to positive, as negative values behave erratically depending on whether the pass uses
     // replace or additive blend mode
     color_ = Color(Max(color.r_, 0.0f), Max(color.g_, 0.0f), Max(color.b_, 0.0f), 1.0f);
+    MarkNetworkUpdate();
 }
 
 void Light::SetRange(float range)
 {
     range_ = Max(range, 0.0f);
     OnMarkedDirty(node_);
+    MarkNetworkUpdate();
 }
 
 void Light::SetFov(float fov)
 {
     fov_ = Clamp(fov, 0.0f, M_MAX_FOV);
     OnMarkedDirty(node_);
+    MarkNetworkUpdate();
 }
 
 void Light::SetAspectRatio(float aspectRatio)
 {
     aspectRatio_ = Max(aspectRatio, M_EPSILON);
     OnMarkedDirty(node_);
+    MarkNetworkUpdate();
 }
 
 void Light::SetShadowNearFarRatio(float nearFarRatio)
 {
     shadowNearFarRatio_ = Clamp(nearFarRatio, 0.0f, 0.5f);
+    MarkNetworkUpdate();
 }
 
 void Light::SetSpecularIntensity(float intensity)
 {
     specularIntensity_ = Max(intensity, 0.0f);
+    MarkNetworkUpdate();
 }
 
 void Light::SetFadeDistance(float distance)
 {
     fadeDistance_ = Max(distance, 0.0f);
+    MarkNetworkUpdate();
 }
 
 void Light::SetShadowBias(const BiasParameters& parameters)
 {
     shadowBias_ = parameters;
     shadowBias_.Validate();
+    MarkNetworkUpdate();
 }
 
 void Light::SetShadowCascade(const CascadeParameters& parameters)
 {
     shadowCascade_ = parameters;
     shadowCascade_.Validate();
+    MarkNetworkUpdate();
 }
 
 void Light::SetShadowFocus(const FocusParameters& parameters)
 {
     shadowFocus_ = parameters;
     shadowFocus_.Validate();
+    MarkNetworkUpdate();
 }
 
 void Light::SetShadowFadeDistance(float distance)
 {
     shadowFadeDistance_ = Max(distance, 0.0f);
+    MarkNetworkUpdate();
 }
 
 void Light::SetShadowIntensity(float intensity)
 {
     shadowIntensity_ = Clamp(intensity, 0.0f, 1.0f);
+    MarkNetworkUpdate();
 }
 
 void Light::SetShadowResolution(float resolution)
 {
     shadowResolution_ = Clamp(resolution, 0.125f, 1.0f);
+    MarkNetworkUpdate();
 }
 
 void Light::SetRampTexture(Texture* texture)
 {
     rampTexture_ = texture;
+    MarkNetworkUpdate();
 }
 
 void Light::SetShapeTexture(Texture* texture)
 {
     shapeTexture_ = texture;
+    MarkNetworkUpdate();
 }
 
 Frustum Light::GetFrustum() const
@@ -365,7 +382,6 @@ Frustum Light::GetFrustum() const
     return ret;
 }
 
-
 Matrix3x4 Light::GetDirLightTransform(Camera* camera, bool getNearQuad)
 {
     if (!camera)

+ 1 - 1
Engine/Graphics/Octree.cpp

@@ -382,7 +382,7 @@ void Octree::RegisterObject(Context* context)
 void Octree::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
 {
     // If any of the (size) attributes change, resize the octree
-    Serializable::OnSetAttribute(attr, src);
+    Component::OnSetAttribute(attr, src);
     Resize(worldBoundingBox_, numLevels_);
 }
 

+ 2 - 0
Engine/Graphics/ParticleEmitter.cpp

@@ -347,6 +347,7 @@ bool ParticleEmitter::LoadParameters(XMLFile* file)
         textureAnimation_ = animations;
     }
     
+    MarkNetworkUpdate();
     return true;
 }
 
@@ -356,6 +357,7 @@ void ParticleEmitter::SetActive(bool enable, bool resetPeriod)
     {
         active_ = enable;
         periodTimer_ = 0.0f;
+        MarkNetworkUpdate();
     }
 }
 

+ 5 - 0
Engine/Graphics/StaticModel.cpp

@@ -274,12 +274,15 @@ void StaticModel::SetModel(Model* model)
     
     SetBoundingBox(model->GetBoundingBox());
     ResetLodLevels();
+    MarkNetworkUpdate();
 }
 
 void StaticModel::SetMaterial(Material* material)
 {
     for (unsigned i = 0; i < materials_.Size(); ++i)
         materials_[i] = material;
+    
+    MarkNetworkUpdate();
 }
 
 bool StaticModel::SetMaterial(unsigned index, Material* material)
@@ -291,12 +294,14 @@ bool StaticModel::SetMaterial(unsigned index, Material* material)
     }
     
     materials_[index] = material;
+    MarkNetworkUpdate();
     return true;
 }
 
 void StaticModel::SetSoftwareLodLevel(unsigned level)
 {
     softwareLodLevel_ = level;
+    MarkNetworkUpdate();
 }
 
 Material* StaticModel::GetMaterial(unsigned index) const

+ 8 - 8
Engine/Graphics/View.cpp

@@ -790,8 +790,8 @@ void View::GetBatches()
                     ShadowBatchQueue& shadowQueue = lightQueue.shadowSplits_[j];
                     Camera* shadowCamera = split.shadowCamera_;
                     shadowQueue.shadowCamera_ = shadowCamera;
-                    shadowQueue.nearSplit_ = split.shadowNearSplit_;
-                    shadowQueue.farSplit_ = split.shadowFarSplit_;
+                    shadowQueue.nearSplit_ = split.nearSplit_;
+                    shadowQueue.farSplit_ = split.farSplit_;
                     
                     // Setup the shadow split viewport and finalize shadow camera parameters
                     shadowQueue.shadowViewport_ = GetShadowMapViewport(light, j, lightQueue.shadowMap_);
@@ -1853,9 +1853,9 @@ void View::ProcessShadowSplit(LightQueryResult& query, unsigned splitIndex, unsi
     // For directional light check that the split is inside the visible scene: if not, can skip the split
     if (type == LIGHT_DIRECTIONAL)
     {
-        if (sceneViewBox_.min_.z_ > split.shadowFarSplit_)
+        if (sceneViewBox_.min_.z_ > split.farSplit_)
             return;
-        if (sceneViewBox_.max_.z_ < split.shadowNearSplit_)
+        if (sceneViewBox_.max_.z_ < split.nearSplit_)
             return;
     }
     
@@ -1892,8 +1892,8 @@ void View::ProcessShadowCasters(LightQueryResult& query, const PODVector<Drawabl
     if (type != LIGHT_DIRECTIONAL)
         lightViewFrustum = sceneFrustum_.Transformed(lightView);
     else
-        lightViewFrustum = camera_->GetSplitFrustum(Max(sceneViewBox_.min_.z_, split.shadowNearSplit_),
-            Min(sceneViewBox_.max_.z_, split.shadowFarSplit_)).Transformed(lightView);
+        lightViewFrustum = camera_->GetSplitFrustum(Max(sceneViewBox_.min_.z_, split.nearSplit_),
+            Min(sceneViewBox_.max_.z_, split.farSplit_)).Transformed(lightView);
     
     BoundingBox lightViewFrustumBox(lightViewFrustum);
      
@@ -2055,8 +2055,8 @@ void View::SetupShadowCameras(LightQueryResult& query)
             ShadowQueryResult& split = query.shadowSplits_[i];
             Camera* shadowCamera = renderer_->GetShadowCamera();
             split.shadowCamera_ = shadowCamera;
-            split.shadowNearSplit_ = nearSplit;
-            split.shadowFarSplit_ = farSplit;
+            split.nearSplit_ = nearSplit;
+            split.farSplit_ = farSplit;
             SetupDirLightShadowCamera(shadowCamera, light, nearSplit, farSplit);
             
             nearSplit = farSplit;

+ 2 - 2
Engine/Graphics/View.h

@@ -61,9 +61,9 @@ struct ShadowQueryResult
     /// Combined bounding box of the shadow casters in light view or projection space.
     BoundingBox shadowCasterBox_;
     /// Directional light shadow near split distance.
-    float shadowNearSplit_;
+    float nearSplit_;
     /// Directional light shadow far split distance.
-    float shadowFarSplit_;
+    float farSplit_;
 };
 
 /// Intermediate light processing result.

+ 9 - 1
Engine/Graphics/Zone.cpp

@@ -78,7 +78,7 @@ void Zone::RegisterObject(Context* context)
 
 void Zone::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
 {
-    Serializable::OnSetAttribute(attr, src);
+    Component::OnSetAttribute(attr, src);
     
     // If bounding box, override mode, visibility or priority changes, dirty the drawable as applicable
     switch (attr.offset_)
@@ -102,16 +102,19 @@ void Zone::SetBoundingBox(const BoundingBox& box)
 {
     boundingBox_ = box;
     OnMarkedDirty(node_);
+    MarkNetworkUpdate();
 }
 
 void Zone::SetAmbientColor(const Color& color)
 {
     ambientColor_ = Color(color, 1.0f);
+    MarkNetworkUpdate();
 }
 
 void Zone::SetFogColor(const Color& color)
 {
     fogColor_ = Color(color, 1.0f);
+    MarkNetworkUpdate();
 }
 
 void Zone::SetFogStart(float start)
@@ -120,6 +123,7 @@ void Zone::SetFogStart(float start)
         start = 0.0f;
     
     fogStart_ = start;
+    MarkNetworkUpdate();
 }
 
 void Zone::SetFogEnd(float end)
@@ -128,21 +132,25 @@ void Zone::SetFogEnd(float end)
         end = 0.0f;
     
     fogEnd_ = end;
+    MarkNetworkUpdate();
 }
 
 void Zone::SetPriority(int priority)
 {
     priority_ = priority;
+    MarkNetworkUpdate();
 }
 
 void Zone::SetOverride(bool enable)
 {
     override_ = enable;
+    MarkNetworkUpdate();
 }
 
 void Zone::SetAmbientGradient(bool enable)
 {
     ambientGradient_ = enable;
+    MarkNetworkUpdate();
 }
 
 const Color& Zone::GetAmbientStartColor()

+ 5 - 8
Engine/Network/Connection.cpp

@@ -496,13 +496,12 @@ void Connection::ProcessSceneUpdate(int msgID, MemoryBuffer& msg)
             
             // Read initial user variables
             unsigned numVars = msg.ReadVLE();
-            VariantMap& vars = node->GetVars();
+            const VariantMap& vars = node->GetVars();
             while (numVars)
             {
-                --numVars;
-                
                 ShortStringHash key = msg.ReadShortStringHash();
-                vars[key] = msg.ReadVariant();
+                node->SetVar(key, msg.ReadVariant());
+                --numVars;
             }
             
             // Read components
@@ -547,13 +546,11 @@ void Connection::ProcessSceneUpdate(int msgID, MemoryBuffer& 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();
-                VariantMap& vars = node->GetVars();
                 while (changedVars)
                 {
-                    --changedVars;
-                    
                     ShortStringHash key = msg.ReadShortStringHash();
-                    vars[key] = msg.ReadVariant();
+                    node->SetVar(key, msg.ReadVariant());
+                    --changedVars;
                 }
             }
             else

+ 4 - 0
Engine/Network/NetworkPriority.cpp

@@ -60,21 +60,25 @@ void NetworkPriority::RegisterObject(Context* context)
 void NetworkPriority::SetBasePriority(float priority)
 {
     basePriority_ = Max(priority, 0.0f);
+    MarkNetworkUpdate();
 }
 
 void NetworkPriority::SetDistanceFactor(float factor)
 {
     distanceFactor_ = Max(factor, 0.0f);
+    MarkNetworkUpdate();
 }
 
 void NetworkPriority::SetMinPriority(float priority)
 {
     minPriority_ = Max(priority, 0.0f);
+    MarkNetworkUpdate();
 }
 
 void NetworkPriority::SetAlwaysUpdateOwner(bool enable)
 {
     alwaysUpdateOwner_ = enable;
+    MarkNetworkUpdate();
 }
 
 bool NetworkPriority::CheckUpdate(float distance, float& accumulator)

+ 17 - 1
Engine/Physics/CollisionShape.cpp

@@ -236,7 +236,7 @@ void CollisionShape::RegisterObject(Context* context)
 
 void CollisionShape::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
 {
-    Serializable::OnSetAttribute(attr, src);
+    Component::OnSetAttribute(attr, src);
     dirty_ = true;
 }
 
@@ -260,6 +260,7 @@ void CollisionShape::SetBox(const Vector3& size, const Vector3& position, const
     
     UpdateShape();
     NotifyRigidBody();
+    MarkNetworkUpdate();
 }
 
 void CollisionShape::SetSphere(float diameter, const Vector3& position, const Quaternion& rotation)
@@ -272,6 +273,7 @@ void CollisionShape::SetSphere(float diameter, const Vector3& position, const Qu
     
     UpdateShape();
     NotifyRigidBody();
+    MarkNetworkUpdate();
 }
 
 void CollisionShape::SetCylinder(float diameter, float height, const Vector3& position, const Quaternion& rotation)
@@ -284,6 +286,7 @@ void CollisionShape::SetCylinder(float diameter, float height, const Vector3& po
     
     UpdateShape();
     NotifyRigidBody();
+    MarkNetworkUpdate();
 }
 
 void CollisionShape::SetCapsule(float diameter, float height, const Vector3& position, const Quaternion& rotation)
@@ -296,6 +299,7 @@ void CollisionShape::SetCapsule(float diameter, float height, const Vector3& pos
     
     UpdateShape();
     NotifyRigidBody();
+    MarkNetworkUpdate();
 }
 
 void CollisionShape::SetCone(float diameter, float height, const Vector3& position, const Quaternion& rotation)
@@ -308,6 +312,7 @@ void CollisionShape::SetCone(float diameter, float height, const Vector3& positi
     
     UpdateShape();
     NotifyRigidBody();
+    MarkNetworkUpdate();
 }
 
 void CollisionShape::SetTriangleMesh(Model* model, unsigned lodLevel, const Vector3& scale, const Vector3& position, const Quaternion& rotation)
@@ -327,6 +332,7 @@ void CollisionShape::SetTriangleMesh(Model* model, unsigned lodLevel, const Vect
     
     UpdateShape();
     NotifyRigidBody();
+    MarkNetworkUpdate();
 }
 
 void CollisionShape::SetConvexHull(Model* model, unsigned lodLevel, const Vector3& scale, const Vector3& position, const Quaternion& rotation)
@@ -346,6 +352,7 @@ void CollisionShape::SetConvexHull(Model* model, unsigned lodLevel, const Vector
     
     UpdateShape();
     NotifyRigidBody();
+    MarkNetworkUpdate();
 }
 
 void CollisionShape::SetShapeType(ShapeType type)
@@ -355,6 +362,7 @@ void CollisionShape::SetShapeType(ShapeType type)
         shapeType_ = type;
         UpdateShape();
         NotifyRigidBody();
+        MarkNetworkUpdate();
     }
 }
 
@@ -365,6 +373,7 @@ void CollisionShape::SetSize(const Vector3& size)
         size_ = size;
         UpdateShape();
         NotifyRigidBody();
+        MarkNetworkUpdate();
     }
 }
 
@@ -374,6 +383,7 @@ void CollisionShape::SetPosition(const Vector3& position)
     {
         position_ = position;
         NotifyRigidBody();
+        MarkNetworkUpdate();
     }
 }
 
@@ -383,6 +393,7 @@ void CollisionShape::SetRotation(const Quaternion& rotation)
     {
         rotation_ = rotation;
         NotifyRigidBody();
+        MarkNetworkUpdate();
     }
 }
 
@@ -393,6 +404,7 @@ void CollisionShape::SetTransform(const Vector3& position, const Quaternion& rot
         position_ = position;
         rotation_ = rotation;
         NotifyRigidBody();
+        MarkNetworkUpdate();
     }
 }
 
@@ -405,6 +417,7 @@ void CollisionShape::SetMargin(float margin)
         if (shape_)
             shape_->setMargin(margin);
         margin_ = margin;
+        MarkNetworkUpdate();
     }
 }
 
@@ -418,6 +431,7 @@ void CollisionShape::SetModel(Model* model)
             UpdateShape();
             NotifyRigidBody();
         }
+        MarkNetworkUpdate();
     }
 }
 
@@ -431,6 +445,7 @@ void CollisionShape::SetLodLevel(unsigned lodLevel)
             UpdateShape();
             NotifyRigidBody();
         }
+        MarkNetworkUpdate();
     }
 }
 
@@ -483,6 +498,7 @@ void CollisionShape::SetModelAttr(ResourceRef value)
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     model_ = cache->GetResource<Model>(value.id_);
     dirty_ = true;
+    MarkNetworkUpdate();
 }
 
 ResourceRef CollisionShape::GetModelAttr() const

+ 1 - 1
Engine/Physics/Joint.cpp

@@ -72,7 +72,7 @@ void Joint::RegisterObject(Context* context)
 
 void Joint::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
 {
-    Serializable::OnSetAttribute(attr, src);
+    Component::OnSetAttribute(attr, src);
     
     // Change of the joint type or connected body requires the joint to be recreated
     if (attr.offset_ == offsetof(Joint, type_) || attr.offset_ == offsetof(Joint, otherBodyNodeID_))

+ 50 - 1
Engine/Physics/RigidBody.cpp

@@ -119,7 +119,7 @@ void RigidBody::RegisterObject(Context* context)
 
 void RigidBody::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
 {
-    Serializable::OnSetAttribute(attr, src);
+    Component::OnSetAttribute(attr, src);
     dirty_ = true;
 }
 
@@ -163,6 +163,8 @@ void RigidBody::setWorldTransform(const btTransform &worldTrans)
         delayed.worldRotation_ = newWorldRotation;
         physicsWorld_->AddDelayedWorldTransform(delayed);
     }
+    
+    MarkNetworkUpdate();
 }
 
 void RigidBody::SetMass(float mass)
@@ -173,6 +175,7 @@ void RigidBody::SetMass(float mass)
     {
         mass_ = mass;
         AddBodyToWorld();
+        MarkNetworkUpdate();
     }
 }
 
@@ -187,6 +190,8 @@ void RigidBody::SetPosition(Vector3 position)
         btTransform interpTrans = body_->getInterpolationWorldTransform();
         interpTrans.setOrigin(worldTrans.getOrigin());
         body_->setInterpolationWorldTransform(interpTrans);
+        
+        MarkNetworkUpdate();
     }
 }
 
@@ -201,6 +206,8 @@ void RigidBody::SetRotation(Quaternion rotation)
         btTransform interpTrans = body_->getInterpolationWorldTransform();
         interpTrans.setRotation(worldTrans.getRotation());
         body_->setInterpolationWorldTransform(interpTrans);
+        
+        MarkNetworkUpdate();
     }
 }
 
@@ -217,6 +224,8 @@ void RigidBody::SetTransform(const Vector3& position, const Quaternion& rotation
         interpTrans.setOrigin(worldTrans.getOrigin());
         interpTrans.setRotation(worldTrans.getRotation());
         body_->setInterpolationWorldTransform(interpTrans);
+        
+        MarkNetworkUpdate();
     }
 }
 
@@ -227,25 +236,35 @@ void RigidBody::SetLinearVelocity(Vector3 velocity)
         body_->setLinearVelocity(ToBtVector3(velocity));
         if (velocity != Vector3::ZERO)
             Activate();
+        MarkNetworkUpdate();
     }
 }
 
 void RigidBody::SetLinearFactor(Vector3 factor)
 {
     if (body_)
+    {
         body_->setLinearFactor(ToBtVector3(factor));
+        MarkNetworkUpdate();
+    }
 }
 
 void RigidBody::SetLinearRestThreshold(float threshold)
 {
     if (body_)
+    {
         body_->setSleepingThresholds(threshold, body_->getAngularSleepingThreshold());
+        MarkNetworkUpdate();
+    }
 }
 
 void RigidBody::SetLinearDamping(float damping)
 {
     if (body_)
+    {
         body_->setDamping(damping, body_->getAngularDamping());
+        MarkNetworkUpdate();
+    }
 }
 
 void RigidBody::SetAngularVelocity(Vector3 velocity)
@@ -255,51 +274,73 @@ void RigidBody::SetAngularVelocity(Vector3 velocity)
         body_->setAngularVelocity(ToBtVector3(velocity));
         if (velocity != Vector3::ZERO)
             Activate();
+        MarkNetworkUpdate();
     }
 }
 
 void RigidBody::SetAngularFactor(Vector3 factor)
 {
     if (body_)
+    {
         body_->setAngularFactor(ToBtVector3(factor));
+        MarkNetworkUpdate();
+    }
 }
 
 void RigidBody::SetAngularRestThreshold(float threshold)
 {
     if (body_)
+    {
         body_->setSleepingThresholds(body_->getLinearSleepingThreshold(), threshold);
+        MarkNetworkUpdate();
+    }
 }
 
 void RigidBody::SetAngularDamping(float damping)
 {
     if (body_)
+    {
         body_->setDamping(body_->getLinearDamping(), damping);
+        MarkNetworkUpdate();
+    }
 }
 
 void RigidBody::SetFriction(float friction)
 {
     if (body_)
+    {
         body_->setFriction(friction);
+        MarkNetworkUpdate();
+    }
 }
 
 void RigidBody::SetRestitution(float restitution)
 {
     if (body_)
+    {
         body_->setRestitution(restitution);
+        MarkNetworkUpdate();
+    }
 }
 
 void RigidBody::SetCcdRadius(float radius)
 {
     radius = Max(radius, 0.0f);
     if (body_)
+    {
         body_->setCcdSweptSphereRadius(radius);
+        MarkNetworkUpdate();
+    }
 }
 
 void RigidBody::SetCcdMotionThreshold(float threshold)
 {
     threshold = Max(threshold, 0.0f);
     if (body_)
+    {
         body_->setCcdMotionThreshold(threshold);
+        MarkNetworkUpdate();
+    }
 }
 
 void RigidBody::SetUseGravity(bool enable)
@@ -319,6 +360,8 @@ void RigidBody::SetUseGravity(bool enable)
             body_->setGravity(world->getGravity());
         else
             body_->setGravity(btVector3(0.0f, 0.0f, 0.0f));
+        
+        MarkNetworkUpdate();
     }
 }
 
@@ -328,6 +371,7 @@ void RigidBody::SetKinematic(bool enable)
     {
         kinematic_ = enable;
         AddBodyToWorld();
+        MarkNetworkUpdate();
     }
 }
 
@@ -337,6 +381,7 @@ void RigidBody::SetPhantom(bool enable)
     {
         phantom_ = enable;
         AddBodyToWorld();
+        MarkNetworkUpdate();
     }
 }
 
@@ -346,6 +391,7 @@ void RigidBody::SetCollisionLayer(unsigned layer)
     {
         collisionLayer_ = layer;
         AddBodyToWorld();
+        MarkNetworkUpdate();
     }
 }
 
@@ -355,6 +401,7 @@ void RigidBody::SetCollisionMask(unsigned mask)
     {
         collisionMask_ = mask;
         AddBodyToWorld();
+        MarkNetworkUpdate();
     }
 }
 
@@ -365,12 +412,14 @@ void RigidBody::SetCollisionLayerAndMask(unsigned layer, unsigned mask)
         collisionLayer_ = layer;
         collisionMask_ = mask;
         AddBodyToWorld();
+        MarkNetworkUpdate();
     }
 }
 
 void RigidBody::SetCollisionEventMode(CollisionEventMode mode)
 {
     collisionEventMode_ = mode;
+    MarkNetworkUpdate();
 }
 
 void RigidBody::ApplyForce(const Vector3& force)

+ 13 - 1
Engine/Scene/Component.cpp

@@ -24,8 +24,8 @@
 #include "Precompiled.h"
 #include "Component.h"
 #include "Context.h"
-#include "Node.h"
 #include "ReplicationState.h"
+#include "Scene.h"
 #include "XMLElement.h"
 
 #include "DebugNew.h"
@@ -43,6 +43,12 @@ Component::~Component()
 {
 }
 
+void Component::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
+{
+    Serializable::OnSetAttribute(attr, src);
+    MarkNetworkUpdate();
+}
+
 bool Component::Save(Serializer& dest)
 {
     // Write type and ID
@@ -141,6 +147,12 @@ void Component::CleanupConnection(Connection* connection)
     }
 }
 
+void Component::MarkNetworkUpdate()
+{
+    if (id_ < FIRST_LOCAL_ID && node_)
+        node_->MarkNetworkUpdate();
+}
+
 void Component::SetID(unsigned id)
 {
     id_ = id;

+ 4 - 0
Engine/Scene/Component.h

@@ -45,6 +45,8 @@ public:
     /// Destruct.
     virtual ~Component();
     
+    /// Handle attribute write access.
+    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src);
     /// Save as binary data. Return true if successful.
     virtual bool Save(Serializer& dest);
     /// Save as XML data. Return true if successful.
@@ -84,6 +86,8 @@ public:
     void PrepareNetworkUpdate();
     /// Clean up all references to a network connection that is about to be removed.
     void CleanupConnection(Connection* connection);
+    /// Mark for attribute check on the next network update.
+    void MarkNetworkUpdate();
     
 protected:
     /// Handle scene node being assigned at creation.

+ 76 - 11
Engine/Scene/Node.cpp

@@ -50,7 +50,8 @@ Node::Node(Context* context) :
     scale_(Vector3::ONE),
     worldTransform_(Matrix3x4::IDENTITY),
     rotateCount_(0),
-    dirty_(false)
+    dirty_(false),
+    networkUpdate_(false)
 {
 }
 
@@ -98,6 +99,12 @@ void Node::OnEvent(Object* sender, bool broadcast, StringHash eventType, Variant
         Object::OnEvent(sender, broadcast, eventType, eventData);
 }
 
+void Node::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
+{
+    Serializable::OnSetAttribute(attr, src);
+    MarkNetworkUpdate();
+}
+
 bool Node::Load(Deserializer& source)
 {
     SceneResolver resolver;
@@ -230,6 +237,8 @@ void Node::SetName(const String& name)
 {
     name_ = name;
     nameHash_ = StringHash(name);
+    
+    MarkNetworkUpdate();
 }
 
 void Node::SetPosition(const Vector3& position)
@@ -237,15 +246,18 @@ void Node::SetPosition(const Vector3& position)
     position_ = position;
     if (!dirty_)
         MarkDirty();
+    
+    MarkNetworkUpdate();
 }
 
 void Node::SetRotation(const Quaternion& rotation)
 {
     rotation_ = rotation;
+    rotateCount_ = 0;
     if (!dirty_)
         MarkDirty();
     
-    rotateCount_ = 0;
+    MarkNetworkUpdate();
 }
 
 void Node::SetDirection(const Vector3& direction)
@@ -258,6 +270,8 @@ void Node::SetScale(float scale)
     scale_ = Vector3(scale, scale, scale).Abs();
     if (!dirty_)
         MarkDirty();
+    
+    MarkNetworkUpdate();
 }
 
 void Node::SetScale(const Vector3& scale)
@@ -265,38 +279,43 @@ void Node::SetScale(const Vector3& scale)
     scale_ = scale.Abs();
     if (!dirty_)
         MarkDirty();
+    
+    MarkNetworkUpdate();
 }
 
 void Node::SetTransform(const Vector3& position, const Quaternion& rotation)
 {
     position_ = position;
     rotation_ = rotation;
+    rotateCount_ = 0;
     if (!dirty_)
         MarkDirty();
     
-    rotateCount_ = 0;
+    MarkNetworkUpdate();
 }
 
 void Node::SetTransform(const Vector3& position, const Quaternion& rotation, float scale)
 {
     position_ = position;
     rotation_ = rotation;
+    rotateCount_ = 0;
     scale_ = Vector3(scale, scale, scale);
     if (!dirty_)
         MarkDirty();
     
-    rotateCount_ = 0;
+    MarkNetworkUpdate();
 }
 
 void Node::SetTransform(const Vector3& position, const Quaternion& rotation, const Vector3& scale)
 {
     position_ = position;
     rotation_ = rotation;
+    rotateCount_ = 0;
     scale_ = scale;
     if (!dirty_)
         MarkDirty();
     
-    rotateCount_ = 0;
+    MarkNetworkUpdate();
 }
 
 void Node::SetWorldPosition(const Vector3& position)
@@ -370,6 +389,8 @@ void Node::Translate(const Vector3& delta)
     position_ += delta;
     if (!dirty_)
         MarkDirty();
+    
+    MarkNetworkUpdate();
 }
 
 void Node::TranslateRelative(const Vector3& delta)
@@ -377,6 +398,8 @@ void Node::TranslateRelative(const Vector3& delta)
     position_ += rotation_ * delta;
     if (!dirty_)
         MarkDirty();
+    
+    MarkNetworkUpdate();
 }
 
 void Node::Rotate(const Quaternion& delta, bool fixedAxis)
@@ -395,6 +418,8 @@ void Node::Rotate(const Quaternion& delta, bool fixedAxis)
     
     if (!dirty_)
         MarkDirty();
+    
+    MarkNetworkUpdate();
 }
 
 void Node::Yaw(float angle, bool fixedAxis)
@@ -434,6 +459,8 @@ void Node::Scale(float scale)
     scale_ *= scale;
     if (!dirty_)
         MarkDirty();
+    
+    MarkNetworkUpdate();
 }
 
 void Node::Scale(const Vector3& scale)
@@ -441,6 +468,8 @@ void Node::Scale(const Vector3& scale)
     scale_ *= scale;
     if (!dirty_)
         MarkDirty();
+    
+    MarkNetworkUpdate();
 }
 
 void Node::SetOwner(Connection* owner)
@@ -502,6 +531,7 @@ void Node::AddChild(Node* node)
     
     node->parent_ = this;
     node->MarkDirty();
+    node->MarkNetworkUpdate();
 }
 
 void Node::RemoveChild(Node* node)
@@ -622,6 +652,12 @@ void Node::SetParent(Node* parent)
     }
 }
 
+void Node::SetVar(ShortStringHash key, const Variant& value)
+{
+    vars_[key] = value;
+    MarkNetworkUpdate();
+}
+
 void Node::AddListener(Component* component)
 {
     if (!component)
@@ -775,6 +811,15 @@ bool Node::HasComponent(ShortStringHash type) const
     return false;
 }
 
+const Variant& Node::GetVar(ShortStringHash key) const
+{
+    VariantMap::ConstIterator i = vars_.Find(key);
+    if (i != vars_.End())
+        return i->second_;
+    else
+        return Variant::EMPTY;
+}
+
 Component* Node::GetComponent(ShortStringHash type) const
 {
     for (Vector<SharedPtr<Component> >::ConstIterator i = components_.Begin(); i != components_.End(); ++i)
@@ -787,7 +832,7 @@ Component* Node::GetComponent(ShortStringHash type) const
 
 void Node::PrepareNetworkUpdate()
 {
-    // Process dependencies first
+    // Update dependency nodes list first
     dependencyNodes_.Clear();
     
     // Add the parent node, but if it is local, traverse to the first non-local node
@@ -800,10 +845,18 @@ void Node::PrepareNetworkUpdate()
             dependencyNodes_.Push(current);
     }
     
-    // Then let the components add their dependencies
+    // Let the components add their dependencies, and check their attributes at the same time
     for (Vector<SharedPtr<Component> >::ConstIterator i = components_.Begin(); i != components_.End(); ++i)
-        (*i)->GetDependencyNodes(dependencyNodes_);
+    {
+        Component* component = *i;
+        if (component->GetID() < FIRST_LOCAL_ID)
+        {
+            component->GetDependencyNodes(dependencyNodes_);
+            component->PrepareNetworkUpdate();
+        }
+    }
     
+    // Then check for node attribute changes
     const Vector<AttributeInfo>* attributes = GetNetworkAttributes();
     if (attributes)
     {
@@ -846,7 +899,7 @@ void Node::PrepareNetworkUpdate()
         }
     }
     
-    // Check for user var changes
+    // Finally check for user var changes
     for (VariantMap::ConstIterator i = vars_.Begin(); i != vars_.End(); ++i)
     {
         VariantMap::ConstIterator j = previousVars_.Find(i->first_);
@@ -867,6 +920,8 @@ void Node::PrepareNetworkUpdate()
             }
         }
     }
+    
+    networkUpdate_ = false;
 }
 
 void Node::CleanupConnection(Connection* connection)
@@ -881,11 +936,19 @@ void Node::CleanupConnection(Connection* connection)
     }
 }
 
+void Node::MarkNetworkUpdate()
+{
+    if (id_ < FIRST_LOCAL_ID && !networkUpdate_ && scene_)
+    {
+        scene_->MarkNetworkUpdate(this);
+        networkUpdate_ = true;
+    }
+}
+
 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;
@@ -1113,7 +1176,8 @@ Component* Node::CreateComponent(ShortStringHash type, unsigned id, CreateMode m
     newComponent->SetNode(this);
     newComponent->OnMarkedDirty(this);
     
-    // Mark node dirty in all replication states
+    // Check attributes of the new component on next network update, and mark node dirty in all replication states
+    MarkNetworkUpdate();
     MarkReplicationDirty();
     
     return newComponent;
@@ -1155,6 +1219,7 @@ void Node::RemoveChild(Vector<SharedPtr<Node> >::Iterator i)
 {
     (*i)->parent_ = 0;
     (*i)->MarkDirty();
+    (*i)->MarkNetworkUpdate();
     children_.Erase(i);
 }
 

+ 14 - 5
Engine/Scene/Node.h

@@ -58,6 +58,8 @@ public:
     
     /// Handle event. Targeted events will be forwarded to all components.
     virtual void OnEvent(Object* sender, bool broadcast, StringHash eventType, VariantMap& eventData);
+    /// Handle attribute write access.
+    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src);
     /// Load from binary data. Return true if successful.
     virtual bool Load(Deserializer& source);
     /// Load from XML data. Return true if successful.
@@ -151,6 +153,8 @@ public:
     void Remove();
     /// %Set parent scene node. Retains the world transform.
     void SetParent(Node* parent);
+    /// %Set a user variable.
+    void SetVar(ShortStringHash key, const Variant& value);
     /// Add listener component that is notified of node being dirtied. Can either be in the same node or another.
     void AddListener(Component* component);
     /// Remove listener component.
@@ -266,8 +270,10 @@ public:
     bool HasComponent(ShortStringHash type) const;
     /// Return listener components.
     const Vector<WeakPtr<Component> > GetListeners() const { return listeners_; }
-    /// Return user variables.
-    VariantMap& GetVars() { return vars_; }
+    /// Return a user variable.
+    const Variant& GetVar(ShortStringHash key) const;
+    /// Return all user variables.
+    const VariantMap& GetVars() const { return vars_; }
     /// Return first component derived from class.
     template <class T> T* GetDerivedComponent() const;
     /// Return components derived from class.
@@ -307,18 +313,19 @@ public:
     void PrepareNetworkUpdate();
     /// Clean up all references to a network connection that is about to be removed.
     void CleanupConnection(Connection* connection);
+    /// Mark for attribute check on the next network update.
+    void MarkNetworkUpdate();
     /// Mark node dirty in scene replication states.
     void MarkReplicationDirty();
     
-    /// User variables.
-    VariantMap vars_;
-    
 protected:
     /// Create a component with specific ID.
     Component* CreateComponent(ShortStringHash type, unsigned id, CreateMode mode);
     /// Create a child node with specific ID.
     Node* CreateChild(unsigned id, CreateMode mode);
     
+    /// User variables.
+    VariantMap vars_;
     /// Per-user network replication states.
     PODVector<NodeReplicationState*> replicationStates_;
     
@@ -370,6 +377,8 @@ private:
     unsigned char rotateCount_;
     /// World transform needs update flag.
     mutable bool dirty_;
+    /// Network update queued flag.
+    bool networkUpdate_;
 };
 
 template <class T> T* Node::CreateComponent(CreateMode mode) { return static_cast<T*>(CreateComponent(T::GetTypeStatic(), mode)); }

+ 24 - 9
Engine/Scene/Scene.cpp

@@ -612,6 +612,8 @@ void Scene::NodeAdded(Node* node)
         }
         
         replicatedNodes_[id] = node;
+        
+        MarkNetworkUpdate(node);
         MarkReplicationDirty(node);
     }
     else
@@ -709,13 +711,14 @@ String Scene::GetVarNamesAttr() const
 
 void Scene::PrepareNetworkUpdate()
 {
-    Node::PrepareNetworkUpdate();
-    
-    for (HashMap<unsigned, Node*>::Iterator i = replicatedNodes_.Begin(); i != replicatedNodes_.End(); ++i)
-        i->second_->PrepareNetworkUpdate();
+    for (HashSet<unsigned>::Iterator i = networkUpdateNodes_.Begin(); i != networkUpdateNodes_.End(); ++i)
+    {
+        Node* node = GetNode(*i);
+        if (node)
+            node->PrepareNetworkUpdate();
+    }
     
-    for (HashMap<unsigned, Component*>::Iterator i = replicatedComponents_.Begin(); i != replicatedComponents_.End(); ++i)
-        i->second_->PrepareNetworkUpdate();
+    networkUpdateNodes_.Clear();
 }
 
 void Scene::CleanupConnection(Connection* connection)
@@ -729,12 +732,24 @@ void Scene::CleanupConnection(Connection* connection)
         i->second_->CleanupConnection(connection);
 }
 
+void Scene::MarkNetworkUpdate(Node* node)
+{
+    if (node)
+    {
+        unsigned id = node->GetID();
+        if (id < FIRST_LOCAL_ID)
+            networkUpdateNodes_.Insert(id);
+    }
+}
+
 void Scene::MarkReplicationDirty(Node* node)
 {
     unsigned id = node->GetID();
-    
-    for (PODVector<NodeReplicationState*>::Iterator i = replicationStates_.Begin(); i != replicationStates_.End(); ++i)
-        (*i)->sceneState_->dirtyNodes_.Insert(id);
+    if (id < FIRST_LOCAL_ID)
+    {
+        for (PODVector<NodeReplicationState*>::Iterator i = replicationStates_.Begin(); i != replicationStates_.End(); ++i)
+            (*i)->sceneState_->dirtyNodes_.Insert(id);
+    }
 }
 
 void Scene::HandleUpdate(StringHash eventType, VariantMap& eventData)

+ 4 - 0
Engine/Scene/Scene.h

@@ -164,6 +164,8 @@ public:
     void PrepareNetworkUpdate();
     /// Clean up all references to a network connection that is about to be removed.
     void CleanupConnection(Connection* connection);
+    /// Mark a node for attribute check on the next network update.
+    void MarkNetworkUpdate(Node* node);
     /// Mark a node dirty in scene replication states. The node does not need to have own replication state yet.
     void MarkReplicationDirty(Node* node);
     
@@ -195,6 +197,8 @@ private:
     Vector<SharedPtr<PackageFile> > requiredPackageFiles_;
     /// Registered node user variable reverse mappings.
     HashMap<ShortStringHash, String> varNames_;
+    /// Node IDs to check for attribute changes on the next network update.
+    HashSet<unsigned> networkUpdateNodes_;
     /// Delayed dirty notification queue for components.
     PODVector<Component*> delayedDirtyComponents_;
     /// Mutex for the delayed dirty notification queue.

+ 4 - 0
Engine/Script/ScriptInstance.cpp

@@ -120,6 +120,7 @@ void ScriptInstance::SetScriptFile(ScriptFile* scriptFile)
     scriptFile_ = scriptFile;
     
     CreateObject();
+    MarkNetworkUpdate();
 }
 
 void ScriptInstance::SetClassName(const String& className)
@@ -130,11 +131,13 @@ void ScriptInstance::SetClassName(const String& className)
     ReleaseObject();
     className_ = className;
     CreateObject();
+    MarkNetworkUpdate();
 }
 
 void ScriptInstance::SetActive(bool active)
 {
     active_ = active;
+    MarkNetworkUpdate();
 }
 
 void ScriptInstance::SetFixedUpdateFps(int fps)
@@ -143,6 +146,7 @@ void ScriptInstance::SetFixedUpdateFps(int fps)
     fixedUpdateInterval_ = fixedUpdateFps_ ? (1.0f / fixedUpdateFps_) : 0.0f;
     fixedUpdateAcc_ = 0.0f;
     fixedPostUpdateAcc_ = 0.0f;
+    MarkNetworkUpdate();
 }
 
 bool ScriptInstance::Execute(const String& declaration, const VariantVector& parameters)

+ 2 - 2
Engine/UI/ListView.cpp

@@ -46,7 +46,7 @@ int GetItemIndent(UIElement* item)
 {
     if (!item)
         return 0;
-    return item->vars_[indentHash].GetInt();
+    return item->GetVar(indentHash).GetInt();
 }
 
 OBJECTTYPESTATIC(ListView);
@@ -97,7 +97,7 @@ void ListView::SetStyle(const XMLElement& element)
                 UIElement* item = root->GetChild(itemElem.GetAttribute("name"), true);
                 AddItem(item);
                 if (itemElem.HasAttribute("indent"))
-                    item->vars_[indentHash] = itemElem.GetInt("indent");
+                    item->SetVar(indentHash, itemElem.GetInt("indent"));
                 itemElem = itemElem.GetNext("listitem");
             }
         }

+ 3 - 3
Engine/UI/Menu.cpp

@@ -121,7 +121,7 @@ void Menu::ShowPopup(bool enable)
             root->AddChild(popup_);
         popup_->SetPosition(GetScreenPosition() + popupOffset_);
         popup_->SetVisible(true);
-        popup_->vars_[originHash] = (void*)this;
+        popup_->SetVar(originHash, (void*)this);
         popup_->BringToFront();
     }
     else
@@ -136,7 +136,7 @@ void Menu::ShowPopup(bool enable)
                 menu->ShowPopup(false);
         }
         
-        popup_->vars_[originHash].Clear();
+        popup_->SetVar(originHash, Variant());
         popup_->SetVisible(false);
         popup_->Remove();
     }
@@ -212,7 +212,7 @@ void Menu::HandleFocusChanged(StringHash eventType, VariantMap& eventData)
         if (element == this || element == popup_)
             return;
         if (element->GetParent() == root)
-            element = static_cast<UIElement*>(element->vars_[originHash].GetPtr());
+            element = static_cast<UIElement*>(element->GetVar(originHash).GetPtr());
         else
             element = element->GetParent();
     }

+ 14 - 0
Engine/UI/UIElement.cpp

@@ -847,6 +847,11 @@ void UIElement::SetParent(UIElement* parent)
         parent->AddChild(this);
 }
 
+void UIElement::SetVar(ShortStringHash key, const Variant& value)
+{
+    vars_[key] = value;
+}
+
 IntVector2 UIElement::GetScreenPosition()
 {
     if (positionDirty_)
@@ -1001,6 +1006,15 @@ unsigned UIElement::GetUIntColor()
     return uintColor_;
 }
 
+const Variant& UIElement::GetVar(ShortStringHash key) const
+{
+    VariantMap::ConstIterator i = vars_.Find(key);
+    if (i != vars_.End())
+        return i->second_;
+    else
+        return Variant::EMPTY;
+}
+
 IntVector2 UIElement::ScreenToElement(const IntVector2& screenPosition)
 {
     return screenPosition - GetScreenPosition();

+ 8 - 5
Engine/UI/UIElement.h

@@ -247,6 +247,8 @@ public:
     void Remove();
     /// %Set parent element. Same as parent->AddChild(this).
     void SetParent(UIElement* parent);
+    /// %Set a user variable.
+    void SetVar(ShortStringHash key, const Variant& value);
     
     /// Return name.
     const String& GetName() const { return name_; }
@@ -334,8 +336,10 @@ public:
     UIElement* GetRoot() const;
     /// Return precalculated 32-bit color. Only valid when no gradient.
     unsigned GetUIntColor();
-    /// Return user variables.
-    VariantMap& GetVars() { return vars_; }
+    /// Return a user variable.
+    const Variant& GetVar(ShortStringHash key) const;
+    /// Return all user variables.
+    const VariantMap& GetVars() const { return vars_; }
     
     /// Convert screen coordinates to element coordinates.
     IntVector2 ScreenToElement(const IntVector2& screenPosition);
@@ -362,9 +366,6 @@ public:
     void GetBatchesWithOffset(IntVector2& offset, PODVector<UIBatch>& batches, PODVector<UIQuad>& quads, IntRect
         currentScissor);
     
-    /// User variables.
-    VariantMap vars_;
-    
 protected:
     /// Mark screen position as needing an update.
     void MarkDirty();
@@ -379,6 +380,8 @@ protected:
     IntRect clipBorder_;
     /// Colors.
     Color color_[MAX_UIELEMENT_CORNERS];
+    /// User variables.
+    VariantMap vars_;
     /// Priority.
     int priority_;
     /// Bring to front when focused flag.