开源服务器框架NoahFrame分享:第三章 属性管理

发表于2017-10-19
评论2 6.2k浏览
NF(https://github.com/ketoo/NoahGameFrame)全称为 NoahFrame/NoahGameFrame。
交流QQ群:341159815


NF最早为客户端设计,后来随着时代的变化,而为自己又转为服务器开发,故在吸收了众多引擎的优点后(包含Ogre的插件模式&模块化管理机制,Bigworld的数据管理&配置机制,类似MYGUI的接口层次设计),经过多年演化和实践,变成了一套游戏开发J解决方案。方案中包含开源的服务器架构,网络库(站在libevent的肩膀上),和unity3d的demo源码。现在NF已经在多个公司的多个项目中使用,其中包含知名产品 《全民无双》。


关键词


NoahGameFrame/NoahFrame/NF
集群/负载均衡/分布式
网关服务器 GateServer 心跳 多线程/线程池 开源网络框架/模型
一致性hash算法/ConsistentHash
游戏开发中的设计模式/数据结构
Socket Nagle/粘包/开源游戏服务器/ Game Server





NF当初设计的目的,是想拥有一种方法,可以统一管理对象以及数据,并使用统一的接口而又不需要随着业务增加而扩充新的接口和数据字段,这些体验源自ogre对于node的抽象,因后来参考了Bigworld的数据管理方式,设计了抽象的NFIClass,NFIObject 和 NFIElementModule,它们是NF中最重要的3个类,是支撑NF新概念面向数据编程的重要基石。

NFIClass类,顾名思义,就是NF Class。
主要用户声明数据结构体,类似程序语言(C , C# JAVA等)编程中使用class Dog, class Cat(然而实际又不一样,虽然实际上他代替了变成语言的class,struct等部分功能)。传统情况下,大家对于对象增加属性,一直都是类似结构体加入各种字段来实现,比如一个Dog先有HP,则是如下:

  1. class Dog
  2. {
  3.     int nHP;
  4. };
复制代码



如果想要加入MP;则需要修改成如下:

  1. class Dog
  2. {
  3.     int nHP;
  4.     int nMP;
  5. };
复制代码



然后又还需要增加名字,配置ID,等级,则需要修改如下


  1. class Dog
  2. {
  3.     int nHP;
  4.     int nMP;
  5.     string strName;
  6.     string strID;
  7.     int nLevel;
  8. };
复制代码


and so on................

然后随着业务的增加,还需要增加死亡是否能复活,头像,客户端模型包等。。。。那么我们又要继续修改,如果哪天因为Dog, Cat太像了,然后还需要抽象共同的属性结构体,重构接口。。。陷入无休止的基础系统维护当中!然后项目没完没了,然后项目未上线便开始处于维护状态,然后人心涣散项目跪掉。虽然话语夸张,但也不是没有公司这样死过,就看谁死的轰轰烈烈而已。
而在NF架构中,则完全没有这些烦恼,一切均统一化配置管理,不需增加额外代码。

下面就从NF的设计源头开始说说,如何改善这种局面。
正常来说,任何抽象对象,我们主要关注他俩样,属性和行为。这里先来说属性。通常来说,属性都可以抽象成通用的数据对象(想看行为如何抽象成通用的接口层,请继续看后面几章)。

我们分析,HP, MP, Name这几个其实都是成员属性,如果有一个通用的属性来容纳他们,这个属性叫 Property,则Dog可以抽象成如下:

  1. class Dog
  2. {
  3.         Property nHP;
  4.         Property nMP;
  5.         Property strName;
  6.         Property strID;
  7.         Property nLevel;
  8. };
复制代码



此时,我们的精力就在于如何抽象一个通用的属性Property.我们知道,Property是肯定可以抽象出来的,因为在目前已知道的数据类型中,描述一个对象的基础属性, 也就int, double, string, vector2, vector3, GUID 等几个类型,而string又可以容纳一切数据类型,因此针对这些类型的数据,我们可以抽象出统一的属性接口,Property则长成如下样貌:


于是Property应该长这样:
  1. class Property
  2. {
  3.     public bool SetInt(const NFINT64 value);
  4.     public bool SetFloat(const double value);
  5.     public bool SetString(const std::string& value);
  6.     public bool SetObject(const NFGUID& value);
  7.     public bool SetVector2(const NFVector2& value);
  8.     public bool SetVector3(const NFVector3& value);

  9.     public NFINT64 GetInt() const;
  10.     public double GetFloat() const;
  11.     public const std::string& GetString() const;
  12.     public const NFGUID& GetObject() const;
  13.     public const NFVector2& GetVector2() const;
  14.     public const NFVector3& GetVector3() const;
  15. }
复制代码


到此,我们可以很方便的避免了数据操作的细节,全部抽象成合适的接口(请参考https://github.com/ketoo/NoahGameFrame/blob/develop/NFComm/NFCore/NFCProperty.h

虽然都抽象成统一的属性了,如果每一次新业务,都要增加这些,岂不是很麻烦? 因此当时想到了,增加一个管理器吧,其实这个很简单,重要的是各种简单的内容,组合起来就能创造出一些有趣的新内容,于是Dog类变成了如下:

  1. class Dog
  2. {
  3.     map<string, Property> mPropertyMap;
  4. }
复制代码



然后我们又想根据名字,从容器中获取属性,然后修改属性,或者添加新的属性,这样每次新业务,我们就不会再添加新的字段,很方便,不是吗? 但是这个远达不到我们想要的解决方案那样方便,因为有人担心,那么name哪来统一管理,各种设置接口如何设计,然后如果有多种类似的结构如何处理?不要担心,且继续看,我们为了避免后续增加新的模块,我们先把属性管理器拆分出来,则为如下:


  1. class PropertyManager
  2. {
  3.     public bool SetInt(const std::string& strPropertyName, const NFINT64 value);
  4.     public bool SetFloat(const std::string& strPropertyName, const double value);
  5.     public bool SetString(const std::string& strPropertyName, const std::string& value);
  6.     public bool SetObject(const std::string& strPropertyName, const NFGUID& value);
  7.     public bool SetVector2(const std::string& strPropertyName, const NFVector2& value);
  8.     public bool SetVector3(const std::string& strPropertyName, const NFVector3& value);

  9.     public NFINT64 GetInt(const std::string& strPropertyName) const;
  10.     public double GetFloat(const std::string& strPropertyName) const;
  11.     public const std::string& GetString(const std::string& strPropertyName) const;
  12.     public const NFGUID& GetObject(const std::string& strPropertyName) const;
  13.     public const NFVector2& GetVector2(const std::string& strPropertyName) const;
  14.     public const NFVector3& GetVector3(const std::string& strPropertyName) const;
  15. }
复制代码
  1. class Dog
  2. {
  3.     PropertyManager mxPropertyManager;
  4. };
复制代码

到现在,我们增加通用的管理机器,方便各种添加,删除,和操作接口,正是这些基础接口,可以节省开发中50%以上的时间,而关键在于,稳定,不出错,也省却了大部分调试时间。
正式代码请参考:https://github.com/ketoo/NoahGameFrame/blob/develop/NFComm/NFCore/NFCPropertyManager.h




那么对于现在这个NFClass类来说,他可以叫NPC,可以叫Dog,可以叫Cat,都没有任何关系,因为他已经支持动态的各种属性添加等事项.那么抽象的数据容器有了,接下来,我们如何方便的,可以在excel,或者xml中,把数据能自动的导入呢,虽然中途需要各种代码要码,但是终究是可以实现的。

首先,我们去掉了在程序语言中增加各种字段,那么当然避免不了的,我们的结构描述文本,肯定会有类似的机制来保证可以添加新字段,暂且用XML来表达吧,如下:

  1. <XML>
  2.      <Propertys>
  3.           <Property Id="HP" Type="int"  />
  4.           <Property Id="MP" Type="string" />
  5.           <Property Id="Name" Type="int" />
  6.           <Property Id="ID" Type="string" />
  7.           <Property Id="Level" Type="int" />
  8.      </Propertys>
  9. </XML>
复制代码



还记得之前我们说的 PropertyManager类中的map<string, Property> mPropertyMap 的Key哪来吗?就是上面的Id字段,HP,MP,NAME等。
自此,我们可以通过程序逻辑,把这些内容一一映射到内存中,让一个NFClass类拥有任何的属性(多维数据用record,再复杂的属性,都是由基础属性组成的,别较真什么嵌套,抽象不出来数据请自己补喝脑白金)。
真实代码请参考:https://github.com/ketoo/NoahGameFrame/blob/develop/NFComm/NFConfigPlugin/NFCClassModule.h


自此,我们可以很方便的通过xml来描述一个抽象的对象,给一个抽象的对象添加各种类别的数据,这个是数据驱动的基石。

NF项目为开源的分布式服务器解决方案,其中包含了网络库,actor库,以及数据驱动等新技术,能大幅提升开发效率节省开发周期以及提高程序的稳定性。
如感觉对您有帮助,请给与star,同时也邀请广大同行参与开发和维护,作者QQ 342006,交流QQ群 341159815。
欢迎转载,转载请注明来源,本文版权归作者所有!

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引