瀏覽代碼

Refactor attributes. Minor changes.

Eugene Kozlov 8 年之前
父節點
當前提交
7a3ec74ea4

+ 8 - 57
Source/Urho3D/Core/Attribute.h

@@ -65,63 +65,16 @@ public:
 struct AttributeInfo
 {
     /// Construct empty.
-    AttributeInfo() :
-        type_(VAR_NONE),
-        offset_(0),
-        enumNames_(nullptr),
-        mode_(AM_DEFAULT),
-        ptr_(nullptr)
-    {
-    }
-
-    /// Construct offset attribute.
-    AttributeInfo(VariantType type, const char* name, size_t offset, const Variant& defaultValue, unsigned mode) :
-        type_(type),
-        name_(name),
-        offset_((unsigned)offset),
-        enumNames_(nullptr),
-        defaultValue_(defaultValue),
-        mode_(mode),
-        ptr_(nullptr)
-    {
-    }
-
-    /// Construct offset enum attribute.
-    AttributeInfo(const char* name, size_t offset, const char** enumNames, const Variant& defaultValue, unsigned mode) :
-        type_(VAR_INT),
-        name_(name),
-        offset_((unsigned)offset),
-        enumNames_(enumNames),
-        defaultValue_(defaultValue),
-        mode_(mode),
-        ptr_(nullptr)
-    {
-    }
+    AttributeInfo() { }
 
-    /// Construct accessor attribute.
-    AttributeInfo(VariantType type, const char* name, AttributeAccessor* accessor, const Variant& defaultValue, unsigned mode) :
+    /// Construct attribute.
+    AttributeInfo(VariantType type, const char* name, SharedPtr<AttributeAccessor> accessor, const char** enumNames, const Variant& defaultValue, unsigned mode) :
         type_(type),
         name_(name),
-        offset_(0),
-        enumNames_(nullptr),
-        accessor_(accessor),
-        defaultValue_(defaultValue),
-        mode_(mode),
-        ptr_(nullptr)
-    {
-    }
-
-    /// Construct accessor enum attribute.
-    AttributeInfo(const char* name, AttributeAccessor* accessor, const char** enumNames, const Variant& defaultValue,
-        unsigned mode) :
-        type_(VAR_INT),
-        name_(name),
-        offset_(0),
         enumNames_(enumNames),
         accessor_(accessor),
         defaultValue_(defaultValue),
-        mode_(mode),
-        ptr_(nullptr)
+        mode_(mode)
     {
     }
 
@@ -139,23 +92,21 @@ struct AttributeInfo
     }
 
     /// Attribute type.
-    VariantType type_;
+    VariantType type_ = VAR_NONE;
     /// Name.
     String name_;
-    /// Byte offset from start of object.
-    unsigned offset_;
     /// Enum names.
-    const char** enumNames_;
+    const char** enumNames_ = nullptr;
     /// Helper object for accessor mode.
     SharedPtr<AttributeAccessor> accessor_;
     /// Default value for network replication.
     Variant defaultValue_;
     /// Attribute mode: whether to use for serialization, network replication, or both.
-    unsigned mode_;
+    unsigned mode_ = AM_DEFAULT;
     /// Attribute metadata.
     VariantMap metadata_;
     /// Attribute data pointer if elsewhere than in the Serializable.
-    void* ptr_;
+    void* ptr_ = nullptr;
 };
 
 /// Attribute handle returned by Context::RegisterAttribute and used to chain attribute setup calls.

+ 11 - 26
Source/Urho3D/Graphics/Light.cpp

@@ -149,38 +149,23 @@ void Light::RegisterObject(Context* context)
     URHO3D_ACCESSOR_ATTRIBUTE("Shadow Fade Distance", GetShadowFadeDistance, SetShadowFadeDistance, float, 0.0f, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Shadow Intensity", GetShadowIntensity, SetShadowIntensity, float, 0.0f, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Shadow Resolution", GetShadowResolution, SetShadowResolution, float, 1.0f, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Focus To Scene", bool, shadowFocus_.focus_, true, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Non-uniform View", bool, shadowFocus_.nonUniform_, true, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Auto-Reduce Size", bool, shadowFocus_.autoSize_, true, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("CSM Splits", Vector4, shadowCascade_.splits_, Vector4(DEFAULT_SHADOWSPLIT, 0.0f, 0.0f, 0.0f), AM_DEFAULT);
-    URHO3D_ATTRIBUTE("CSM Fade Start", float, shadowCascade_.fadeStart_, DEFAULT_SHADOWFADESTART, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("CSM Bias Auto Adjust", float, shadowCascade_.biasAutoAdjust_, DEFAULT_BIASAUTOADJUST, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("View Size Quantize", float, shadowFocus_.quantize_, DEFAULT_SHADOWQUANTIZE, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("View Size Minimum", float, shadowFocus_.minView_, DEFAULT_SHADOWMINVIEW, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Depth Constant Bias", float, shadowBias_.constantBias_, DEFAULT_CONSTANTBIAS, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Depth Slope Bias", float, shadowBias_.slopeScaledBias_, DEFAULT_SLOPESCALEDBIAS, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Normal Offset", float, shadowBias_.normalOffset_, DEFAULT_NORMALOFFSET, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Focus To Scene", bool, shadowFocus_.focus_, ValidateShadowFocus, true, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Non-uniform View", bool, shadowFocus_.nonUniform_, ValidateShadowFocus, true, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Auto-Reduce Size", bool, shadowFocus_.autoSize_, ValidateShadowFocus, true, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("CSM Splits", Vector4, shadowCascade_.splits_, ValidateShadowCascade, Vector4(DEFAULT_SHADOWSPLIT, 0.0f, 0.0f, 0.0f), AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("CSM Fade Start", float, shadowCascade_.fadeStart_, ValidateShadowCascade, DEFAULT_SHADOWFADESTART, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("CSM Bias Auto Adjust", float, shadowCascade_.biasAutoAdjust_, ValidateShadowCascade, DEFAULT_BIASAUTOADJUST, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("View Size Quantize", float, shadowFocus_.quantize_, ValidateShadowFocus, DEFAULT_SHADOWQUANTIZE, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("View Size Minimum", float, shadowFocus_.minView_, ValidateShadowFocus, DEFAULT_SHADOWMINVIEW, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Depth Constant Bias", float, shadowBias_.constantBias_, ValidateShadowBias, DEFAULT_CONSTANTBIAS, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Depth Slope Bias", float, shadowBias_.slopeScaledBias_, ValidateShadowBias, DEFAULT_SLOPESCALEDBIAS, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Normal Offset", float, shadowBias_.normalOffset_, ValidateShadowBias, DEFAULT_NORMALOFFSET, AM_DEFAULT);
     URHO3D_ATTRIBUTE("Near/Farclip Ratio", float, shadowNearFarRatio_, DEFAULT_SHADOWNEARFARRATIO, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Max Extrusion", GetShadowMaxExtrusion, SetShadowMaxExtrusion, float, DEFAULT_SHADOWMAXEXTRUSION, AM_DEFAULT);
     URHO3D_ATTRIBUTE("View Mask", int, viewMask_, DEFAULT_VIEWMASK, AM_DEFAULT);
     URHO3D_ATTRIBUTE("Light Mask", int, lightMask_, DEFAULT_LIGHTMASK, AM_DEFAULT);
 }
 
-void Light::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
-{
-    Serializable::OnSetAttribute(attr, src);
-
-    // Validate the bias, cascade & focus parameters
-    if (attr.offset_ >= offsetof(Light, shadowBias_) && attr.offset_ < (offsetof(Light, shadowBias_) + sizeof(BiasParameters)))
-        shadowBias_.Validate();
-    else if (attr.offset_ >= offsetof(Light, shadowCascade_) &&
-             attr.offset_ < (offsetof(Light, shadowCascade_) + sizeof(CascadeParameters)))
-        shadowCascade_.Validate();
-    else if (attr.offset_ >= offsetof(Light, shadowFocus_) &&
-             attr.offset_ < (offsetof(Light, shadowFocus_) + sizeof(FocusParameters)))
-        shadowFocus_.Validate();
-}
-
 void Light::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResult>& results)
 {
     // Do not record a raycast result for a directional light, as it would block all other results

+ 7 - 3
Source/Urho3D/Graphics/Light.h

@@ -110,7 +110,7 @@ struct URHO3D_API CascadeParameters
     }
 
     /// Far clip values of the splits.
-    float splits_[4];
+    Vector4 splits_;
     /// The point relative to the total shadow range where shadow fade begins (0.0 - 1.0)
     float fadeStart_;
     /// Automatic depth bias adjustment strength.
@@ -163,8 +163,6 @@ public:
     /// Register object factory. Drawable must be registered first.
     static void RegisterObject(Context* context);
 
-    /// Handle attribute change.
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src) override;
     /// Process octree raycast. May be called from a worker thread.
     virtual void ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResult>& results) override;
     /// Calculate distance and prepare batches for rendering. May be called from worker thread(s), possibly re-entrantly.
@@ -343,6 +341,12 @@ protected:
     virtual void OnWorldBoundingBoxUpdate() override;
 
 private:
+    /// Validate shadow focus.
+    void ValidateShadowFocus() { shadowFocus_.Validate(); }
+    /// Validate shadow cascade.
+    void ValidateShadowCascade() { shadowCascade_.Validate(); }
+    /// Validate shadow bias.
+    void ValidateShadowBias() { shadowBias_.Validate(); }
     /// Light type.
     LightType lightType_;
     /// Color.

+ 3 - 10
Source/Urho3D/Graphics/Octree.cpp

@@ -337,16 +337,9 @@ void Octree::RegisterObject(Context* context)
     Vector3 defaultBoundsMin = -Vector3::ONE * DEFAULT_OCTREE_SIZE;
     Vector3 defaultBoundsMax = Vector3::ONE * DEFAULT_OCTREE_SIZE;
 
-    URHO3D_ATTRIBUTE("Bounding Box Min", Vector3, worldBoundingBox_.min_, defaultBoundsMin, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Bounding Box Max", Vector3, worldBoundingBox_.max_, defaultBoundsMax, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Number of Levels", int, numLevels_, DEFAULT_OCTREE_LEVELS, AM_DEFAULT);
-}
-
-void Octree::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
-{
-    // If any of the (size) attributes change, resize the octree
-    Serializable::OnSetAttribute(attr, src);
-    SetSize(worldBoundingBox_, numLevels_);
+    URHO3D_ATTRIBUTE_EX("Bounding Box Min", Vector3, worldBoundingBox_.min_, UpdateOctreeSize, defaultBoundsMin, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Bounding Box Max", Vector3, worldBoundingBox_.max_, UpdateOctreeSize, defaultBoundsMax, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Number of Levels", int, numLevels_, UpdateOctreeSize, DEFAULT_OCTREE_LEVELS, AM_DEFAULT);
 }
 
 void Octree::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)

+ 2 - 2
Source/Urho3D/Graphics/Octree.h

@@ -171,8 +171,6 @@ public:
     /// Register object factory.
     static void RegisterObject(Context* context);
 
-    /// Handle attribute change.
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src) override;
     /// Visualize the component as debug geometry.
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest) override;
 
@@ -205,6 +203,8 @@ public:
 private:
     /// Handle render update in case of headless execution.
     void HandleRenderUpdate(StringHash eventType, VariantMap& eventData);
+    /// Update octree size.
+    void UpdateOctreeSize() { SetSize(worldBoundingBox_, numLevels_); }
 
     /// Drawable objects that require update.
     PODVector<Drawable*> drawableUpdates_;

+ 6 - 20
Source/Urho3D/Graphics/Terrain.cpp

@@ -128,14 +128,14 @@ void Terrain::RegisterObject(Context* context)
         AM_DEFAULT);
     URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Material", GetMaterialAttr, SetMaterialAttr, ResourceRef, ResourceRef(Material::GetTypeStatic()),
         AM_DEFAULT);
-    URHO3D_ATTRIBUTE("North Neighbor NodeID", unsigned, northID_, 0, AM_DEFAULT | AM_NODEID);
-    URHO3D_ATTRIBUTE("South Neighbor NodeID", unsigned, southID_, 0, AM_DEFAULT | AM_NODEID);
-    URHO3D_ATTRIBUTE("West Neighbor NodeID", unsigned, westID_, 0, AM_DEFAULT | AM_NODEID);
-    URHO3D_ATTRIBUTE("East Neighbor NodeID", unsigned, eastID_, 0, AM_DEFAULT | AM_NODEID);
-    URHO3D_ATTRIBUTE("Vertex Spacing", Vector3, spacing_, DEFAULT_SPACING, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("North Neighbor NodeID", unsigned, northID_, MarkNeighborsDirty, 0, AM_DEFAULT | AM_NODEID);
+    URHO3D_ATTRIBUTE_EX("South Neighbor NodeID", unsigned, southID_, MarkNeighborsDirty, 0, AM_DEFAULT | AM_NODEID);
+    URHO3D_ATTRIBUTE_EX("West Neighbor NodeID", unsigned, westID_, MarkNeighborsDirty, 0, AM_DEFAULT | AM_NODEID);
+    URHO3D_ATTRIBUTE_EX("East Neighbor NodeID", unsigned, eastID_, MarkNeighborsDirty, 0, AM_DEFAULT | AM_NODEID);
+    URHO3D_ATTRIBUTE_EX("Vertex Spacing", Vector3, spacing_, MarkTerrainDirty, DEFAULT_SPACING, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Patch Size", GetPatchSize, SetPatchSizeAttr, int, DEFAULT_PATCH_SIZE, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Max LOD Levels", GetMaxLodLevels, SetMaxLodLevelsAttr, unsigned, MAX_LOD_LEVELS, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Smooth Height Map", bool, smoothing_, false, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Smooth Height Map", bool, smoothing_, MarkTerrainDirty, false, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Is Occluder", IsOccluder, SetOccluder, bool, false, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Can Be Occluded", IsOccludee, SetOccludee, bool, true, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Cast Shadows", GetCastShadows, SetCastShadows, bool, false, AM_DEFAULT);
@@ -150,20 +150,6 @@ void Terrain::RegisterObject(Context* context)
     URHO3D_ACCESSOR_ATTRIBUTE("Occlusion LOD level", GetOcclusionLodLevel, SetOcclusionLodLevelAttr, unsigned, M_MAX_UNSIGNED, AM_DEFAULT);
 }
 
-void Terrain::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
-{
-    Serializable::OnSetAttribute(attr, src);
-
-    // Change of any non-accessor attribute requires recreation of the terrain, or setting the neighbor terrains
-    if (!attr.accessor_)
-    {
-        if (attr.mode_ & AM_NODEID)
-            neighborsDirty_ = true;
-        else
-            recreateTerrain_ = true;
-    }
-}
-
 void Terrain::ApplyAttributes()
 {
     if (recreateTerrain_)

+ 9 - 7
Source/Urho3D/Graphics/Terrain.h

@@ -47,8 +47,6 @@ public:
     /// Register object factory.
     static void RegisterObject(Context* context);
 
-    /// Handle attribute write access.
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src) override;
     /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
     virtual void ApplyAttributes() override;
     /// Handle enabled/disabled state change.
@@ -117,10 +115,10 @@ public:
 
     /// Return maximum number of LOD levels for terrain patches. This can be between 1-4.
     unsigned GetMaxLodLevels() const { return maxLodLevels_; }
-    
+
     /// Return LOD level used for occlusion.
     unsigned GetOcclusionLodLevel() const { return occlusionLodLevel_; }
-    
+
     /// Return whether smoothing is in use.
     bool GetSmoothing() const { return smoothing_; }
 
@@ -145,13 +143,13 @@ public:
 
     /// Return north neighbor terrain.
     Terrain* GetNorthNeighbor() const { return north_; }
-    
+
     /// Return south neighbor terrain.
     Terrain* GetSouthNeighbor() const { return south_; }
-    
+
     /// Return west neighbor terrain.
     Terrain* GetWestNeighbor() const { return west_; }
-    
+
     /// Return east neighbor terrain.
     Terrain* GetEastNeighbor() const { return east_; }
 
@@ -238,6 +236,10 @@ private:
     void HandleNeighborTerrainCreated(StringHash eventType, VariantMap& eventData);
     /// Update edge patch neighbors when neighbor terrain(s) change or are recreated.
     void UpdateEdgePatchNeighbors();
+    /// Mark neighbors dirty.
+    void MarkNeighborsDirty() { neighborsDirty_ = true; }
+    /// Mark terrain dirty.
+    void MarkTerrainDirty() { recreateTerrain_ = true; }
 
     /// Shared index buffer.
     SharedPtr<IndexBuffer> indexBuffer_;

+ 2 - 2
Source/Urho3D/Graphics/View.cpp

@@ -2541,7 +2541,7 @@ void View::SetupShadowCameras(LightQueryResult& query)
 {
     Light* light = query.light_;
 
-    int splits = 0;
+    unsigned splits = 0;
 
     switch (light->GetLightType())
     {
@@ -2615,7 +2615,7 @@ void View::SetupShadowCameras(LightQueryResult& query)
         break;
     }
 
-    query.numSplits_ = (unsigned)splits;
+    query.numSplits_ = splits;
 }
 
 void View::SetupDirLightShadowCamera(Camera* shadowCamera, Light* light, float nearSplit, float farSplit)

+ 3 - 13
Source/Urho3D/Graphics/Zone.cpp

@@ -73,8 +73,8 @@ void Zone::RegisterObject(Context* context)
     context->RegisterFactory<Zone>(SCENE_CATEGORY);
 
     URHO3D_ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Bounding Box Min", Vector3, boundingBox_.min_, DEFAULT_BOUNDING_BOX_MIN, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Bounding Box Max", Vector3, boundingBox_.max_, DEFAULT_BOUNDING_BOX_MAX, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Bounding Box Min", Vector3, boundingBox_.min_, MarkNodeDirty, DEFAULT_BOUNDING_BOX_MIN, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Bounding Box Max", Vector3, boundingBox_.max_, MarkNodeDirty, DEFAULT_BOUNDING_BOX_MAX, AM_DEFAULT);
     URHO3D_ATTRIBUTE("Ambient Color", Color, ambientColor_, DEFAULT_AMBIENT_COLOR, AM_DEFAULT);
     URHO3D_ATTRIBUTE("Fog Color", Color, fogColor_, DEFAULT_FOG_COLOR, AM_DEFAULT);
     URHO3D_ATTRIBUTE("Fog Start", float, fogStart_, DEFAULT_FOG_START, AM_DEFAULT);
@@ -84,7 +84,7 @@ void Zone::RegisterObject(Context* context)
     URHO3D_ATTRIBUTE("Height Fog Mode", bool, heightFog_, false, AM_DEFAULT);
     URHO3D_ATTRIBUTE("Override Mode", bool, override_, false, AM_DEFAULT);
     URHO3D_ATTRIBUTE("Ambient Gradient", bool, ambientGradient_, false, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Priority", int, priority_, 0, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Priority", int, priority_, MarkNodeDirty, 0, AM_DEFAULT);
     URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Zone Texture", GetZoneTextureAttr, SetZoneTextureAttr, ResourceRef,
         ResourceRef(TextureCube::GetTypeStatic()), AM_DEFAULT);
     URHO3D_ATTRIBUTE("Light Mask", int, lightMask_, DEFAULT_LIGHTMASK, AM_DEFAULT);
@@ -92,16 +92,6 @@ void Zone::RegisterObject(Context* context)
     URHO3D_ACCESSOR_ATTRIBUTE("Zone Mask", GetZoneMask, SetZoneMask, unsigned, DEFAULT_ZONEMASK, AM_DEFAULT);
 }
 
-void Zone::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
-{
-    Serializable::OnSetAttribute(attr, src);
-
-    // If bounding box or priority changes, dirty the drawable as applicable
-    if ((attr.offset_ >= offsetof(Zone, boundingBox_) && attr.offset_ < (offsetof(Zone, boundingBox_) + sizeof(BoundingBox))) ||
-        attr.offset_ == offsetof(Zone, priority_))
-        OnMarkedDirty(node_);
-}
-
 void Zone::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {
     if (debug && IsEnabledEffective())

+ 2 - 2
Source/Urho3D/Graphics/Zone.h

@@ -42,8 +42,6 @@ public:
     /// Register object factory. Drawable must be registered first.
     static void RegisterObject(Context* context);
 
-    /// Handle attribute write access.
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src) override;
     /// Visualize the component as debug geometry.
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest) override;
 
@@ -131,6 +129,8 @@ protected:
     void UpdateAmbientGradient();
     /// Clear zone reference from drawables inside the bounding box.
     void ClearDrawablesZone();
+    /// Mark node transform dirty.
+    void MarkNodeDirty() { OnMarkedDirty(node_); }
 
     /// Cached inverse world transform matrix.
     mutable Matrix3x4 inverseWorld_;

+ 6 - 0
Source/Urho3D/Math/Vector4.h

@@ -174,6 +174,12 @@ public:
         return *this;
     }
 
+    /// Return const value by index.
+    float operator[](unsigned index) const { return (&x_)[index]; }
+
+    /// Return mutable value by index.
+    float& operator[](unsigned index) { return (&x_)[index]; }
+
     /// Calculate dot product.
     float DotProduct(const Vector4& rhs) const { return x_ * rhs.x_ + y_ * rhs.y_ + z_ * rhs.z_ + w_ * rhs.w_; }
 

+ 1 - 9
Source/Urho3D/Navigation/OffMeshConnection.cpp

@@ -58,21 +58,13 @@ void OffMeshConnection::RegisterObject(Context* context)
     context->RegisterFactory<OffMeshConnection>(NAVIGATION_CATEGORY);
 
     URHO3D_ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Endpoint NodeID", int, endPointID_, 0, AM_DEFAULT | AM_NODEID);
+    URHO3D_ATTRIBUTE_EX("Endpoint NodeID", int, endPointID_, MarkEndPointDirty, 0, AM_DEFAULT | AM_NODEID);
     URHO3D_ATTRIBUTE("Radius", float, radius_, DEFAULT_RADIUS, AM_DEFAULT);
     URHO3D_ATTRIBUTE("Bidirectional", bool, bidirectional_, true, AM_DEFAULT);
     URHO3D_ATTRIBUTE("Flags Mask", unsigned, mask_, DEFAULT_MASK_FLAG, AM_DEFAULT);
     URHO3D_ATTRIBUTE("Area Type", unsigned, areaId_, DEFAULT_AREA, AM_DEFAULT);
 }
 
-void OffMeshConnection::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
-{
-    Serializable::OnSetAttribute(attr, src);
-
-    if (attr.offset_ == offsetof(OffMeshConnection, endPointID_))
-        endPointDirty_ = true;
-}
-
 void OffMeshConnection::ApplyAttributes()
 {
     if (endPointDirty_)

+ 2 - 2
Source/Urho3D/Navigation/OffMeshConnection.h

@@ -40,8 +40,6 @@ public:
     /// Register object factory.
     static void RegisterObject(Context* context);
 
-    /// Handle attribute write access.
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src) override;
     /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
     virtual void ApplyAttributes() override;
     /// Visualize the component as debug geometry.
@@ -74,6 +72,8 @@ public:
     unsigned GetAreaID() const { return areaId_; }
 
 private:
+    /// Mark end point dirty.
+    void MarkEndPointDirty() { endPointDirty_ = true; }
     /// Endpoint node.
     WeakPtr<Node> endPoint_;
     /// Endpoint node ID.

+ 5 - 14
Source/Urho3D/Physics/CollisionShape.cpp

@@ -481,23 +481,14 @@ void CollisionShape::RegisterObject(Context* context)
     context->RegisterFactory<CollisionShape>(PHYSICS_CATEGORY);
 
     URHO3D_ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
-    URHO3D_ENUM_ATTRIBUTE("Shape Type", shapeType_, typeNames, SHAPE_BOX, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Size", Vector3, size_, Vector3::ONE, AM_DEFAULT);
+    URHO3D_ENUM_ATTRIBUTE_EX("Shape Type", shapeType_, MarkShapeDirty, typeNames, SHAPE_BOX, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Size", Vector3, size_, MarkShapeDirty, Vector3::ONE, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Offset Position", GetPosition, SetPosition, Vector3, Vector3::ZERO, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Offset Rotation", GetRotation, SetRotation, Quaternion, Quaternion::IDENTITY, AM_DEFAULT);
     URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Model", GetModelAttr, SetModelAttr, ResourceRef, ResourceRef(Model::GetTypeStatic()), AM_DEFAULT);
-    URHO3D_ATTRIBUTE("LOD Level", int, lodLevel_, 0, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Collision Margin", float, margin_, DEFAULT_COLLISION_MARGIN, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("CustomGeometry ComponentID", unsigned, customGeometryID_, 0, AM_DEFAULT | AM_COMPONENTID);
-}
-
-void CollisionShape::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
-{
-    Serializable::OnSetAttribute(attr, src);
-
-    // Change of any non-accessor attribute requires recreation of the collision shape
-    if (!attr.accessor_)
-        recreateShape_ = true;
+    URHO3D_ATTRIBUTE_EX("LOD Level", int, lodLevel_, MarkShapeDirty, 0, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Collision Margin", float, margin_, MarkShapeDirty, DEFAULT_COLLISION_MARGIN, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("CustomGeometry ComponentID", unsigned, customGeometryID_, MarkShapeDirty, 0, AM_DEFAULT | AM_COMPONENTID);
 }
 
 void CollisionShape::ApplyAttributes()

+ 2 - 2
Source/Urho3D/Physics/CollisionShape.h

@@ -150,8 +150,6 @@ public:
     /// Register object factory.
     static void RegisterObject(Context* context);
 
-    /// Handle attribute write access.
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src) override;
     /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
     virtual void ApplyAttributes() override;
     /// Handle enabled/disabled state change.
@@ -283,6 +281,8 @@ private:
     void HandleTerrainCreated(StringHash eventType, VariantMap& eventData);
     /// Update trimesh or convex shape after a model has reloaded itself.
     void HandleModelReloadFinished(StringHash eventType, VariantMap& eventData);
+    /// Mark shape dirty.
+    void MarkShapeDirty() { recreateShape_ = true; }
 
     /// Physics world.
     WeakPtr<PhysicsWorld> physicsWorld_;

+ 22 - 32
Source/Urho3D/Physics/Constraint.cpp

@@ -84,42 +84,17 @@ void Constraint::RegisterObject(Context* context)
     context->RegisterFactory<Constraint>(PHYSICS_CATEGORY);
 
     URHO3D_ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
-    URHO3D_ENUM_ATTRIBUTE("Constraint Type", constraintType_, typeNames, CONSTRAINT_POINT, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Position", Vector3, position_, Vector3::ZERO, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Rotation", Quaternion, rotation_, Quaternion::IDENTITY, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Other Body Position", Vector3, otherPosition_, Vector3::ZERO, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Other Body Rotation", Quaternion, otherRotation_, Quaternion::IDENTITY, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Other Body NodeID", unsigned, otherBodyNodeID_, 0, AM_DEFAULT | AM_NODEID);
+    URHO3D_ENUM_ATTRIBUTE_EX("Constraint Type", constraintType_, MarkConstraintDirty, typeNames, CONSTRAINT_POINT, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Position", Vector3, position_, AdjustOtherBodyPosition, Vector3::ZERO, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Rotation", Quaternion, rotation_, MarkFramesDirty, Quaternion::IDENTITY, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Other Body Position", Vector3, otherPosition_, MarkFramesDirty, Vector3::ZERO, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Other Body Rotation", Quaternion, otherRotation_, MarkFramesDirty, Quaternion::IDENTITY, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Other Body NodeID", unsigned, otherBodyNodeID_, MarkConstraintDirty, 0, AM_DEFAULT | AM_NODEID);
     URHO3D_ACCESSOR_ATTRIBUTE("High Limit", GetHighLimit, SetHighLimit, Vector2, Vector2::ZERO, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Low Limit", GetLowLimit, SetLowLimit, Vector2, Vector2::ZERO, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("ERP Parameter", GetERP, SetERP, float, 0.0f, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("CFM Parameter", GetCFM, SetCFM, float, 0.0f, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Disable Collision", bool, disableCollision_, false, AM_DEFAULT);
-}
-
-void Constraint::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
-{
-    Serializable::OnSetAttribute(attr, src);
-
-    if (!attr.accessor_)
-    {
-        // Convenience for editing static constraints: if not connected to another body, adjust world position to match local
-        // (when deserializing, the proper other body position will be read after own position, so this calculation is safely
-        // overridden and does not accumulate constraint error
-        if (attr.offset_ == offsetof(Constraint, position_) && constraint_ && !otherBody_)
-        {
-            btTransform ownBody = constraint_->getRigidBodyA().getWorldTransform();
-            btVector3 worldPos = ownBody * ToBtVector3(position_ * cachedWorldScale_ - ownBody_->GetCenterOfMass());
-            otherPosition_ = ToVector3(worldPos);
-        }
-
-        // Certain attribute changes require recreation of the constraint
-        if (attr.offset_ == offsetof(Constraint, constraintType_) || attr.offset_ == offsetof(Constraint, otherBodyNodeID_) ||
-            attr.offset_ == offsetof(Constraint, disableCollision_))
-            recreateConstraint_ = true;
-        else
-            framesDirty_ = true;
-    }
+    URHO3D_ATTRIBUTE_EX("Disable Collision", bool, disableCollision_, MarkConstraintDirty, false, AM_DEFAULT);
 }
 
 void Constraint::ApplyAttributes()
@@ -602,4 +577,19 @@ void Constraint::ApplyLimits()
         constraint_->setParam(BT_CONSTRAINT_STOP_CFM, cfm_);
 }
 
+void Constraint::AdjustOtherBodyPosition()
+{
+    // Convenience for editing static constraints: if not connected to another body, adjust world position to match local
+    // (when deserializing, the proper other body position will be read after own position, so this calculation is safely
+    // overridden and does not accumulate constraint error
+    if (constraint_ && !otherBody_)
+    {
+        btTransform ownBody = constraint_->getRigidBodyA().getWorldTransform();
+        btVector3 worldPos = ownBody * ToBtVector3(position_ * cachedWorldScale_ - ownBody_->GetCenterOfMass());
+        otherPosition_ = ToVector3(worldPos);
+    }
+
+    MarkFramesDirty();
+}
+
 }

+ 6 - 2
Source/Urho3D/Physics/Constraint.h

@@ -57,8 +57,6 @@ public:
     /// Register object factory.
     static void RegisterObject(Context* context);
 
-    /// Handle attribute write access.
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src) override;
     /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
     virtual void ApplyAttributes() override;
     /// Handle enabled/disabled state change.
@@ -160,6 +158,12 @@ private:
     void CreateConstraint();
     /// Apply high and low constraint limits.
     void ApplyLimits();
+    /// Adjust other body position.
+    void AdjustOtherBodyPosition();
+    /// Mark constraint dirty.
+    void MarkConstraintDirty() { recreateConstraint_ = true; }
+    /// Mark frames dirty.
+    void MarkFramesDirty() { framesDirty_ = true; }
 
     /// Physics world.
     WeakPtr<PhysicsWorld> physicsWorld_;

+ 7 - 16
Source/Urho3D/Physics/RigidBody.cpp

@@ -98,7 +98,7 @@ void RigidBody::RegisterObject(Context* context)
     URHO3D_ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
     URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Physics Rotation", GetRotation, SetRotation, Quaternion, Quaternion::IDENTITY, AM_FILE | AM_NOEDIT);
     URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Physics Position", GetPosition, SetPosition, Vector3, Vector3::ZERO, AM_FILE | AM_NOEDIT);
-    URHO3D_ATTRIBUTE("Mass", float, mass_, DEFAULT_MASS, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Mass", float, mass_, MarkBodyDirty, DEFAULT_MASS, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Friction", GetFriction, SetFriction, float, DEFAULT_FRICTION, AM_DEFAULT);
     URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Anisotropic Friction", GetAnisotropicFriction, SetAnisotropicFriction, Vector3, Vector3::ONE,
         AM_DEFAULT);
@@ -113,30 +113,21 @@ void RigidBody::RegisterObject(Context* context)
     URHO3D_ACCESSOR_ATTRIBUTE("Angular Damping", GetAngularDamping, SetAngularDamping, float, 0.0f, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Linear Rest Threshold", GetLinearRestThreshold, SetLinearRestThreshold, float, 0.8f, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Angular Rest Threshold", GetAngularRestThreshold, SetAngularRestThreshold, float, 1.0f, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Collision Layer", int, collisionLayer_, DEFAULT_COLLISION_LAYER, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Collision Mask", int, collisionMask_, DEFAULT_COLLISION_MASK, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Collision Layer", int, collisionLayer_, MarkBodyDirty, DEFAULT_COLLISION_LAYER, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Collision Mask", int, collisionMask_, MarkBodyDirty, DEFAULT_COLLISION_MASK, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Contact Threshold", GetContactProcessingThreshold, SetContactProcessingThreshold, float, BT_LARGE_FLOAT,
         AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("CCD Radius", GetCcdRadius, SetCcdRadius, float, 0.0f, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("CCD Motion Threshold", GetCcdMotionThreshold, SetCcdMotionThreshold, float, 0.0f, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Network Angular Velocity", GetNetAngularVelocityAttr, SetNetAngularVelocityAttr, PODVector<unsigned char>,
         Variant::emptyBuffer, AM_NET | AM_LATESTDATA | AM_NOEDIT);
-    URHO3D_ENUM_ATTRIBUTE("Collision Event Mode", collisionEventMode_, collisionEventModeNames, COLLISION_ACTIVE, AM_DEFAULT);
+    URHO3D_ENUM_ATTRIBUTE_EX("Collision Event Mode", collisionEventMode_, MarkBodyDirty, collisionEventModeNames, COLLISION_ACTIVE, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Use Gravity", GetUseGravity, SetUseGravity, bool, true, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Is Kinematic", bool, kinematic_, false, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Is Trigger", bool, trigger_, false, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Is Kinematic", bool, kinematic_, MarkBodyDirty, false, AM_DEFAULT);
+    URHO3D_ATTRIBUTE_EX("Is Trigger", bool, trigger_, MarkBodyDirty, false, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Gravity Override", GetGravityOverride, SetGravityOverride, Vector3, Vector3::ZERO, AM_DEFAULT);
 }
 
-void RigidBody::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
-{
-    Serializable::OnSetAttribute(attr, src);
-
-    // Change of any non-accessor attribute requires the rigid body to be re-added to the physics world
-    if (!attr.accessor_)
-        readdBody_ = true;
-}
-
 void RigidBody::ApplyAttributes()
 {
     if (readdBody_)
@@ -270,7 +261,7 @@ void RigidBody::SetRotation(const Quaternion& rotation)
                 interpTrans.setOrigin(worldTrans.getOrigin());
             body_->setInterpolationWorldTransform(interpTrans);
         }
-        
+
         body_->updateInertiaTensor();
 
         Activate();

+ 2 - 2
Source/Urho3D/Physics/RigidBody.h

@@ -59,8 +59,6 @@ public:
     /// Register object factory.
     static void RegisterObject(Context* context);
 
-    /// Handle attribute write access.
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src) override;
     /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
     virtual void ApplyAttributes() override;
     /// Handle enabled/disabled state change.
@@ -262,6 +260,8 @@ private:
     void HandleTargetPosition(StringHash eventType, VariantMap& eventData);
     /// Handle SmoothedTransform target rotation update.
     void HandleTargetRotation(StringHash eventType, VariantMap& eventData);
+    /// Mark body dirty.
+    void MarkBodyDirty() { readdBody_ = true; }
 
     /// Bullet rigid body.
     UniquePtr<btRigidBody> body_;

+ 7 - 7
Source/Urho3D/Scene/Serializable.cpp

@@ -45,11 +45,9 @@ static unsigned RemapAttributeIndex(const Vector<AttributeInfo>* attributes, con
     for (unsigned i = 0; i < attributes->Size(); ++i)
     {
         const AttributeInfo& attr = attributes->At(i);
-        // Compare either the accessor or offset to avoid name string compare
+        // Compare accessor to avoid name string compare
         if (attr.accessor_.Get() && attr.accessor_.Get() == netAttr.accessor_.Get())
             return i;
-        else if (!attr.accessor_.Get() && attr.offset_ == netAttr.offset_)
-            return i;
     }
 
     return netAttrIndex; // Could not remap
@@ -74,8 +72,9 @@ void Serializable::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
         return;
     }
 
-    // Calculate the destination address
-    void* dest = attr.ptr_ ? attr.ptr_ : reinterpret_cast<unsigned char*>(this) + attr.offset_;
+    // Get the destination address
+    assert(attr.ptr_);
+    void* dest = attr.ptr_;
 
     switch (attr.type_)
     {
@@ -178,8 +177,9 @@ void Serializable::OnGetAttribute(const AttributeInfo& attr, Variant& dest) cons
         return;
     }
 
-    // Calculate the source address
-    const void* src = attr.ptr_ ? attr.ptr_ : reinterpret_cast<const unsigned char*>(this) + attr.offset_;
+    // Get the source address
+    assert(attr.ptr_);
+    const void* src = attr.ptr_;
 
     switch (attr.type_)
     {

+ 119 - 169
Source/Urho3D/Scene/Serializable.h

@@ -144,201 +144,131 @@ private:
     bool temporary_;
 };
 
-/// Template implementation of the enum attribute accessor invoke helper class.
-template <typename T, typename U> class EnumAttributeAccessorImpl : public AttributeAccessor
+/// Template implementation of the variant attribute accessor.
+/// \tparam TClassType Serializable class type.
+/// \tparam TGetFunction `getFunction(const TClassType& self, Variant& value)`
+/// \tparam TSetFunction `setFunction(TClassType& self, const Variant& value)`
+template <class TClassType, class TGetFunction, class TSetFunction>
+class VariantAttributeAccessorImpl : public AttributeAccessor
 {
 public:
-    using GetFunctionPtr = U (T::*)() const;
-    using SetFunctionPtr = void (T::*)(U);
-
-    /// Construct with function pointers.
-    EnumAttributeAccessorImpl(GetFunctionPtr getFunction, SetFunctionPtr setFunction) :
-        getFunction_(getFunction),
-        setFunction_(setFunction)
-    {
-        assert(getFunction_);
-        assert(setFunction_);
-    }
+    /// Construct.
+    VariantAttributeAccessorImpl(TGetFunction getFunction, TSetFunction setFunction) : getFunction_(getFunction), setFunction_(setFunction) { }
 
     /// Invoke getter function.
-    virtual void Get(const Serializable* ptr, Variant& dest) const override
+    virtual void Get(const Serializable* ptr, Variant& value) const override
     {
         assert(ptr);
-        const T* classPtr = static_cast<const T*>(ptr);
-        dest = (int)(classPtr->*getFunction_)();
+        const auto classPtr = static_cast<const TClassType*>(ptr);
+        getFunction_(*classPtr, value);
     }
 
     /// Invoke setter function.
     virtual void Set(Serializable* ptr, const Variant& value) override
     {
         assert(ptr);
-        T* classPtr = static_cast<T*>(ptr);
-        (classPtr->*setFunction_)((U)value.GetInt());
+        auto classPtr = static_cast<TClassType*>(ptr);
+        setFunction_(*classPtr, value);
     }
 
-    /// Class-specific pointer to getter function.
-    GetFunctionPtr getFunction_;
-    /// Class-specific pointer to setter function.
-    SetFunctionPtr setFunction_;
+private:
+    /// Get functor.
+    TGetFunction getFunction_;
+    /// Set functor.
+    TSetFunction setFunction_;
 };
 
-/// Template implementation of the enum attribute accessor that uses free functions invoke helper class.
-template <typename T, typename U> class EnumAttributeAccessorFreeImpl : public AttributeAccessor
+/// Make variant attribute accessor implementation.
+template <class TClassType, class TGetFunction, class TSetFunction>
+SharedPtr<AttributeAccessor> MakeVariantAttributeAccessorImpl(TGetFunction getFunction, TSetFunction setFunction)
 {
-public:
-    using GetFunctionPtr = U(*)(const T*);
-    using SetFunctionPtr = void(*)(T*, U);
+    return SharedPtr<AttributeAccessor>(new VariantAttributeAccessorImpl<TClassType, TGetFunction, TSetFunction>(getFunction, setFunction));
+}
 
-    /// Construct with function pointers.
-    EnumAttributeAccessorFreeImpl(GetFunctionPtr getFunction, SetFunctionPtr setFunction) :
-        getFunction_(getFunction),
-        setFunction_(setFunction)
-    {
-        assert(getFunction_);
-        assert(setFunction_);
-    }
+#if 0
+/// Template implementation of the attribute accessor.
+/// \tparam TClassType Serializable class type.
+/// \tparam TVariantType Internal variant type.
+/// \tparam TAttributeType Real attribute type.
+/// \tparam TGetFunction Get function with signature `getFunction(const TClassType& self, TAttributeType& value)`
+/// \tparam TSetFunction Set function with signature `setFunction(TClassType& self, const TAttributeType& value)`
+template <class TClassType, class TVariantType, class TAttributeType, class TGetFunction, class TSetFunction>
+class AttributeAccessorImpl : public AttributeAccessor
+{
+public:
+    /// Construct.
+    AttributeAccessorImpl(TGetFunction getFunction, TSetFunction setFunction) : getFunction_(getFunction), setFunction_(setFunction) { }
 
     /// Invoke getter function.
-    virtual void Get(const Serializable* ptr, Variant& dest) const override
+    virtual void Get(const Serializable* ptr, Variant& value) const override
     {
         assert(ptr);
-        const T* classPtr = static_cast<const T*>(ptr);
-        dest = (*getFunction_)(classPtr);
+        const auto classPtr = static_cast<const TClassType*>(ptr);
+        TAttributeType attributeValue;
+        getFunction_(*classPtr, attributeValue);
+        value = static_cast<TVariantType>(attributeValue);
     }
 
     /// Invoke setter function.
     virtual void Set(Serializable* ptr, const Variant& value) override
     {
         assert(ptr);
-        T* classPtr = static_cast<T*>(ptr);
-        (*setFunction_)(classPtr, (U)value.GetInt());
+        auto classPtr = static_cast<TClassType*>(ptr);
+        const TAttributeType attributeValue = static_cast<TAttributeType>(value.Get<TVariantType>());
+        setFunction_(*classPtr, value);
     }
 
-    /// Class-specific pointer to getter function.
-    GetFunctionPtr getFunction_;
-    /// Class-specific pointer to setter function.
-    SetFunctionPtr setFunction_;
-};
-
-/// Attribute trait (default use const reference for object type).
-template <typename T> struct AttributeTrait
-{
-    /// Get function return type.
-    using ReturnType = const T&;
-    /// Set function parameter type.
-    using ParameterType = const T&;
-};
-
-/// Int attribute trait.
-template <> struct AttributeTrait<int>
-{
-    using ReturnType = int;
-    using ParameterType = int;
-};
-
-/// unsigned attribute trait.
-template <> struct AttributeTrait<unsigned>
-{
-    using ReturnType = unsigned;
-    using ParameterType = unsigned;
-};
-
-/// Bool attribute trait.
-template <> struct AttributeTrait<bool>
-{
-    using ReturnType = bool;
-    using ParameterType = bool;
-};
-
-/// Float attribute trait.
-template <> struct AttributeTrait<float>
-{
-    using ReturnType = float;
-    using ParameterType = float;
-};
-
-/// Mixed attribute trait (use const reference for set function only).
-template <typename T> struct MixedAttributeTrait
-{
-    using ReturnType = T;
-    using ParameterType = const T&;
+private:
+    /// Get functor.
+    TGetFunction getFunction_;
+    /// Set functor.
+    TSetFunction setFunction_;
 };
 
-/// Template implementation of the attribute accessor invoke helper class.
-template <typename T, typename U, typename Trait> class AttributeAccessorImpl : public AttributeAccessor
+/// Make attribute accessor implementation.
+template <class TClassType, class TVariantType, class TAttributeType, class TGetFunction, class TSetFunction>
+SharedPtr<AttributeAccessor> MakeAttributeAccessorImpl(TGetFunction getFunction, TSetFunction setFunction)
 {
-public:
-    using GetFunctionPtr = typename Trait::ReturnType (T::*)() const;
-    using SetFunctionPtr = void (T::*)(typename Trait::ParameterType);
+    return MakeShared<VariantAttributeAccessorImpl<TClassType, TVariantType, TAttributeType, TGetFunction, TSetFunction>>(getFunction, setFunction);
+}
 
-    /// Construct with function pointers.
-    AttributeAccessorImpl(GetFunctionPtr getFunction, SetFunctionPtr setFunction) :
-        getFunction_(getFunction),
-        setFunction_(setFunction)
-    {
-        assert(getFunction_);
-        assert(setFunction_);
-    }
+#endif
 
-    /// Invoke getter function.
-    virtual void Get(const Serializable* ptr, Variant& dest) const override
-    {
-        assert(ptr);
-        const T* classPtr = static_cast<const T*>(ptr);
-        dest = (classPtr->*getFunction_)();
-    }
+/// Make variant attribute accessor with inline get and set code snippets.
+#define URHO3D_MAKE_INLINE_ATTRIBUTE_ACCESSOR(getSnippet, setSnippet) \
+    Urho3D::MakeVariantAttributeAccessorImpl<ClassName>( \
+        [](const ClassName& self, Variant& value) { getSnippet; }, \
+        [](ClassName& self, const Variant& value) { setSnippet; })
 
-    /// Invoke setter function.
-    virtual void Set(Serializable* ptr, const Variant& value) override
-    {
-        assert(ptr);
-        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_;
-};
+/// Make member attribute accessor.
+#define URHO3D_MAKE_MEMBER_ATTRIBUTE_ACCESSOR(typeName, variable) URHO3D_MAKE_INLINE_ATTRIBUTE_ACCESSOR( \
+    value = self.variable, \
+    self.variable = value.Get<typeName>())
 
-/// Template implementation of the attribute accessor that uses free functions invoke helper class.
-template <typename T, typename U, typename Trait> class AttributeAccessorFreeImpl : public AttributeAccessor
-{
-public:
-    using GetFunctionPtr = typename Trait::ReturnType(*)(const T*);
-    using SetFunctionPtr = void(*)(T*, typename Trait::ParameterType);
+/// Make member attribute accessor with custom epilogue.
+#define URHO3D_MAKE_MEMBER_ATTRIBUTE_ACCESSOR_EX(typeName, variable, epilogue) URHO3D_MAKE_INLINE_ATTRIBUTE_ACCESSOR( \
+    value = self.variable, \
+    self.variable = value.Get<typeName>(); self.epilogue())
 
-    /// Construct with function pointers.
-    AttributeAccessorFreeImpl(GetFunctionPtr getFunction, SetFunctionPtr setFunction) :
-        getFunction_(getFunction),
-        setFunction_(setFunction)
-    {
-        assert(getFunction_);
-        assert(setFunction_);
-    }
+/// Make get/set attribute accessor.
+#define URHO3D_MAKE_GET_SET_ATTRIBUTE_ACCESSOR(getFunction, setFunction, typeName) URHO3D_MAKE_INLINE_ATTRIBUTE_ACCESSOR( \
+    value = self.getFunction(), \
+    self.setFunction(value.Get<typeName>()))
 
-    /// Invoke getter function.
-    virtual void Get(const Serializable* ptr, Variant& dest) const override
-    {
-        assert(ptr);
-        const T* classPtr = static_cast<const T*>(ptr);
-        dest = (*getFunction_)(classPtr);
-    }
+/// Make member enum attribute accessor
+#define URHO3D_MAKE_MEMBER_ENUM_ATTRIBUTE_ACCESSOR(variable) URHO3D_MAKE_INLINE_ATTRIBUTE_ACCESSOR( \
+    value = static_cast<int>(self.variable), \
+    self.variable = static_cast<decltype(self.variable)>(value.Get<int>()))
 
-    /// Invoke setter function.
-    virtual void Set(Serializable* ptr, const Variant& value) override
-    {
-        assert(ptr);
-        T* classPtr = static_cast<T*>(ptr);
-        (*setFunction_)(classPtr, value.Get<U>());
-    }
+/// Make member enum attribute accessor with custom epilogue.
+#define URHO3D_MAKE_MEMBER_ENUM_ATTRIBUTE_ACCESSOR_EX(variable, epilogue) URHO3D_MAKE_INLINE_ATTRIBUTE_ACCESSOR( \
+    value = static_cast<int>(self.variable), \
+    self.variable = static_cast<decltype(self.variable)>(value.Get<int>()); self.epilogue())
 
-    /// Class-specific pointer to getter function.
-    GetFunctionPtr getFunction_;
-    /// Class-specific pointer to setter function.
-    SetFunctionPtr setFunction_;
-};
+/// Make get/set enum attribute accessor.
+#define URHO3D_MAKE_GET_SET_ENUM_ATTRIBUTE_ACCESSOR(getFunction, setFunction, typeName) URHO3D_MAKE_INLINE_ATTRIBUTE_ACCESSOR( \
+    value = static_cast<int>(self.getFunction()), \
+    self.setFunction(static_cast<typeName>(value.Get<int>())))
 
 /// Attribute metadata.
 namespace AttributeMetadata
@@ -350,26 +280,46 @@ namespace AttributeMetadata
 // The following macros need to be used within a class member function such as ClassName::RegisterObject().
 // A variable called "context" needs to exist in the current scope and point to a valid Context object.
 
+/// Define an attribute with custom setter and getter functional objects.
+#define URHO3D_CUSTOM_ATTRIBUTE_IMPL(typeName, name, enumNames, defaultValue, mode, getFunction, setFunction) \
+    context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo( \
+        Urho3D::GetVariantType<typeName >(), name, MakeVariantAttributeAccessorImpl<ClassName>(getFunction, setFunction), enumNames, defaultValue, mode))
+/// Define an attribute with getter and setter inline code snippets.
+#define URHO3D_INLINE_ATTRIBUTE_IMPL(typeName, name, enumNames, defaultValue, mode, getSnippet, setSnippet) \
+    URHO3D_CUSTOM_ATTRIBUTE_IMPL(typeName, name, enumNames, defaultValue, mode, \
+        [](const ClassName& self, Variant& value) { getSnippet; }, \
+        [](ClassName& self, const Variant& value) { setSnippet; })
+
 /// Copy attributes from a base class.
 #define URHO3D_COPY_BASE_ATTRIBUTES(sourceClassName) context->CopyBaseAttributes<sourceClassName, ClassName>()
 /// Remove attribute by name.
 #define URHO3D_REMOVE_ATTRIBUTE(name) context->RemoveAttribute<ClassName>(name)
-/// Define an attribute that points to a memory offset in the object.
-#define URHO3D_ATTRIBUTE(name, typeName, variable, defaultValue, mode) context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo(Urho3D::GetVariantType<typeName >(), name, offsetof(ClassName, variable), defaultValue, mode))
-/// Define an attribute that points to a memory offset in the object, and uses zero-based enum values, which are mapped to names through an array of C string pointers.
-#define URHO3D_ENUM_ATTRIBUTE(name, variable, enumNames, defaultValue, mode) context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo(name, offsetof(ClassName, variable), enumNames, defaultValue, mode))
+/// Define an object member attribute.
+#define URHO3D_ATTRIBUTE(name, typeName, variable, defaultValue, mode) context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo( \
+    Urho3D::GetVariantType<typeName >(), name, URHO3D_MAKE_MEMBER_ATTRIBUTE_ACCESSOR(typeName, variable), nullptr, defaultValue, mode))
+/// Define an object member attribute. Custom epilogue member function is called when attribute set.
+#define URHO3D_ATTRIBUTE_EX(name, typeName, variable, epilogue, defaultValue, mode) context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo( \
+    Urho3D::GetVariantType<typeName >(), name, URHO3D_MAKE_MEMBER_ATTRIBUTE_ACCESSOR_EX(typeName, variable, epilogue), nullptr, defaultValue, mode))
 /// Define an attribute that uses get and set functions.
-#define URHO3D_ACCESSOR_ATTRIBUTE(name, getFunction, setFunction, typeName, defaultValue, mode) context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo(Urho3D::GetVariantType<typeName >(), name, new Urho3D::AttributeAccessorImpl<ClassName, typeName, Urho3D::AttributeTrait<typeName > >(&ClassName::getFunction, &ClassName::setFunction), defaultValue, mode))
-/// Define an attribute that uses get and set free functions.
-#define URHO3D_ACCESSOR_ATTRIBUTE_FREE(name, getFunction, setFunction, typeName, defaultValue, mode) context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo(Urho3D::GetVariantType<typeName >(), name, new Urho3D::AttributeAccessorFreeImpl<ClassName, typeName, Urho3D::AttributeTrait<typeName > >(getFunction, setFunction), defaultValue, mode))
-/// Define an attribute that uses get and set functions, and uses zero-based enum values, which are mapped to names through an array of C string pointers.
-#define URHO3D_ENUM_ACCESSOR_ATTRIBUTE(name, getFunction, setFunction, typeName, enumNames, defaultValue, mode) context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo(name, new Urho3D::EnumAttributeAccessorImpl<ClassName, typeName >(&ClassName::getFunction, &ClassName::setFunction), enumNames, defaultValue, mode))
-/// Define an attribute that uses get and set free functions, and uses zero-based enum values, which are mapped to names through an array of C string pointers.
-#define URHO3D_ENUM_ACCESSOR_ATTRIBUTE_FREE(name, getFunction, setFunction, typeName, enumNames, defaultValue, mode) context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo(name, new Urho3D::EnumAttributeAccessorFreeImpl<ClassName, typeName >(getFunction, setFunction), enumNames, defaultValue, mode))
-/// Define an attribute that uses get and set functions, where the get function returns by value, but the set function uses a reference.
-#define URHO3D_MIXED_ACCESSOR_ATTRIBUTE(name, getFunction, setFunction, typeName, defaultValue, mode) context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo(Urho3D::GetVariantType<typeName >(), name, new Urho3D::AttributeAccessorImpl<ClassName, typeName, Urho3D::MixedAttributeTrait<typeName > >(&ClassName::getFunction, &ClassName::setFunction), defaultValue, mode))
-/// Define an attribute that uses get and set free functions, where the get function returns by value, but the set function uses a reference.
-#define URHO3D_MIXED_ACCESSOR_ATTRIBUTE_FREE(name, getFunction, setFunction, typeName, defaultValue, mode) context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo(Urho3D::GetVariantType<typeName >(), name, new Urho3D::AttributeAccessorFreeImpl<ClassName, typeName, Urho3D::MixedAttributeTrait<typeName > >(getFunction, setFunction), defaultValue, mode))
+#define URHO3D_ACCESSOR_ATTRIBUTE(name, getFunction, setFunction, typeName, defaultValue, mode) context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo( \
+    Urho3D::GetVariantType<typeName >(), name, URHO3D_MAKE_GET_SET_ATTRIBUTE_ACCESSOR(getFunction, setFunction, typeName), nullptr, defaultValue, mode))
+/// Define an object member attribute. Zero-based enum values are mapped to names through an array of C string pointers.
+#define URHO3D_ENUM_ATTRIBUTE(name, variable, enumNames, defaultValue, mode) context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo( \
+    VAR_INT, name, URHO3D_MAKE_MEMBER_ENUM_ATTRIBUTE_ACCESSOR(variable), enumNames, static_cast<int>(defaultValue), mode))
+/// Define an object member attribute. Zero-based enum values are mapped to names through an array of C string pointers. Custom epilogue member function is called when attribute set.
+#define URHO3D_ENUM_ATTRIBUTE_EX(name, variable, epilogue, enumNames, defaultValue, mode) context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo( \
+    VAR_INT, name, URHO3D_MAKE_MEMBER_ENUM_ATTRIBUTE_ACCESSOR_EX(variable, epilogue), enumNames, static_cast<int>(defaultValue), mode))
+/// Define an attribute that uses get and set functions. Zero-based enum values are mapped to names through an array of C string pointers.
+#define URHO3D_ENUM_ACCESSOR_ATTRIBUTE(name, getFunction, setFunction, typeName, enumNames, defaultValue, mode) context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo( \
+    VAR_INT, name, URHO3D_MAKE_GET_SET_ENUM_ATTRIBUTE_ACCESSOR(getFunction, setFunction, typeName), enumNames, static_cast<int>(defaultValue), mode))
+/// Define an attribute with custom setter and getter.
+#define URHO3D_CUSTOM_ATTRIBUTE(name, typeName, getFunction, setFunction, defaultValue, mode) context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo( \
+    URHO3D_CUSTOM_ATTRIBUTE_IMPL(typeName, name, nullptr, defaultValue, mode, getFunction, setFunction)
+/// Define an enum attribute with custom setter and getter. Zero-based enum values are mapped to names through an array of C string pointers.
+#define URHO3D_CUSTOM_ENUM_ATTRIBUTE(name, getFunction, setFunction, enumNames, defaultValue, mode) context->RegisterAttribute<ClassName>(Urho3D::AttributeInfo( \
+    URHO3D_CUSTOM_ATTRIBUTE_IMPL(int, name, enumNames, defaultValue, mode, getFunction, setFunction)
+/// Deprecated. Use URHO3D_ACCESSOR_ATTRIBUTE instead.
+#define URHO3D_MIXED_ACCESSOR_ATTRIBUTE(name, getFunction, setFunction, typeName, defaultValue, mode) URHO3D_ACCESSOR_ATTRIBUTE(name, getFunction, setFunction, typeName, defaultValue, mode)
 /// Update the default value of an already registered attribute.
 #define URHO3D_UPDATE_ATTRIBUTE_DEFAULT_VALUE(name, defaultValue) context->UpdateAttributeDefaultValue<ClassName>(name, defaultValue)
 

+ 1 - 9
Source/Urho3D/Urho2D/Constraint2D.cpp

@@ -55,15 +55,7 @@ Constraint2D::~Constraint2D()
 void Constraint2D::RegisterObject(Context* context)
 {
     URHO3D_ACCESSOR_ATTRIBUTE("Collide Connected", GetCollideConnected, SetCollideConnected, bool, false, AM_DEFAULT);
-    URHO3D_ATTRIBUTE("Other Body NodeID", unsigned, otherBodyNodeID_, 0, AM_DEFAULT | AM_NODEID);
-}
-
-void Constraint2D::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
-{
-    Serializable::OnSetAttribute(attr, src);
-
-    if (!attr.accessor_ && attr.offset_ == offsetof(Constraint2D, otherBodyNodeID_))
-        otherBodyNodeIDDirty_ = true;
+    URHO3D_ATTRIBUTE_EX("Other Body NodeID", unsigned, otherBodyNodeID_, MarkOtherBodyNodeIDDirty, 0, AM_DEFAULT | AM_NODEID);
 }
 
 void Constraint2D::ApplyAttributes()

+ 2 - 2
Source/Urho3D/Urho2D/Constraint2D.h

@@ -45,8 +45,6 @@ public:
     /// Register object factory.
     static void RegisterObject(Context* context);
 
-    /// Handle attribute write access.
-    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src) override;
     /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
     virtual void ApplyAttributes() override;
     /// Handle enabled/disabled state change.
@@ -89,6 +87,8 @@ protected:
     void RecreateJoint();
     /// Initialize joint def.
     void InitializeJointDef(b2JointDef* jointDef);
+    /// Mark other body node ID dirty.
+    void MarkOtherBodyNodeIDDirty() { otherBodyNodeIDDirty_ = true; }
 
     /// Physics world.
     WeakPtr<PhysicsWorld2D> physicsWorld_;