#include <iostream>
//下面说的是在服务器差异脏标记,不管在想客户端还是在数据库中经常用到的一种方法
//由于其实直接拿为进行操作,所以速度是非常快的,也许刚开始不太好理解,但只要熟悉之后就非常好用
//对于包而言如果Update包只改变一项你把整个结构体都发过去会大大增加网络带快,现在基本都走差异更新
enum UpdateAtt
{
eUpdate1,
eUpdate2,
eUpdate3,
eUpdate4,
eUpdate5,
eUpdate6,
eUpdate7,
eUpdateMax
};
#define Update_Att_Max_Num ((eUpdateMax >> 3) + 1)
/**
*@brief 脏标记类,关键理解此类
*/
struct UserAttrFlag
{
bool IsUpdated(int nAttr) const
{
if(m_Flags[nAttr/8] & (1<<(nAttr%8)))
return true;
return false;
}
void SetBitFlag(int nAttr)
{
m_Flags[nAttr/8] |= (1<<(nAttr%8));
}
void ClearBitFlag()
{
memset(m_Flags, 0, Update_Att_Max_Num);
}
char m_Flags[Update_Att_Max_Num];
};
struct UserReg
{
unsigned char m_AttrOffset[eUpdateMax]; ///< 角色属性偏移量,距离首地址的偏移位置
unsigned char m_AttrSize[eUpdateMax]; ///< 角色属性大小
};
struct Update
{
private://需要将此类的所有数据成员设置成私有,针对没个成员提供set/get接口
int nUpdate1;
int nUpdate2;
int nUpdate3;
int nUpdate4;
int nUpdate5;
int nUpdate6;
int nUpdate7;
public:
void SetUpdate1(int nData)
{
if(nUpdate1 != nData)
{
nUpdate1 = nData;
SetDBEnumAttrDirty(eUpdate1);
}
}
void SetUpdate7(int nData)
{
if(nUpdate7 != nData)
{
nUpdate7 = nData;
SetDBEnumAttrDirty(eUpdate7);
}
}
int GetUpdate1(){return nUpdate1;}
int GetUpdate7(){return nUpdate7;}
Update()
{
RegisterAttributes();
memset(&m_attrFlag, 0, sizeof(m_attrFlag));
}
void RegisterAttributes();//注册标记
void _RegAttr(UpdateAtt eAtt, int nOffset, int nSize)
{
/// 角色属性偏移量,距离首地址的偏移位置,之前看到天龙代码直接存的是变量的地址,这种只适合单线程使用,多线程会发生内存拷贝
/// 所以用偏移量更合适
m_attReg.m_AttrOffset[eAtt] = nOffset;
m_attReg.m_AttrSize[eAtt] = nSize;
}
void SetDBEnumAttrDirty(UpdateAtt eUpdateAttr)
{
m_attrFlag.SetBitFlag(eUpdateAttr);
}
UserAttrFlag m_attrFlag;//属相脏标记
UserReg m_attReg;//属相注册
public:
const UserAttrFlag* GetAttrFlag() const {return &m_attrFlag;}
const UserReg* GetAttrReg() const {return &m_attReg;}
};
void Update::RegisterAttributes()
{
#define REG_DB_ATTR(type,var) _RegAttr(type, ((int)&(var) - (int)this), sizeof(var));
REG_DB_ATTR(eUpdate1, nUpdate1)
REG_DB_ATTR(eUpdate2, nUpdate2)
REG_DB_ATTR(eUpdate3, nUpdate3)
REG_DB_ATTR(eUpdate4, nUpdate4)
REG_DB_ATTR(eUpdate5, nUpdate5)
REG_DB_ATTR(eUpdate6, nUpdate6)
REG_DB_ATTR(eUpdate7, nUpdate7)
}
int main()
{
Update update;
update.SetUpdate1(12);
update.SetUpdate7(120);
char* buff = new char[1024];//拼buff
//拼包操作
int nOffset = 0;
for (int i = 0; i < eUpdateMax; i++)
{
if (update.GetAttrFlag()->IsUpdated(i))//判断其是否为脏
{
memcpy(buff + nOffset, &i, sizeof(int));
nOffset += sizeof(int);
memcpy(buff + nOffset, &update + update.GetAttrReg()->m_AttrOffset[i], update.GetAttrReg()->m_AttrSize[i]);
nOffset += update.GetAttrReg()->m_AttrSize[i];
}
}
getchar();
return 0;
}
//现在简单解释一下脏标记的处理
/*
#define Update_Att_Max_Num ((eUpdateMax >> 3) + 1)
定义的宏,根据此宏就知道定义多大的char数组,他是吧将8个枚举作为一个char,而一个char正好是8位,+1是为了不足8位,也为其分配一个char
void SetBitFlag(int nAttr)
{
m_Flags[nAttr/8] |= (1<<(nAttr%8));
}
m_Flags[nAttr/8]是定位其在那个char上,如0,肯定是char[0],如果是9就是char[1]
(1<<(nAttr%8))表示其在某个char的哪个位上,如9,(1<<(nAttr%8))就是第二个char[1]&10,这样就正确的设置到对应的位上了
其实将char[..]看出1011111这样位,而此种方法方法就是能更方便的操作,c++没有对应的位的类型,stl里面好像有个bitset,但复杂类型在某些场合不适用的
|=或操作正好将其设置成1
bool IsUpdated(int nAttr) const
{
if(m_Flags[nAttr/8] & (1<<(nAttr%8)))
return true;
return false;
}
上面那个理解了,这个就好理解了
m_Flags[nAttr/8]也是定位哪个char,(1<<(nAttr%8)))也是对应char那个位置
&操作获取对应为是0,还是1
对于其他的大小和偏移量,为了打包更好获取其地址和大小,更快的打包而设置的,好理解
*/
/*
额外补充
对于DBSvr怎么解析,是根绝实现定义好的function,然后根据类型直接调用,那个类型里面关于数据类型都是写死的,个人觉得不是太好
客户端我问了,他是根绝枚举,硬猜是哪个包,已醉
*/