https://www.gamedev.net/blog/1930/entry-2260620-ecs-ii-messaging/
Queries:
Last time I mentioned that as a special case of the messaging in my ECS, I implemented queries. The queries syntax is actually quite similar to messaging:
// 1. declare the query classclass CollisionQuery : public ecs::Query{public: CollisionQuery(const math::Rect& rect, const ecs::Entity& entity); const math::Rect rect; const ecs::Entity* pEntity; ecs::EntityHandle entityCollided; bool bCollision;};// 2. register a system for itvoid CollisionSystem::Init(ecs::MessageManager& messages){ // register this system to respond to the query messages.RegisterQuery(*this);}// 3. this is how you use it:CollisionQuery query(rect, *pEntity);if(m_pMessages->Query(query)){ // we got a response to the query}// 4. the system receives & handles the query:bool CollisionSystem::HandleQuery(ecs::BaseQuery& query) const{ if(auto pCollision = query.Convert()) { const math::Rect collRect = math::Rect(pCollision->rect.x, pCollision->rect.y, pCollision->rect.width -1, pCollision->rect.height-1); auto vEntity = m_pEntities->EntitiesWithComponents(); for(auto pEntity : vEntity) { if(pEntity == pCollision->pEntity) continue; auto pPosition = pEntity->GetComponent(); auto pCollisionComp = pEntity->GetComponent(); const math::Rect rect = calculateRect(pEntity, pPosition->v, pCollisionComp->vSize.x, pCollisionComp->vSize.y); if(collRect.Inside(rect)) { pCollision->entityCollided = pEntity; pCollision->bCollision = true; return true; } } return true; } return false;}
So what are the main differences in both systems?
- Messages can be received by N number of systems, while only one system can respond to a query
- Messages can alter the state of the system, while queries are passed in a "const" method
- Messages cannot be altered, but queries obviously can take output-arguments
Basically messages are for delivering information to the system, and queries are for extracting it.
Component serialization:
Now with all the I presented, the ECS is nearly functional. The only thing that was left is loading of components, and editor interaction. The design I am going to present was good enough for the beginning, but as things progressed, it really become aweful to work with
The intermediate file-format used in my engine is XML. So the first thing that came to mind when talking about loading components, was to have an interface that can be registered to a loader, and is called whenever a component is to be loaded:
// 1. the interfaceclass IComponentLoader{public: virtual IComponentLoader(void) = default; virtual void Load(Entity& entity, const xml::Node& node) const = 0;}// 2. and a basic implementationvoid PositionLoader::Load(ecs::Entity& entity, const xml::Node& node) const{ const auto x = node.FirstNode(L"X")->ToFloat(); const auto y = node.FirstNode(L"Y")->ToFloat(); entity.AttachComponent(x, y);}// 3. which can be registered// this loader is executed whenever a "Position"-component-node is encounteredecs::ComponentLoader::RegisterLoader(L"Position");
The loader would then simply pick the correct interface from a map, and call Load on it. The same thing happening for saving, as you can imagine. At the beginning of the project, this was only half bad. There where only a handful of components, and writing the load/save interface did not really take that long a time. But as there began to be more and more components, this became really tedious. Not only did I have to implement out those interfaces, but a ton more (mainly for editor interaction). So adding a component could easily need 10+ files and take about half an hour. Its still not too bad in the grand scheme of things, but there certainly had to be a better solution.
And thus I built my own custom RTTI-system. Its only half-insane as it sounds... next article, things should start to get more interesting, when I tell you more about this type-system I cam up with.