/*
* Copyright (c) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "component_query.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
CORE_BEGIN_NAMESPACE()
using BASE_NS::array_view;
using BASE_NS::move;
ComponentQuery::~ComponentQuery()
{
UnregisterEcsListeners();
}
bool ComponentQuery::ResultRow::IsValidComponentId(size_t index) const
{
if (index < components.size()) {
return components[index] != IComponentManager::INVALID_COMPONENT_ID;
}
return false;
}
void ComponentQuery::SetupQuery(
const IComponentManager& baseComponentSet, const array_view operations, bool enableEntityLookup)
{
const size_t componentCount = operations.size() + 1;
enableLookup_ = enableEntityLookup;
result_.clear();
valid_ = false;
// Unregistering any old listeners because the operations might not use the same managers.
UnregisterEcsListeners();
managers_.clear();
managers_.reserve(componentCount);
operationMethods_.clear();
operationMethods_.reserve(componentCount);
managers_.push_back(const_cast(&baseComponentSet));
operationMethods_.push_back(Operation::REQUIRE);
for (auto& operation : operations) {
managers_.push_back(const_cast(&operation.target));
CORE_ASSERT(managers_.back());
operationMethods_.push_back(operation.method);
}
if (enableListeners_) {
RegisterEcsListeners();
}
}
bool ComponentQuery::Execute()
{
if (enableListeners_ && valid_) {
// No changes detected since previous execute.
return false;
}
if (managers_.empty()) {
// Query setup not done.
return false;
}
if (!managers_[0]) {
// Base manager is null.
return false;
}
const IComponentManager& baseComponentSet = *managers_[0];
const auto baseComponents = baseComponentSet.GetComponentCount();
result_.resize(baseComponents);
auto& em = baseComponentSet.GetEcs().GetEntityManager();
const size_t managerCount = managers_.size();
size_t index = 0U;
for (IComponentManager::ComponentId id = 0; id < baseComponents; ++id) {
if (const Entity entity = baseComponentSet.GetEntity(id); em.IsAlive(entity)) {
auto& row = result_[index];
row.entity = entity;
row.components.resize(managerCount, IComponentManager::INVALID_COMPONENT_ID);
row.components[0U] = id;
bool valid = true;
// NOTE: starting from index 1 that is the first manager after the base component set.
for (size_t i = 1; valid && (i < managerCount); ++i) {
const auto& manager = managers_[i];
const auto componentId =
manager ? manager->GetComponentId(entity) : IComponentManager::INVALID_COMPONENT_ID;
row.components[i] = componentId;
switch (operationMethods_[i]) {
case Operation::REQUIRE: {
// for required components ID must be valid
valid = (componentId != IComponentManager::INVALID_COMPONENT_ID);
break;
}
case Operation::OPTIONAL: {
// for optional ID doesn't matter
break;
}
default: {
valid = false;
}
}
}
if (valid) {
if (enableLookup_) {
mapping_[entity] = index;
}
++index;
}
}
}
result_.resize(index);
valid_ = true;
return true;
}
void ComponentQuery::Execute(
const IComponentManager& baseComponentSet, const array_view operations, bool enableEntityLookup)
{
SetupQuery(baseComponentSet, operations, enableEntityLookup);
Execute();
}
void ComponentQuery::SetEcsListenersEnabled(bool enableListeners)
{
enableListeners_ = enableListeners;
if (enableListeners_) {
RegisterEcsListeners();
} else {
UnregisterEcsListeners();
}
}
bool ComponentQuery::IsValid() const
{
return valid_;
}
array_view ComponentQuery::GetResults() const
{
return { result_.data(), result_.size() };
}
const ComponentQuery::ResultRow* ComponentQuery::FindResultRow(Entity entity) const
{
if (EntityUtil::IsValid(entity)) {
const auto it = mapping_.find(entity);
if (it != mapping_.end() && it->second < result_.size()) {
return &(result_[it->second]);
}
}
return nullptr;
}
void ComponentQuery::RegisterEcsListeners()
{
if (!registered_ && !managers_.empty()) {
// Listen to changes in managers so the result can be automatically invalidated.
ecs_ = &managers_[0]->GetEcs();
for (auto& manager : managers_) {
if (manager) {
ecs_->AddListener(*manager, *this);
}
}
ecs_->AddListener(static_cast(*this));
registered_ = true;
}
}
void ComponentQuery::UnregisterEcsListeners()
{
if (registered_ && !managers_.empty() && ecs_) {
for (auto& manager : managers_) {
if (manager) {
ecs_->RemoveListener(*manager, *this);
}
}
ecs_->RemoveListener(static_cast(*this));
registered_ = false;
}
}
void ComponentQuery::OnEntityEvent(const IEcs::EntityListener::EventType type, const array_view entities)
{
if (!valid_) {
// Listener is only used to invalidate the quety. If the query is already invalid -> no need to check anything.
return;
}
if (type == IEcs::EntityListener::EventType::ACTIVATED || type == IEcs::EntityListener::EventType::DEACTIVATED) {
const auto managerCount = managers_.size();
for (const auto& entity : entities) {
// We are only interested in entities that have all the required managers.
bool isRelevantEntity = true;
for (size_t i = 0; i < managerCount; ++i) {
if (operationMethods_[i] == Operation::OPTIONAL) {
continue;
}
if (managers_[i] && !managers_[i]->HasComponent(entity)) {
// This entity is missing a required manager -> irrelevant.
isRelevantEntity = false;
break;
}
}
if (isRelevantEntity) {
// Marking this query as invalid. No need to iterate entities further.
valid_ = false;
return;
}
}
} else if (type == IEcs::EntityListener::EventType::DESTROYED) {
if (enableLookup_) {
for (const auto& entity : entities) {
mapping_.erase(entity);
}
}
}
}
void ComponentQuery::OnComponentEvent(const IEcs::ComponentListener::EventType type,
const IComponentManager& /* componentManager */, const array_view /* entities */)
{
// We only get events from relevant managers. If they have new or deleted components, the query is no longer valid.
if (type == IEcs::ComponentListener::EventType::CREATED || type == IEcs::ComponentListener::EventType::DESTROYED) {
valid_ = false;
}
}
CORE_END_NAMESPACE()