Type System
The type system allows ERS to understand the data that is part of a simulation model. Most of the time ERS uses it in the background without requiring users to do anything. But sometimes it may be useful to provide additional data to ERS, or retrieve data from the type system. Some features of ERS require providing the additional data. Adding type information is required to:
- Allow the entity inspector widget in the ERS Debugger to correctly display the component's name and fields in the user interface. When a component is not registered in the type system, it will only show that an entity has the component, but the data of the component will not be visible.
- Perform serialization.
TypeInfo and Field
ERS stores the information about types in two objects: TypeInfo ("Type Information") and Field.
The first stores the information about a whole type, like a data component.
The latter stores information about the fields inside the type.
struct SomeComponent : public Ers::DataComponent // Type "SomeComponent"
{
int X; // Field "X"
bool Y; // Field "Y"
}
struct SomeComponent : IDataComponent // Type "SomeComponent"
{
public int X; // Field "X"
public bool Y; // Field "Y"
}
Not all types are supported by the type system. The table below shows all types that are supported by ERS.
| Type | C++ | C# |
|---|---|---|
| 32-bit float (Single precision) | float |
float |
| Boolean | bool |
bool |
| 8-bit unsigned integer | uint8_t |
byte |
| 32-bit signed integer | int32_t |
int |
| 32-bit unsigned integer | uint32_t |
uint |
| 64-bit signed integer | int64_t |
long |
| 64-bit unsigned integer | uint64_t |
ulong |
| ERS Entity | EntityID |
ulong (often aliased as Entity) |
| String | std::string |
string |
| 2D Vector | Ers::Vector2 |
Vector2 |
| 3D Vector | Ers::Vector3 |
Vector3 |
| 4D Vector | Ers::Vector4 |
Vector4 |
| RGBA Color | Ers::Color |
Color |
Built-in types in the type system.
Adding type information
Data components
To add type information to a data component, implement the GetTypeInfo() method.
Inside the method, register a struct using the TypeRegistry and add each field.
struct StorageBoxComponent : public Ers::DataComponent
{
Ers::Vector3 Dimensions;
float Weight;
EntityID Owner;
static const Ers::TypeInfo* GetTypeInfo()
{
Ers::TypeInfo* typeInfo = Ers::TypeRegistry::RegisterStruct("storage_box");
typeInfo->AddField("dimensions", Ers::BuiltinType::Vector3, offsetof(StorageBoxComponent, Dimensions));
typeInfo->AddField("weight", Ers::BuiltinType::Float32, offsetof(StorageBoxComponent, Weight));
// Third parameter: set the field to be read-only
typeInfo->AddField("owner", Ers::BuiltinType::Entity, offsetof(StorageBoxComponent, Owner), true);
return typeInfo;
}
};
To add type information, use the TypeInfo and Field attributes and set the struct layout to "sequential".
If the struct layout is not set to sequential, ERS will not generate the type information.
[TypeInfo("storage_box")]
[StructLayout(LayoutKind.Sequential)]
struct StorageBoxComponent : IDataComponent
{
[Field("dimensions")]
public Vector3 Dimensions;
[Field("weight")]
public float Weight;
// Third parameter: set the field to be read-only
[Field("owner", BuiltinType.Entity, true)]
public Entity Owner;
}
Fields can be made read-only so they cannot be modified in the entity inspector widget or other UI. This way, it is possible to view the data, but only allow the simulation itself to modify the data.
Note for C#
Notice how (in C#) the attributes are automatically able to determine the types of the fields, except for the entity.
This is because entities are aliases of ulong in C#,
which makes it so ERS cannot know automatically whether to interpret it as an actual ulong or an entity.
Therefore, the field type should always be explicitly defined for entities.
C++ does not have this issue, because there all types are always explicitly defined.
Script behaviors
Adding type information to script behaviors is currently not supported. Serialization of script behaviors is performed via the serialization callback.
Retrieving type information
Type information can be retrieved via TypeRegistry's GetTypeInfo function.
const Ers::TypeInfo vector2TypeInfo = Ers::TypeRegistry::GetTypeInfo(Ers::BuiltinType::Vector2);
const std::string expectedName = "vector2";
assert(vector2TypeInfo.GetName() == expectedName);
assert(vector2TypeInfo.GetSize() == 8);
assert(vector2TypeInfo.FieldCount() == 2);
for (const Field& field : vector2TypeInfo.GetFields())
{
// ...
}
// Custom types
const Ers::TypeInfo customTypeInfo = Ers::TypeRegistry::RegisterStruct("custom_type");
const uint32_t customTypeId = customTypeInfo.GetID(); // Save for later
// Somewhere else / later
const Ers::TypeInfo* customTypeInfo = Ers::TypeRegistry::GetTypeInfo(customTypeId);
// or
const Ers::TypeInfo* customTypeInfo = Ers::TypeRegistry::GetTypeInfo("custom_type");
TypeInfo vector2TypeInfo = TypeRegistry.GetTypeInfo(BuiltinType.Vector2);
Debug.Assert(vector2TypeInfo.Name == "vector2");
Debug.Assert(vector2TypeInfo.Size == (nuint)8);
Debug.Assert(vector2TypeInfo.FieldCount == (nuint)2);
foreach (Field field in vector2TypeInfo.GetFields())
{
// ...
}
// Custom types
TypeInfo customType = TypeRegistry.RegisterStruct("custom_type");
uint customTypeId = customType.ID; // Save for later
// Somewhere else / later
TypeInfo? customTypeInfo = TypeRegistry.GetTypeInfo(customTypeId);
// or
TypeInfo? customTypeInfo = TypeRegistry.GetTypeInfo("custom_type");
The GetTypeInfo function returns nullable TypeInfo objects when retrieving custom types,
because it may be possible that an invalid ID or name is given.