在D中透明地同步对象中的任意属性

时间:2010-09-30 14:23:32

标签: reflection synchronization operator-overloading d

假设我有一个类似的课程:

class Gerbil{
    int id;
    float x,y,z;
}

让我们进一步说这是实时模拟的一部分,我有一个服务器/客户端设置,我在服务器端更改属性:

//...
gerbil.x = 9.0;
//...

现在我想将此更改发送给客户端以同步世界状态。然而,问题是我有大量的沙鼠,这些沙鼠也可能有很长的属性列表 - 不仅仅是x,y,z,如图所示。

我的问题是:我们是否可以透明地拦截这些属性赋值,并从中编译差异?

通过阅读D参考,我得到的印象opAssign可能是正确的,只有实际上没有如何使用它的例子? (D Ref. / opAssign)我想它看起来像这样,但我只是从臀部开始拍摄:

void opAssign(string name)(float val){ //Just guessing here
     if(name in floatProps){
         if(isServer){
             changedProps.push(this.id, name, val);
         }
         floatProps[name] = val;
     }
}

然后当我们这样做时会调用opAssign:

gerbil.x = 9.0; //Same as  gerbil.opAssign!("x")(9.0)  ??

除了可能错误的语法之外,这是朝着正确方向迈出的一步吗?什么是正确的语法?性能怎么样?它看起来可能很慢?是否有更快,更“直接”的方式?

我真正想避免的是精心设置,如:

gerbil.incProp(Prop.X, 9.0);

感谢您的时间。

2 个答案:

答案 0 :(得分:4)

基于Jonathan的回答,我在许多库中使用这样的代码:

public template property(string name, T) {
    mixin(`protected T _`~name~`;` ~
      propertyGetter!(name, T) ~ propertySetter!(name, T));
}
public template property(string name, T, T def)
{
   mixin(`protected T _`~name~` = `~def.stringof~`;` ~
      propertyGetter!(name, T) ~ propertySetter!(name, T));
}
template propertyGetter(string name, T) {
    enum propertyGetter = `public T `~name~`(){ return _`~name~`; }`;
}
template propertySetter(string name, T) {
    enum propertySetter = `public typeof(this) `~name~`(T value){ _`~name~` = value;`~
              `/* notify somebody that I've changed here */`~
              `return this; }`;
}

mixin字符串有点难看,但它们保留了正确的行数。

我向我的类添加属性,如下所示:

class Gerbil {
    mixin property!("id", int);
    mixin property!("x", float);
    mixin property!("y", float, 11.0);  // give this one a default value
}

如果您愿意,可以向propertySetter模板添加一些代码,该模板通知某些监视器已更改(传递id,属性名称和新值)。然后,监视器可以将此信息传输到服务器端的相应监视器,该监视器将找到具有正确id的对象并将指定的属性设置为新值。

答案 1 :(得分:3)

重载opAssign()就像在C ++中重载赋值运算符一样。它用于分配对象本身,而不是其成员之一。它真的不会做你想要的。我相信你最接近的是属性:

class Gerbil
{
public:

    @property int id()
    {
        return _id;
    }

    @property id(int newID)
    {
        //... Do whatever interception you want.
        _id = newID;
    }

    @property float x()
    {
        return _x;
    }

    @property x(float newX)
    {
        //... Do whatever interception you want.
        _x = newX;
    }

    @property float y()
    {
        return _y;
    }

    @property y(float newY)
    {
        //... Do whatever interception you want.
        _y = newY;
    }

    @property float z()
    {
        return _z;
    }

    @property z(float newZ)
    {
        //... Do whatever interception zou want.
        _z = newZ;
    }

private:

    int _id;
    float _x, _y, _z;
}

@property启用属性语法,以便您可以将该函数用作变量。所以,

//...
auto copyOfGerbilX = gerbil.x; //translates to gerbil.x()
gerbil.x = 9.0;  //translates to gerbile.x(9.0)
//...
即使x是函数而不是变量,

现在也是合法的。您可以在函数中插入所需的任何特殊处理代码。并且因为用于访问变量的语法就像它们是公共成员变量一样,您可以自由地重构代码,以便在类定义中将它们作为属性或公共成员变量进行切换(假设您没有尝试过)就像拿一个地址一样,因为这并不意味着变量与函数相同。

但是,如果您正在寻找的是一种不必自己完成所有这些功能的通用方法,那么就没有直接构造它。我相信你可以使用编译时反射和字符串mixins或模板mixin来查看变量列表,然后为你生成每个属性函数。但是,对于每个函数,额外的处理代码必须基本相同,并且您必须小心生成的代码才是您想要的。我确信它是可行的,但我必须解决这个问题才能产生可​​行的解决方案。

要生成此类代码,您需要查看__traitsstd.traits以了解编译时反射以及template mixinsstring mixins用于代码生成。我会考虑生成这样的代码,而不是手工编写代码。它应该是非常可行的,但它并不一定容易,调试它可能很有趣,如果你需要相当不错的D模板和mixins才能做到正确。

但实质上,您正在寻找的是使用@property函数,以便您可以添加处理程序代码,然后可能使用编译时反射和mixins为您生成代码,但生成这样的代码是一种相当先进的技术,所以你可能要等到你对D更有经验之后再尝试。