This article will focus on the main base class of the framework: the CoreModifiable class. It looks at Reference Counting and Instances Tree, Attributes, Virtual Methods, Aggregates, and Serialization.

Table of Contents
In the first article of this series, we have offered a general overview of the Kigs framework. This article will focus on the main base class of the framework: the CoreModifiable class.
All high level classes have to inherit CoreModifiable, or another CoreModifiable inherited class, in order to have access to instance factory, reference counting, serialization, attributes...
Here is a basic example of class declaration:
class SimpleSampleClass : public CoreModifiable
{
public:
DECLARE_CLASS_INFO(SimpleSampleClass, CoreModifiable, Application);
DECLARE_INLINE_CONSTRUCTOR(SimpleSampleClass) {}
protected:
void InitModifiable() override;
};
Instead of DECLARE_CLASS_INFO, DECLARE_ABSTRACT_CLASS_INFO can be used to create a base class that can't be directly instantiated. Parameters are class name, parent class name and module name. Module name parameter is just an helper parameter.
Instead of DECLARE_INLINE_CONSTRUCTOR, DECLARE_CONSTRUCTOR can be used, associated with IMPLEMENT_CONSTRUCTOR (probably in the .cpp file).
Then the .cpp file will look like that:
IMPLEMENT_CLASS_INFO(SimpleSampleClass)
void SimpleSampleClass::InitModifiable()
{
ParentClassType::InitModifiable();
if (_isInit)
{
}
}
`ParentClassType` is a helper typedef used to call methods on parent class.
`_isInit` is also a helper macro used to test if the class was correctly initialized...
Then, in the initialization of the module or application, classes must be declared (to factory):
DECLARE_FULL_CLASS_INFO(KigsCore::Instance(),
SimpleSampleClass, SimpleSampleClass, Application);
Parameters are: the current singleton instance of KigsCore, the name of the class to instantiate, the name given to the instance factory, and the Module name.
The name of an instance can be retrieved with the getName() method:
std::string name=simpleclass1->getName();
Testing instance type is possible using `isSubType` method:
if(simpleclass1->isSubType("SimpleSampleClass"))
{
SimpleSampleClass* castSimpleClass=simpleclass1->as<SimpleSampleClass>();
}
So now an instance of the class SimpleSampleClass can be asked to the instance factory:
CMSP simpleclass1 = KigsCore::GetInstanceOf("simpleclass1", "SimpleSampleClass");
When created, the instance has a ref count of 1.
- `
addItem` increases ref count of the added instance by 1. - `
removeItem` decreases ref count of the removed instance by 1.
SmartPointer classes are used to easily manage ref counting. CMSP is a class inheriting SmartPointer<CoreModifiable>.
SP and SmartPointer are equivalent.
{
SmartPointer<SimpleSampleClassBase> sp=KigsCore::GetInstanceOf
("simpleclass1", "SimpleSampleClass");
}
Pointers on CoreModifiable inherited instances can be wrapped in SmartPointer using SharedFromThis method
SmartPointer<SimpleSampleClassBase> sp=instance1->SharedFromThis();
Operator `->` is used to access functionality of the instance in the SmartPointer:
float test;
sp->getValue("test",test);
And retrieving the instance pointer itself is done using get() method:
SimpleSampleClassBase* simpleinstance = sp.get();
Instances inheriting CoreModifiable class maintain lists of their parents and sons instances (not in an inheritance point of view). So it is possible to construct trees of instances.
CMSP simpleclass1 = KigsCore::GetInstanceOf("simpleclass1", "SimpleSampleClass");
simpleclass1->Init();
CMSP simpleclass2 = KigsCore::GetInstanceOf("simpleclass2", "SimpleSampleClass");
simpleclass2->Init();
CMSP simpleclass3 = KigsCore::GetInstanceOf("simpleclass3", "SimpleSampleClass");
simpleclass3->Init();
simpleclass1->addItem(simpleclass2); simpleclass1->addItem(simpleclass3);
addItem(simpleclass1);
It is then easy to retrieve instances in the tree using `GetInstanceByPath` method:
CMSP simpleclass2 =
GetInstanceByPath("SimpleSampleClass:simpleclass1/simpleclass2");
CMSP simpleclass1 =
simpleclass2->GetInstanceByPath("/Sample2/SimpleSampleClass:simpleclass1");
CMSP simpleclass3 = simpleclass2->GetInstanceByPath("../simpleclass3");
simpleclass3 = GetInstanceByPath("*/simpleclass3");
- If path starts with `
/`, then start search by root parents (parents of this without parent). - If path contains `
../`, then continue search from parents instance. - If path contains `
*/`, then search all sons instance at this level in path.
Search can be done in sons:
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();
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();
GetSonInstancesByType("CoreModifiable", instances);
printf("GetSonInstancesByType result :\n");
for (auto i : instances)
{
printf("found instance named : %s\n", i->getName().c_str());
}
instances.clear();
GetSonInstancesByType("SimpleSampleClass", instances,true);
printf("Recursive GetSonInstancesByType result :\n");
for (auto i : instances)
{
printf("found instance named : %s\n", i->getName().c_str());
}
Or at a 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();
instances = GetInstances("SimpleSampleClass");
printf("GetInstances result :\n");
for (auto i : instances)
{
printf("found instance named : %s\n", i->getName().c_str());
}
The CoreModifiable attributes are detailed in a next article.
Here is just a brief overview.
CoreModifiable can have "compile time" attributes that can be declared in the class as below:
class SimpleSampleClass : public CoreModifiable
{
public:
DECLARE_CLASS_INFO(SimpleSampleClass, CoreModifiable, Application);
DECLARE_INLINE_CONSTRUCTOR(SimpleSampleClass) {}
protected:
maUInt m_version = BASE_ATTRIBUTE(Version, 0);
maString m_desc = BASE_ATTRIBUTE(Description, "");
};
Another way to declare several attributes, mapped on member variables is to use WRAP_ATTRIBUTES macro:
class SimpleSampleClass : public CoreModifiable
{
public:
DECLARE_CLASS_INFO(SimpleSampleClass, CoreModifiable, Application);
DECLARE_INLINE_CONSTRUCTOR(SimpleSampleClass) {}
protected:
u32 mVersion = 0;
std::string mDescription = "";
WRAP_ATTRIBUTES(mVersion,mDescription);
};
WRAP_ATTRIBUTES macro remove the first character (here 'm' prefix) of the attribute to define the name of the attribute (used by getters/setters, serialization...)
Then, on an instance of SimpleSampleClass, the attributes can be accessed with getValue / setValue methods:
simpleclass1->setValue("Version",5);
std::string desc;
simpleclass1->getValue("Description",desc);
It's also possible to add or remove attributes dynamically:
simpleclass1->AddDynamicAttribute(ATTRIBUTE_TYPE::BOOL, "isON");
simpleclass1->RemoveDynamicAttribute("isON");
All the attributes of an instance are serialized to and from XML files when the instance is serialized.
Two methods are useful to overload to manage initialization of an instance:
virtual void InitModifiable();
virtual void UninitModifiable();
InitModifiable is called by Init() method.
Here is a classic way to overload InitModifiable:
void SimpleSampleClass::InitModifiable()
{
if (_isInit)
{
return;
}
ParentClassType::InitModifiable();
if (_isInit)
{
bool somethingWentWrong=false;
...
if(somethingWentWrong)
{
UnInit();
return;
}
}
}
Of course, it's also a good thing to add a virtual destructor to free all allocations done by the class or add some specific destruction code.
If a special behaviour is needed when an instance is added to another, for example to check if an instance of a specific type is added to another, the following methods can be overloaded:
virtual void addUser(CoreModifiable* user);
virtual void removeUser(CoreModifiable* user);
virtual bool addItem(const CMSP& item, ItemPosition pos = Last);
virtual bool removeItem(const CMSP& item);
virtual void Update(const Timer& timer, void* addParam);
The Update method of an instance is called at each application loop if the instance was added to auto update:
KigsCore::GetCoreApplication()->AddAutoUpdate(instanceToAutoUpdate);
Of course, the instance is automatically removed from auto update when destroyed or manually by calling RemoveAutoUpdate:
KigsCore::GetCoreApplication()->RemoveAutoUpdate(instanceToAutoUpdate);
Update method can also be called manually by CallUpdate method or RecursiveUpdate method.
CoreModifiable attributes can notify their owners when they change (when accessed by "setValue"), calling the "NotifyUpdate" method with their ID.
virtual void NotifyUpdate(const u32 labelid);
The detailed specifications of the CoreModifiable methods are be described in a next article.
Here is just a brief overview.
The class CoreModifiable allows defining methods callable by their name (string) with a fixed prototype:
bool methodName(CoreModifiable* sender,std::vector<CoreModifiableAttribute*>& params,
void* privateParams);
Helpers macro are available to facilitate things: DECLARE_METHOD(methodName), DECLARE_VIRTUAL_METHOD(methodName), DECLARE_PURE_VIRTUAL_METHOD(methodName),
DECLARE_OVERRIDE_METHOD(methodname),DEFINE_METHOD(classtype,methodName).
In class declaration:
DECLARE_METHOD(incrementParam);
and then in cpp file, class definition:
DEFINE_METHOD(SimpleSampleBaseClass, incrementParam)
{
float val=0;
if (params[0]->getValue(val,this)) {
params[0]->setValue(val + 1.0f,this);
}
return true;
}
The list of CoreModifiable methods must be declared in the class header using COREMODIFIABLE_METHODS(method1,method2...) helper macro.
COREMODIFIABLE_METHODS(incrementParam);
The method can then be called on a CoreModifiable instance pointer (without knowing the exact instance type):
CoreModifiableAttribute* param = item->getAttribute("CountWhenAdded");
if (param)
{
std::vector<CoreModifiableAttribute*> sendParams;
sendParams.push_back(param);
item->CallMethod("incrementParam", sendParams);
std::cout << item->getName() << " parameter CountWhenAdded = "
<< item->getValue<int>("CountWhenAdded") << std::endl;
}
Any CoreModifiable member method can be accessed by its name using WRAP_METHODS helper macro:
void printMessage();
WRAP_METHODS(printMessage);
WRAP_METHODS can take several coma separated parameters.
Then the method can be called with SimpleCall method:
simpleclass1->SimpleCall("printMessage");
For methods with parameters and return value, the SimpleCall method will be used like this:
int returnedValue = instance->SimpleCall<int>("DoSomethingFun",42,"yes");
Two or more CoreModifiable instances of different types can be aggregated all together.
For example, let's define a material class managing Color and Shininess:
class SimpleMaterialClass : public CoreModifiable
{
public:
DECLARE_CLASS_INFO(SimpleMaterialClass, CoreModifiable, Application);
DECLARE_INLINE_CONSTRUCTOR(SimpleMaterialClass)
{ std::cout << "SimpleMaterialClass constructor" << std::endl; }
protected:
maVect3DF m_Color = BASE_ATTRIBUTE(Color,1.0,0.0,0.0);
maFloat m_Shininess = BASE_ATTRIBUTE(Shininess, 0.5);
};
If an instance of material is aggregate with an instance of SimpleSampleClass:
CMSP material= KigsCore::GetInstanceOf("material", "SimpleMaterialClass");
simpleclass3->aggregateWith(material);
It's then possible to directly get or set "Shininess" or "Color" values on simpleclass3:
float shine=0.0f;
simpleclass3->getValue("Shininess", shine);
std::cout << simpleclass3->getName() << " has Shininess value of "
<< shine << " thanks to aggregate with SimpleMaterialClass " << std::endl;
The opposite is also true, it's possible to retrieve SimpleSampleClass values from "material" instance. And calling CoreModifiable methods is also available the same way.
Export is only available when project is built in StaticDebug or StaticReleaseTools configuration. In StaticRelease, KigsID (used as map key for instances name...) are optimized and std::string used to construct them are not preserved.
#ifdef KIGS_TOOLS
CoreModifiable::Export("Sample1.xml", simpleclass.get(), true);
#endif // KIGS_TOOLS
Corresponding Sample1.xml file will look like this:
="1.0"="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 is always available (in all build configurations).
CMSP imported=CoreModifiable::Import("Sample1.xml");
Find all the sample code from this article in Sample2 project on GitHub (browse the code).
Already Published in this Series
- Kigs Framework Introduction (1/8) - Overview
- Kigs Framework Introduction (2/8) - CoreModifiable
- Kigs Framework Introduction (3/8) - Attributes
- Kigs Framework Introduction (4/8) - Methods
- Kigs Framework Introduction (5/8) - CoreItem
- Kigs Framework Introduction (6/8) - Signal, Slot, Notification
- Kigs Framework Introduction (7/8) - Lua Binding
- Kigs Framework Introduction (8/8) - Data Driven Application
- 31st January, 2020: Initial version
- 2nd February, 2020: Small fix in
addItem / removeItem prototype - 7th February, 2020: Added latest published article in the series
- 14th February, 2020: Article (4/8) added to the series
- 21st February, 2020: Article (5/8) added to the series and little bug fix in code
- 2nd March, 2020: Article (6/8) added to the series
- 19th March, 2020: Article (7/8) added to the series, and fix GetInstances methods prototype in samples
- 17th June, 2020 : Added final article of the series
- 1st March, 2023: Update after framework refactoring