65.9K
CodeProject 正在变化。 阅读更多。
Home

Kigs框架介绍 (2/8) - CoreModifiable

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.61/5 (8投票s)

2020年1月31日

MIT

6分钟阅读

viewsIcon

31513

Kigs 框架是一个多用途、跨平台、免费开源的 C++ 框架。本文将重点介绍该框架的主要基类:CoreModifiable 类。

Kigs Logo

目录

引言

本系列的第一篇文章中,我们概述了 Kigs 框架。本文将重点介绍该框架的主要基类:CoreModifiable 类。

类类型和名称

所有高级类都必须继承 CoreModifiable 或另一个继承自 CoreModifiable 的类,以便访问实例工厂、引用计数、序列化、属性等。

这是一个类声明的基本示例

// this class inherits CoreModifiable directly
class SimpleSampleClass : public CoreModifiable
{
public:
    // helper Macro to setup everything needed
    DECLARE_CLASS_INFO(SimpleSampleClass, CoreModifiable, Application);
    // helper Macro to declare an inline constructor (empty here)
    DECLARE_INLINE_CONSTRUCTOR(SimpleSampleClass) {}
protected:
    // override initialization method called explicitly with "Init()" 
    // or implicitly when importing from XML for example
    void InitModifiable() override;
};

可以使用 DECLARE_ABSTRACT_CLASS_INFO 来创建不能直接实例化的基类,而不是使用 DECLARE_CLASS_INFO。参数是类名、父类名和模块名。模块名参数只是一个辅助参数。

可以使用 DECLARE_CONSTRUCTOR 结合 IMPLEMENT_CONSTRUCTOR(可能在 .cpp 文件中)来代替 DECLARE_INLINE_CONSTRUCTOR

然后 .cpp 文件将如下所示

// Helper macro, setup implementation
IMPLEMENT_CLASS_INFO(SimpleSampleClass)
// override InitModifiable method
void SimpleSampleClass::InitModifiable()
{
    // call parent InitModifiable method
    ParentClassType::InitModifiable();
    // check if parent initialization was OK
    if (_isInit)
    {
        // initialize things
    }
}

`ParentClassType` 是一个辅助 typedef,用于调用父类的方法。

`_isInit` 也是一个辅助宏,用于测试类是否已正确初始化...

然后,在模块或应用程序的初始化中,必须声明(注册到工厂)类

DECLARE_FULL_CLASS_INFO(KigsCore::Instance(), 
                        SimpleSampleClass, SimpleSampleClass, Application);

参数是:当前的 KigsCore 单例实例、要实例化的类名、赋予实例工厂的名称以及模块名。

可以使用 getName() 方法获取实例的名称

std::string name=simpleclass1->getName();

实例类型

可以使用 `isSubType` 方法测试实例类型

// test if a cast can be done
if(simpleclass1->isSubType("SimpleSampleClass"))
{
       SimpleSampleClass* castSimpleClass=simpleclass1->as<SimpleSampleClass>();
}

引用计数和实例树

这样就可以从实例工厂请求 SimpleSampleClass 类的实例了

// ask for a SimpleSampleClassBase instance named simpleclass1
CMSP simpleclass1 = KigsCore::GetInstanceOf("simpleclass1", "SimpleSampleClass");

创建时,实例的引用计数为 1。

  • `addItem` 将添加实例的引用计数加 1。
  • `removeItem` 将移除实例的引用计数减 1。

智能指针

SmartPointer 类用于轻松管理引用计数。CMSP 是继承 SmartPointer<CoreModifiable> 的一个类。
SPSmartPointer 是等效的。

{
    // sp is a smart pointer on an instance of SimpleSampleClass named "simpleclass1"
    SmartPointer<SimpleSampleClassBase> sp=KigsCore::GetInstanceOf
                                           ("simpleclass1", "SimpleSampleClass");
}   // exiting the block scope will automatically delete the instance "simpleclass1"

可以利用 SharedFromThis 方法将指向继承 CoreModifiable 的实例的指针包装到 SmartPointer

// smartpointer with no ref count increase
SmartPointer<SimpleSampleClassBase> sp=instance1->SharedFromThis();

运算符 `->` 用于在 SmartPointer 中访问实例的功能

float test;
sp->getValue("test",test);

通过 get() 方法可以获取实例指针本身

SimpleSampleClassBase* simpleinstance = sp.get();

实例树

继承 CoreModifiable 类的实例会维护其父节点和子节点实例的列表(非继承意义上的)。因此,可以构建实例树。

// ask for a SimpleSampleClassBase instance named simpleclass1
CMSP simpleclass1 = KigsCore::GetInstanceOf("simpleclass1", "SimpleSampleClass");
// Initialize class
simpleclass1->Init();

// ask for two other instances
CMSP simpleclass2 = KigsCore::GetInstanceOf("simpleclass2", "SimpleSampleClass");
simpleclass2->Init();
CMSP simpleclass3 = KigsCore::GetInstanceOf("simpleclass3", "SimpleSampleClass");
simpleclass3->Init();

// and add simpleclass2 and simpleclass3 to simpleclass1
simpleclass1->addItem(simpleclass2); // simpleclass2 count ref is now 2
simpleclass1->addItem(simpleclass3); // simpleclass3 count ref is now 2

// add simpleclass1 to this
addItem(simpleclass1);

然后可以使用 `GetInstanceByPath` 方法轻松地在树中检索实例

// retrieve instances in the instances tree using "path"
CMSP simpleclass2 = 
     GetInstanceByPath("SimpleSampleClass:simpleclass1/simpleclass2");
CMSP simpleclass1 = 
     simpleclass2->GetInstanceByPath("/Sample2/SimpleSampleClass:simpleclass1");
CMSP simpleclass3 = simpleclass2->GetInstanceByPath("../simpleclass3");
simpleclass3 = GetInstanceByPath("*/simpleclass3");
  • 如果路径以 `/` 开头,则从根父节点(没有父节点的父节点)开始搜索。
  • 如果路径包含 `../`,则从父实例继续搜索。
  • 如果路径包含 `*/`,则搜索路径中该级别下的所有子实例。

按名称或类型搜索实例

可以在子节点中进行搜索

// retrieve all instances named "simpleclass1" in sons list
std::vector<CMSP> instances;
GetSonInstancesByName("CoreModifiable", "simpleclass1",instances);
printf("GetSonInstancesByName result :\n");
for (auto i : instances)
{
    printf("found instance named : %s\n", i->getName().c_str());
}
instances.clear();
// retrieve all instances named "simpleclass2" recursively in sons list
GetSonInstancesByName("CoreModifiable", "simpleclass2", instances,true);
printf("Recursive GetSonInstancesByName result :\n");
for (auto i : instances)
{
   printf("found instance named : %s\n", i->getName().c_str());
}
instances.clear();
// retrieve all instances of type CoreModifiable in sons list
GetSonInstancesByType("CoreModifiable", instances);
printf("GetSonInstancesByType result :\n");
for (auto i : instances)
{
   printf("found instance named : %s\n", i->getName().c_str());
}
instances.clear();
// retrieve all instances of type SimpleSampleClass recursively in sons list
GetSonInstancesByType("SimpleSampleClass", instances,true);
printf("Recursive GetSonInstancesByType result :\n");
for (auto i : instances)
{
    printf("found instance named : %s\n", i->getName().c_str());
}

或在全局范围内搜索

// retrieve all instances named "simpleclass1" at global scope
instances = GetInstancesByName("CoreModifiable", "simpleclass1");
printf("GetInstancesByName result :\n");
for (auto i : instances)
{
    printf("found instance named : %s\n", i->getName().c_str());
}
instances.clear();
// retrieve all instances of type SimpleSampleClass at global scope
instances = GetInstances("SimpleSampleClass");
printf("GetInstances result :\n");
for (auto i : instances)
{
    printf("found instance named : %s\n", i->getName().c_str());
}

属性

CoreModifiable 属性将在下一篇文章中详细介绍。

这里只是一个简要概述。

声明

CoreModifiable 可以拥有“编译时”属性,这些属性可以在类中声明,如下所示

class SimpleSampleClass : public CoreModifiable
{
public:
    DECLARE_CLASS_INFO(SimpleSampleClass, CoreModifiable, Application);
    DECLARE_INLINE_CONSTRUCTOR(SimpleSampleClass) {}
protected:
    // unsigned int attribute "Version"
    maUInt      m_version = BASE_ATTRIBUTE(Version, 0);
    // string attribute "Description"
    maString    m_desc = BASE_ATTRIBUTE(Description, "");
};

另一种声明多个属性(映射到成员变量)的方法是使用 WRAP_ATTRIBUTES 宏

class SimpleSampleClass : public CoreModifiable
{
public:
	DECLARE_CLASS_INFO(SimpleSampleClass, CoreModifiable, Application);
	DECLARE_INLINE_CONSTRUCTOR(SimpleSampleClass) {}
protected:
	// unsigned int attribute "Version"
	u32            mVersion = 0;
        // string attribute "Description"
	std::string    mDescription = "";

    WRAP_ATTRIBUTES(mVersion,mDescription);
};

WRAP_ATTRIBUTES 宏会移除属性的第一个字符(此处为 'm' 前缀),以定义属性的名称(由 getter/setter、序列化等使用)。

访问

然后在 SimpleSampleClass 的实例上,可以使用 getValue / setValue 方法访问属性

simpleclass1->setValue("Version",5);
std::string desc;
simpleclass1->getValue("Description",desc);

动态属性

还可以动态添加或删除属性

simpleclass1->AddDynamicAttribute(ATTRIBUTE_TYPE::BOOL, "isON");
simpleclass1->RemoveDynamicAttribute("isON");

实例的所有属性在实例被序列化时会被序列化到 XML 文件中,并从 XML 文件中读取。

虚方法

初始化/反初始化

有两个有用的方法可以重载以管理实例的初始化

// Init the modifiable and set the _isInit flag if OK. 
// Need to call ParentClassType::InitModifiable() when overriding !
virtual    void InitModifiable();

// Called when init has failed. 
// Need to call ParentClassType::UninitModifiable() when overriding !
virtual    void UninitModifiable();

InitModifiableInit() 方法调用。

这是一个重载 InitModifiable 的经典方法

// InitModifiable overload sample code
void SimpleSampleClass::InitModifiable()
{
    // check for multiple init
    if (_isInit)
    {
        // init was already done, just return
        return;
    }
    // call parent class InitModifiable
    ParentClassType::InitModifiable();
    // if everything is OK, do this initialization
    if (_isInit)
    {
        bool somethingWentWrong=false;
        // here is some initialization code for this
        ...
        // check if something went wrong
        if(somethingWentWrong)
        {
            // call Uninit
            UnInit();
            return;
        }
    }
}

当然,添加一个虚析构函数来释放类分配的所有内存或添加一些特定的销毁代码也是一个好主意。

添加/删除子节点或父节点

如果当一个实例被添加到另一个实例时需要特殊的行为,例如检查是否添加了特定类型的实例到另一个实例,则可以重载以下方法

// add the given parent to list. Need to call ParentClassType::addUser(...) when overriding !
virtual void addUser(CoreModifiable* user);

// remove the given parent from list. 
// Need to call ParentClassType::removeUser(...) when overriding !
virtual void removeUser(CoreModifiable* user);
    
// add a son. Need to call ParentClassType::addItem(...) when overriding !
virtual bool addItem(const CMSP& item, ItemPosition pos = Last);
    
// remove a son. Need to call ParentClassType::removeItem(...) when overriding !
virtual bool removeItem(const CMSP& item);

更新

// Update method. Call to ParentClassType::Update is not necessary when overriding
virtual void Update(const Timer&  timer, void* addParam);

如果实例被添加到自动更新中,其 Update 方法将在每个应用程序循环中被调用

// add instanceToAutoUpdate to application auto update system
KigsCore::GetCoreApplication()->AddAutoUpdate(instanceToAutoUpdate);

当然,当实例被销毁或通过调用 RemoveAutoUpdate 手动移除时,它会自动从自动更新中移除

// remove instanceToAutoUpdate from application auto update system
KigsCore::GetCoreApplication()->RemoveAutoUpdate(instanceToAutoUpdate);

Update 方法也可以通过 CallUpdate 方法或 RecursiveUpdate 方法手动调用。

属性设置通知

CoreModifiable 属性可以在它们发生变化时(通过“setValue”访问时)通知它们的宿主,并使用其 ID 调用“NotifyUpdate”方法。

// Called when an attribute that has its notification level set to Owner is modified. 
// Need to call ParentClassType::NotifyUpdate(...) when overriding !
virtual void NotifyUpdate(const u32 labelid);

方法

更快捷的方式(但更受限制)

CoreModifiable 方法的详细规格将在下一篇文章中介绍。

这里只是一个简要概述。

CoreModifiable 类允许定义可以通过名称(string)调用的方法,这些方法具有固定的原型

bool    methodName(CoreModifiable* sender,std::vector<CoreModifiableAttribute*>& params,
                   void* privateParams);

提供了辅助宏来简化操作:DECLARE_METHOD(methodName)DECLARE_VIRTUAL_METHOD(methodName)DECLARE_PURE_VIRTUAL_METHOD(methodName)

DECLARE_OVERRIDE_METHOD(methodname)DEFINE_METHOD(classtype,methodName)

在类声明中

// method that add 1 to the given parameter
DECLARE_METHOD(incrementParam);

然后在 cpp 文件中,定义类

DEFINE_METHOD(SimpleSampleBaseClass, incrementParam)
{
    float val=0;
    // access first param (we could check for param name here)
    if (params[0]->getValue(val,this)) // if first param value can be get as float
    {
        // increment value
        params[0]->setValue(val + 1.0f,this);
    }
    return true;
}

CoreModifiable 方法列表必须在类头文件中使用 COREMODIFIABLE_METHODS(method1,method2...) 辅助宏声明。

// declare all CoreModifiable methods
COREMODIFIABLE_METHODS(incrementParam);

然后可以在 CoreModifiable 实例指针上调用该方法(无需知道确切的实例类型)

CoreModifiableAttribute* param = item->getAttribute("CountWhenAdded");
if (param)
{
    // call incrementParam method
    std::vector<CoreModifiableAttribute*> sendParams;
    sendParams.push_back(param);
    item->CallMethod("incrementParam", sendParams);
    std::cout << item->getName() << " parameter CountWhenAdded = " 
    << item->getValue<int>("CountWhenAdded") << std::endl;
}

更简单的方式

可以使用 WRAP_METHODS 辅助宏按名称访问任何 CoreModifiable 成员方法

// simple method
void    printMessage();
// ask possible call by name
WRAP_METHODS(printMessage);

WRAP_METHODS 可以接受多个逗号分隔的参数。

然后可以使用 SimpleCall 方法调用该方法

simpleclass1->SimpleCall("printMessage");

对于带有参数和返回值的函数,SimpleCall 方法的用法如下

int returnedValue = instance->SimpleCall<int>("DoSomethingFun",42,"yes");

聚合

可以聚合两个或多个不同类型的 CoreModifiable 实例。

例如,定义一个管理 ColorShininess 的材质类

class SimpleMaterialClass : public CoreModifiable
{
public:
    DECLARE_CLASS_INFO(SimpleMaterialClass, CoreModifiable, Application);
    DECLARE_INLINE_CONSTRUCTOR(SimpleMaterialClass) 
    { std::cout << "SimpleMaterialClass constructor" << std::endl; }

protected:

    // RGB color
    maVect3DF    m_Color = BASE_ATTRIBUTE(Color,1.0,0.0,0.0);
    // shininess
    maFloat        m_Shininess = BASE_ATTRIBUTE(Shininess, 0.5);
};

如果材质实例与 SimpleSampleClass 的实例聚合

// create an instance of SimpleMaterialClass
CMSP material= KigsCore::GetInstanceOf("material", "SimpleMaterialClass");
// manage simpleclass3 and material as one unique object
simpleclass3->aggregateWith(material);

然后可以直接在 simpleclass3 上获取或设置“Shininess”或“Color”值

float shine=0.0f;
simpleclass3->getValue("Shininess", shine);
std::cout << simpleclass3->getName() << " has Shininess value of " 
<< shine << " thanks to aggregate with SimpleMaterialClass " << std::endl;

反之亦然,可以从“material”实例中检索 SimpleSampleClass 的值。并且同样可以使用这种方式调用 CoreModifiable 方法。

序列化

导出

导出仅在项目以 StaticDebugStaticReleaseTools 配置构建时可用。在 StaticRelease 中,KigsID(用作实例名称的映射键...)经过优化,用于构造它们的 std::string 已不再保留。

// only if export is available
#ifdef KIGS_TOOLS
    // export Sample1 and its sons in Sample1.xml file
    CoreModifiable::Export("Sample1.xml", simpleclass.get(), true);
#endif // KIGS_TOOLS

相应的 Sample1.xml 文件将如下所示

<?xml version="1.0" encoding="utf-8"?>
<Inst N="simpleclass" T="SimpleSampleClass">
    <Inst N="localtimer" T="Timer">
        <Attr N="Time" V="0.001191"/>
        <Attr T="float" N="floatValue" V="12.000000" Dyn="yes"/>
    </Inst>
</Inst>

导入

Import 始终可用(在所有构建配置中)。

// import instances from file "Sample1.xml"
CMSP imported=CoreModifiable::Import("Sample1.xml");

在 GitHub 上的 Sample2 项目中查找本文的所有示例代码(浏览代码)。

本系列已发布内容:

  1. Kigs框架介绍 (1/8) - 概述
  2. Kigs框架介绍 (2/8) - CoreModifiable
  3. Kigs框架介绍 (3/8) - 属性
  4. Kigs框架介绍 (4/8) - 方法
  5. Kigs框架介绍 (5/8) - CoreItem
  6. Kigs框架介绍 (6/8) - 信号,槽,通知
  7. Kigs框架介绍 (7/8) - Lua绑定
  8. Kigs框架介绍 (8/8) - 数据驱动应用程序

历史

  • 2020 年 1 月 31 日:初始版本
  • 2020 年 2 月 2 日:在 addItem / removeItem 原型中进行的小修复
  • 2020 年 2 月 7 日:添加了系列中最新发布的文章
  • 2020年2月14日:将文章(4/8)添加到本系列
  • 2020 年 2 月 21 日:文章 (5/8) 添加到系列中,并在代码中进行了小错误修复
  • 2020年3月2日:将文章(6/8)添加到本系列
  • 2020 年 3 月 19 日:文章 (7/8) 添加到系列中,并修复了示例中的 GetInstances 方法原型
  • 2020年6月17日:添加了该系列的最后一篇文章
  • 2023 年 3 月 1 日:框架重构后更新
© . All rights reserved.