Difference between revisions of "W4 Component System"
Line 43: | Line 43: | ||
Let's consider an example of creating a component that will rotate a container: | Let's consider an example of creating a component that will rotate a container: | ||
<syntaxhighlight lang="c++"> | <syntaxhighlight lang="c++"> | ||
− | // | + | //Declare a class inheriting from IComponent |
class MyComponent: public IComponent | class MyComponent: public IComponent | ||
{ | { | ||
− | // | + | //Mandatory macro. The first argument is your class, the second one is a base class |
W4_COMPONENT(MyComponent, IComponent) | W4_COMPONENT(MyComponent, IComponent) | ||
− | // | + | //If it is necessary to prohibit cloning of a component when cloning a container, |
− | // | + | //add a call to the W4_COMPONENT_DISABLE_CLONING macro |
public: | public: | ||
− | // | + | //If you have not forbidden cloning of the component, |
− | // | + | //you need to implement the cloning constructor |
MyComponent(CloneTag, const MyComponent& from, Node& owner) | MyComponent(CloneTag, const MyComponent& from, Node& owner) | ||
− | : Super(CloneTag{}, from, owner) // | + | : Super(CloneTag{}, from, owner) //call the parental clone constructor |
{ | { | ||
− | // | + | //clone the necessary data |
m_rotationPerSecond = from.m_rotationPerSecond; | m_rotationPerSecond = from.m_rotationPerSecond; | ||
} | } | ||
− | // | + | //Do not forget about the virtual destructor in case you plan |
− | // | + | //to inherit from this component |
virtual ~MyComponent() | virtual ~MyComponent() | ||
{ | { | ||
} | } | ||
− | // | + | //If you need to perform any action immediately after creating a component, |
− | // | + | //overload this method. Input - data for initialization (it may be empty) |
− | |||
virtual void initialize(const variant::Variant& data) override | virtual void initialize(const variant::Variant& data) override | ||
{ | { | ||
//НЕ ЗАБЫВАЕМ ЗВАТЬ МЕТОД РОДИТЕЛЯ! | //НЕ ЗАБЫВАЕМ ЗВАТЬ МЕТОД РОДИТЕЛЯ! | ||
Super::initialize(data); | Super::initialize(data); | ||
− | // | + | //Check if there is data for initialization |
if (data.valid() && data.is<float>()) | if (data.valid() && data.is<float>()) | ||
{ | { | ||
− | // | + | //If data is passed for initialization, |
− | // | + | //then we initialize our value with this data |
m_rotationPerSecond = data.get<float>(); | m_rotationPerSecond = data.get<float>(); | ||
} | } | ||
else | else | ||
{ | { | ||
− | // | + | //If the required data is not passed, |
− | // | + | //the value is initialized to zero |
m_rotationPerSecond = PI; | m_rotationPerSecond = PI; | ||
} | } | ||
Line 133: | Line 132: | ||
}; | }; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
== Usage example == | == Usage example == |
Revision as of 12:25, 4 August 2020
Contents
Scope
The W4 framework comes with a component system. In this case, a component is the ability to extend the functionality of an object without inheritance.
System features:
- All components inherit from the base IComponent class.
- Each component has a unique identifier for its type, by which the component can always be obtained from the container.
- The container for components is the Node class (see W4 Coordinate system and Node structure).
- A global list of components is maintained, allowing access to all components of a particular type from anywhere in the application.
IComponent class
Let's consider the main methods of the IComponent public interface:
- - this method allows you to get the id of the component;
const Id& id() const;
- - enable/disable component. Using this method, you can temporarily disable the functionality of the component and avoid construction and destruction (save the component internal state). A disabled component disappears from the global list of components, and the update() method is no longer called for it;
void enable(bool isEnabled);
- - this method lets you know if the component is enabled;
bool isEnabled() const;
- - this method casts the pointer/reference to the base component to the component of the desired type;
template<typename T> T& as();
- - this method allows you to get the owner of the given component (the container in which it is contained);
::w4::core::Node & getOwner();
- - this method allows you to get information about the type of the component by the pointer/reference to the base component.
const core::TypeInfo& getTypeInfo() const;
This class has methods that are not recommended to be called directly (this can lead to incorrect application behavior). But these methods can be overloaded if you write your own component (when overloading, do not forget to call the same method on the parent class Super::someMethod(args)):
- - this method is called when a component is added to the container. Data required for initialization of a specific component type can be passed as an argument;
virtual void initialize(const variant::Variant & data);
- - this method is called when a component is removed from a container and then destroyed. When writing your own component, if you need to perform any actions before destroying the component, all these actions should be performed in the overloaded finalize method;
virtual void finalize();
- - this method is called every frame for all components that have this method overloaded. The incoming parameter is the time elapsed since the last call;
virtual void update(float dt);
- - this method is called when the component is enabled. The method can be overloaded to respond to event data.
virtual void onEnabled(bool value);
Component container
Container public interface for components (Node):
- - this method allows you to add a component of type T with id (if necessary, you can pass initialization data). If a component of type T with this identifier already exists, the application execution is stopped with an error;
template<typename T> T& addComponent(const IComponent::Id& id, const variant::Variant& data);
- - this method is similar to the previous one, but the id is generated automatically to exclude possible errors during execution;
template<typename T> T& addComponent(const variant::Variant& data);
- - this method allows you to remove a component with a specific id from the container and then destroy the component. If a component with such an identifier does not exist, a warning is displayed, but the program continues to run;
template<typename T> void removeComponent(const IComponent::Id& id);
- - this method allows you to remove one component of type T from the list (Note! The components in the list are not stored in the order they were added, so you cannot know which component will be removed. You should use this method only if you are sure that the component of type T is the only one). If components of this type do not exist, a warning is displayed, but program execution continues;
template<typename T> void removeFirstComponent();
- - this method allows you to remove all components of type T from the container and then destroy them;
template<typename T> void removeAllComponents();
- - this method lets you know if the container has a component of type T with the specified id;
template<typename T> bool hasComponent(const IComponent::Id& id) const;
- - this method allows you to get from the container a component of type T with the specified id. If a component of this type with this id does not exist, the program execution is interrupted;
template<typename T> T& getComponent(const IComponent::Id& id);
- - this method allows you to get from the container one of the components of type T (Note! The components in the list are not stored in the order they were added, so you cannot know which component will be removed. You should use this method only if you are sure that the component of type T is the only one). If there are no components of this type, then the program execution is interrupted;
template<typename T> T& Node::getFirstComponent();
- - this method allows you to get from the container a list of all components of type T (or inherited from type T) stored in it.
template<typename T> const std::unordered_set<T*>& getAllComponents() const;
Global Component List
The following method is used to interact with the global list of components:
static const std::unordered_set<T*>& ComponentsSystem::getComponents();
- this static method allows you to get a list of all components of type T or those inherited from type T.
Component creation
Let's consider an example of creating a component that will rotate a container:
//Declare a class inheriting from IComponent
class MyComponent: public IComponent
{
//Mandatory macro. The first argument is your class, the second one is a base class
W4_COMPONENT(MyComponent, IComponent)
//If it is necessary to prohibit cloning of a component when cloning a container,
//add a call to the W4_COMPONENT_DISABLE_CLONING macro
public:
//If you have not forbidden cloning of the component,
//you need to implement the cloning constructor
MyComponent(CloneTag, const MyComponent& from, Node& owner)
: Super(CloneTag{}, from, owner) //call the parental clone constructor
{
//clone the necessary data
m_rotationPerSecond = from.m_rotationPerSecond;
}
//Do not forget about the virtual destructor in case you plan
//to inherit from this component
virtual ~MyComponent()
{
}
//If you need to perform any action immediately after creating a component,
//overload this method. Input - data for initialization (it may be empty)
virtual void initialize(const variant::Variant& data) override
{
//НЕ ЗАБЫВАЕМ ЗВАТЬ МЕТОД РОДИТЕЛЯ!
Super::initialize(data);
//Check if there is data for initialization
if (data.valid() && data.is<float>())
{
//If data is passed for initialization,
//then we initialize our value with this data
m_rotationPerSecond = data.get<float>();
}
else
{
//If the required data is not passed,
//the value is initialized to zero
m_rotationPerSecond = PI;
}
W4_LOG_INFO("Component created with speed %f", m_rotationPerSecond);
}
//если перед разрушением компонента необходимо произвести какие-то
//дополнительные действия - необходимо перегрузить этот метод
virtual void finalize() override
{
W4_LOG_INFO("Component will be removed with speed %f", m_rotationPerSecond);
//НЕ ЗАБЫВАЕМ ЗВАТЬ МЕТОД РОДИТЕЛЯ!
Super::finalize();
}
//даём возможность изменять скорость вращения
void setSpeed(float speed)
{
m_rotationPerSecond = speed;
}
//если в вашем компоненте необходимо обновлять состояние в зависимости от времени
//необходимо перегрузить этот метод
virtual void update(float dt) override
{
//будем вращать контейнер
auto dr = m_rotationPerSecond * dt;
getOwner().rotateLocal(Rotator{dr, dr, dr});
//НЕ ЗАБЫВАЕМ ЗВАТЬ МЕТОД РОДИТЕЛЯ!
Super::update(dt);
}
protected:
//если нужно производить какие-либо действия при включении/выключении компонента
//необходимо перегрузить этот метод
virtual void onEnabled(bool enabled) override
{
if (enabled)
{
W4_LOG_INFO("component enabled");
}
else
{
W4_LOG_INFO("component disabled");
}
//НЕ ЗАБЫВАЕМ ЗВАТЬ МЕТОД РОДИТЕЛЯ!
Super::onEnabled(enabled);
}
private:
//компонент хранит очень важное значение
float m_rotationPerSecond;
};