This functionality can be used to add simple reflection capabilities to C++ code without special compiler support and/or post-processing of the compiled output.
The reflection API gives the user a TypeDatabase static class to manage instances of the Type class.
Reflection is enabled for all Cogs Entities, Components, Fields and Enums.
The reflection type database requires unique names for all types added. Field reflection is handled locally for each Component to allow same name in several Components.
Reflection is used to generate interface classes to Cogs in languages like C++, C#, Typescript/JavasScript and Python. It is also used by the Cogs Debug GUI to display and edit contents of Cogs Entities.
See existing code for patterns of how to add reflection to Cogs types.
Adding a range of a component field can be useful for entity editors. For example limiting a rotation angle to 0..2PI. The range is only a hint and not used in any field value setting checking.
For interface code generation doxygen type code documentation of data types and enum values will be parsed and used.
When adding new types to the code, include types in Cogs.Core code-generation and check result.
#include
#include
using namespace Cogs::Reflection;
namespace MyDefs {
//! Doxygen style documentation shall be used. Allows documented Component from Code Generation (C++ .Net, ..)
class MyComponent : public Cogs::ComponentModel::Component
{
public:
// Reflection classes must have default constructor.
MyComponent() = default;
//! Register the type in the type system.
static void registerType();
//! Visibility docs, Doxygen style for Code generation
bool visible = true;
};
}
//! The getName template must be specialized in order to support
//! reflection for our class.
//! Always return a string literal.
template<> Cogs::StringView getName() { return "MyComponent"; }
//! This is normally done in a member function.
//! This function must be called before the component is used.
//! From Cogs.Core/Source/Types.cpp or when loading the extension owning the component.
void MyDefs::MyComponent::registerType()
{
Cogs::Reflection::Field fields[] = {
{ "visible", &MyDefs::MyComponent::visible },
};
// Register type.
Cogs::Reflection::TypeDatabase::createType()
// Name of base class.
.setBase()
// Register the component fields.
.setFields(fields);
}
Showing an enum storing a bitmask of values. The doxygen style documentation of the enum is used when generating language bindings for C#, TypeScript etc.
namespace Cogs::Core
{
/*!
* Options for COGS picking. Bitmask.
*/
enum class PickingFlags : uint32_t {
//! No flags specified,
None = 0,
//! Check picking for entities with SpriteRenderComponent. I.e. Text, Annotation, Billboard, Overlay etc.
PickSprites = 1 << 0,
//! Return ID if sub-entity picked, not set: return root parent entity.
ReturnChildEntity = 1 << 1,
//! For multi-pick return - remove entries with same EntityId.
RemoveDuplicateEntities = 1 << 2,
};
// Adds custom and/or operators for the enum
ENABLE_ENUM_FLAGS(PickingFlags);
}
Adding reflection for enum is done at system or extension initialization.
#include
// Use EnumeratorDef to allow compile-time initialization.
static constexpr EnumeratorDef pickingEnums[] = {
{ "None", PickingFlags::None },
{ "PickSprites", PickingFlags::PickSprites },
{ "ReturnChildEntity", PickingFlags::ReturnChildEntity },
{ "RemoveDuplicateEntities", PickingFlags::RemoveDuplicateEntities },
};
// Register enum type for reflection
TypeDatabase::createType()
// Inform type system that it is an enum and add value reflection
.setEnumerators(pickingEnums)
// Optional: Mark enum is a bit-mask
.setEnumFlags();
#include
using namespace Cogs::Reflection;
void useReflection()
{
const Type & type = TypeDatabase::getType("MyComponent");
// Check that value and not Enumeration
assert(type.isValid());
assert(!type.isEnum());
// Expect class to inherit from "Component" - not handling several levels.
const Type * base = type.getBase();
assert(base);
assert(base->getName() == "Component");
// Use the type here
...
}
void createInstance()
{
// The type of the returned pointer will be cast to the
// given template type.
//
ComponentModel::Component* instance = TypeDatabase::createInstance("MyComponent");
}
For example usage see Cogs.Codegenerator or Cogs Debug GUI EntityInspector.
auto def = context->store->getEntityDefinition("Cube");
// Loop through components:
for (auto& c : def.components) {
const auto& type = TypeDatabase::getType(c);
auto numFields = c.getNumFields();
for (Reflection::FieldId i = 0; i < numFields; ++i) {
const auto& field = *type.getField(i);
if (fieldType.isEnum()) {
// Reflect on the enum type
}
}
}