Difference between revisions of "W4 Component System"
(18 intermediate revisions by the same user not shown) | |||
Line 4: | Line 4: | ||
System features: | System features: | ||
* All components inherit from the base '''IComponent''' class. | * All components inherit from the base '''IComponent''' class. | ||
− | * Each component | + | * Each component carries a unique identifier of 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]]). | * 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. | * A global list of components is maintained, allowing access to all components of a particular type from anywhere in the application. | ||
Line 10: | Line 10: | ||
== IComponent class == | == IComponent class == | ||
Let's consider the main methods of the '''IComponent''' public interface: | Let's consider the main methods of the '''IComponent''' public interface: | ||
− | + | {| class="wikitable" | |
− | + | |- | |
− | + | | <syntaxhighlight lang="c++">const Id& id() const;</syntaxhighlight> || This method allows you to get the id of the component | |
− | + | |- | |
− | + | | <syntaxhighlight lang="c++">void enable(bool isEnabled);</syntaxhighlight> || Enable/disable component. Using this method, you can temporarily disable the functionality of the component and avoid construction and destruction (and 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 | |
− | + | |- | |
+ | | <syntaxhighlight lang="c++">bool isEnabled() const;</syntaxhighlight> || This method informs you if the component is enabled | ||
+ | |- | ||
+ | | <syntaxhighlight lang="c++">template<typename T> T& as();</syntaxhighlight> || This method casts the pointer/reference to the base component, to the component of the desired type | ||
+ | |- | ||
+ | | <syntaxhighlight lang="c++">::w4::core::Node& getOwner();</syntaxhighlight> || This method allows you to get the owner of the given component (the container in which it is held) | ||
+ | |- | ||
+ | | <syntaxhighlight lang="c++">const core::TypeInfo& getTypeInfo() const;</syntaxhighlight> || This method allows you to get information about the type of the component by the pointer/reference to the base component | ||
+ | |} | ||
− | + | This class has methods that are not recommended to be called directly as it may lead to incorrect application behavior. However 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 class has methods that are not recommended to be called directly | + | {| class="wikitable" |
− | + | |- | |
− | + | | <syntaxhighlight lang="c++">virtual void initialize(const variant::Variant & data);</syntaxhighlight> || This method is called when a component is added to a container. Data required for initialization of a specific component type can be passed as an argument | |
− | + | |- | |
− | + | | <syntaxhighlight lang="c++">virtual void finalize();</syntaxhighlight> || 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, they should be carried out in the overloaded '''finalize''' method | |
+ | |- | ||
+ | | <syntaxhighlight lang="c++">virtual void update(float dt);</syntaxhighlight> || This method is called by every frame for all components having this method overloaded. The incoming parameter is the time elapsed since the last call | ||
+ | |- | ||
+ | | <syntaxhighlight lang="c++">virtual void onEnabled(bool value);</syntaxhighlight> || This method is called when the component is enabled. The method can be overloaded to respond to event data | ||
+ | |} | ||
== Component container == | == Component container == | ||
Container public interface for components (Node): | Container public interface for components (Node): | ||
− | + | {| class="wikitable" | |
− | + | |- | |
− | + | | <syntaxhighlight lang="c++">template<typename T> T& addComponent(const IComponent::Id& id, const variant::Variant& data);</syntaxhighlight> || This method allows you to add a component of type T with its 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 | |
− | + | |- | |
− | + | | <syntaxhighlight lang="c++">template<typename T> T& addComponent(const variant::Variant& data);</syntaxhighlight> || This method is similar to the previous one, but the id is generated automatically to exclude possible errors during execution | |
− | + | |- | |
− | + | | <syntaxhighlight lang="c++">template<typename T> void removeComponent(const IComponent::Id& id);</syntaxhighlight> || This method removes a component with a specific id from the container with further destruction of the component. If a component with such an identifier does not exist, a warning is displayed, thou the program continues to run | |
− | + | |- | |
− | + | | <syntaxhighlight lang="c++">template<typename T> void removeFirstComponent();</syntaxhighlight> || 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 are not able to 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 existing). If components of this type do not exist, a warning is displayed, but the program execution continues | |
− | + | |- | |
+ | | <syntaxhighlight lang="c++">template<typename T> void removeAllComponents();</syntaxhighlight> || This method allows you to remove all components of type T from the container and then destroy them | ||
+ | |- | ||
+ | | <syntaxhighlight lang="c++">template<typename T> bool hasComponent(const IComponent::Id& id) const;</syntaxhighlight> || This method lets you know if the container has a component of type T with the specified id | ||
+ | |- | ||
+ | | <syntaxhighlight lang="c++">template<typename T> T& getComponent(const IComponent::Id& id);</syntaxhighlight> || This method gets (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 | ||
+ | |- | ||
+ | | <syntaxhighlight lang="c++">template<typename T> T& Node::getFirstComponent();</syntaxhighlight> || 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 are not able to 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 existing). If there no components of this type are available, the program execution is terminated | ||
+ | |- | ||
+ | | <syntaxhighlight lang="c++">template<typename T> const std::unordered_set<T*>& getAllComponents() const;</syntaxhighlight> || This method receives from the container a list of all components of type T (or inherited from type T) stored in it | ||
+ | |} | ||
== Global Component List == | == Global Component List == | ||
− | The following method is used | + | The following method is used for interaction with the global list of components: |
− | + | {| class="wikitable" | |
− | <syntaxhighlight lang="c++">static const std::unordered_set<T*>& ComponentsSystem::getComponents();</syntaxhighlight> | + | |- |
+ | | <syntaxhighlight lang="c++">static const std::unordered_set<T*>& ComponentsSystem::getComponents();</syntaxhighlight> || this is a static method that allows you to get a list of all type T components , as well as components inherited from type T | ||
+ | |} | ||
== Component creation == | == Component creation == | ||
Line 49: | Line 74: | ||
//Mandatory macro. The first argument is your class, the second one is a base class | //Mandatory macro. The first argument is your class, the second one is a base class | ||
W4_COMPONENT(MyComponent, IComponent) | W4_COMPONENT(MyComponent, IComponent) | ||
− | // | + | //Should you need to ban cloning a component when cloning a container, |
//add a call to the W4_COMPONENT_DISABLE_CLONING macro | //add a call to the W4_COMPONENT_DISABLE_CLONING macro | ||
public: | public: | ||
Line 60: | Line 85: | ||
m_rotationPerSecond = from.m_rotationPerSecond; | m_rotationPerSecond = from.m_rotationPerSecond; | ||
} | } | ||
− | // | + | //Remember about the virtual destructor in case you plan to inherit |
− | // | + | //from this component later on |
virtual ~MyComponent() | virtual ~MyComponent() | ||
{ | { | ||
} | } | ||
− | //If you need to perform any action immediately after creating a component, | + | //If you need to perform any action immediately after creating a component, overload this method. |
− | // | + | //Input - data for initialization (there is a possibility that it might be empty) |
virtual void initialize(const variant::Variant& data) override | virtual void initialize(const variant::Variant& data) override | ||
{ | { | ||
//Note! Do not forget to call the parental method! | //Note! Do not forget to call the parental method! | ||
Super::initialize(data); | Super::initialize(data); | ||
− | //Check if there is data for initialization | + | //Check if there is any data for initialization |
if (data.valid() && data.is<float>()) | if (data.valid() && data.is<float>()) | ||
{ | { | ||
//If data is passed for initialization, | //If data is passed for initialization, | ||
− | //then we | + | //then we initiate rotational speed using this data |
m_rotationPerSecond = data.get<float>(); | m_rotationPerSecond = data.get<float>(); | ||
} | } | ||
Line 113: | Line 138: | ||
} | } | ||
protected: | protected: | ||
− | // | + | //If you need to perform any actions when turning the component on or off, |
//you must overload this method | //you must overload this method | ||
virtual void onEnabled(bool enabled) override | virtual void onEnabled(bool enabled) override | ||
Line 135: | Line 160: | ||
== Usage example == | == Usage example == | ||
− | Now | + | Now let’s try to implement the component above to write a plain app for cube rotation: |
<syntaxhighlight lang="c++"> | <syntaxhighlight lang="c++"> | ||
#include "W4Framework.h" | #include "W4Framework.h" | ||
Line 146: | Line 171: | ||
//Mandatory macro. The first argument is your class, the second one is a base class | //Mandatory macro. The first argument is your class, the second one is a base class | ||
W4_COMPONENT(MyComponent, IComponent) | W4_COMPONENT(MyComponent, IComponent) | ||
− | // | + | //Should you need to ban cloning a component when cloning a container, |
//add a call to the W4_COMPONENT_DISABLE_CLONING macro | //add a call to the W4_COMPONENT_DISABLE_CLONING macro | ||
public: | public: | ||
Line 157: | Line 182: | ||
m_rotationPerSecond = from.m_rotationPerSecond; | m_rotationPerSecond = from.m_rotationPerSecond; | ||
} | } | ||
− | // | + | //Remember about the virtual destructor in case you plan to inherit from this component later on |
− | // | + | //from this component later on |
virtual ~MyComponent() | virtual ~MyComponent() | ||
{ | { | ||
} | } | ||
− | //If you need to perform any action immediately after creating a component, | + | //If you need to perform any action immediately after creating a component, overload this method. |
− | // | + | //Input - data for initialization (there is a possibility that it might be empty) |
virtual void initialize(const variant::Variant& data) override | virtual void initialize(const variant::Variant& data) override | ||
{ | { | ||
//Note! Do not forget to call the parental method! | //Note! Do not forget to call the parental method! | ||
Super::initialize(data); | Super::initialize(data); | ||
− | //Check if there is data for initialization | + | //Check if there is any data for initialization |
if (data.valid() && data.is<float>()) | if (data.valid() && data.is<float>()) | ||
{ | { | ||
//If data is passed for initialization, | //If data is passed for initialization, | ||
− | //then we | + | //then we initiate rotational speed using this data |
m_rotationPerSecond = data.get<float>(); | m_rotationPerSecond = data.get<float>(); | ||
} | } | ||
Line 193: | Line 218: | ||
} | } | ||
− | //Add the | + | //Add the feature of changing the rotation speed |
void setSpeed(float speed) | void setSpeed(float speed) | ||
{ | { | ||
Line 199: | Line 224: | ||
} | } | ||
− | // | + | //Provided the component requires to update its state depending on time, |
//you need to overload this method | //you need to overload this method | ||
virtual void update(float dt) override | virtual void update(float dt) override | ||
Line 210: | Line 235: | ||
} | } | ||
protected: | protected: | ||
− | // | + | //If you need to perform any actions when turning the component on or off, |
//you must overload this method | //you must overload this method | ||
virtual void onEnabled(bool enabled) override | virtual void onEnabled(bool enabled) override | ||
Line 247: | Line 272: | ||
if (evt.key == event::Keyboard::Key::W) | if (evt.key == event::Keyboard::Key::W) | ||
{ | { | ||
− | //When you press the W button, the cube | + | //When you press the W button, the cube starts rotating counterclockwise |
m_cube->getFirstComponent<MyComponent>().setSpeed(PI); | m_cube->getFirstComponent<MyComponent>().setSpeed(PI); | ||
} | } | ||
else if (evt.key == event::Keyboard::Key::S) | else if (evt.key == event::Keyboard::Key::S) | ||
{ | { | ||
− | //When you press the W button, the cube | + | //When you press the W button, the cube starts rotating clockwise |
m_cube->getFirstComponent<MyComponent>().setSpeed(-PI); | m_cube->getFirstComponent<MyComponent>().setSpeed(-PI); | ||
} | } |
Latest revision as of 13:41, 3 September 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 carries a unique identifier of 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:
const Id& id() const;
|
This method allows you to get the id of the component |
void enable(bool isEnabled);
|
Enable/disable component. Using this method, you can temporarily disable the functionality of the component and avoid construction and destruction (and 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 |
bool isEnabled() const;
|
This method informs you if the component is enabled |
template<typename T> T& as();
|
This method casts the pointer/reference to the base component, to the component of the desired type |
::w4::core::Node& getOwner();
|
This method allows you to get the owner of the given component (the container in which it is held) |
const core::TypeInfo& getTypeInfo() const;
|
This method allows you to get information about the type of the component by the pointer/reference to the base component |
This class has methods that are not recommended to be called directly as it may lead to incorrect application behavior. However 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)):
virtual void initialize(const variant::Variant & data);
|
This method is called when a component is added to a container. Data required for initialization of a specific component type can be passed as an argument |
virtual void finalize();
|
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, they should be carried out in the overloaded finalize method |
virtual void update(float dt);
|
This method is called by every frame for all components having this method overloaded. The incoming parameter is the time elapsed since the last call |
virtual void onEnabled(bool value);
|
This method is called when the component is enabled. The method can be overloaded to respond to event data |
Component container
Container public interface for components (Node):
template<typename T> T& addComponent(const IComponent::Id& id, const variant::Variant& data);
|
This method allows you to add a component of type T with its 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 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> void removeComponent(const IComponent::Id& id);
|
This method removes a component with a specific id from the container with further destruction of the component. If a component with such an identifier does not exist, a warning is displayed, thou the program continues to run |
template<typename T> void removeFirstComponent();
|
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 are not able to 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 existing). If components of this type do not exist, a warning is displayed, but the program execution continues |
template<typename T> void removeAllComponents();
|
This method allows you to remove all components of type T from the container and then destroy them |
template<typename T> bool hasComponent(const IComponent::Id& id) const;
|
This method lets you know if the container has a component of type T with the specified id |
template<typename T> T& getComponent(const IComponent::Id& id);
|
This method gets (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& Node::getFirstComponent();
|
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 are not able to 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 existing). If there no components of this type are available, the program execution is terminated |
template<typename T> const std::unordered_set<T*>& getAllComponents() const;
|
This method receives from the container a list of all components of type T (or inherited from type T) stored in it |
Global Component List
The following method is used for interaction with the global list of components:
static const std::unordered_set<T*>& ComponentsSystem::getComponents();
|
this is a static method that allows you to get a list of all type T components , as well as components 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)
//Should you need to ban cloning 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;
}
//Remember about the virtual destructor in case you plan to inherit
//from this component later on
virtual ~MyComponent()
{
}
//If you need to perform any action immediately after creating a component, overload this method.
//Input - data for initialization (there is a possibility that it might be empty)
virtual void initialize(const variant::Variant& data) override
{
//Note! Do not forget to call the parental method!
Super::initialize(data);
//Check if there is any data for initialization
if (data.valid() && data.is<float>())
{
//If data is passed for initialization,
//then we initiate rotational speed using this data
m_rotationPerSecond = data.get<float>();
}
else
{
//If the required data is not passed,
//the rotational speed is initialized to zero
m_rotationPerSecond = PI;
}
W4_LOG_INFO("Component created with speed %f", m_rotationPerSecond);
}
//If you need to perform any additional actions before destroying
//the component, you must overload this method
virtual void finalize() override
{
W4_LOG_INFO("Component will be removed with speed %f", m_rotationPerSecond);
//Note! Do not forget to call the parental method!
Super::finalize();
}
//Add the ability to change the rotation speed
void setSpeed(float speed)
{
m_rotationPerSecond = speed;
}
//If the component needs to update its state depending on time,
//you need to overload this method
virtual void update(float dt) override
{
//Rotate the container
auto dr = m_rotationPerSecond * dt;
getOwner().rotateLocal(Rotator{dr, dr, dr});
//Note! Do not forget to call the parental method!
Super::update(dt);
}
protected:
//If you need to perform any actions when turning the component on or off,
//you must overload this method
virtual void onEnabled(bool enabled) override
{
if (enabled)
{
W4_LOG_INFO("component enabled");
}
else
{
W4_LOG_INFO("component disabled");
}
//Note! Do not forget to call the parental method!
Super::onEnabled(enabled);
}
private:
//The component stores the rotational speed
float m_rotationPerSecond;
};
Usage example
Now let’s try to implement the component above to write a plain app for cube rotation:
#include "W4Framework.h"
W4_USE_UNSTRICT_INTERFACE
//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)
//Should you need to ban cloning 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;
}
//Remember about the virtual destructor in case you plan to inherit from this component later on
//from this component later on
virtual ~MyComponent()
{
}
//If you need to perform any action immediately after creating a component, overload this method.
//Input - data for initialization (there is a possibility that it might be empty)
virtual void initialize(const variant::Variant& data) override
{
//Note! Do not forget to call the parental method!
Super::initialize(data);
//Check if there is any data for initialization
if (data.valid() && data.is<float>())
{
//If data is passed for initialization,
//then we initiate rotational speed using this data
m_rotationPerSecond = data.get<float>();
}
else
{
//If the required data is not passed,
//the rotational speed is initialized to zero
m_rotationPerSecond = PI;
}
W4_LOG_INFO("Component created with speed %f", m_rotationPerSecond);
}
//If you need to perform any additional actions before destroying
//the component, you must overload this method
virtual void finalize() override
{
W4_LOG_INFO("Component will be removed with speed %f", m_rotationPerSecond);
//Note! Do not forget to call the parental method!
Super::finalize();
}
//Add the feature of changing the rotation speed
void setSpeed(float speed)
{
m_rotationPerSecond = speed;
}
//Provided the component requires to update its state depending on time,
//you need to overload this method
virtual void update(float dt) override
{
//Rotate the container
auto dr = m_rotationPerSecond * dt;
getOwner().rotateLocal(Rotator{dr, dr, dr});
//Note! Do not forget to call the parental method!
Super::update(dt);
}
protected:
//If you need to perform any actions when turning the component on or off,
//you must overload this method
virtual void onEnabled(bool enabled) override
{
if (enabled)
{
W4_LOG_INFO("component enabled");
}
else
{
W4_LOG_INFO("component disabled");
}
//Note! Do not forget to call the parental method!
Super::onEnabled(enabled);
}
private:
//The component stores the rotational speed
float m_rotationPerSecond;
};
class MyGame: public IGame
{
void onStart() override
{
//Create a cube
m_cube = Mesh::create::cube({1.f, 1.f, 1.f});
//Add the cube to the root
Render::getRoot()->addChild(m_cube);
//Add the component to the cube
m_cube->addComponent<MyComponent>(PI);
}
void onKey(const event::Keyboard::Down& evt) override
{
if (evt.key == event::Keyboard::Key::W)
{
//When you press the W button, the cube starts rotating counterclockwise
m_cube->getFirstComponent<MyComponent>().setSpeed(PI);
}
else if (evt.key == event::Keyboard::Key::S)
{
//When you press the W button, the cube starts rotating clockwise
m_cube->getFirstComponent<MyComponent>().setSpeed(-PI);
}
else if (evt.key == event::Keyboard::Key::Space)
{
//When pressing the spacebar, turn the rotation on/off
m_cube->getFirstComponent<MyComponent>().enable(!m_cube->getFirstComponent<MyComponent>().isEnabled());
}
}
sptr<Mesh> m_cube;
};
W4_RUN(MyGame)