Browse Source

Removed most OnGetAttribute() / OnSetAttribute() functions in favor of accessor attributes.
Added compare to attribute default values when sending initial node or component attributes.
Network updates for parent nodes are now sent before their child nodes.
Renamed PostLoad() to OnFinishUpdate().
Fixed kNet null pointer assert in debug mode.
Re-enabled memory leak checking in debug mode.

Lasse Öörni 14 years ago
parent
commit
7b15b34c83
44 changed files with 1024 additions and 996 deletions
  1. 33 47
      Engine/Audio/SoundSource.cpp
  2. 9 5
      Engine/Audio/SoundSource.h
  3. 3 3
      Engine/Audio/SoundSource3D.cpp
  4. 8 6
      Engine/Core/Attribute.h
  5. 4 4
      Engine/Core/Variant.cpp
  6. 2 2
      Engine/Engine/PhysicsAPI.cpp
  7. 5 4
      Engine/Engine/SceneAPI.cpp
  8. 89 113
      Engine/Graphics/AnimatedModel.cpp
  9. 18 7
      Engine/Graphics/AnimatedModel.h
  10. 30 51
      Engine/Graphics/AnimationController.cpp
  11. 7 7
      Engine/Graphics/AnimationController.h
  12. 50 81
      Engine/Graphics/BillboardSet.cpp
  13. 9 4
      Engine/Graphics/BillboardSet.h
  14. 12 12
      Engine/Graphics/Camera.cpp
  15. 9 9
      Engine/Graphics/Drawable.cpp
  16. 46 67
      Engine/Graphics/Light.cpp
  17. 9 4
      Engine/Graphics/Light.h
  18. 5 14
      Engine/Graphics/Octree.cpp
  19. 73 91
      Engine/Graphics/ParticleEmitter.cpp
  20. 12 8
      Engine/Graphics/ParticleEmitter.h
  21. 26 45
      Engine/Graphics/StaticModel.cpp
  22. 9 6
      Engine/Graphics/StaticModel.h
  23. 10 13
      Engine/Graphics/Zone.cpp
  24. 115 91
      Engine/Network/Connection.cpp
  25. 9 2
      Engine/Network/Connection.h
  26. 100 1
      Engine/Network/Network.cpp
  27. 2 0
      Engine/Network/Network.h
  28. 6 0
      Engine/Network/NetworkEvents.h
  29. 10 8
      Engine/Network/Protocol.h
  30. 23 40
      Engine/Physics/CollisionShape.cpp
  31. 7 6
      Engine/Physics/CollisionShape.h
  32. 36 51
      Engine/Physics/Joint.cpp
  33. 15 8
      Engine/Physics/Joint.h
  34. 15 15
      Engine/Physics/PhysicsWorld.cpp
  35. 13 13
      Engine/Physics/RigidBody.cpp
  36. 62 35
      Engine/Scene/Node.cpp
  37. 7 4
      Engine/Scene/Node.h
  38. 7 15
      Engine/Scene/Scene.cpp
  39. 1 3
      Engine/Scene/Scene.h
  40. 6 7
      Engine/Scene/Serializable.cpp
  41. 41 12
      Engine/Scene/Serializable.h
  42. 56 76
      Engine/Script/ScriptInstance.cpp
  43. 14 5
      Engine/Script/ScriptInstance.h
  44. 1 1
      Urho3D/Urho3D.cpp

+ 33 - 47
Engine/Audio/SoundSource.cpp

@@ -134,53 +134,14 @@ void SoundSource::RegisterObject(Context* context)
 {
     context->RegisterFactory<SoundSource>();
     
-    ENUM_ATTRIBUTE(SoundSource, "Sound Type", soundType_, typeNames, SOUND_EFFECT);
-    ATTRIBUTE(SoundSource, VAR_FLOAT, "Frequency", frequency_, 0.0f);
-    ATTRIBUTE(SoundSource, VAR_FLOAT, "Gain", gain_, 1.0f);
-    ATTRIBUTE(SoundSource, VAR_FLOAT, "Attenuation", attenuation_, 1.0f);
-    ATTRIBUTE(SoundSource, VAR_FLOAT, "Panning", panning_, 0.0f);
-    ATTRIBUTE(SoundSource, VAR_BOOL, "Autoremove on Stop", autoRemove_, false);
-    ATTRIBUTE(SoundSource, VAR_RESOURCEREF, "Sound", sound_, ResourceRef(Sound::GetTypeStatic()));
-    ATTRIBUTE(SoundSource, VAR_INT, "Play Position", position_, 0);
-}
-
-void SoundSource::OnSetAttribute(const AttributeInfo& attr, const Variant& value)
-{
-    ResourceCache* cache = GetSubsystem<ResourceCache>();
-    
-    switch (attr.offset_)
-    {
-    case offsetof(SoundSource, sound_):
-        Play(cache->GetResource<Sound>(value.GetResourceRef().id_));
-        break;
-        
-    case offsetof(SoundSource, position_):
-        if (sound_)
-            SetPlayPosition(sound_->GetStart() + value.GetInt());
-        break;
-        
-    default:
-        Serializable::OnSetAttribute(attr, value);
-        break;
-    }
-}
-
-Variant SoundSource::OnGetAttribute(const AttributeInfo& attr)
-{
-    switch (attr.offset_)
-    {
-    case offsetof(SoundSource, sound_):
-        return GetResourceRef(sound_, Sound::GetTypeStatic());
-        
-    case offsetof(SoundSource, position_):
-        if (sound_)
-            return (int)(GetPlayPosition() - sound_->GetStart());
-        else
-            return 0;
-        
-    default:
-        return Serializable::OnGetAttribute(attr);
-    }
+    ENUM_ATTRIBUTE(SoundSource, "Sound Type", soundType_, typeNames, SOUND_EFFECT, AM_DEFAULT);
+    ATTRIBUTE(SoundSource, VAR_FLOAT, "Frequency", frequency_, 0.0f, AM_DEFAULT);
+    ATTRIBUTE(SoundSource, VAR_FLOAT, "Gain", gain_, 1.0f, AM_DEFAULT);
+    ATTRIBUTE(SoundSource, VAR_FLOAT, "Attenuation", attenuation_, 1.0f, AM_DEFAULT);
+    ATTRIBUTE(SoundSource, VAR_FLOAT, "Panning", panning_, 0.0f, AM_DEFAULT);
+    ATTRIBUTE(SoundSource, VAR_BOOL, "Autoremove on Stop", autoRemove_, false, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(SoundSource, VAR_RESOURCEREF, "Sound", GetSoundAttr, SetSoundAttr, ResourceRef, ResourceRef(Sound::GetTypeStatic()), AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(SoundSource, VAR_INT, "Play Position", GetPositionAttr, SetPositionAttr, int, 0, AM_DEFAULT);
 }
 
 void SoundSource::Play(Sound* sound)
@@ -522,6 +483,31 @@ void SoundSource::Mix(int* dest, unsigned samples, int mixRate, bool stereo, boo
         timePosition_ += ((float)samples / (float)mixRate) * frequency_ / sound_->GetFrequency();
 }
 
+void SoundSource::SetSoundAttr(ResourceRef value)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    Play(cache->GetResource<Sound>(value.id_));
+}
+
+void SoundSource::SetPositionAttr(int value)
+{
+    if (sound_)
+        SetPlayPosition(sound_->GetStart() + value);
+}
+
+ResourceRef SoundSource::GetSoundAttr() const
+{
+    return GetResourceRef(sound_, Sound::GetTypeStatic());
+}
+
+int SoundSource::GetPositionAttr() const
+{
+    if (sound_)
+        return (int)(GetPlayPosition() - sound_->GetStart());
+    else
+        return 0;
+}
+
 void SoundSource::MixMonoToMono(Sound* sound, int* dest, unsigned samples, int mixRate)
 {
     float totalGain = audio_->GetSoundSourceMasterGain(soundType_) * attenuation_ * gain_;

+ 9 - 5
Engine/Audio/SoundSource.h

@@ -45,11 +45,6 @@ public:
     /// Register object factory
     static void RegisterObject(Context* context);
     
-    /// Handle attribute write access
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& value);
-    /// Handle attribute read access
-    virtual Variant OnGetAttribute(const AttributeInfo& attr);
-    
     /// Play a sound
     void Play(Sound* sound);
     /// Play a sound with specified frequency
@@ -107,6 +102,15 @@ public:
     /// Mix sound source output to a 32-bit clipping buffer. Called by Sound
     void Mix(int* dest, unsigned samples, int mixRate, bool stereo, bool interpolate);
     
+    /// Set sound attribute
+    void SetSoundAttr(ResourceRef value);
+    /// Set sound position attribute
+    void SetPositionAttr(int value);
+    /// Return sound attribute
+    ResourceRef GetSoundAttr() const;
+    /// Return sound position attribute
+    int GetPositionAttr() const;
+    
 protected:
     /// Audio subsystem
     WeakPtr<Audio> audio_;

+ 3 - 3
Engine/Audio/SoundSource3D.cpp

@@ -52,9 +52,9 @@ void SoundSource3D::RegisterObject(Context* context)
     context->RegisterFactory<SoundSource3D>();
     context->CopyBaseAttributes<SoundSource, SoundSource3D>();
     
-    ATTRIBUTE(SoundSource3D, VAR_FLOAT, "Near Distance", nearDistance_, DEFAULT_NEARDISTANCE);
-    ATTRIBUTE(SoundSource3D, VAR_FLOAT, "Far Distance", farDistance_, DEFAULT_FARDISTANCE);
-    ATTRIBUTE(SoundSource3D, VAR_FLOAT, "Rolloff Factor", rolloffFactor_, DEFAULT_ROLLOFF);
+    ATTRIBUTE(SoundSource3D, VAR_FLOAT, "Near Distance", nearDistance_, DEFAULT_NEARDISTANCE, AM_DEFAULT);
+    ATTRIBUTE(SoundSource3D, VAR_FLOAT, "Far Distance", farDistance_, DEFAULT_FARDISTANCE, AM_DEFAULT);
+    ATTRIBUTE(SoundSource3D, VAR_FLOAT, "Rolloff Factor", rolloffFactor_, DEFAULT_ROLLOFF, AM_DEFAULT);
 }
 
 void SoundSource3D::Update(float timeStep)

+ 8 - 6
Engine/Core/Attribute.h

@@ -26,14 +26,16 @@
 #include "Ptr.h"
 #include "Variant.h"
 
-/// Attribute used only for disk serialization
-static const unsigned AM_SERIALIZATION = 0x1;
+/// Attribute used for file serialization
+static const unsigned AM_FILE = 0x1;
 /// Attribute used for network replication
-static const unsigned AM_NETWORK = 0x2;
-/// Attribute used for both (default)
-static const unsigned AM_BOTH = 0x3;
+static const unsigned AM_NET = 0x2;
+/// Attribute used for both file serialization and network replication (default)
+static const unsigned AM_DEFAULT = 0x3;
 /// Attribute should use latest data grouping instead of delta update in network replication
 static const unsigned AM_LATESTDATA = 0x4;
+/// Attribute should not be shown in the editor
+static const unsigned AM_NOEDIT = 0x8;
 
 class Serializable;
 
@@ -55,7 +57,7 @@ struct AttributeInfo
         type_(VAR_NONE),
         offset_(0),
         enumNames_(0),
-        mode_(AM_BOTH)
+        mode_(AM_DEFAULT)
     {
     }
     

+ 4 - 4
Engine/Core/Variant.cpp

@@ -436,22 +436,22 @@ template<> void* Variant::Get<void*>() const
     return GetPtr();
 }
 
-template<> const ResourceRef& Variant::Get<const ResourceRef&>() const
+template<> ResourceRef Variant::Get<ResourceRef>() const
 {
     return GetResourceRef();
 }
 
-template<> const ResourceRefList& Variant::Get<const ResourceRefList&>() const
+template<> ResourceRefList Variant::Get<ResourceRefList>() const
 {
     return GetResourceRefList();
 }
 
-template<> const VariantVector& Variant::Get<const VariantVector&>() const
+template<> VariantVector Variant::Get<VariantVector>() const
 {
     return GetVariantVector();
 }
 
-template<> const VariantMap& Variant::Get<const VariantMap&>() const
+template<> VariantMap Variant::Get<VariantMap>() const
 {
     return GetVariantMap();
 }

+ 2 - 2
Engine/Engine/PhysicsAPI.cpp

@@ -197,9 +197,9 @@ static void RegisterJoint(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Joint", "void Clear()", asMETHOD(Joint, Clear), asCALL_THISCALL);
     engine->RegisterObjectMethod("Joint", "bool SetBall(const Vector3&in, RigidBody@+, RigidBody@+)", asMETHOD(Joint, SetBall), asCALL_THISCALL);
     engine->RegisterObjectMethod("Joint", "bool SetHinge(const Vector3&in, const Vector3&in, RigidBody@+, RigidBody@+)", asMETHOD(Joint, SetHinge), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Joint", "void set_position(const Vector3&in)", asMETHOD(Joint, SetPosition), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Joint", "void set_position(Vector3)", asMETHOD(Joint, SetPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("Joint", "Vector3 get_position() const", asMETHOD(Joint, GetPosition), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Joint", "void set_axis(const Vector3&in)", asMETHOD(Joint, SetAxis), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Joint", "void set_axis(Vector3)", asMETHOD(Joint, SetAxis), asCALL_THISCALL);
     engine->RegisterObjectMethod("Joint", "Vector3 get_axis() const", asMETHOD(Joint, GetAxis), asCALL_THISCALL);
     engine->RegisterObjectMethod("Joint", "RigidBody@+ get_bodyA() const", asMETHOD(Joint, GetBodyA), asCALL_THISCALL);
     engine->RegisterObjectMethod("Joint", "RigidBody@+ get_bodyB() const", asMETHOD(Joint, GetBodyB), asCALL_THISCALL);

+ 5 - 4
Engine/Engine/SceneAPI.cpp

@@ -28,9 +28,11 @@
 
 static void RegisterSerializable(asIScriptEngine* engine)
 {
-    engine->RegisterGlobalProperty("const uint AM_SERIALIZATION", (void*)AM_SERIALIZATION);
-    engine->RegisterGlobalProperty("const uint AM_NETWORK", (void*)AM_NETWORK);
-    engine->RegisterGlobalProperty("const uint AM_BOTH", (void*)AM_BOTH);
+    engine->RegisterGlobalProperty("const uint AM_FILE", (void*)AM_FILE);
+    engine->RegisterGlobalProperty("const uint AM_NET", (void*)AM_NET);
+    engine->RegisterGlobalProperty("const uint AM_DEFAULT", (void*)AM_DEFAULT);
+    engine->RegisterGlobalProperty("const uint AM_LATESTDATA", (void*)AM_LATESTDATA);
+    engine->RegisterGlobalProperty("const uint AM_NOEDIT", (void*)AM_NOEDIT);
     
     RegisterSerializable<Serializable>(engine, "Serializable");
 }
@@ -90,7 +92,6 @@ static void RegisterScene(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Scene", "bool LoadAsyncXML(File@+)", asMETHOD(Scene, LoadAsyncXML), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void StopAsyncLoading()", asMETHOD(Scene, StopAsyncLoading), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void Clear()", asMETHOD(Scene, Clear), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Scene", "void ClearNonLocal()", asMETHOD(Scene, ClearNonLocal), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void AddRequiredPackageFile(PackageFile@+)", asMETHOD(Scene, AddRequiredPackageFile), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void ClearRequiredPackageFiles()", asMETHOD(Scene, ClearRequiredPackageFiles), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "Component@+ GetComponentByID(uint)", asMETHOD(Scene, GetComponentByID), asCALL_THISCALL);

+ 89 - 113
Engine/Graphics/AnimatedModel.cpp

@@ -67,7 +67,8 @@ AnimatedModel::AnimatedModel(Context* context) :
     animationOrderDirty_(true),
     morphsDirty_(true),
     skinningDirty_(true),
-    isMaster_(true)
+    isMaster_(true),
+    assignBonesPending_(false)
 {
 }
 
@@ -80,121 +81,18 @@ void AnimatedModel::RegisterObject(Context* context)
     context->RegisterFactory<AnimatedModel>();
     context->CopyBaseAttributes<Drawable, AnimatedModel>();
     
-    ATTRIBUTE(AnimatedModel, VAR_RESOURCEREF, "Model", model_, ResourceRef(Model::GetTypeStatic()));
-    ATTRIBUTE(AnimatedModel, VAR_RESOURCEREFLIST, "Materials", materials_, ResourceRefList(Material::GetTypeStatic()));
-    ATTRIBUTE(AnimatedModel, VAR_FLOAT, "Animation LOD Bias", animationLodBias_, 1.0f);
-    ATTRIBUTE(AnimatedModel, VAR_INT, "Raycast/Occlusion LOD Level", softwareLodLevel_, M_MAX_UNSIGNED);
-    ATTRIBUTE(AnimatedModel, VAR_BUFFER, "Bone Animation Enabled", skeleton_, PODVector<unsigned char>());
-    ATTRIBUTE(AnimatedModel, VAR_BUFFER, "Animation States", animationStates_, PODVector<unsigned char>());
+    ACCESSOR_ATTRIBUTE(AnimatedModel, VAR_RESOURCEREF, "Model", GetModelAttr, SetModelAttr, ResourceRef, ResourceRef(Model::GetTypeStatic()), AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(AnimatedModel, VAR_RESOURCEREFLIST, "Materials", GetMaterialsAttr, SetMaterialsAttr, ResourceRefList, ResourceRefList(Material::GetTypeStatic()), AM_DEFAULT);
+    ATTRIBUTE(AnimatedModel, VAR_FLOAT, "Animation LOD Bias", animationLodBias_, 1.0f, AM_DEFAULT);
+    ATTRIBUTE(AnimatedModel, VAR_INT, "Raycast/Occlusion LOD Level", softwareLodLevel_, M_MAX_UNSIGNED, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(AnimatedModel, VAR_BUFFER, "Bone Animation Enabled", GetBonesEnabledAttr, SetBonesEnabledAttr, PODVector<unsigned char>, PODVector<unsigned char>(), AM_FILE);
+    ACCESSOR_ATTRIBUTE(AnimatedModel, VAR_BUFFER, "Animation States", GetAnimationStatesAttr, SetAnimationStatesAttr, PODVector<unsigned char>, PODVector<unsigned char>(), AM_FILE);
 }
 
-void AnimatedModel::OnSetAttribute(const AttributeInfo& attr, const Variant& value)
+void AnimatedModel::OnFinishUpdate()
 {
-    ResourceCache* cache = GetSubsystem<ResourceCache>();
-    
-    switch (attr.offset_)
-    {
-    case offsetof(AnimatedModel, model_):
-        // When loading a scene, set model without creating the bone nodes (will be assigned later during post-load)
-        SetModel(cache->GetResource<Model>(value.GetResourceRef().id_), !inSerialization_);
-        break;
-        
-    case offsetof(AnimatedModel, materials_):
-        {
-            const ResourceRefList& refs = value.GetResourceRefList();
-            for (unsigned i = 0; i < refs.ids_.Size(); ++i)
-                SetMaterial(i, cache->GetResource<Material>(refs.ids_[i]));
-        }
-        break;
-        
-    case offsetof(AnimatedModel, skeleton_):
-        {
-            MemoryBuffer buf(value.GetBuffer());
-            Vector<Bone>& bones = skeleton_.GetModifiableBones();
-            unsigned numBones = buf.ReadVLE();
-            for (unsigned i = 0; i < numBones && i < bones.Size(); ++i)
-                bones[i].animated_ = buf.ReadBool();
-        }
-        break;
-        
-    case offsetof(AnimatedModel, animationStates_):
-        {
-            // The animation states will at first be created without bone node references as well
-            /// \todo This format is unsuitable for network serialization
-            RemoveAllAnimationStates();
-            MemoryBuffer buf(value.GetBuffer());
-            unsigned numAnimations = buf.ReadVLE();
-            for (unsigned i = 0; i < numAnimations; ++i)
-            {
-                AnimationState* state = AddAnimationState(cache->GetResource<Animation>(buf.ReadStringHash()));
-                if (state)
-                {
-                    state->SetStartBone(skeleton_.GetBone(buf.ReadStringHash()));
-                    state->SetLooped(buf.ReadBool());
-                    state->SetWeight(buf.ReadFloat());
-                    state->SetTime(buf.ReadFloat());
-                    state->SetLayer(buf.ReadInt());
-                    state->SetUseNlerp(buf.ReadBool());
-                }
-                else
-                    buf.Seek(sizeof(StringHash) + 1 + sizeof(float) + sizeof(float) + sizeof(int) + 1);
-            }
-        }
-        break;
-        
-    default:
-        Serializable::OnSetAttribute(attr, value);
-        break;
-    }
-}
-
-Variant AnimatedModel::OnGetAttribute(const AttributeInfo& attr)
-{
-    switch (attr.offset_)
-    {
-    case offsetof(AnimatedModel, model_):
-        return GetResourceRef(model_, Model::GetTypeStatic());
-        
-    case offsetof(AnimatedModel, materials_):
-        return GetResourceRefList(materials_);
-        
-    case offsetof(AnimatedModel, skeleton_):
-        {
-            VectorBuffer buf;
-            const Vector<Bone>& bones = skeleton_.GetBones();
-            buf.WriteVLE(bones.Size());
-            for (Vector<Bone>::ConstIterator i = bones.Begin(); i != bones.End(); ++i)
-                buf.WriteBool(i->animated_);
-            return buf.GetBuffer();
-        }
-        
-    case offsetof(AnimatedModel, animationStates_):
-        {
-            VectorBuffer buf;
-            buf.WriteVLE(animationStates_.Size());
-            for (Vector<SharedPtr<AnimationState> >::Iterator i = animationStates_.Begin(); i != animationStates_.End(); ++i)
-            {
-                AnimationState* state = *i;
-                Bone* startBone = state->GetStartBone();
-                buf.WriteStringHash(state->GetAnimation()->GetNameHash());
-                buf.WriteStringHash(startBone ? startBone->nameHash_ : StringHash());
-                buf.WriteBool(state->IsLooped());
-                buf.WriteFloat(state->GetWeight());
-                buf.WriteFloat(state->GetTime());
-                buf.WriteInt(state->GetLayer());
-                buf.WriteBool(state->GetUseNlerp());
-            }
-            return buf.GetBuffer();
-        }
-        
-    default:
-        return Serializable::OnGetAttribute(attr);
-    }
-}
-
-void AnimatedModel::PostLoad()
-{
-    AssignBoneNodes();
+    if (assignBonesPending_)
+        AssignBoneNodes();
 }
 
 void AnimatedModel::ProcessRayQuery(RayOctreeQuery& query, float initialDistance)
@@ -696,6 +594,82 @@ void AnimatedModel::SetSkeleton(const Skeleton& skeleton, bool createBones)
     // Reserve space for skinning matrices
     skinMatrices_.Resize(skeleton_.GetNumBones());
     RefreshGeometryBoneMappings();
+    
+    assignBonesPending_ = !createBones;
+}
+
+void AnimatedModel::SetModelAttr(ResourceRef value)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    // When loading a scene, set model without creating the bone nodes (will be assigned later during post-load)
+    SetModel(cache->GetResource<Model>(value.id_), !inSerialization_);
+}
+
+void AnimatedModel::SetBonesEnabledAttr(PODVector<unsigned char> value)
+{
+    MemoryBuffer buf(value);
+    Vector<Bone>& bones = skeleton_.GetModifiableBones();
+    unsigned numBones = buf.ReadVLE();
+    for (unsigned i = 0; i < numBones && i < bones.Size(); ++i)
+        bones[i].animated_ = buf.ReadBool();
+}
+
+void AnimatedModel::SetAnimationStatesAttr(PODVector<unsigned char> value)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    // The animation states will at first be created without bone node references
+    RemoveAllAnimationStates();
+    MemoryBuffer buf(value);
+    unsigned numAnimations = buf.ReadVLE();
+    for (unsigned i = 0; i < numAnimations; ++i)
+    {
+        AnimationState* state = AddAnimationState(cache->GetResource<Animation>(buf.ReadStringHash()));
+        if (state)
+        {
+            state->SetStartBone(skeleton_.GetBone(buf.ReadStringHash()));
+            state->SetLooped(buf.ReadBool());
+            state->SetWeight(buf.ReadFloat());
+            state->SetTime(buf.ReadFloat());
+            state->SetLayer(buf.ReadInt());
+            state->SetUseNlerp(buf.ReadBool());
+        }
+        else
+            buf.Seek(sizeof(StringHash) + 1 + sizeof(float) + sizeof(float) + sizeof(int) + 1);
+    }
+}
+
+ResourceRef AnimatedModel::GetModelAttr() const
+{
+    return GetResourceRef(model_, Model::GetTypeStatic());
+}
+
+PODVector<unsigned char> AnimatedModel::GetBonesEnabledAttr() const
+{
+    VectorBuffer buf;
+    const Vector<Bone>& bones = skeleton_.GetBones();
+    buf.WriteVLE(bones.Size());
+    for (Vector<Bone>::ConstIterator i = bones.Begin(); i != bones.End(); ++i)
+        buf.WriteBool(i->animated_);
+    return buf.GetBuffer();
+}
+
+PODVector<unsigned char> AnimatedModel::GetAnimationStatesAttr() const
+{
+    VectorBuffer buf;
+    buf.WriteVLE(animationStates_.Size());
+    for (Vector<SharedPtr<AnimationState> >::ConstIterator i = animationStates_.Begin(); i != animationStates_.End(); ++i)
+    {
+        AnimationState* state = *i;
+        Bone* startBone = state->GetStartBone();
+        buf.WriteStringHash(state->GetAnimation()->GetNameHash());
+        buf.WriteStringHash(startBone ? startBone->nameHash_ : StringHash());
+        buf.WriteBool(state->IsLooped());
+        buf.WriteFloat(state->GetWeight());
+        buf.WriteFloat(state->GetTime());
+        buf.WriteInt(state->GetLayer());
+        buf.WriteBool(state->GetUseNlerp());
+    }
+    return buf.GetBuffer();
 }
 
 void AnimatedModel::OnNodeSet(Node* node)
@@ -745,6 +719,8 @@ void AnimatedModel::OnWorldBoundingBoxUpdate()
 
 void AnimatedModel::AssignBoneNodes()
 {
+    assignBonesPending_ = false;
+    
     if (!node_)
         return;
     

+ 18 - 7
Engine/Graphics/AnimatedModel.h

@@ -46,12 +46,8 @@ public:
     /// Register object factory
     static void RegisterObject(Context* context);
     
-    /// Handle attribute write access
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& value);
-    /// Handle attribute read access
-    virtual Variant OnGetAttribute(const AttributeInfo& attr);
-    /// Perform post-load after the whole scene has been loaded
-    virtual void PostLoad();
+    /// Perform finalization after a scene load or network update
+    virtual void OnFinishUpdate();
     /// Process renderer raycast
     virtual void ProcessRayQuery(RayOctreeQuery& query, float initialDistance);
     /// Update before octree reinsertion. Animation is updated here
@@ -125,6 +121,19 @@ public:
     /// Return whether is the master (first) animated model
     bool IsMaster() const { return isMaster_; }
     
+    /// Set model attribute
+    void SetModelAttr(ResourceRef value);
+    /// Set bones' animation enabled attribute
+    void SetBonesEnabledAttr(PODVector<unsigned char> value);
+    /// Set animation states attribute
+    void SetAnimationStatesAttr(PODVector<unsigned char> value);
+    /// Return model attribute
+    ResourceRef GetModelAttr() const;
+    /// Return bones' animation enabled attribute
+    PODVector<unsigned char> GetBonesEnabledAttr() const;
+    /// Return animation states attribute
+    PODVector<unsigned char> GetAnimationStatesAttr() const;
+    
 protected:
     /// Handle node being assigned
     virtual void OnNodeSet(Node* node);
@@ -134,7 +143,7 @@ protected:
     virtual void OnWorldBoundingBoxUpdate();
     
 private:
-    /// Assign skeleton and animation bone node references as a postprocess. Called by PostLoad
+    /// Assign skeleton and animation bone node references as a postprocess. Called by OnFinishUpdate
     void AssignBoneNodes();
     /// Mark animation and skinning to require an update
     void MarkAnimationDirty();
@@ -195,4 +204,6 @@ private:
     bool skinningDirty_;
     /// Master model flag
     bool isMaster_;
+    /// Bone nodes assignment pending flag
+    bool assignBonesPending_;
 };

+ 30 - 51
Engine/Graphics/AnimationController.cpp

@@ -53,57 +53,7 @@ void AnimationController::RegisterObject(Context* context)
 {
     context->RegisterFactory<AnimationController>();
     
-    ATTRIBUTE(AnimationController, VAR_BUFFER, "Animations", animations_, PODVector<unsigned char>());
-}
-
-
-void AnimationController::OnSetAttribute(const AttributeInfo& attr, const Variant& value)
-{
-    switch (attr.offset_)
-    {
-    case offsetof(AnimationController, animations_):
-        {
-            MemoryBuffer buf(value.GetBuffer());
-            animations_.Resize(buf.ReadVLE());
-            for (Vector<AnimationControl>::Iterator i = animations_.Begin(); i != animations_.End(); ++i)
-            {
-                i->hash_ = buf.ReadStringHash();
-                i->speed_ = buf.ReadFloat();
-                i->targetWeight_ = buf.ReadFloat();
-                i->fadeTime_ = buf.ReadFloat();
-                i->autoFadeTime_ = buf.ReadFloat();
-            }
-        }
-        break;
-        
-    default:
-        Serializable::OnSetAttribute(attr, value);
-        break;
-    }
-}
-
-Variant AnimationController::OnGetAttribute(const AttributeInfo& attr)
-{
-    switch (attr.offset_)
-    {
-    case offsetof(AnimationController, animations_):
-        {
-            VectorBuffer buf;
-            buf.WriteVLE(animations_.Size());
-            for (Vector<AnimationControl>::ConstIterator i = animations_.Begin(); i != animations_.End(); ++i)
-            {
-                buf.WriteStringHash(i->hash_);
-                buf.WriteFloat(i->speed_);
-                buf.WriteFloat(i->targetWeight_);
-                buf.WriteFloat(i->fadeTime_);
-                buf.WriteFloat(i->autoFadeTime_);
-            }
-            return buf.GetBuffer();
-        }
-        
-    default:
-        return Serializable::OnGetAttribute(attr);
-    }
+    ACCESSOR_ATTRIBUTE(AnimationController, VAR_BUFFER, "Animations", GetAnimationsAttr, SetAnimationsAttr, PODVector<unsigned char>, PODVector<unsigned char>(), AM_DEFAULT);
 }
 
 void AnimationController::Update(float timeStep)
@@ -542,6 +492,35 @@ float AnimationController::GetAutoFade(const String& name) const
     return animations_[index].autoFadeTime_;
 }
 
+void AnimationController::SetAnimationsAttr(PODVector<unsigned char> value)
+{
+    MemoryBuffer buf(value);
+    animations_.Resize(buf.ReadVLE());
+    for (Vector<AnimationControl>::Iterator i = animations_.Begin(); i != animations_.End(); ++i)
+    {
+        i->hash_ = buf.ReadStringHash();
+        i->speed_ = buf.ReadFloat();
+        i->targetWeight_ = buf.ReadFloat();
+        i->fadeTime_ = buf.ReadFloat();
+        i->autoFadeTime_ = buf.ReadFloat();
+    }
+}
+
+PODVector<unsigned char> AnimationController::GetAnimationsAttr() const
+{
+    VectorBuffer buf;
+    buf.WriteVLE(animations_.Size());
+    for (Vector<AnimationControl>::ConstIterator i = animations_.Begin(); i != animations_.End(); ++i)
+    {
+        buf.WriteStringHash(i->hash_);
+        buf.WriteFloat(i->speed_);
+        buf.WriteFloat(i->targetWeight_);
+        buf.WriteFloat(i->fadeTime_);
+        buf.WriteFloat(i->autoFadeTime_);
+    }
+    return buf.GetBuffer();
+}
+
 void AnimationController::OnNodeSet(Node* node)
 {
     if (node)

+ 7 - 7
Engine/Graphics/AnimationController.h

@@ -67,11 +67,6 @@ public:
     /// Register object factory
     static void RegisterObject(Context* context);
     
-    /// Handle attribute write access
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& value);
-    /// Handle attribute read access
-    virtual Variant OnGetAttribute(const AttributeInfo& attr);
-    
     /// Update the animations. Is called from HandleScenePostUpdate()
     void Update(float timeStep);
     /// Play an animation and set full target weight. Name must be the full resource name. Return true on success
@@ -132,11 +127,16 @@ public:
     float GetFadeTime(const String& name) const;
     /// Return animation autofade time
     float GetAutoFade(const String& name) const;
-
+    
+    /// Set animations attribute
+    void SetAnimationsAttr(PODVector<unsigned char> value);
+    /// Return animations attribute
+    PODVector<unsigned char> GetAnimationsAttr() const;
+    
 protected:
     /// Handle node being assigned
     virtual void OnNodeSet(Node* node);
-
+    
 private:
     /// Find the internal index and animation state of an animation
     void FindAnimation(const String& name, unsigned& index, AnimationState*& state) const;

+ 50 - 81
Engine/Graphics/BillboardSet.cpp

@@ -76,87 +76,12 @@ void BillboardSet::RegisterObject(Context* context)
     context->RegisterFactory<BillboardSet>();
     context->CopyBaseAttributes<Drawable, BillboardSet>();
     
-    ATTRIBUTE(BillboardSet, VAR_FLOAT, "Animation LOD Bias", animationLodBias_, 1.0f);
-    ATTRIBUTE(BillboardSet, VAR_BOOL, "Relative Position", relative_, true);
-    ATTRIBUTE(BillboardSet, VAR_BOOL, "Relative Scale", scaled_, true);
-    ATTRIBUTE(BillboardSet, VAR_BOOL, "Sort By Distance", sorted_, false);
-    ATTRIBUTE(BillboardSet, VAR_RESOURCEREF, "Material", material_, ResourceRef(Material::GetTypeStatic()));
-    ATTRIBUTE(BillboardSet, VAR_BUFFER, "Billboards", billboards_, PODVector<unsigned char>());
-}
-
-void BillboardSet::OnSetAttribute(const AttributeInfo& attr, const Variant& value)
-{
-    ResourceCache* cache = GetSubsystem<ResourceCache>();
-    
-    switch (attr.offset_)
-    {
-    case offsetof(BillboardSet, relative_):
-        SetRelative(value.GetBool());
-        break;
-    
-    case offsetof(BillboardSet, scaled_):
-        SetScaled(value.GetBool());
-        break;
-    
-    case offsetof(BillboardSet, sorted_):
-        SetSorted(value.GetBool());
-        break;
-    
-    case offsetof(BillboardSet, material_):
-        SetMaterial(cache->GetResource<Material>(value.GetResourceRef().id_));
-        break;
-        
-    case offsetof(BillboardSet, billboards_):
-        {
-            MemoryBuffer buf(value.GetBuffer());
-            unsigned numBillboards = buf.ReadVLE();
-            SetNumBillboards(numBillboards);
-            for (PODVector<Billboard>::Iterator i = billboards_.Begin(); i != billboards_.End(); ++i)
-            {
-                i->position_ = buf.ReadVector3();
-                i->size_ = buf.ReadVector2();
-                i->uv_ = buf.ReadRect();
-                i->color_ = buf.ReadColor();
-                i->rotation_ = buf.ReadFloat();
-                i->enabled_ = buf.ReadBool();
-            }
-            Updated();
-        }
-        break;
-        
-    default:
-        Serializable::OnSetAttribute(attr, value);
-        break;
-    }
-}
-
-Variant BillboardSet::OnGetAttribute(const AttributeInfo& attr)
-{
-    switch (attr.offset_)
-    {
-    case offsetof(BillboardSet, material_):
-        return GetResourceRef(material_, Material::GetTypeStatic());
-        
-    case offsetof(BillboardSet, billboards_):
-        {
-            VectorBuffer buf;
-            buf.WriteVLE(billboards_.Size());
-            for (PODVector<Billboard>::ConstIterator i = billboards_.Begin(); i != billboards_.End(); ++i)
-            {
-                buf.WriteVector3(i->position_);
-                buf.WriteVector2(i->size_);
-                buf.WriteRect(i->uv_);
-                buf.WriteColor(i->color_);
-                buf.WriteFloat(i->rotation_);
-                buf.WriteBool(i->enabled_);
-            }
-            return buf.GetBuffer();
-        }
-        
-    default:
-        return Serializable::OnGetAttribute(attr);
-    }
-    
+    ATTRIBUTE(BillboardSet, VAR_FLOAT, "Animation LOD Bias", animationLodBias_, 1.0f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(BillboardSet, VAR_BOOL, "Relative Position", IsRelative, SetRelative, bool, true, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(BillboardSet, VAR_BOOL, "Relative Scale", IsScaled, SetScaled, bool, true, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(BillboardSet, VAR_BOOL, "Sort By Distance", IsSorted, SetSorted, bool, false, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(BillboardSet, VAR_RESOURCEREF, "Material", GetMaterialAttr, SetMaterialAttr, ResourceRef, ResourceRef(Material::GetTypeStatic()), AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(BillboardSet, VAR_BUFFER, "Billboards", GetBillboardsAttr, SetBillboardsAttr, PODVector<unsigned char>, PODVector<unsigned char>(), AM_DEFAULT);
 }
 
 void BillboardSet::UpdateDistance(const FrameInfo& frame)
@@ -279,6 +204,50 @@ Billboard* BillboardSet::GetBillboard(unsigned index)
     return index < billboards_.Size() ? &billboards_[index] : (Billboard*)0;
 }
 
+void BillboardSet::SetMaterialAttr(ResourceRef value)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    SetMaterial(cache->GetResource<Material>(value.id_));
+}
+
+void BillboardSet::SetBillboardsAttr(PODVector<unsigned char> value)
+{
+    MemoryBuffer buf(value);
+    unsigned numBillboards = buf.ReadVLE();
+    SetNumBillboards(numBillboards);
+    for (PODVector<Billboard>::Iterator i = billboards_.Begin(); i != billboards_.End(); ++i)
+    {
+        i->position_ = buf.ReadVector3();
+        i->size_ = buf.ReadVector2();
+        i->uv_ = buf.ReadRect();
+        i->color_ = buf.ReadColor();
+        i->rotation_ = buf.ReadFloat();
+        i->enabled_ = buf.ReadBool();
+    }
+    Updated();
+}
+
+ResourceRef BillboardSet::GetMaterialAttr() const
+{
+    return GetResourceRef(material_, Material::GetTypeStatic());
+}
+
+PODVector<unsigned char> BillboardSet::GetBillboardsAttr() const
+{
+    VectorBuffer buf;
+    buf.WriteVLE(billboards_.Size());
+    for (PODVector<Billboard>::ConstIterator i = billboards_.Begin(); i != billboards_.End(); ++i)
+    {
+        buf.WriteVector3(i->position_);
+        buf.WriteVector2(i->size_);
+        buf.WriteRect(i->uv_);
+        buf.WriteColor(i->color_);
+        buf.WriteFloat(i->rotation_);
+        buf.WriteBool(i->enabled_);
+    }
+    return buf.GetBuffer();
+}
+
 void BillboardSet::OnMarkedDirty(Node* node)
 {
     if (node == node_)

+ 9 - 4
Engine/Graphics/BillboardSet.h

@@ -63,10 +63,6 @@ public:
     /// Register object factory
     static void RegisterObject(Context* context);
     
-    /// Handle attribute write access
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& value);
-    /// Handle attribute read access
-    virtual Variant OnGetAttribute(const AttributeInfo& attr);
     /// Calculate distance for rendering
     virtual void UpdateDistance(const FrameInfo& frame);
     /// Prepare geometry for rendering
@@ -108,6 +104,15 @@ public:
     /// Return animation LOD bias
     float GetAnimationLodBias() const { return animationLodBias_; }
     
+    /// Set material attribute
+    void SetMaterialAttr(ResourceRef value);
+    /// Set billboards attribute
+    void SetBillboardsAttr(PODVector<unsigned char> value);
+    /// Return material attribute
+    ResourceRef GetMaterialAttr() const;
+    /// Return billboards attribute
+    PODVector<unsigned char> GetBillboardsAttr() const;
+    
 protected:
     /// Transform has changed. Mark billboards dirty if necessary
     virtual void OnMarkedDirty(Node* node);

+ 12 - 12
Engine/Graphics/Camera.cpp

@@ -60,18 +60,18 @@ void Camera::RegisterObject(Context* context)
 {
     context->RegisterFactory<Camera>();
     
-    ATTRIBUTE(Camera, VAR_FLOAT, "Near Clip", nearClip_, DEFAULT_NEARCLIP);
-    ATTRIBUTE(Camera, VAR_FLOAT, "Far Clip", farClip_, DEFAULT_FARCLIP);
-    ATTRIBUTE(Camera, VAR_FLOAT, "FOV", fov_, DEFAULT_FOV);
-    ATTRIBUTE(Camera, VAR_FLOAT, "Aspect Ratio", aspectRatio_, 1.0f);
-    ATTRIBUTE(Camera, VAR_BOOL, "Auto Aspect Ratio", autoAspectRatio_, true);
-    ATTRIBUTE(Camera, VAR_BOOL, "Orthographic", orthographic_, false);
-    ATTRIBUTE(Camera, VAR_FLOAT, "Orthographic Size", orthoSize_, DEFAULT_ORTHOSIZE);
-    ATTRIBUTE(Camera, VAR_FLOAT, "Zoom", zoom_, 1.0f);
-    ATTRIBUTE(Camera, VAR_FLOAT, "LOD Bias", lodBias_, 1.0f);
-    ATTRIBUTE(Camera, VAR_INT, "View Mask", viewMask_, DEFAULT_VIEWMASK);
-    ATTRIBUTE(Camera, VAR_INT, "View Override Flags", viewOverrideFlags_, VOF_NONE);
-    ATTRIBUTE(Camera, VAR_VECTOR2, "Projection Offset", projectionOffset_, Vector2::ZERO);
+    ATTRIBUTE(Camera, VAR_FLOAT, "Near Clip", nearClip_, DEFAULT_NEARCLIP, AM_DEFAULT);
+    ATTRIBUTE(Camera, VAR_FLOAT, "Far Clip", farClip_, DEFAULT_FARCLIP, AM_DEFAULT);
+    ATTRIBUTE(Camera, VAR_FLOAT, "FOV", fov_, DEFAULT_FOV, AM_DEFAULT);
+    ATTRIBUTE(Camera, VAR_FLOAT, "Aspect Ratio", aspectRatio_, 1.0f, AM_DEFAULT);
+    ATTRIBUTE(Camera, VAR_BOOL, "Auto Aspect Ratio", autoAspectRatio_, true, AM_DEFAULT);
+    ATTRIBUTE(Camera, VAR_BOOL, "Orthographic", orthographic_, false, AM_DEFAULT);
+    ATTRIBUTE(Camera, VAR_FLOAT, "Orthographic Size", orthoSize_, DEFAULT_ORTHOSIZE, AM_DEFAULT);
+    ATTRIBUTE(Camera, VAR_FLOAT, "Zoom", zoom_, 1.0f, AM_DEFAULT);
+    ATTRIBUTE(Camera, VAR_FLOAT, "LOD Bias", lodBias_, 1.0f, AM_DEFAULT);
+    ATTRIBUTE(Camera, VAR_INT, "View Mask", viewMask_, DEFAULT_VIEWMASK, AM_DEFAULT);
+    ATTRIBUTE(Camera, VAR_INT, "View Override Flags", viewOverrideFlags_, VOF_NONE, AM_DEFAULT);
+    ATTRIBUTE(Camera, VAR_VECTOR2, "Projection Offset", projectionOffset_, Vector2::ZERO, AM_DEFAULT);
 }
 
 void Camera::SetNearClip(float nearClip)

+ 9 - 9
Engine/Graphics/Drawable.cpp

@@ -69,15 +69,15 @@ Drawable::~Drawable()
 
 void Drawable::RegisterObject(Context* context)
 {
-    ATTRIBUTE(Drawable, VAR_BOOL, "Is Visible", visible_, true);
-    ATTRIBUTE(Drawable, VAR_BOOL, "Is Occluder", occluder_, false);
-    ATTRIBUTE(Drawable, VAR_BOOL, "Cast Shadows", castShadows_, false);
-    ATTRIBUTE(Drawable, VAR_FLOAT, "Draw Distance", drawDistance_, 0.0f);
-    ATTRIBUTE(Drawable, VAR_FLOAT, "Shadow Distance", shadowDistance_, 0.0f);
-    ATTRIBUTE(Drawable, VAR_FLOAT, "LOD Bias", lodBias_, 1.0f);
-    ATTRIBUTE(Drawable, VAR_INT, "View Mask", viewMask_, DEFAULT_VIEWMASK);
-    ATTRIBUTE(Drawable, VAR_INT, "Light Mask", lightMask_, DEFAULT_LIGHTMASK);
-    ATTRIBUTE(Drawable, VAR_INT, "Max Lights", maxLights_, 0);
+    ATTRIBUTE(Drawable, VAR_BOOL, "Is Visible", visible_, true, AM_DEFAULT);
+    ATTRIBUTE(Drawable, VAR_BOOL, "Is Occluder", occluder_, false, AM_DEFAULT);
+    ATTRIBUTE(Drawable, VAR_BOOL, "Cast Shadows", castShadows_, false, AM_DEFAULT);
+    ATTRIBUTE(Drawable, VAR_FLOAT, "Draw Distance", drawDistance_, 0.0f, AM_DEFAULT);
+    ATTRIBUTE(Drawable, VAR_FLOAT, "Shadow Distance", shadowDistance_, 0.0f, AM_DEFAULT);
+    ATTRIBUTE(Drawable, VAR_FLOAT, "LOD Bias", lodBias_, 1.0f, AM_DEFAULT);
+    ATTRIBUTE(Drawable, VAR_INT, "View Mask", viewMask_, DEFAULT_VIEWMASK, AM_DEFAULT);
+    ATTRIBUTE(Drawable, VAR_INT, "Light Mask", lightMask_, DEFAULT_LIGHTMASK, AM_DEFAULT);
+    ATTRIBUTE(Drawable, VAR_INT, "Max Lights", maxLights_, 0, AM_DEFAULT);
 }
 
 void Drawable::ProcessRayQuery(RayOctreeQuery& query, float initialDistance)

+ 46 - 67
Engine/Graphics/Light.cpp

@@ -112,73 +112,30 @@ void Light::RegisterObject(Context* context)
     context->RemoveAttribute<Light>("LOD Bias");
     context->RemoveAttribute<Light>("Max Lights");
     
-    ENUM_ATTRIBUTE(Light, "Light Type", lightType_, typeNames, DEFAULT_LIGHTTYPE);
-    ATTRIBUTE(Light, VAR_COLOR, "Color", color_, Color());
-    ATTRIBUTE(Light, VAR_FLOAT, "Specular Intensity", specularIntensity_, 0.0f);
-    ATTRIBUTE(Light, VAR_FLOAT, "Range", range_, 0.0f);
-    ATTRIBUTE(Light, VAR_FLOAT, "Spotlight FOV", fov_, DEFAULT_FOV);
-    ATTRIBUTE(Light, VAR_FLOAT, "Spotlight Aspect Ratio", aspectRatio_, 1.0f);
-    ATTRIBUTE(Light, VAR_FLOAT, "Fade Distance", fadeDistance_, 0.0f);
-    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Fade Distance", shadowFadeDistance_, 0.0f);
-    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Intensity", shadowIntensity_, 0.0f);
-    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Map Resolution", shadowResolution_, 1.0f);
-    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Camera Near/Far Ratio", shadowNearFarRatio_, DEFAULT_SHADOWNEARFARRATIO);
-    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Constant Bias", shadowBias_.constantBias_, DEFAULT_CONSTANTBIAS);
-    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Slope-scaled Bias", shadowBias_.slopeScaledBias_, DEFAULT_SLOPESCALEDBIAS);
-    ATTRIBUTE(Light, VAR_INT, "Shadow Splits", shadowCascade_.splits_, 1);
-    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Split Lambda", shadowCascade_.lambda_, DEFAULT_LAMBDA);
-    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Split Fade Range", shadowCascade_.splitFadeRange_, DEFAULT_SHADOWFADERANGE);
-    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Split Max Range", shadowCascade_.shadowRange_, M_LARGE_VALUE);
-    ATTRIBUTE(Light, VAR_BOOL, "Shadow Focus", shadowFocus_.focus_, true);
-    ATTRIBUTE(Light, VAR_BOOL, "Shadow Focus Allow Non-uniform", shadowFocus_.nonUniform_, true);
-    ATTRIBUTE(Light, VAR_BOOL, "Shadow Focus Allow Zoom-out", shadowFocus_.zoomOut_, true);
-    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Focus Quantization", shadowFocus_.quantize_, DEFAULT_SHADOWQUANTIZE);
-    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Focus Min. View", shadowFocus_.minView_, DEFAULT_SHADOWMINVIEW);
-    ATTRIBUTE(Light, VAR_RESOURCEREF, "Attenuation Ramp Texture", rampTexture_, ResourceRef(Texture2D::GetTypeStatic()));
-    ATTRIBUTE(Light, VAR_RESOURCEREF, "Light Shape Texture", shapeTexture_, ResourceRef(Texture2D::GetTypeStatic()));
-}
-
-void Light::OnSetAttribute(const AttributeInfo& attr, const Variant& value)
-{
-    ResourceCache* cache = GetSubsystem<ResourceCache>();
-    
-    switch (attr.offset_)
-    {
-    case offsetof(Light, rampTexture_):
-        {
-            ResourceRef ref = value.GetResourceRef();
-            rampTexture_ = static_cast<Texture*>(cache->GetResource(ref.type_, ref.id_));
-        }
-        break;
-        
-    case offsetof(Light, shapeTexture_):
-        {
-            ResourceRef ref = value.GetResourceRef();
-            shapeTexture_ = static_cast<Texture*>(cache->GetResource(ref.type_, ref.id_));
-        }
-        break;
-    
-    default:
-        Serializable::OnSetAttribute(attr, value);
-        break;
-    }
-}
-
-Variant Light::OnGetAttribute(const AttributeInfo& attr)
-{
-    switch (attr.offset_)
-    {
-    case offsetof(Light, rampTexture_):
-        return GetResourceRef(rampTexture_, Texture2D::GetTypeStatic());
-        break;
-        
-    case offsetof(Light, shapeTexture_):
-        return GetResourceRef(shapeTexture_, Texture2D::GetTypeStatic());
-        break;
-    
-    default:
-        return Serializable::OnGetAttribute(attr);
-    }
+    ENUM_ATTRIBUTE(Light, "Light Type", lightType_, typeNames, DEFAULT_LIGHTTYPE, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_COLOR, "Color", color_, Color(), AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Specular Intensity", specularIntensity_, 0.0f, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Range", range_, 0.0f, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Spotlight FOV", fov_, DEFAULT_FOV, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Spotlight Aspect Ratio", aspectRatio_, 1.0f, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Fade Distance", fadeDistance_, 0.0f, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Fade Distance", shadowFadeDistance_, 0.0f, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Intensity", shadowIntensity_, 0.0f, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Map Resolution", shadowResolution_, 1.0f, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Camera Near/Far Ratio", shadowNearFarRatio_, DEFAULT_SHADOWNEARFARRATIO, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Constant Bias", shadowBias_.constantBias_, DEFAULT_CONSTANTBIAS, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Slope-scaled Bias", shadowBias_.slopeScaledBias_, DEFAULT_SLOPESCALEDBIAS, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_INT, "Shadow Splits", shadowCascade_.splits_, 1, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Split Lambda", shadowCascade_.lambda_, DEFAULT_LAMBDA, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Split Fade Range", shadowCascade_.splitFadeRange_, DEFAULT_SHADOWFADERANGE, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Split Max Range", shadowCascade_.shadowRange_, M_LARGE_VALUE, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_BOOL, "Shadow Focus", shadowFocus_.focus_, true, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_BOOL, "Shadow Focus Allow Non-uniform", shadowFocus_.nonUniform_, true, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_BOOL, "Shadow Focus Allow Zoom-out", shadowFocus_.zoomOut_, true, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Focus Quantization", shadowFocus_.quantize_, DEFAULT_SHADOWQUANTIZE, AM_DEFAULT);
+    ATTRIBUTE(Light, VAR_FLOAT, "Shadow Focus Min. View", shadowFocus_.minView_, DEFAULT_SHADOWMINVIEW, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(Light, VAR_RESOURCEREF, "Attenuation Ramp Texture", GetRampTextureAttr, SetRampTextureAttr, ResourceRef, ResourceRef(Texture2D::GetTypeStatic()), AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(Light, VAR_RESOURCEREF, "Light Shape Texture", GetShapeTextureAttr, SetShapeTextureAttr, ResourceRef, ResourceRef(Texture2D::GetTypeStatic()), AM_DEFAULT);
 }
 
 void Light::UpdateDistance(const FrameInfo& frame)
@@ -412,6 +369,28 @@ const Matrix3x4& Light::GetVolumeTransform(Camera& camera)
     return volumeTransform_;
 }
 
+void Light::SetRampTextureAttr(ResourceRef value)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    rampTexture_ = static_cast<Texture*>(cache->GetResource(value.type_, value.id_));
+}
+
+void Light::SetShapeTextureAttr(ResourceRef value)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    shapeTexture_ = static_cast<Texture*>(cache->GetResource(value.type_, value.id_));
+}
+
+ResourceRef Light::GetRampTextureAttr() const
+{
+    return GetResourceRef(rampTexture_, Texture2D::GetTypeStatic());
+}
+
+ResourceRef Light::GetShapeTextureAttr() const
+{
+    return GetResourceRef(shapeTexture_, Texture2D::GetTypeStatic());
+}
+
 void Light::OnWorldBoundingBoxUpdate()
 {
     switch (lightType_)

+ 9 - 4
Engine/Graphics/Light.h

@@ -145,10 +145,6 @@ public:
     /// Register object factory
     static void RegisterObject(Context* context);
     
-    /// Handle attribute write access
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& value);
-    /// Handle attribute read access
-    virtual Variant OnGetAttribute(const AttributeInfo& attr);
     /// Calculate distance for rendering
     virtual void UpdateDistance(const FrameInfo& frame);
     /// Add debug geometry to the debug graphics
@@ -259,6 +255,15 @@ public:
     /// Return light volume model transform. For directional lights, the view transform must be overridden
     const Matrix3x4& GetVolumeTransform(Camera& camera);
     
+    /// Set ramp texture attribute
+    void SetRampTextureAttr(ResourceRef value);
+    /// Set shape texture attribute
+    void SetShapeTextureAttr(ResourceRef value);
+    /// Return ramp texture attribute
+    ResourceRef GetRampTextureAttr() const;
+    /// Return shape texture attribute
+    ResourceRef GetShapeTextureAttr() const;
+    
 protected:
     /// Update world-space bounding box
     virtual void OnWorldBoundingBoxUpdate();

+ 5 - 14
Engine/Graphics/Octree.cpp

@@ -281,25 +281,16 @@ void Octree::RegisterObject(Context* context)
 {
     context->RegisterFactory<Octree>();
     
-    ATTRIBUTE(Octree, VAR_VECTOR3, "Bounding Box Min", worldBoundingBox_.min_, Vector3(-DEFAULT_OCTREE_SIZE, -DEFAULT_OCTREE_SIZE, -DEFAULT_OCTREE_SIZE));
-    ATTRIBUTE(Octree, VAR_VECTOR3, "Bounding Box Max", worldBoundingBox_.max_, Vector3(DEFAULT_OCTREE_SIZE, DEFAULT_OCTREE_SIZE, DEFAULT_OCTREE_SIZE));
-    ATTRIBUTE(Octree, VAR_INT, "Number of Levels", numLevels_, DEFAULT_OCTREE_LEVELS);
+    ATTRIBUTE(Octree, VAR_VECTOR3, "Bounding Box Min", worldBoundingBox_.min_, Vector3(-DEFAULT_OCTREE_SIZE, -DEFAULT_OCTREE_SIZE, -DEFAULT_OCTREE_SIZE), AM_DEFAULT);
+    ATTRIBUTE(Octree, VAR_VECTOR3, "Bounding Box Max", worldBoundingBox_.max_, Vector3(DEFAULT_OCTREE_SIZE, DEFAULT_OCTREE_SIZE, DEFAULT_OCTREE_SIZE), AM_DEFAULT);
+    ATTRIBUTE(Octree, VAR_INT, "Number of Levels", numLevels_, DEFAULT_OCTREE_LEVELS, AM_DEFAULT);
 }
 
 void Octree::OnSetAttribute(const AttributeInfo& attr, const Variant& value)
 {
+    // If any of the (size) attributes changes, resize the octree
     Serializable::OnSetAttribute(attr, value);
-    
-    // If any of the size attributes changes, resize the octree
-    /// \todo This may lead to unnecessary resizing, however it is fast once child nodes have been removed
-    switch (attr.offset_)
-    {
-    case offsetof(Octree, worldBoundingBox_.min_):
-    case offsetof(Octree, worldBoundingBox_.max_):
-    case offsetof(Octree, numLevels_):
-        Resize(worldBoundingBox_, numLevels_);
-        break;
-    }
+    Resize(worldBoundingBox_, numLevels_);
 }
 
 void Octree::Resize(const BoundingBox& box, unsigned numLevels)

+ 73 - 91
Engine/Graphics/ParticleEmitter.cpp

@@ -80,79 +80,15 @@ ParticleEmitter::~ParticleEmitter()
 void ParticleEmitter::RegisterObject(Context* context)
 {
     context->RegisterFactory<ParticleEmitter>();
-
-    ATTRIBUTE(ParticleEmitter, VAR_FLOAT, "Animation LOD Bias", animationLodBias_, 1.0f);
-    ATTRIBUTE(ParticleEmitter, VAR_BOOL, "Relative Scale", scaled_, true);
-    ATTRIBUTE(ParticleEmitter, VAR_BOOL, "Is Active", active_, true);
-    ATTRIBUTE(ParticleEmitter, VAR_FLOAT, "Period Timer", periodTimer_, 0.0f);
-    ATTRIBUTE(ParticleEmitter, VAR_FLOAT, "Emission Timer", emissionTimer_, 0.0f);
-    ATTRIBUTE(ParticleEmitter, VAR_RESOURCEREF, "Parameter Source", parameterSource_, ResourceRef(XMLFile::GetTypeStatic()));
-    ATTRIBUTE_MODE(ParticleEmitter, VAR_BUFFER, "Particles", particles_, PODVector<unsigned char>(), AM_SERIALIZATION);
-    ATTRIBUTE_MODE(ParticleEmitter, VAR_BUFFER, "Billboards", billboards_, PODVector<unsigned char>(), AM_SERIALIZATION);
-}
-
-void ParticleEmitter::OnSetAttribute(const AttributeInfo& attr, const Variant& value)
-{
-    ResourceCache* cache = GetSubsystem<ResourceCache>();
     
-    switch (attr.offset_)
-    {
-    case offsetof(ParticleEmitter, parameterSource_):
-        LoadParameters(cache->GetResource<XMLFile>(value.GetResourceRef().id_));
-        break;
-    
-    case offsetof(ParticleEmitter, particles_):
-        {
-            MemoryBuffer buf(value.GetBuffer());
-            SetNumParticles(buf.ReadVLE());
-            for (PODVector<Particle>::Iterator i = particles_.Begin(); i != particles_.End(); ++i)
-            {
-                i->velocity_ = buf.ReadVector3();
-                i->size_ = buf.ReadVector2();
-                i->timer_ = buf.ReadFloat();
-                i->timeToLive_ = buf.ReadFloat();
-                i->scale_ = buf.ReadFloat();
-                i->rotationSpeed_ = buf.ReadFloat();
-                i->colorIndex_ = buf.ReadVLE();
-                i->texIndex_ = buf.ReadVLE();
-            }
-        }
-        break;
-        
-    default:
-        BillboardSet::OnSetAttribute(attr, value);
-        break;
-    }
-}
-
-Variant ParticleEmitter::OnGetAttribute(const AttributeInfo& attr)
-{
-    switch (attr.offset_)
-    {
-    case offsetof(ParticleEmitter, parameterSource_):
-        return GetResourceRef(parameterSource_, XMLFile::GetTypeStatic());
-        
-    case offsetof(ParticleEmitter, particles_):
-        {
-            VectorBuffer buf;
-            buf.WriteVLE(particles_.Size());
-            for (PODVector<Particle>::ConstIterator i = particles_.Begin(); i != particles_.End(); ++i)
-            {
-                buf.WriteVector3(i->velocity_);
-                buf.WriteVector2(i->size_);
-                buf.WriteFloat(i->timer_);
-                buf.WriteFloat(i->timeToLive_);
-                buf.WriteFloat(i->scale_);
-                buf.WriteFloat(i->rotationSpeed_);
-                buf.WriteVLE(i->colorIndex_);
-                buf.WriteVLE(i->texIndex_);
-            }
-            return buf.GetBuffer();
-        }
-        
-    default:
-        return BillboardSet::OnGetAttribute(attr);
-    }
+    ATTRIBUTE(ParticleEmitter, VAR_FLOAT, "Animation LOD Bias", animationLodBias_, 1.0f, AM_DEFAULT);
+    ATTRIBUTE(ParticleEmitter, VAR_BOOL, "Relative Scale", scaled_, true, AM_DEFAULT);
+    ATTRIBUTE(ParticleEmitter, VAR_BOOL, "Is Active", active_, true, AM_DEFAULT);
+    ATTRIBUTE(ParticleEmitter, VAR_FLOAT, "Period Timer", periodTimer_, 0.0f, AM_DEFAULT);
+    ATTRIBUTE(ParticleEmitter, VAR_FLOAT, "Emission Timer", emissionTimer_, 0.0f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(ParticleEmitter, VAR_RESOURCEREF, "Parameter Source", GetParameterSourceAttr, SetParameterSourceAttr, ResourceRef, ResourceRef(XMLFile::GetTypeStatic()), AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(ParticleEmitter, VAR_BUFFER, "Particles", GetParticlesAttr, SetParticlesAttr, PODVector<unsigned char>, PODVector<unsigned char>(), AM_FILE);
+    ACCESSOR_ATTRIBUTE(ParticleEmitter, VAR_BUFFER, "Billboards", GetBillboardsAttr, SetBillboardsAttr, PODVector<unsigned char>, PODVector<unsigned char>(), AM_FILE);
 }
 
 void ParticleEmitter::Update(float timeStep)
@@ -424,29 +360,50 @@ void ParticleEmitter::SetActive(bool enable, bool resetPeriod)
     }
 }
 
-void ParticleEmitter::SetNumParticles(int num)
+void ParticleEmitter::SetParameterSourceAttr(ResourceRef value)
 {
-    num = Max(num, 0);
-    particles_.Resize(num);
-    SetNumBillboards(num);
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    LoadParameters(cache->GetResource<XMLFile>(value.id_));
 }
 
-void ParticleEmitter::SetParticleColor(const Color& color)
+void ParticleEmitter::SetParticlesAttr(PODVector<unsigned char> value)
 {
-    ColorFade newColor;
-    newColor.color_ = color;
-    newColor.time_ = 0.0f;
-    
-    colors_.Clear();
-    colors_.Push(newColor);
+    MemoryBuffer buf(value);
+    SetNumParticles(buf.ReadVLE());
+    for (PODVector<Particle>::Iterator i = particles_.Begin(); i != particles_.End(); ++i)
+    {
+        i->velocity_ = buf.ReadVector3();
+        i->size_ = buf.ReadVector2();
+        i->timer_ = buf.ReadFloat();
+        i->timeToLive_ = buf.ReadFloat();
+        i->scale_ = buf.ReadFloat();
+        i->rotationSpeed_ = buf.ReadFloat();
+        i->colorIndex_ = buf.ReadVLE();
+        i->texIndex_ = buf.ReadVLE();
+    }
 }
 
-void ParticleEmitter::SetParticleColors(const Vector<ColorFade>& colors)
+ResourceRef ParticleEmitter::GetParameterSourceAttr() const
 {
-    if (!colors.Size())
-        return;
-    
-    colors_ = colors;
+    return GetResourceRef(parameterSource_, XMLFile::GetTypeStatic());
+}
+
+PODVector<unsigned char> ParticleEmitter::GetParticlesAttr() const
+{
+    VectorBuffer buf;
+    buf.WriteVLE(particles_.Size());
+    for (PODVector<Particle>::ConstIterator i = particles_.Begin(); i != particles_.End(); ++i)
+    {
+        buf.WriteVector3(i->velocity_);
+        buf.WriteVector2(i->size_);
+        buf.WriteFloat(i->timer_);
+        buf.WriteFloat(i->timeToLive_);
+        buf.WriteFloat(i->scale_);
+        buf.WriteFloat(i->rotationSpeed_);
+        buf.WriteVLE(i->colorIndex_);
+        buf.WriteVLE(i->texIndex_);
+    }
+    return buf.GetBuffer();
 }
 
 void ParticleEmitter::OnNodeSet(Node* node)
@@ -461,11 +418,29 @@ void ParticleEmitter::OnNodeSet(Node* node)
     BillboardSet::OnNodeSet(node);
 }
 
-void ParticleEmitter::HandleScenePostUpdate(StringHash eventType, VariantMap& eventData)
+void ParticleEmitter::SetNumParticles(int num)
 {
-    using namespace ScenePostUpdate;
+    num = Max(num, 0);
+    particles_.Resize(num);
+    SetNumBillboards(num);
+}
+
+void ParticleEmitter::SetParticleColor(const Color& color)
+{
+    ColorFade newColor;
+    newColor.color_ = color;
+    newColor.time_ = 0.0f;
     
-    Update(eventData[P_TIMESTEP].GetFloat());
+    colors_.Clear();
+    colors_.Push(newColor);
+}
+
+void ParticleEmitter::SetParticleColors(const Vector<ColorFade>& colors)
+{
+    if (!colors.Size())
+        return;
+    
+    colors_ = colors;
 }
 
 bool ParticleEmitter::EmitNewParticle()
@@ -593,3 +568,10 @@ void ParticleEmitter::GetVector3MinMax(const XMLElement& element, Vector3& minVa
         maxValue = element.GetVector3("max");
     }
 }
+
+void ParticleEmitter::HandleScenePostUpdate(StringHash eventType, VariantMap& eventData)
+{
+    using namespace ScenePostUpdate;
+    
+    Update(eventData[P_TIMESTEP].GetFloat());
+}

+ 12 - 8
Engine/Graphics/ParticleEmitter.h

@@ -78,10 +78,6 @@ public:
     /// Register object factory
     static void RegisterObject(Context* context);
     
-    /// Handle attribute write access
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& value);
-    /// Handle attribute read access
-    virtual Variant OnGetAttribute(const AttributeInfo& attr);
     /// Update the particle system. Is called from HandleScenePostUpdate()
     void Update(float timeStep);
     /// Load emitter parameters from an XML file. Return true if successful
@@ -96,7 +92,19 @@ public:
     /// Return whether emitter is active
     bool IsActive() const { return active_; }
     
+    /// Set parameter source attribute
+    void SetParameterSourceAttr(ResourceRef value);
+    /// Set particles attribute
+    void SetParticlesAttr(PODVector<unsigned char> value);
+    /// Return parameter source attribute
+    ResourceRef GetParameterSourceAttr() const;
+    /// Return particles attribute
+    PODVector<unsigned char> GetParticlesAttr() const;
+    
 protected:
+    /// Handle node being assigned
+    virtual void OnNodeSet(Node* node);
+    
     /// Set number of particles
     void SetNumParticles(int num);
     /// Set color of particles
@@ -114,10 +122,6 @@ protected:
     /// Read a Vector3 from an XML element
     void GetVector3MinMax(const XMLElement& element, Vector3& minValue, Vector3& maxValue);
     
-protected:
-    /// Handle node being assigned
-    virtual void OnNodeSet(Node* node);
-    
 private:
     /// Handle scene post-update event
     void HandleScenePostUpdate(StringHash eventType, VariantMap& eventData);

+ 26 - 45
Engine/Graphics/StaticModel.cpp

@@ -57,53 +57,11 @@ void StaticModel::RegisterObject(Context* context)
     context->RegisterFactory<StaticModel>();
     context->CopyBaseAttributes<Drawable, StaticModel>();
     
-    ATTRIBUTE(StaticModel, VAR_RESOURCEREF, "Model", model_, ResourceRef(Model::GetTypeStatic()));
-    ATTRIBUTE(StaticModel, VAR_RESOURCEREFLIST, "Materials", materials_, ResourceRefList(Material::GetTypeStatic()));
-    ATTRIBUTE(StaticModel, VAR_INT, "Raycast/Occlusion LOD Level", softwareLodLevel_, M_MAX_UNSIGNED);
+    ACCESSOR_ATTRIBUTE(StaticModel, VAR_RESOURCEREF, "Model", GetModelAttr, SetModelAttr, ResourceRef, ResourceRef(Model::GetTypeStatic()), AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(StaticModel, VAR_RESOURCEREFLIST, "Materials", GetMaterialsAttr, SetMaterialsAttr, ResourceRefList, ResourceRefList(Material::GetTypeStatic()), AM_DEFAULT);
+    ATTRIBUTE(StaticModel, VAR_INT, "Raycast/Occlusion LOD Level", softwareLodLevel_, M_MAX_UNSIGNED, AM_DEFAULT);
 }
 
-void StaticModel::OnSetAttribute(const AttributeInfo& attr, const Variant& value)
-{
-    ResourceCache* cache = GetSubsystem<ResourceCache>();
-    
-    switch (attr.offset_)
-    {
-    case offsetof(StaticModel, model_):
-        SetModel(cache->GetResource<Model>(value.GetResourceRef().id_));
-        break;
-        
-    case offsetof(StaticModel, materials_):
-        {
-            const ResourceRefList& refs = value.GetResourceRefList();
-            for (unsigned i = 0; i < refs.ids_.Size(); ++i)
-                SetMaterial(i, cache->GetResource<Material>(refs.ids_[i]));
-        }
-        break;
-        
-    default:
-        Serializable::OnSetAttribute(attr, value);
-        break;
-    }
-}
-
-Variant StaticModel::OnGetAttribute(const AttributeInfo& attr)
-{
-    switch (attr.offset_)
-    {
-    case offsetof(StaticModel, model_):
-        return GetResourceRef(model_, Model::GetTypeStatic());
-        break;
-        
-    case offsetof(StaticModel, materials_):
-        return GetResourceRefList(materials_);
-        break;
-    
-    default:
-        return Serializable::OnGetAttribute(attr);
-    }
-}
-
-
 void StaticModel::ProcessRayQuery(RayOctreeQuery& query, float initialDistance)
 {
     PROFILE(RaycastStaticModel);
@@ -313,6 +271,29 @@ void StaticModel::SetNumGeometries(unsigned num)
     ResetLodLevels();
 }
 
+void StaticModel::SetModelAttr(ResourceRef value)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    SetModel(cache->GetResource<Model>(value.id_));
+}
+
+void StaticModel::SetMaterialsAttr(ResourceRefList value)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    for (unsigned i = 0; i < value.ids_.Size(); ++i)
+        SetMaterial(i, cache->GetResource<Material>(value.ids_[i]));
+}
+
+ResourceRef StaticModel::GetModelAttr() const
+{
+    return GetResourceRef(model_, Model::GetTypeStatic());
+}
+
+ResourceRefList StaticModel::GetMaterialsAttr() const
+{
+    return GetResourceRefList(materials_);
+}
+
 void StaticModel::OnWorldBoundingBoxUpdate()
 {
     worldBoundingBox_ = boundingBox_.Transformed(GetWorldTransform());

+ 9 - 6
Engine/Graphics/StaticModel.h

@@ -40,10 +40,6 @@ public:
     /// Register object factory
     static void RegisterObject(Context* context);
     
-    /// Handle attribute write access
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& value);
-    /// Handle attribute read access
-    virtual Variant OnGetAttribute(const AttributeInfo& attr);
     /// Process renderer raycast
     virtual void ProcessRayQuery(RayOctreeQuery& query, float initialDistance);
     /// Prepare geometry for rendering
@@ -75,9 +71,16 @@ public:
     /// Return software LOD level
     unsigned GetSoftwareLodLevel() const { return softwareLodLevel_; }
     
+    /// Set model attribute
+    void SetModelAttr(ResourceRef value);
+    /// Set materials attribute
+    void SetMaterialsAttr(ResourceRefList value);
+    /// Return model attribute
+    ResourceRef GetModelAttr() const;
+    /// Return materials attribute
+    ResourceRefList GetMaterialsAttr() const;
+    
 protected:
-    /// Construct with node flags, initial octant pointer and name
-    StaticModel(unsigned flags, Octant* octant, const String& name);
     /// Update the world bounding box
     virtual void OnWorldBoundingBoxUpdate();
     /// Set the bounding box

+ 10 - 13
Engine/Graphics/Zone.cpp

@@ -53,29 +53,26 @@ void Zone::RegisterObject(Context* context)
     context->RegisterFactory<Zone>();
     context->CopyBaseAttributes<Drawable, Zone>();
     
-    ATTRIBUTE(Zone, VAR_VECTOR3, "Bounding Box Min", boundingBox_.min_, Vector3::ZERO);
-    ATTRIBUTE(Zone, VAR_VECTOR3, "Bounding Box Max", boundingBox_.max_, Vector3::ZERO);
-    ATTRIBUTE(Zone, VAR_COLOR, "Ambient Color", ambientColor_, DEFAULT_AMBIENT_COLOR);
-    ATTRIBUTE(Zone, VAR_COLOR, "Fog Color", fogColor_, DEFAULT_FOG_COLOR);
-    ATTRIBUTE(Zone, VAR_FLOAT, "Fog Start", fogStart_, DEFAULT_FOGSTART);
-    ATTRIBUTE(Zone, VAR_FLOAT, "Fog End", fogEnd_, DEFAULT_FOGEND);
-    ATTRIBUTE(Zone, VAR_INT, "Zone Priority", priority_, 0);
+    ATTRIBUTE(Zone, VAR_VECTOR3, "Bounding Box Min", boundingBox_.min_, Vector3::ZERO, AM_DEFAULT);
+    ATTRIBUTE(Zone, VAR_VECTOR3, "Bounding Box Max", boundingBox_.max_, Vector3::ZERO, AM_DEFAULT);
+    ATTRIBUTE(Zone, VAR_COLOR, "Ambient Color", ambientColor_, DEFAULT_AMBIENT_COLOR, AM_DEFAULT);
+    ATTRIBUTE(Zone, VAR_COLOR, "Fog Color", fogColor_, DEFAULT_FOG_COLOR, AM_DEFAULT);
+    ATTRIBUTE(Zone, VAR_FLOAT, "Fog Start", fogStart_, DEFAULT_FOGSTART, AM_DEFAULT);
+    ATTRIBUTE(Zone, VAR_FLOAT, "Fog End", fogEnd_, DEFAULT_FOGEND, AM_DEFAULT);
+    ATTRIBUTE(Zone, VAR_INT, "Zone Priority", priority_, 0, AM_DEFAULT);
 }
 
 void Zone::OnSetAttribute(const AttributeInfo& attr, const Variant& value)
 {
+    Serializable::OnSetAttribute(attr, value);
+    
+    // If bounding box changes, dirty the drawable as applicable
     switch (attr.offset_)
     {
     case offsetof(Zone, boundingBox_.min_):
     case offsetof(Zone, boundingBox_.max_):
-        Serializable::OnSetAttribute(attr, value);
-        // If bounding box changes, dirty the drawable as applicable
         OnMarkedDirty(node_);
         break;
-    
-    default:
-        Serializable::OnSetAttribute(attr, value);
-        break;
     }
 }
 

+ 115 - 91
Engine/Network/Connection.cpp

@@ -130,7 +130,10 @@ void Connection::SetScene(Scene* newScene)
     scene_ = newScene;
     sceneLoaded_ = false;
     
-    if (isClient_ && scene_)
+    if (!scene_)
+        return;
+    
+    if (isClient_)
     {
         sceneState_.Clear();
         
@@ -140,6 +143,16 @@ void Connection::SetScene(Scene* newScene)
         msg_.WriteString(scene_->GetFileName());
         SendMessage(MSG_LOADSCENE, true, true, msg_);
     }
+    else
+    {
+        // Make sure there is no existing async loading
+        scene_->StopAsyncLoading();
+    }
+}
+
+void Connection::SetSceneLoaded(bool loaded)
+{
+    sceneLoaded_ = loaded;
 }
 
 void Connection::SetIdentity(const VariantMap& identity)
@@ -171,18 +184,13 @@ void Connection::ProcessReplication()
     const Map<unsigned, Node*>& nodes = scene_->GetAllNodes();
     
     // Check for new or changed 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_);
-    }
+    // Start from the root node (scene) so that the scene-wide components get sent first
+    processedNodes_.Clear();
+    ProcessNode(scene_);
+    
+    // Then go through the rest of the nodes
+    for (Map<unsigned, Node*>::ConstIterator i = nodes.Begin(); i != nodes.End() && i->first_ < FIRST_LOCAL_ID; ++i)
+        ProcessNode(i->second_);
     
     // Check for removed nodes
     for (Map<unsigned, NodeReplicationState>::Iterator i = sceneState_.Begin(); i != sceneState_.End();)
@@ -234,6 +242,31 @@ String Connection::ToString() const
     return GetAddress() + ":" + String(GetPort());
 }
 
+void Connection::ProcessNode(Node* node)
+{
+    unsigned nodeID = node->GetID();
+    if (processedNodes_.Contains(nodeID))
+        return;
+    
+    processedNodes_.Insert(nodeID);
+    
+    // Process parent node first
+    Node* parent = node->GetParent();
+    if (parent)
+    {
+        // If parent is local, proceed to first non-local node in hierarchy
+        while (parent->GetID() >= FIRST_LOCAL_ID)
+            parent = parent->GetParent();
+        ProcessNode(parent);
+    }
+    
+    // Check if the client's scene state already has this node
+    if (sceneState_.Find(nodeID) != sceneState_.End())
+        ProcessExistingNode(node);
+    else
+        ProcessNewNode(node);
+}
+
 void Connection::ProcessNewNode(Node* node)
 {
     msg_.Clear();
@@ -243,18 +276,7 @@ void Connection::ProcessNewNode(Node* node)
     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);
-    }
+    WriteInitialAttributes(msg_, node, newNodeState.attributes_);
     
     // Write node's variable map
     const VariantMap& vars = node->GetVars();
@@ -283,21 +305,7 @@ void Connection::ProcessNewNode(Node* node)
         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);
-            }
-        }
+        WriteInitialAttributes(msg_, component, newComponentState.attributes_);
         
         newNodeState.components_[component->GetID()] = newComponentState;
     }
@@ -320,20 +328,17 @@ void Connection::ProcessExistingNode(Node* node)
         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))
+        if (!(attr.mode_ & AM_NET))
             continue;
         
         // Check for attribute change
-        currentAttributes_[index] = node->GetAttribute(i);
-        if (currentAttributes_[index] != nodeState.attributes_[index])
+        Variant value = node->GetAttribute(i);
+        if (value != nodeState.attributes_[index])
         {
+            nodeState.attributes_[index] = value;
             if (attr.mode_ & AM_LATESTDATA)
                 latestData = true;
             else
@@ -351,9 +356,10 @@ void Connection::ProcessExistingNode(Node* node)
     const VariantMap& vars = node->GetVars();
     for (VariantMap::ConstIterator i = vars.Begin(); i != vars.End(); ++i)
     {
-        VariantMap::ConstIterator j = nodeState.vars_.Find(i->first_);
+        VariantMap::Iterator j = nodeState.vars_.Find(i->first_);
         if (j == nodeState.vars_.End() || i->second_ != j->second_)
         {
+            j->second_ = i->second_;
             changedVars_.Insert(i->first_);
             deltaUpdate = true;
         }
@@ -372,14 +378,11 @@ void Connection::ProcessExistingNode(Node* node)
         for (unsigned i = 0; i < attributes->Size(); ++i)
         {
             const AttributeInfo& attr = attributes->At(i);
-            if (!(attr.mode_ & AM_NETWORK))
+            if (!(attr.mode_ & AM_NET))
                 continue;
             
             if (deltaUpdateBits_[index << 3] & (1 << (index & 7)))
-            {
-                msg_.WriteVariantData(currentAttributes_[index]);
-                nodeState.attributes_[index] = currentAttributes_[index];
-            }
+                msg_.WriteVariantData(nodeState.attributes_[index]);
             
             ++index;
         }
@@ -391,7 +394,6 @@ void Connection::ProcessExistingNode(Node* node)
             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_);
@@ -408,14 +410,11 @@ void Connection::ProcessExistingNode(Node* node)
         for (unsigned i = 0; i < attributes->Size(); ++i)
         {
             const AttributeInfo& attr = attributes->At(i);
-            if (!(attr.mode_ & AM_NETWORK))
+            if (!(attr.mode_ & AM_NET))
                 continue;
             
             if (attr.mode_ & AM_LATESTDATA)
-            {
-                msg_.WriteVariantData(currentAttributes_[index]);
-                nodeState.attributes_[index] = currentAttributes_[index];
-            }
+                msg_.WriteVariantData(nodeState.attributes_[index]);
             
             ++index;
         }
@@ -445,21 +444,7 @@ void Connection::ProcessExistingNode(Node* node)
             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);
-                }
-            }
+            WriteInitialAttributes(msg_, component, newComponentState.attributes_);
             
             SendMessage(MSG_CREATECOMPONENT, true, true, msg_);
             nodeState.components_[component->GetID()] = newComponentState;
@@ -478,20 +463,17 @@ void Connection::ProcessExistingNode(Node* node)
                 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))
+                if (!(attr.mode_ & AM_NET))
                     continue;
                 
                 // Check for attribute change
-                currentAttributes_[index] = component->GetAttribute(k);
-                if (currentAttributes_[index] != nodeState.attributes_[index])
+                Variant value = component->GetAttribute(k);
+                if (value != componentState.attributes_[index])
                 {
+                    componentState.attributes_[index] = value;
                     if (attr.mode_ & AM_LATESTDATA)
                         latestData = true;
                     else
@@ -517,14 +499,11 @@ void Connection::ProcessExistingNode(Node* node)
                 for (unsigned k = 0; k < attributes->Size(); ++k)
                 {
                     const AttributeInfo& attr = attributes->At(k);
-                    if (!(attr.mode_ & AM_NETWORK))
+                    if (!(attr.mode_ & AM_NET))
                         continue;
                     
                     if (deltaUpdateBits_[index << 3] & (1 << (index & 7)))
-                    {
-                        msg_.WriteVariantData(currentAttributes_[index]);
-                        componentState.attributes_[index] = currentAttributes_[index];
-                    }
+                        msg_.WriteVariantData(componentState.attributes_[index]);
                     
                     ++index;
                 }
@@ -543,14 +522,11 @@ void Connection::ProcessExistingNode(Node* node)
                 for (unsigned k = 0; k < attributes->Size(); ++k)
                 {
                     const AttributeInfo& attr = attributes->At(k);
-                    if (!(attr.mode_ & AM_NETWORK))
+                    if (!(attr.mode_ & AM_NET))
                         continue;
                     
                     if (attr.mode_ & AM_LATESTDATA)
-                    {
-                        msg_.WriteVariantData(currentAttributes_[index]);
-                        componentState.attributes_[index] = currentAttributes_[index];
-                    }
+                        msg_.WriteVariantData(componentState.attributes_[index]);
                     
                     ++index;
                 }
@@ -575,3 +551,51 @@ void Connection::ProcessExistingNode(Node* node)
         }
     }
 }
+
+void Connection::WriteInitialAttributes(VectorBuffer& msg, Serializable* serializable, Vector<Variant>& attributeState)
+{
+    const Vector<AttributeInfo>* attributes = serializable->GetAttributes();
+    unsigned numAttributes = serializable->GetNumNetworkAttributes();
+    if (!numAttributes)
+        return;
+    
+    attributeState.Resize(numAttributes);
+    
+    deltaUpdateBits_.Resize((numAttributes + 7) >> 3);
+    for (unsigned i = 0; i < deltaUpdateBits_.Size(); ++i)
+        deltaUpdateBits_[i] = 0;
+    
+    unsigned index = 0;
+    for (unsigned i = 0; i < attributes->Size(); ++i)
+    {
+        const AttributeInfo& attr = attributes->At(i);
+        if (!(attr.mode_ & AM_NET))
+            continue;
+        
+        Variant value = serializable->GetAttribute(i);
+        if (value != attr.defaultValue_)
+        {
+            attributeState[index] = value;
+            deltaUpdateBits_[index >> 3] |= (1 << (index & 7));
+        }
+        else
+            attributeState[index] = attr.defaultValue_;
+        
+        ++index;
+    }
+    
+    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_NET))
+            continue;
+        
+        if (deltaUpdateBits_[index << 3] & (1 << (index & 7)))
+            msg.WriteVariantData(attributeState[index]);
+        
+        ++index;
+    }
+}

+ 9 - 2
Engine/Network/Connection.h

@@ -38,6 +38,7 @@
 
 class Node;
 class Scene;
+class Serializable;
 
 /// Connection in a networked scene
 class Connection : public Object
@@ -64,6 +65,8 @@ public:
     void SendRemoteEvent(Node* receiver, StringHash eventType, bool inOrder, const VariantMap& eventData = VariantMap());
     /// Assign scene. On the server, this will cause the client to load it
     void SetScene(Scene* newScene);
+    /// Set scene loaded flag
+    void SetSceneLoaded(bool loaded);
     /// Assign identity. Called by Network
     void SetIdentity(const VariantMap& identity);
     /// Set new controls. Moves the current controls as previous
@@ -101,10 +104,14 @@ public:
     String ToString() const;
     
 private:
+    /// Process a node during network update. Will recurse to process parent node(s) first
+    void ProcessNode(Node* node);
     /// 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);
+    /// Write initial attributes (that differ from the default value) for a node or a component
+    void WriteInitialAttributes(VectorBuffer& msg, Serializable* serializable, Vector<Variant>& attributeState);
     
     /// kNet message connection
     kNet::SharedPtr<kNet::MessageConnection> connection_;
@@ -120,10 +127,10 @@ private:
     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_;
+    /// Internal set for processed node ID's during an update
+    HashSet<unsigned> processedNodes_;
     /// Reused message buffer
     VectorBuffer msg_;
     /// Current controls

+ 100 - 1
Engine/Network/Network.cpp

@@ -24,6 +24,8 @@
 #include "Precompiled.h"
 #include "Context.h"
 #include "CoreEvents.h"
+#include "File.h"
+#include "FileSystem.h"
 #include "Log.h"
 #include "MemoryBuffer.h"
 #include "Network.h"
@@ -31,6 +33,7 @@
 #include "Profiler.h"
 #include "Protocol.h"
 #include "Scene.h"
+#include "SceneEvents.h"
 #include "StringUtils.h"
 
 #include <kNet.h>
@@ -51,6 +54,7 @@ Network::Network(Context* context) :
     network_ = new kNet::Network();
     
     SubscribeToEvent(E_BEGINFRAME, HANDLER(Network, HandleBeginFrame));
+    SubscribeToEvent(E_ASYNCLOADFINISHED, HANDLER(Network, HandleAsyncLoadFinished));
 }
 
 Network::~Network()
@@ -358,7 +362,7 @@ void Network::Update(float timeStep)
     }
     
     // Process client connections if the server has been started
-    kNet::NetworkServer* server = network_->GetServer();
+    kNet::SharedPtr<kNet::NetworkServer> server = network_->GetServer();
     if (server)
     {
         server->Process();
@@ -429,6 +433,50 @@ bool Network::OnServerMessage(Connection* connection, int msgID, MemoryBuffer& m
 {
     switch (msgID)
     {
+    case MSG_LOADSCENE:
+        {
+            String fileName = msg.ReadString();
+            Scene* scene = connection->GetScene();
+            if (scene)
+            {
+                if (fileName.Empty())
+                    scene->Clear();
+                else
+                {
+                    String extension = GetExtension(fileName);
+                    SharedPtr<File> file(new File(context_, fileName));
+                    bool success;
+                    
+                    if (extension == ".xml")
+                        success = scene->LoadAsyncXML(file);
+                    else
+                        success = scene->LoadAsync(file);
+                    
+                    if (!success)
+                    {
+                        using namespace NetworkSceneLoadFailed;
+                        
+                        VariantMap eventData;
+                        eventData[P_CONNECTION] = (void*)connection;
+                        connection->SendEvent(E_NETWORKSCENELOADFAILED, eventData);
+                    }
+                }
+            }
+            else
+                LOGERROR("Received a LoadScene message without an assigned scene");
+        }
+        return true;
+        
+    case MSG_SCENECHECKSUMERROR:
+        {
+            using namespace NetworkSceneLoadFailed;
+            
+            VariantMap eventData;
+            eventData[P_CONNECTION] = (void*)connection;
+            connection->SendEvent(E_NETWORKSCENELOADFAILED, eventData);
+        }
+        return true;
+        
     case MSG_REMOTEEVENT:
     case MSG_REMOTENODEEVENT:
         OnRemoteEvent(connection, msgID, msg);
@@ -470,6 +518,40 @@ bool Network::OnClientMessage(Connection* connection, int msgID, MemoryBuffer& m
         }
         return true;
         
+    case MSG_SCENELOADED:
+        {
+            unsigned checksum = msg.ReadUInt();
+            
+            Scene* scene = connection->GetScene();
+            if (scene)
+            {
+                if (checksum != scene->GetChecksum())
+                {
+                    VectorBuffer replyMsg;
+                    connection->SendMessage(MSG_SCENECHECKSUMERROR, true, true, replyMsg);
+                    
+                    using namespace NetworkSceneLoadFailed;
+                    
+                    VariantMap eventData;
+                    eventData[P_CONNECTION] = (void*)connection;
+                    connection->SendEvent(E_NETWORKSCENELOADFAILED, eventData);
+                }
+                else
+                {
+                    connection->SetSceneLoaded(true);
+                    
+                    using namespace ClientSceneLoaded;
+                    
+                    VariantMap eventData;
+                    eventData[P_CONNECTION] = (void*)connection;
+                    connection->SendEvent(E_CLIENTSCENELOADED, eventData);
+                }
+            }
+            else
+                LOGWARNING("Client sent a SceneLoaded message without an assigned scene");
+        }
+        return true;
+        
     case MSG_REMOTEEVENT:
     case MSG_REMOTENODEEVENT:
         OnRemoteEvent(connection, msgID, msg);
@@ -515,3 +597,20 @@ void Network::HandleBeginFrame(StringHash eventType, VariantMap& eventData)
     
     Update(eventData[P_TIMESTEP].GetFloat());
 }
+
+void Network::HandleAsyncLoadFinished(StringHash eventType, VariantMap& eventData)
+{
+    if (!serverConnection_)
+        return;
+    
+    Scene* scene = serverConnection_->GetScene();
+    
+    using namespace AsyncLoadFinished;
+    
+    if ((void*)scene == eventData[P_SCENE].GetPtr())
+    {
+        VectorBuffer msg;
+        msg.WriteUInt(scene->GetChecksum());
+        serverConnection_->SendMessage(MSG_SCENELOADED, true, true, msg);
+    }
+}

+ 2 - 0
Engine/Network/Network.h

@@ -104,6 +104,8 @@ private:
     void OnRemoteEvent(Connection* connection, int msgID, MemoryBuffer& msg);
     /// Handle begin frame event
     void HandleBeginFrame(StringHash eventType, VariantMap& eventData);
+    /// Handle scene loaded event
+    void HandleAsyncLoadFinished(StringHash eventType, VariantMap& eventData);
     
     /// kNet Network instance
     kNet::Network* network_;

+ 6 - 0
Engine/Network/NetworkEvents.h

@@ -77,3 +77,9 @@ EVENT(E_NETWORKMESSAGE, NetworkMessage)
 EVENT(E_NETWORKUPDATE, NetworkUpdate)
 {
 }
+
+/// Scene load failed, either due to file not found or checksum error
+EVENT(E_NETWORKSCENELOADFAILED, NetworkSceneLoadFailed)
+{
+    PARAM(P_CONNECTION, Connection);      // Connection pointer
+}

+ 10 - 8
Engine/Network/Protocol.h

@@ -32,22 +32,24 @@ static const int MSG_SCENELOADED = 0x7;
 
 // Server->client: load new scene. In case of empty filename the client should just empty the scene
 static const int MSG_LOADSCENE = 0x8;
+// Server->client: wrong scene checksum, can not participate
+static const int MSG_SCENECHECKSUMERROR = 0x9;
 // Server->client: create new node
-static const int MSG_CREATENODE = 0x9;
+static const int MSG_CREATENODE = 0xa;
 // Server->client: node delta update
-static const int MSG_NODEDELTAUPDATE = 0xa;
+static const int MSG_NODEDELTAUPDATE = 0xb;
 // Server->client: node latest data update
-static const int MSG_NODELATESTDATA = 0xb;
+static const int MSG_NODELATESTDATA = 0xc;
 // Server->client: remove node
-static const int MSG_REMOVENODE = 0xc;
+static const int MSG_REMOVENODE = 0xd;
 // Server->client: create new component
-static const int MSG_CREATECOMPONENT = 0xd;
+static const int MSG_CREATECOMPONENT = 0xe;
 // Server->client: component delta update
-static const int MSG_COMPONENTDELTAUPDATE = 0xe;
+static const int MSG_COMPONENTDELTAUPDATE = 0xf;
 // Server->client: component latest data update
-static const int MSG_COMPONENTLATESTDATA = 0xf;
+static const int MSG_COMPONENTLATESTDATA = 0x10;
 // Server->client: remove component
-static const int MSG_REMOVECOMPONENT = 0x10;
+static const int MSG_REMOVECOMPONENT = 0x11;
 
 // Client->server and server->client: remote event
 static const int MSG_REMOTEEVENT = 0x11;

+ 23 - 40
Engine/Physics/CollisionShape.cpp

@@ -323,48 +323,20 @@ void CollisionShape::RegisterObject(Context* context)
 {
     context->RegisterFactory<CollisionShape>();
     
-    ENUM_ATTRIBUTE(CollisionShape, "Shape Type", shapeType_, typeNames, SHAPE_NONE);
-    ATTRIBUTE(CollisionShape, VAR_VECTOR3, "Size", size_, Vector3::ZERO);
-    ATTRIBUTE(CollisionShape, VAR_FLOAT, "Hull Thickness", thickness_, 0.0f);
-    ATTRIBUTE(CollisionShape, VAR_INT, "Model LOD Level", lodLevel_, M_MAX_UNSIGNED);
-    ATTRIBUTE(CollisionShape, VAR_VECTOR3, "Offset Position", position_, Vector3::ZERO);
-    ATTRIBUTE(CollisionShape, VAR_QUATERNION, "Rotation", rotation_, Quaternion::IDENTITY);
-    ATTRIBUTE(CollisionShape, VAR_INT, "Collision Group", collisionGroup_, M_MAX_UNSIGNED);
-    ATTRIBUTE(CollisionShape, VAR_INT, "Collision Mask", collisionMask_, M_MAX_UNSIGNED);
-    ATTRIBUTE(CollisionShape, VAR_FLOAT, "Friction", friction_, DEFAULT_FRICTION);
-    ATTRIBUTE(CollisionShape, VAR_FLOAT, "Bounce", bounce_, DEFAULT_BOUNCE);
-    ATTRIBUTE(CollisionShape, VAR_RESOURCEREF, "Model", model_, ResourceRef(Model::GetTypeStatic()));
+    ENUM_ATTRIBUTE(CollisionShape, "Shape Type", shapeType_, typeNames, SHAPE_NONE, AM_DEFAULT);
+    ATTRIBUTE(CollisionShape, VAR_VECTOR3, "Size", size_, Vector3::ZERO, AM_DEFAULT);
+    ATTRIBUTE(CollisionShape, VAR_FLOAT, "Hull Thickness", thickness_, 0.0f, AM_DEFAULT);
+    ATTRIBUTE(CollisionShape, VAR_INT, "Model LOD Level", lodLevel_, M_MAX_UNSIGNED, AM_DEFAULT);
+    ATTRIBUTE(CollisionShape, VAR_VECTOR3, "Offset Position", position_, Vector3::ZERO, AM_DEFAULT);
+    ATTRIBUTE(CollisionShape, VAR_QUATERNION, "Rotation", rotation_, Quaternion::IDENTITY, AM_DEFAULT);
+    ATTRIBUTE(CollisionShape, VAR_INT, "Collision Group", collisionGroup_, M_MAX_UNSIGNED, AM_DEFAULT);
+    ATTRIBUTE(CollisionShape, VAR_INT, "Collision Mask", collisionMask_, M_MAX_UNSIGNED, AM_DEFAULT);
+    ATTRIBUTE(CollisionShape, VAR_FLOAT, "Friction", friction_, DEFAULT_FRICTION, AM_DEFAULT);
+    ATTRIBUTE(CollisionShape, VAR_FLOAT, "Bounce", bounce_, DEFAULT_BOUNCE, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(CollisionShape, VAR_RESOURCEREF, "Model", GetModelAttr, SetModelAttr, ResourceRef, ResourceRef(Model::GetTypeStatic()), AM_DEFAULT);
 }
 
-void CollisionShape::OnSetAttribute(const AttributeInfo& attr, const Variant& value)
-{
-    ResourceCache* cache = GetSubsystem<ResourceCache>();
-    
-    switch (attr.offset_)
-    {
-    case offsetof(CollisionShape, model_):
-        model_ = cache->GetResource<Model>(value.GetResourceRef().id_);
-        break;
-        
-    default:
-        Serializable::OnSetAttribute(attr, value);
-        break;
-    }
-}
-
-Variant CollisionShape::OnGetAttribute(const AttributeInfo& attr)
-{
-    switch (attr.offset_)
-    {
-    case offsetof(CollisionShape, model_):
-        return GetResourceRef(model_, Model::GetTypeStatic());
-        
-    default:
-        return Serializable::OnGetAttribute(attr);
-    }
-}
-
-void CollisionShape::PostLoad()
+void CollisionShape::OnFinishUpdate()
 {
     CreateGeometry();
 }
@@ -793,6 +765,17 @@ void CollisionShape::OnMarkedDirty(Node* node)
         UpdateTransform();
 }
 
+void CollisionShape::SetModelAttr(ResourceRef value)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    model_ = cache->GetResource<Model>(value.id_);
+}
+
+ResourceRef CollisionShape::GetModelAttr() const
+{
+    return GetResourceRef(model_, Model::GetTypeStatic());
+}
+
 void CollisionShape::OnNodeSet(Node* node)
 {
     if (node)

+ 7 - 6
Engine/Physics/CollisionShape.h

@@ -99,12 +99,8 @@ public:
     /// Register object factory
     static void RegisterObject(Context* context);
     
-    /// Handle attribute write access
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& value);
-    /// Handle attribute read access
-    virtual Variant OnGetAttribute(const AttributeInfo& attr);
-    /// Perform post-load after the whole scene has been loaded
-    virtual void PostLoad();
+    /// Perform finalization after a scene load or network update
+    virtual void OnFinishUpdate();
     
     /// Clear the collision geometry
     void Clear();
@@ -171,6 +167,11 @@ public:
     /// Add debug geometry to the debug graphics
     void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
     
+    /// Set model attribute
+    void SetModelAttr(ResourceRef value);
+    /// Return model attribute
+    ResourceRef GetModelAttr() const;
+    
 protected:
     /// Handle node being assigned
     virtual void OnNodeSet(Node* node);

+ 36 - 51
Engine/Physics/Joint.cpp

@@ -46,6 +46,7 @@ OBJECTTYPESTATIC(Joint);
 Joint::Joint(Context* context) :
     Component(context),
     type_(JOINT_NONE),
+    createdType_(JOINT_NONE),
     joint_(0),
     position_(Vector3::ZERO),
     axis_(Vector3::ZERO)
@@ -61,56 +62,18 @@ void Joint::RegisterObject(Context* context)
 {
     context->RegisterFactory<Joint>();
     
-    ENUM_ATTRIBUTE(Joint, "Joint Type", type_, typeNames, JOINT_NONE);
-    ATTRIBUTE(Joint, VAR_INT, "Body A", bodyA_, 0);
-    ATTRIBUTE(Joint, VAR_INT, "Body B", bodyB_, 0);
-    ATTRIBUTE(Joint, VAR_VECTOR3, "Position", position_, Vector3::ZERO);
-    ATTRIBUTE(Joint, VAR_VECTOR3, "Axis", axis_, Vector3::ZERO);
+    ENUM_ATTRIBUTE(Joint, "Joint Type", type_, typeNames, JOINT_NONE, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(Joint, VAR_INT, "Body A", GetBodyAAttr, SetBodyAAttr, int, 0, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(Joint, VAR_INT, "Body B", GetBodyBAttr, SetBodyBAttr, int, 0, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(Joint, VAR_VECTOR3, "Position", GetPosition, SetPosition, Vector3, Vector3::ZERO, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(Joint, VAR_VECTOR3, "Axis", GetAxis, SetAxis, Vector3, Vector3::ZERO, AM_DEFAULT);
 }
 
-void Joint::OnSetAttribute(const AttributeInfo& attr, const Variant& value)
+void Joint::OnFinishUpdate()
 {
-    Scene* scene = node_ ? node_->GetScene() : 0;
+    if (type_ == createdType_)
+        return;
     
-    switch (attr.offset_)
-    {
-    case offsetof(Joint, bodyA_):
-        bodyA_ = scene ? dynamic_cast<RigidBody*>(scene->GetComponentByID(value.GetInt())) : (RigidBody*)0;
-        break;
-        
-    case offsetof(Joint, bodyB_):
-        bodyB_ = scene ? dynamic_cast<RigidBody*>(scene->GetComponentByID(value.GetInt())) : (RigidBody*)0;
-        break;
-        
-    default:
-        Serializable::OnSetAttribute(attr, value);
-        break;
-    }
-}
-
-Variant Joint::OnGetAttribute(const AttributeInfo& attr)
-{
-    switch (attr.offset_)
-    {
-    case offsetof(Joint, bodyA_):
-        return bodyA_ ? bodyA_->GetID() : 0;
-        
-    case offsetof(Joint, bodyB_):
-        return bodyB_ ? bodyB_->GetID() : 0;
-        
-    case offsetof(Joint, position_):
-        return GetPosition();
-        
-    case offsetof(Joint, axis_):
-        return GetAxis();
-        
-    default:
-        return Serializable::OnGetAttribute(attr);
-    }
-}
-
-void Joint::PostLoad()
-{
     switch (type_)
     {
     case JOINT_NONE:
@@ -137,7 +100,7 @@ void Joint::Clear()
     
     bodyA_.Reset();
     bodyB_.Reset();
-    type_ = JOINT_NONE;
+    createdType_ = type_ = JOINT_NONE;
 }
 
 bool Joint::SetBall(const Vector3& position, RigidBody* bodyA, RigidBody* bodyB)
@@ -161,7 +124,7 @@ bool Joint::SetBall(const Vector3& position, RigidBody* bodyA, RigidBody* bodyB)
     dJointSetBallAnchor(joint_, position.x_, position.y_, position.z_);
     dJointAttach(joint_, bodyA ? bodyA->GetBody() : 0, bodyB ? bodyB->GetBody() : 0);
     
-    type_ = JOINT_BALL;
+    createdType_ = type_ = JOINT_BALL;
     bodyA_ = bodyA;
     bodyB_ = bodyB;
     
@@ -192,14 +155,14 @@ bool Joint::SetHinge(const Vector3& position, const Vector3& axis, RigidBody* bo
     dJointSetHingeAxis(joint_, NormalizedAxis.x_, NormalizedAxis.y_, NormalizedAxis.z_);
     dJointAttach(joint_, bodyA ? bodyA->GetBody() : 0, bodyB ? bodyB->GetBody() : 0);
     
-    type_ = JOINT_HINGE;
+    createdType_ = type_ = JOINT_HINGE;
     bodyA_ = bodyA;
     bodyB_ = bodyB;
     
     return true;
 }
 
-void Joint::SetPosition(const Vector3& position)
+void Joint::SetPosition(Vector3 position)
 {
     switch (type_)
     {
@@ -213,7 +176,7 @@ void Joint::SetPosition(const Vector3& position)
     }
 }
 
-void Joint::SetAxis(const Vector3& axis)
+void Joint::SetAxis(Vector3 axis)
 {
     switch (type_)
     {
@@ -255,6 +218,28 @@ Vector3 Joint::GetAxis() const
     return Vector3::ZERO;
 }
 
+void Joint::SetBodyAAttr(int value)
+{
+    Scene* scene = node_ ? node_->GetScene() : 0;
+    bodyA_ = scene ? dynamic_cast<RigidBody*>(scene->GetComponentByID(value)) : (RigidBody*)0;
+}
+
+void Joint::SetBodyBAttr(int value)
+{
+    Scene* scene = node_ ? node_->GetScene() : 0;
+    bodyB_ = scene ? dynamic_cast<RigidBody*>(scene->GetComponentByID(value)) : (RigidBody*)0;
+}
+
+int Joint::GetBodyAAttr() const
+{
+    return bodyA_ ? bodyA_->GetID() : 0;
+}
+
+int Joint::GetBodyBAttr() const
+{
+    return bodyB_ ? bodyB_->GetID() : 0;
+}
+
 void Joint::OnNodeSet(Node* node)
 {
     if (node)

+ 15 - 8
Engine/Physics/Joint.h

@@ -51,12 +51,8 @@ public:
     /// Register object factory
     static void RegisterObject(Context* context);
     
-    /// Handle attribute write access
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& value);
-    /// Handle attribute read access
-    virtual Variant OnGetAttribute(const AttributeInfo& attr);
-    /// Perform post-load after the whole scene has been loaded
-    virtual void PostLoad();
+    /// Perform finalization after a scene load or network update
+    virtual void OnFinishUpdate();
     
     /// Remove the joint
     void Clear();
@@ -65,9 +61,9 @@ public:
     /// Set a hinge joint
     bool SetHinge(const Vector3& position, const Vector3& axis, RigidBody* bodyA, RigidBody* bodyB = 0);
     /// Set joint world position
-    void SetPosition(const Vector3& position);
+    void SetPosition(Vector3 position);
     /// Set joint world axis if applicable
-    void SetAxis(const Vector3& axis);
+    void SetAxis(Vector3 axis);
     
     /// Return physics world
     PhysicsWorld* GetPhysicsWorld() const { return physicsWorld_; }
@@ -84,6 +80,15 @@ public:
     /// Return joint world axis
     Vector3 GetAxis() const;
     
+    /// Set body A attribute
+    void SetBodyAAttr(int value);
+    /// Set body B attribute
+    void SetBodyBAttr(int value);
+    /// Return body A attribute
+    int GetBodyAAttr() const;
+    /// Return body B attribute
+    int GetBodyBAttr() const;
+    
 protected:
     /// Handle node being assigned
     virtual void OnNodeSet(Node* node);
@@ -97,6 +102,8 @@ private:
     SharedPtr<RigidBody> bodyB_;
     /// Joint type
     JointType type_;
+    /// Last created joint type
+    JointType createdType_;
     /// ODE joint ID
     dJointID joint_;
     /// Joint position for creation during post-load

+ 15 - 15
Engine/Physics/PhysicsWorld.cpp

@@ -134,21 +134,21 @@ void PhysicsWorld::RegisterObject(Context* context)
 {
     context->RegisterFactory<PhysicsWorld>();
     
-    ATTRIBUTE(PhysicsWorld, VAR_INT, "Physics FPS", fps_, DEFAULT_FPS);
-    ATTRIBUTE(PhysicsWorld, VAR_INT, "Max Contacts", maxContacts_, DEFAULT_MAXCONTACTS);
-    ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Bounce Threshold", bounceThreshold_, DEFAULT_BOUNCETHRESHOLD);
-    ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Time Accumulator", timeAcc_, 0.0f);
-    ATTRIBUTE(PhysicsWorld, VAR_INT, "Random Seed", randomSeed_, 0);
-    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_VECTOR3, "Gravity", GetGravity, SetGravity, Vector3, Vector3::ZERO);
-    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Linear Rest Threshold", GetLinearRestThreshold, SetLinearRestThreshold, float, 0.01f);
-    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Linear Damping Threshold", GetLinearDampingThreshold, SetLinearDampingThreshold, float, 0.01f);
-    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Linear Damping Scale", GetLinearDampingScale, SetLinearDampingScale, float, 0.0f);
-    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Angular Rest Threshold", GetAngularRestThreshold, SetAngularRestThreshold, float, 0.01f);
-    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Angular Damping Threshold", GetAngularDampingThreshold, SetAngularDampingThreshold, float, 0.01f);
-    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Angular Damping Scale", GetAngularDampingScale, SetAngularDampingScale, float, 0.0f);
-    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "ERP", GetERP, SetERP, float, 0.2f);
-    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "CFM", GetCFM, SetCFM, float, 0.00001f);
-    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Contact Surface Layer", GetContactSurfaceLayer, SetContactSurfaceLayer, float, 0.0f);
+    ATTRIBUTE(PhysicsWorld, VAR_INT, "Physics FPS", fps_, DEFAULT_FPS, AM_DEFAULT);
+    ATTRIBUTE(PhysicsWorld, VAR_INT, "Max Contacts", maxContacts_, DEFAULT_MAXCONTACTS, AM_DEFAULT);
+    ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Bounce Threshold", bounceThreshold_, DEFAULT_BOUNCETHRESHOLD, AM_DEFAULT);
+    ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Time Accumulator", timeAcc_, 0.0f, AM_DEFAULT);
+    ATTRIBUTE(PhysicsWorld, VAR_INT, "Random Seed", randomSeed_, 0, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_VECTOR3, "Gravity", GetGravity, SetGravity, Vector3, Vector3::ZERO, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Linear Rest Threshold", GetLinearRestThreshold, SetLinearRestThreshold, float, 0.01f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Linear Damping Threshold", GetLinearDampingThreshold, SetLinearDampingThreshold, float, 0.01f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Linear Damping Scale", GetLinearDampingScale, SetLinearDampingScale, float, 0.0f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Angular Rest Threshold", GetAngularRestThreshold, SetAngularRestThreshold, float, 0.01f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Angular Damping Threshold", GetAngularDampingThreshold, SetAngularDampingThreshold, float, 0.01f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Angular Damping Scale", GetAngularDampingScale, SetAngularDampingScale, float, 0.0f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "ERP", GetERP, SetERP, float, 0.2f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "CFM", GetCFM, SetCFM, float, 0.00001f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Contact Surface Layer", GetContactSurfaceLayer, SetContactSurfaceLayer, float, 0.0f, AM_DEFAULT);
 }
 
 void PhysicsWorld::Update(float timeStep)

+ 13 - 13
Engine/Physics/RigidBody.cpp

@@ -67,19 +67,19 @@ void RigidBody::RegisterObject(Context* context)
 {
     context->RegisterFactory<RigidBody>();
     
-    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_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_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);
-    ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Angular Max Velocity", GetAngularMaxVelocity, SetAngularMaxVelocity, float, M_INFINITY);
-    ACCESSOR_ATTRIBUTE(RigidBody, VAR_BOOL, "Is Active", IsActive, SetActive, bool, true);
+    ATTRIBUTE(RigidBody, VAR_FLOAT, "Mass", mass_, DEFAULT_MASS, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(RigidBody, VAR_VECTOR3, "Physics Position", GetPosition, SetPosition, Vector3, Vector3::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE(RigidBody, VAR_QUATERNION, "Physics Rotation", GetRotation, SetRotation, Quaternion, Quaternion::IDENTITY, AM_FILE);
+    ACCESSOR_ATTRIBUTE(RigidBody, VAR_VECTOR3, "Linear Velocity", GetLinearVelocity, SetLinearVelocity, Vector3, Vector3::ZERO, AM_DEFAULT | AM_LATESTDATA);
+    ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Linear Rest Threshold", GetLinearRestThreshold, SetLinearRestThreshold, float, 0.01f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Linear Damping Threshold", GetLinearDampingThreshold, SetLinearDampingThreshold, float, 0.01f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Linear Damping Scale", GetLinearDampingScale, SetLinearDampingScale, float, 0.0f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(RigidBody, VAR_VECTOR3, "Angular Velocity", GetAngularVelocity, SetAngularVelocity, Vector3, Vector3::ZERO, AM_DEFAULT | AM_LATESTDATA);
+    ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Angular Rest Threshold", GetAngularRestThreshold, SetAngularRestThreshold, float, 0.01f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Angular Damping Threshold", GetAngularDampingThreshold, SetAngularDampingThreshold, float, 0.01f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Angular Damping Scale", GetAngularDampingScale, SetAngularDampingScale, float, 0.0f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Angular Max Velocity", GetAngularMaxVelocity, SetAngularMaxVelocity, float, M_INFINITY, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(RigidBody, VAR_BOOL, "Is Active", IsActive, SetActive, bool, true, AM_FILE);
 }
 
 void RigidBody::SetMass(float mass)

+ 62 - 35
Engine/Scene/Node.cpp

@@ -25,6 +25,7 @@
 #include "Component.h"
 #include "Context.h"
 #include "Log.h"
+#include "MemoryBuffer.h"
 #include "Scene.h"
 #include "VectorBuffer.h"
 #include "XMLElement.h"
@@ -65,11 +66,12 @@ void Node::RegisterObject(Context* context)
 {
     context->RegisterFactory<Node>();
     
-    ATTRIBUTE(Node, VAR_STRING, "Name", name_, String());
-    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);
+    REF_ACCESSOR_ATTRIBUTE(Node, VAR_STRING, "Name", GetName, SetName, String, String(), AM_DEFAULT);
+    REF_ACCESSOR_ATTRIBUTE(Node, VAR_VECTOR3, "Position", GetPosition, SetPosition, Vector3, Vector3::ZERO, AM_DEFAULT | AM_LATESTDATA);
+    REF_ACCESSOR_ATTRIBUTE(Node, VAR_QUATERNION, "Rotation", GetRotation, SetRotation, Quaternion, Quaternion::IDENTITY, AM_DEFAULT | AM_LATESTDATA);
+    REF_ACCESSOR_ATTRIBUTE(Node, VAR_VECTOR3, "Scale", GetScale, SetScale, Vector3, Vector3::UNITY, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(Node, VAR_BUFFER, "Parent Node", GetParentAttr, SetParentAttr, PODVector<unsigned char>, PODVector<unsigned char>(), AM_NET | AM_NOEDIT);
+    ATTRIBUTE(Node, VAR_VARIANTMAP, "Variables", vars_, VariantMap(), AM_FILE);
 }
 
 void Node::OnEvent(Object* sender, bool broadcast, StringHash eventType, VariantMap& eventData)
@@ -91,27 +93,13 @@ void Node::OnEvent(Object* sender, bool broadcast, StringHash eventType, Variant
         Object::OnEvent(sender, broadcast, eventType, eventData);
 }
 
-void Node::OnSetAttribute(const AttributeInfo& attr, const Variant& value)
+void Node::OnFinishUpdate()
 {
-    switch (attr.offset_)
-    {
-    case offsetof(Node, name_):
-        SetName(value.GetString());
-        break;
-        
-    case offsetof(Node, position_):
-    case offsetof(Node, rotation_):
-    case offsetof(Node, scale_):
-        Serializable::OnSetAttribute(attr, value);
-        // If transform changes, dirty the node as applicable
-        if (!dirty_)
-            MarkDirty();
-        break;
-    
-    default:
-        Serializable::OnSetAttribute(attr, value);
-        break;
-    }
+    for (unsigned i = 0; i < components_.Size(); ++i)
+        components_[i]->OnFinishUpdate();
+    
+    for (unsigned i = 0; i < children_.Size(); ++i)
+        children_[i]->OnFinishUpdate();
 }
 
 bool Node::Load(Deserializer& source)
@@ -184,15 +172,6 @@ bool Node::SaveXML(XMLElement& dest)
     return true;
 }
 
-void Node::PostLoad()
-{
-    for (unsigned i = 0; i < components_.Size(); ++i)
-        components_[i]->PostLoad();
-    
-    for (unsigned i = 0; i < children_.Size(); ++i)
-        children_[i]->PostLoad();
-}
-
 void Node::SetName(const String& name)
 {
     name_ = name;
@@ -626,6 +605,54 @@ void Node::SetOwner(Connection* owner)
     owner_ = owner;
 }
 
+void Node::SetParentAttr(PODVector<unsigned char> value)
+{
+    Scene* scene = GetScene();
+    if (!scene)
+        return;
+    
+    MemoryBuffer buf(value);
+    unsigned baseNodeID = buf.ReadVLE();
+    if (!baseNodeID)
+        return;
+    
+    if (baseNodeID < FIRST_LOCAL_ID)
+        SetParent(scene->GetNodeByID(baseNodeID));
+    else
+    {
+        Node* baseNode = scene->GetNodeByID(baseNodeID);
+        if (baseNode)
+            SetParent(baseNode->GetChild(buf.ReadStringHash(), true));
+    }
+}
+
+PODVector<unsigned char> Node::GetParentAttr() const
+{
+    VectorBuffer buf;
+    Scene* scene = GetScene();
+    if (scene && parent_)
+    {
+        unsigned parentID = parent_->GetID();
+        if (parentID < FIRST_LOCAL_ID)
+            buf.WriteVLE(parentID);
+        else
+        {
+            // Parent is local: traverse hierarchy to find a non-local base node
+            // This iteration always stops due to the scene (root) being non-local
+            Node* current = parent_;
+            while (current->GetID() >= FIRST_LOCAL_ID)
+                current = current->GetParent();
+            
+            buf.WriteVLE(current->GetID());
+            buf.WriteStringHash(parent_->GetNameHash());
+        }
+    }
+    else
+        buf.WriteVLE(0);
+    
+    return buf.GetBuffer();
+}
+
 bool Node::Load(Deserializer& source, bool readChildren)
 {
     // Remove all children and components first in case this is not a fresh load
@@ -734,7 +761,7 @@ Node* Node::CreateChild(unsigned id, bool local)
     
     // If zero ID specified, let the scene assign
     if (scene_)
-        newNode->SetID(id ? id : scene_->GetFreeunsigned(local));
+        newNode->SetID(id ? id : scene_->GetFreeNodeID(local));
     else
         newNode->SetID(id);
     

+ 7 - 4
Engine/Scene/Node.h

@@ -45,8 +45,8 @@ public:
     
     /// Handle event
     virtual void OnEvent(Object* sender, bool broadcast, StringHash eventType, VariantMap& eventData);
-    /// Handle attribute write access
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& value);
+    /// Perform finalization for components and child nodes. Only called after scene load
+    virtual void OnFinishUpdate();
     /// Load from binary data. Return true if successful
     virtual bool Load(Deserializer& source);
     /// Load from XML data. Return true if successful
@@ -55,8 +55,6 @@ public:
     virtual bool Save(Serializer& dest);
     /// Save as XML data. Return true if successful
     virtual bool SaveXML(XMLElement& dest);
-    /// Perform post-load for components and child nodes
-    virtual void PostLoad();
     
     /// Set name
     void SetName(const String& name);
@@ -241,6 +239,11 @@ public:
     /// Set owner connection for multiplayer
     void SetOwner(Connection* owner);
     
+    /// Set parent attribute (network only)
+    void SetParentAttr(PODVector<unsigned char> value);
+    /// Return parent attribute (network only)
+    PODVector<unsigned char> GetParentAttr() const;
+    
     /// User variables
     VariantMap vars_;
     

+ 7 - 15
Engine/Scene/Scene.cpp

@@ -49,7 +49,7 @@ Scene::Scene(Context* context) :
     asyncLoading_(false)
 {
     // Assign an ID to self so that nodes can refer to this node as a parent
-    SetID(GetFreeunsigned(false));
+    SetID(GetFreeNodeID(false));
     NodeAdded(this);
     
     SubscribeToEvent(E_UPDATE, HANDLER(Scene, HandleUpdate));
@@ -70,10 +70,10 @@ void Scene::RegisterObject(Context* context)
     context->RegisterFactory<Scene>();
     context->CopyBaseAttributes<Node, Scene>();
     
-    ATTRIBUTE(Scene, VAR_INT, "Next Non-Local Node ID", nonLocalNodeID_, FIRST_NONLOCAL_ID);
-    ATTRIBUTE(Scene, VAR_INT, "Next Non-Local Component ID", nonLocalComponentID_, FIRST_NONLOCAL_ID);
-    ATTRIBUTE(Scene, VAR_INT, "Next Local Node ID", localNodeID_, FIRST_LOCAL_ID);
-    ATTRIBUTE(Scene, VAR_INT, "Next Local Component ID", localComponentID_, FIRST_LOCAL_ID);
+    ATTRIBUTE(Scene, VAR_INT, "Next Non-Local Node ID", nonLocalNodeID_, FIRST_NONLOCAL_ID, AM_DEFAULT);
+    ATTRIBUTE(Scene, VAR_INT, "Next Non-Local Component ID", nonLocalComponentID_, FIRST_NONLOCAL_ID, AM_DEFAULT);
+    ATTRIBUTE(Scene, VAR_INT, "Next Local Node ID", localNodeID_, FIRST_LOCAL_ID, AM_DEFAULT);
+    ATTRIBUTE(Scene, VAR_INT, "Next Local Component ID", localComponentID_, FIRST_LOCAL_ID, AM_DEFAULT);
 }
 
 bool Scene::Load(Deserializer& source)
@@ -271,14 +271,6 @@ void Scene::Clear()
     checksum_ = 0;
 }
 
-void Scene::ClearNonLocal()
-{
-    // Because node removal can remove arbitrary other nodes, can not iterate. Instead loop until the first node is local,
-    // or the map is empty
-    while (allNodes_.Size() && allNodes_.Begin()->first_ < FIRST_LOCAL_ID)
-        allNodes_.Begin()->second_->Remove();
-}
-
 void Scene::AddRequiredPackageFile(PackageFile* file)
 {
     if (file)
@@ -325,7 +317,7 @@ float Scene::GetAsyncProgress() const
         return (float)asyncProgress_.loadedNodes_ / (float)asyncProgress_.totalNodes_;
 }
 
-unsigned Scene::GetFreeunsigned(bool local)
+unsigned Scene::GetFreeNodeID(bool local)
 {
     if (!local)
     {
@@ -499,7 +491,7 @@ void Scene::FinishAsyncLoading()
 
 void Scene::FinishLoading(Deserializer* source)
 {
-    PostLoad();
+    OnFinishUpdate();
     
     if (source)
     {

+ 1 - 3
Engine/Scene/Scene.h

@@ -91,8 +91,6 @@ public:
     void SetActive(bool enable);
     /// Clear scene completely of nodes and components
     void Clear();
-    /// Clear scene of all non-local child nodes. Note: if they have local children, they will be removed as well
-    void ClearNonLocal();
     /// Add a required package file for multiplayer. To be called on the server
     void AddRequiredPackageFile(PackageFile* file);
     /// Clear required package files
@@ -122,7 +120,7 @@ public:
     const Map<unsigned, Component*>& GetAllComponents() const { return allComponents_; }
     
     /// Get free node ID, either non-local or local
-    unsigned GetFreeunsigned(bool local);
+    unsigned GetFreeNodeID(bool local);
     /// Get free component ID, either non-local or local
     unsigned GetFreeComponentID(bool local);
     /// Node added. Assign scene pointer and add to ID map

+ 6 - 7
Engine/Scene/Serializable.cpp

@@ -35,8 +35,7 @@ OBJECTTYPESTATIC(Serializable);
 
 Serializable::Serializable(Context* context) :
     Object(context),
-    inSerialization_(false),
-    inNetwork_(false)
+    inSerialization_(false)
 {
 }
 
@@ -193,7 +192,7 @@ bool Serializable::Load(Deserializer& source)
     for (unsigned i = 0; i < attributes->Size(); ++i)
     {
         const AttributeInfo& attr = attributes->At(i);
-        if (!(attr.mode_ & AM_SERIALIZATION))
+        if (!(attr.mode_ & AM_FILE))
             continue;
         
         if (source.IsEof())
@@ -221,7 +220,7 @@ bool Serializable::Save(Serializer& dest)
     for (unsigned i = 0; i < attributes->Size(); ++i)
     {
         const AttributeInfo& attr = attributes->At(i);
-        if (!(attr.mode_ & AM_SERIALIZATION))
+        if (!(attr.mode_ & AM_FILE))
             continue;
         
         if (!dest.WriteVariantData(GetAttribute(i)))
@@ -253,7 +252,7 @@ bool Serializable::LoadXML(const XMLElement& source)
     for (unsigned i = 0; i < attributes->Size(); ++i)
     {
         const AttributeInfo& attr = attributes->At(i);
-        if (!(attr.mode_ & AM_SERIALIZATION))
+        if (!(attr.mode_ & AM_FILE))
             continue;
         
         // We could assume fixed order. However, do name-based lookup instead for more robustness
@@ -320,7 +319,7 @@ bool Serializable::SaveXML(XMLElement& dest)
     for (unsigned i = 0; i < attributes->Size(); ++i)
     {
         const AttributeInfo& attr = attributes->At(i);
-        if (!(attr.mode_ & AM_SERIALIZATION))
+        if (!(attr.mode_ & AM_FILE))
             continue;
         
         XMLElement attrElem = dest.CreateChild("attribute");
@@ -446,7 +445,7 @@ unsigned Serializable::GetNumNetworkAttributes() const
     for (unsigned i = 0; i < attributes->Size(); ++i)
     {
         const AttributeInfo& attr = attributes->At(i);
-        if (attr.mode_ & AM_NETWORK)
+        if (attr.mode_ & AM_NET)
             ++num;
     }
     

+ 41 - 12
Engine/Scene/Serializable.h

@@ -45,6 +45,8 @@ public:
     virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& value);
     /// Handle attribute read access. Default implementation reads the variable at offset, or invokes the get accessor
     virtual Variant OnGetAttribute(const AttributeInfo& attr);
+    /// Perform finalization after a scene load or network update
+    virtual void OnFinishUpdate() {}
     /// Load from binary data. Return true if successful
     virtual bool Load(Deserializer& source);
     /// Save as binary data. Return true if successful
@@ -53,8 +55,6 @@ public:
     virtual bool LoadXML(const XMLElement& source);
     /// Save as XML data. Return true if successful
     virtual bool SaveXML(XMLElement& dest);
-    /// Perform post-load after the whole scene has been loaded
-    virtual void PostLoad() {}
     
     /// Set attribute by index. Return true if successfully set
     bool SetAttribute(unsigned index, const Variant& value);
@@ -75,12 +75,9 @@ public:
 protected:
     /// In serialization -flag
     bool inSerialization_;
-    /// In network replication -flag
-    bool inNetwork_;
 };
 
-
-/// Template implementation of the attribute accessor invoke helper class (stores function pointers of specific class)
+/// Template implementation of the attribute accessor invoke helper class
 template <class T, class U> class AttributeAccessorImpl : public AttributeAccessor
 {
 public:
@@ -114,9 +111,41 @@ public:
     SetFunctionPtr setFunction_;
 };
 
-#define ATTRIBUTE(className, type, name, variable, defaultValue) context->RegisterAttribute<className>(AttributeInfo(type, name, offsetof(className, variable), defaultValue, AM_BOTH))
-#define ATTRIBUTE_MODE(className, type, name, variable, defaultValue, mode) context->RegisterAttribute<className>(AttributeInfo(type, name, offsetof(className, variable), defaultValue, mode))
-#define ENUM_ATTRIBUTE(className, name, variable, enumNames, defaultValue) context->RegisterAttribute<className>(AttributeInfo(VAR_INT, name, offsetof(className, variable), enumNames, defaultValue, AM_BOTH))
-#define ENUM_ATTRIBUTE_MODE(className, name, variable, enumNames, defaultValue, mode) context->RegisterAttribute<className>(AttributeInfo(VAR_INT, name, offsetof(className, variable), enumNames, defaultValue, mode))
-#define ACCESSOR_ATTRIBUTE(className, type, name, getFunction, setFunction, typeName, defaultValue) context->RegisterAttribute<className>(AttributeInfo(type, name, new AttributeAccessorImpl<className, typeName>(&className::getFunction, &className::setFunction), defaultValue, AM_BOTH))
-#define ACCESSOR_ATTRIBUTE_MODE(className, type, name, getFunction, setFunction, typeName, defaultValue, mode) context->RegisterAttribute<className>(AttributeInfo(type, name, new AttributeAccessorImpl<className, typeName>(&className::getFunction, &className::setFunction), defaultValue, mode))
+/// Template implementation of the attribute accessor invoke helper class using const references
+template <class T, class U> class RefAttributeAccessorImpl : public AttributeAccessor
+{
+public:
+    typedef const U& (T::*GetFunctionPtr)() const;
+    typedef void (T::*SetFunctionPtr)(const U&);
+    
+    /// Construct with function pointers
+    RefAttributeAccessorImpl(GetFunctionPtr getFunction, SetFunctionPtr setFunction) :
+        getFunction_(getFunction),
+        setFunction_(setFunction)
+    {
+    }
+    
+    /// Invoke getter function
+    virtual Variant Get(Serializable* ptr)
+    {
+        T* classPtr = static_cast<T*>(ptr);
+        return Variant((classPtr->*getFunction_)());
+    }
+    
+    /// Invoke setter function
+    virtual void Set(Serializable* ptr, const Variant& value)
+    {
+        T* classPtr = static_cast<T*>(ptr);
+        (classPtr->*setFunction_)(value.Get<U>());
+    }
+    
+    /// Class-specific pointer to getter function
+    GetFunctionPtr getFunction_;
+    /// Class-specific pointer to setter function
+    SetFunctionPtr setFunction_;
+};
+
+#define ATTRIBUTE(className, type, name, variable, defaultValue, mode) context->RegisterAttribute<className>(AttributeInfo(type, name, offsetof(className, variable), defaultValue, mode))
+#define ENUM_ATTRIBUTE(className, name, variable, enumNames, defaultValue, mode) context->RegisterAttribute<className>(AttributeInfo(VAR_INT, name, offsetof(className, variable), enumNames, defaultValue, mode))
+#define ACCESSOR_ATTRIBUTE(className, type, name, getFunction, setFunction, typeName, defaultValue, mode) context->RegisterAttribute<className>(AttributeInfo(type, name, new AttributeAccessorImpl<className, typeName>(&className::getFunction, &className::setFunction), defaultValue, mode))
+#define REF_ACCESSOR_ATTRIBUTE(className, type, name, getFunction, setFunction, typeName, defaultValue, mode) context->RegisterAttribute<className>(AttributeInfo(type, name, new RefAttributeAccessorImpl<className, typeName>(&className::getFunction, &className::setFunction), defaultValue, mode))

+ 56 - 76
Engine/Script/ScriptInstance.cpp

@@ -49,7 +49,7 @@ static const String methodDeclarations[] = {
     "void FixedPostUpdate(float)",
     "void Load(Deserializer&)",
     "void Save(Serializer&)",
-    "void PostLoad()"
+    "void OnFinishUpdate()"
 };
 
 OBJECTTYPESTATIC(ScriptInstance);
@@ -76,83 +76,16 @@ void ScriptInstance::RegisterObject(Context* context)
 {
     context->RegisterFactory<ScriptInstance>();
     
-    ATTRIBUTE(ScriptInstance, VAR_RESOURCEREF, "Script File", scriptFile_, ResourceRef(ScriptFile::GetTypeStatic()));
-    ATTRIBUTE(ScriptInstance, VAR_STRING, "Class Name", className_, String());
-    ATTRIBUTE(ScriptInstance, VAR_BOOL, "Active", active_, true);
-    ATTRIBUTE(ScriptInstance, VAR_INT, "Fixed Update FPS", fixedUpdateFps_, 0);
-    ATTRIBUTE_MODE(ScriptInstance, VAR_FLOAT, "Time Accumulator", fixedUpdateAcc_, 0.0f, AM_SERIALIZATION);
-    ATTRIBUTE_MODE(ScriptInstance, VAR_BUFFER, "Delayed Method Calls", delayedMethodCalls_, PODVector<unsigned char>(), AM_SERIALIZATION);
-    ACCESSOR_ATTRIBUTE(ScriptInstance, VAR_BUFFER, "Script Data", GetScriptData, SetScriptData, PODVector<unsigned char>, PODVector<unsigned char>());
+    ACCESSOR_ATTRIBUTE(ScriptInstance, VAR_RESOURCEREF, "Script File", GetScriptFileAttr, SetScriptFileAttr, ResourceRef, ResourceRef(ScriptFile::GetTypeStatic()), AM_DEFAULT);
+    REF_ACCESSOR_ATTRIBUTE(ScriptInstance, VAR_STRING, "Class Name", GetClassName, SetClassName, String, String(), AM_DEFAULT);
+    ATTRIBUTE(ScriptInstance, VAR_BOOL, "Active", active_, true, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(ScriptInstance, VAR_INT, "Fixed Update FPS", GetFixedUpdateFps, SetFixedUpdateFps, int, 0, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(ScriptInstance, VAR_FLOAT, "Time Accumulator", GetFixedUpdateAccAttr, SetFixedUpdateAccAttr, float, 0.0f, AM_FILE);
+    ACCESSOR_ATTRIBUTE(ScriptInstance, VAR_BUFFER, "Delayed Method Calls", GetDelayedMethodCallsAttr, SetDelayedMethodCallsAttr, PODVector<unsigned char>, PODVector<unsigned char>(), AM_FILE);
+    ACCESSOR_ATTRIBUTE(ScriptInstance, VAR_BUFFER, "Script Data", GetScriptData, SetScriptData, PODVector<unsigned char>, PODVector<unsigned char>(), AM_DEFAULT);
 }
 
-void ScriptInstance::OnSetAttribute(const AttributeInfo& attr, const Variant& value)
-{
-    ResourceCache* cache = GetSubsystem<ResourceCache>();
-    
-    switch (attr.offset_)
-    {
-    case offsetof(ScriptInstance, scriptFile_):
-        SetScriptFile(cache->GetResource<ScriptFile>(value.GetResourceRef().id_));
-        break;
-        
-    case offsetof(ScriptInstance, className_):
-        SetClassName(value.GetString());
-        break;
-        
-    case offsetof(ScriptInstance, fixedUpdateFps_):
-        SetFixedUpdateFps(value.GetInt());
-        break;
-        
-    case offsetof(ScriptInstance, fixedUpdateAcc_):
-        Serializable::OnSetAttribute(attr, value);
-        fixedPostUpdateAcc_ = fixedUpdateAcc_;
-        break;
-        
-    case offsetof(ScriptInstance, delayedMethodCalls_):
-        {
-            MemoryBuffer buf(value.GetBuffer());
-            delayedMethodCalls_.Resize(buf.ReadVLE());
-            for (Vector<DelayedMethodCall>::Iterator i = delayedMethodCalls_.Begin(); i != delayedMethodCalls_.End(); ++i)
-            {
-                i->delay_ = buf.ReadFloat();
-                i->declaration_ = buf.ReadString();
-                i->parameters_ = buf.ReadVariantVector();
-            }
-        }
-        break;
-        
-    default:
-        Serializable::OnSetAttribute(attr, value);
-        break;
-    }
-}
-
-Variant ScriptInstance::OnGetAttribute(const AttributeInfo& attr)
-{
-    switch (attr.offset_)
-    {
-    case offsetof(ScriptInstance, scriptFile_):
-        return GetResourceRef(scriptFile_, ScriptFile::GetTypeStatic());
-        
-    case offsetof(ScriptInstance, delayedMethodCalls_):
-        {
-            VectorBuffer buf;
-            buf.WriteVLE(delayedMethodCalls_.Size());
-            for (Vector<DelayedMethodCall>::ConstIterator i = delayedMethodCalls_.Begin(); i != delayedMethodCalls_.End(); ++i)
-            {
-                buf.WriteFloat(i->delay_);
-                buf.WriteString(i->declaration_);
-                buf.WriteVariantVector(i->parameters_);
-            }
-            return buf.GetBuffer();
-        }
-        
-    default:
-        return Serializable::OnGetAttribute(attr);
-    }
-}
-
-void ScriptInstance::PostLoad()
+void ScriptInstance::OnFinishUpdate()
 {
     if (scriptObject_ && methods_[METHOD_POSTLOAD])
         scriptFile_->Execute(scriptObject_, methods_[METHOD_POSTLOAD]);
@@ -303,6 +236,53 @@ void ScriptInstance::AddEventHandler(Object* sender, StringHash eventType, const
     SubscribeToEvent(sender, eventType, HANDLER_USERDATA(ScriptInstance, HandleScriptEvent, (void*)method));
 }
 
+void ScriptInstance::SetScriptFileAttr(ResourceRef value)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    SetScriptFile(cache->GetResource<ScriptFile>(value.id_));
+}
+
+void ScriptInstance::SetDelayedMethodCallsAttr(PODVector<unsigned char> value)
+{
+    MemoryBuffer buf(value);
+    delayedMethodCalls_.Resize(buf.ReadVLE());
+    for (Vector<DelayedMethodCall>::Iterator i = delayedMethodCalls_.Begin(); i != delayedMethodCalls_.End(); ++i)
+    {
+        i->delay_ = buf.ReadFloat();
+        i->declaration_ = buf.ReadString();
+        i->parameters_ = buf.ReadVariantVector();
+    }
+}
+
+void ScriptInstance::SetFixedUpdateAccAttr(float value)
+{
+    fixedUpdateAcc_ = value;
+    fixedPostUpdateAcc_ = value;
+}
+
+ResourceRef ScriptInstance::GetScriptFileAttr() const
+{
+    return GetResourceRef(scriptFile_, ScriptFile::GetTypeStatic());
+}
+
+PODVector<unsigned char> ScriptInstance::GetDelayedMethodCallsAttr() const
+{
+    VectorBuffer buf;
+    buf.WriteVLE(delayedMethodCalls_.Size());
+    for (Vector<DelayedMethodCall>::ConstIterator i = delayedMethodCalls_.Begin(); i != delayedMethodCalls_.End(); ++i)
+    {
+        buf.WriteFloat(i->delay_);
+        buf.WriteString(i->declaration_);
+        buf.WriteVariantVector(i->parameters_);
+    }
+    return buf.GetBuffer();
+}
+
+float ScriptInstance::GetFixedUpdateAccAttr() const
+{
+    return fixedUpdateAcc_;
+}
+
 void ScriptInstance::CreateObject()
 {
     if (!scriptFile_ || className_.Empty())

+ 14 - 5
Engine/Script/ScriptInstance.h

@@ -70,12 +70,8 @@ public:
     /// Register object factory
     static void RegisterObject(Context* context);
     
-    /// Handle attribute write access
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& value);
-    /// Handle attribute read access
-    virtual Variant OnGetAttribute(const AttributeInfo& attr);
     /// Perform post-load after the whole scene has been loaded
-    virtual void PostLoad();
+    virtual void OnFinishUpdate();
     /// Add an event handler. Called by script exposed version of SubscribeToEvent()
     virtual void AddEventHandler(StringHash eventType, const String& handlerName);
     /// Add an event handler for a specific sender. Called by script exposed version of SubscribeToEvent()
@@ -111,6 +107,19 @@ public:
     /// Return fixed updates per second
     int GetFixedUpdateFps() const { return fixedUpdateFps_; }
     
+    /// Set script file attribute
+    void SetScriptFileAttr(ResourceRef value);
+    /// Set delayed method calls attribute
+    void SetDelayedMethodCallsAttr(PODVector<unsigned char> value);
+    /// Set fixed update time accumulator attribute
+    void SetFixedUpdateAccAttr(float value);
+    /// Return script file attribute
+    ResourceRef GetScriptFileAttr() const;
+    /// Return delayed method calls attribute
+    PODVector<unsigned char> GetDelayedMethodCallsAttr() const;
+    /// Return fixed update time accumulator attribute
+    float GetFixedUpdateAccAttr() const;
+    
 private:
     /// (Re)create the script object and check for supported methods if successfully created
     void CreateObject();

+ 1 - 1
Urho3D/Urho3D.cpp

@@ -44,7 +44,7 @@ void Run(const char* cmdLine);
 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, PSTR cmdLine, int showCmd)
 {
     #if defined(_MSC_VER) && defined(_DEBUG)
-    //_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
+    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
     #endif
     
     #if defined(ENABLE_MINIDUMPS) && !defined(_DEBUG)