/*
* 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 "startable_object_controller.h"
#include
#include
#include
#include
#include
#include
#include
#include
META_BEGIN_NAMESPACE()
bool StartableObjectController::Build(const IMetadata::Ptr& data)
{
auto& reg = GetObjectRegistry();
observer_ = reg.Create(ClassId::ObjectHierarchyObserver);
CORE_ASSERT(observer_);
clock_ = reg.Create(ClassId::SystemClock);
CORE_ASSERT(clock_);
observer_->OnHierarchyChanged()->AddHandler(
MakeCallback(this, &StartableObjectController::HierarchyChanged));
META_ACCESS_PROPERTY(StartBehavior)->OnChanged()->AddHandler(MakeCallback([this]() {
if (META_ACCESS_PROPERTY_VALUE(StartBehavior) == StartBehavior::AUTOMATIC) {
// If StartBehavior changes to AUTOMATIC, start all AUTOMATIC startables
StartAll(ControlBehavior::CONTROL_AUTOMATIC);
}
}));
defaultTickerQueue_ = META_NS::GetObjectRegistry().Create(ClassId::ThreadedTaskQueue);
CORE_ASSERT(defaultTickerQueue_);
tickerTask_ = META_NS::MakeCallback([this]() {
TickAll(clock_->GetTime());
return true; // Recurring
});
CORE_ASSERT(tickerTask_);
META_ACCESS_PROPERTY(TickInterval)
->OnChanged()
->AddHandler(MakeCallback(this, &StartableObjectController::UpdateTicker));
META_ACCESS_PROPERTY(TickOrder)->OnChanged()->AddHandler(
MakeCallback(this, &StartableObjectController::InvalidateTickables));
UpdateTicker();
return true;
}
void StartableObjectController::Destroy()
{
if (tickerQueue_ && tickerToken_) {
tickerQueue_->CancelTask(tickerToken_);
}
InvalidateTickables();
SetTarget({}, {});
observer_.reset();
}
bool StartableObjectController::SetStartableQueueId(
const BASE_NS::Uid& startStartableQueueId, const BASE_NS::Uid& stopStartableQueueId)
{
startQueueId_ = startStartableQueueId;
stopQueueId_ = stopStartableQueueId;
return true;
}
void StartableObjectController::SetTarget(const IObject::Ptr& hierarchyRoot, HierarchyChangeModeValue mode)
{
if (!observer_) {
return;
}
InvalidateTickables();
target_ = hierarchyRoot;
bool automatic = META_ACCESS_PROPERTY_VALUE(StartBehavior) == StartBehavior::AUTOMATIC;
if (automatic && !hierarchyRoot) {
StopAll(ControlBehavior::CONTROL_AUTOMATIC);
}
observer_->SetTarget(hierarchyRoot, mode);
if (automatic && hierarchyRoot) {
StartAll(ControlBehavior::CONTROL_AUTOMATIC);
}
}
IObject::Ptr StartableObjectController::GetTarget() const
{
return observer_->GetTarget();
}
BASE_NS::vector StartableObjectController::GetAllObserved() const
{
return observer_->GetAllObserved();
}
bool StartableObjectController::StartAll(ControlBehavior behavior)
{
if (const auto root = target_.lock()) {
return AddOperation({ StartableOperation::START, target_ }, startQueueId_);
}
return false;
}
bool StartableObjectController::StopAll(ControlBehavior behavior)
{
if (auto root = target_.lock()) {
return AddOperation({ StartableOperation::STOP, target_ }, stopQueueId_);
}
return false;
}
template
void IterateChildren(const BASE_NS::vector& children, bool reverse, Callback&& callback)
{
if (reverse) {
for (auto it = children.rbegin(); it != children.rend(); ++it) {
callback(*it);
}
} else {
for (auto&& child : children) {
callback(child);
}
}
}
template
void IterateHierarchy(const IObject::Ptr& root, bool reverse, Callback&& callback)
{
if (const auto container = interface_cast(root)) {
IterateChildren(container->GetAll(), reverse, callback);
}
if (const auto content = interface_cast(root)) {
if (auto object = GetValue(content->Content())) {
callback(object);
}
}
}
template
void IterateAttachments(const IObject::Ptr& object, bool reverse, Callback&& callback)
{
if (const auto attach = interface_cast(object)) {
if (auto container = attach->GetAttachmentContainer(false)) {
IterateChildren(container->GetAll(), reverse, BASE_NS::forward(callback));
}
}
}
template
void IterateStartables(const IObject::Ptr& object, bool reverse, Callback&& callback)
{
IterateAttachments(object, reverse, BASE_NS::forward(callback));
}
template
void IterateTickables(const IObject::Ptr& object, TraversalType order, Callback&& callback)
{
if (!object) {
return;
}
bool rootFirst = order != TraversalType::DEPTH_FIRST_POST_ORDER;
if (rootFirst) {
IterateAttachments(object, false, BASE_NS::forward(callback));
}
IterateShared(
object,
[&callback](const IObject::Ptr& object) {
IterateAttachments(object, false, callback);
return true;
},
order);
if (!rootFirst) {
IterateAttachments(object, false, BASE_NS::forward(callback));
}
}
void StartableObjectController::HierarchyChanged(const HierarchyChangedInfo& info)
{
if (info.change == HierarchyChangeType::ADDED || info.change == HierarchyChangeType::REMOVING ||
info.change == HierarchyChangeType::MOVED) {
// Any hierarchy change (add/remove/move) invalidates the tick order
InvalidateTickables();
if (info.change == HierarchyChangeType::ADDED) {
AddOperation({ StartableOperation::START, info.object }, startQueueId_);
} else if (info.change == HierarchyChangeType::REMOVING) {
AddOperation({ StartableOperation::STOP, info.object }, stopQueueId_);
}
}
}
BASE_NS::vector StartableObjectController::GetAllStartables() const
{
BASE_NS::vector startables;
auto add = [&startables](const IStartable::Ptr& startable) { startables.push_back(startable); };
if (const auto root = target_.lock()) {
IterateStartables(root, false, add);
IterateShared(
root,
[&add](const IObject::Ptr& object) {
IterateStartables(object, false, add);
return true;
},
TraversalType::DEPTH_FIRST_POST_ORDER);
}
return startables;
}
void StartableObjectController::StartHierarchy(const IObject::Ptr& root, ControlBehavior behavior)
{
const auto traversal = META_ACCESS_PROPERTY_VALUE(TraversalType);
if (traversal != TraversalType::DEPTH_FIRST_POST_ORDER && traversal != TraversalType::FULL_HIERARCHY) {
CORE_LOG_E("Only DEPTH_FIRST_POST_ORDER is supported");
}
if (!root) {
return;
}
IterateHierarchy(root, false, [this, behavior](const IObject::Ptr& object) { StartHierarchy(object, behavior); });
// Don't traverse hierarchy for attachments
IterateStartables(
root, false, [this, behavior](const IStartable::Ptr& startable) { StartStartable(startable.get(), behavior); });
StartStartable(interface_cast(root), behavior);
}
void StartableObjectController::StartStartable(IStartable* const startable, ControlBehavior behavior)
{
if (startable) {
const auto state = GetValue(startable->StartableState());
if (state == StartableState::ATTACHED) {
const auto mode = GetValue(startable->StartableMode());
if (behavior == ControlBehavior::CONTROL_ALL || mode == StartBehavior::AUTOMATIC) {
startable->Start();
}
}
}
}
void StartableObjectController::StopHierarchy(const IObject::Ptr& root, ControlBehavior behavior)
{
const auto traversal = META_ACCESS_PROPERTY_VALUE(TraversalType);
if (traversal != TraversalType::DEPTH_FIRST_POST_ORDER && traversal != TraversalType::FULL_HIERARCHY) {
CORE_LOG_E("Only DEPTH_FIRST_POST_ORDER is supported");
}
if (!root) {
return;
}
StopStartable(interface_cast(root), behavior);
IterateStartables(
root, true, [this, behavior](const IStartable::Ptr& startable) { StopStartable(startable.get(), behavior); });
IterateHierarchy(root, true, [this, behavior](const IObject::Ptr& object) { StopHierarchy(object, behavior); });
}
void StartableObjectController::StopStartable(IStartable* const startable, ControlBehavior behavior)
{
if (startable) {
const auto state = GetValue(startable->StartableState());
if (state == StartableState::STARTED) {
const auto mode = GetValue(startable->StartableMode());
if (behavior == ControlBehavior::CONTROL_ALL || mode == StartBehavior::AUTOMATIC) {
startable->Stop();
}
}
}
}
bool StartableObjectController::HasTasks(const BASE_NS::Uid& queueId) const
{
std::shared_lock lock(mutex_);
if (auto it = operations_.find(queueId); it != operations_.end()) {
return !it->second.empty();
}
return false;
}
void StartableObjectController::RunTasks(const BASE_NS::Uid& queueId)
{
BASE_NS::vector operations;
{
std::unique_lock lock(mutex_);
// Take tasks for the given queue id
if (auto it = operations_.find(queueId); it != operations_.end()) {
operations.swap(it->second);
}
}
for (auto&& op : operations) {
// This may potentially end up calling Start/StopHierarchy several times
// for the same subtrees, but we will accept that. Start/Stop will only
// be called once since the functions check for current state.
if (auto root = op.root_.lock()) {
switch (op.operation_) {
case StartableOperation::START:
++executingStart_;
StartHierarchy(root, ControlBehavior::CONTROL_AUTOMATIC);
--executingStart_;
break;
case StartableOperation::STOP:
StopHierarchy(root, ControlBehavior::CONTROL_AUTOMATIC);
break;
default:
break;
}
}
}
}
bool StartableObjectController::ProcessOps(const BASE_NS::Uid& queueId)
{
if (!HasTasks(queueId)) {
// No tasks for the given queue, bail out
return true;
}
auto task = [queueId, internal = IStartableObjectControllerInternal::WeakPtr {
GetSelf() }]() {
if (auto me = internal.lock()) {
me->RunTasks(queueId);
}
};
if (queueId != BASE_NS::Uid {} && !executingStart_) {
if (auto queue = GetTaskQueueRegistry().GetTaskQueue(queueId)) {
queue->AddWaitableTask(CreateWaitableTask(BASE_NS::move(task)));
return true;
}
CORE_LOG_W("Cannot get task queue '%s'. Running the task synchronously.", BASE_NS::to_string(queueId).c_str());
}
// Just run the task immediately if we don't have a queue to defer it to
task();
return true;
}
bool StartableObjectController::AddOperation(StartableOperation&& operation, const BASE_NS::Uid& queueId)
{
auto object = operation.root_.lock();
if (!object) {
return false;
}
// Note that queueId may be {}, but it is still a valid key for our queue map
{
std::unique_lock lock(mutex_);
auto& ops = operations_[queueId];
for (auto it = ops.begin(); it != ops.end(); ++it) {
// If we already have an operation in queue for a given object, cancel the existing operation
// and just add the new one
if ((*it).root_.lock() == object) {
ops.erase(it);
break;
}
}
ops.emplace_back(BASE_NS::move(operation));
}
return ProcessOps(queueId);
}
void StartableObjectController::InvalidateTickables()
{
std::unique_lock lock(mutex_);
tickables_.clear();
tickablesValid_ = false;
}
BASE_NS::vector StartableObjectController::GetTickables() const
{
BASE_NS::vector weaks;
{
std::unique_lock lock(tickMutex_);
if (!tickablesValid_) {
auto add = [this](const ITickable::Ptr& tickable) { tickables_.push_back(tickable); };
IterateTickables(target_.lock(), META_ACCESS_PROPERTY_VALUE(TickOrder), add);
tickablesValid_ = true;
}
weaks = tickables_;
}
BASE_NS::vector tickables;
tickables.reserve(weaks.size());
for (auto&& t : weaks) {
if (auto tick = t.lock()) {
tickables.emplace_back(BASE_NS::move(tick));
}
}
return tickables;
}
void StartableObjectController::UpdateTicker()
{
auto queue = tickQueueId_ != BASE_NS::Uid {} ? META_NS::GetTaskQueueRegistry().GetTaskQueue(tickQueueId_)
: defaultTickerQueue_;
if (tickerQueue_ && tickerToken_) {
tickerQueue_->CancelTask(tickerToken_);
tickerToken_ = {};
}
tickerQueue_ = queue;
if (const auto interval = META_ACCESS_PROPERTY_VALUE(TickInterval); interval != TimeSpan::Infinite()) {
if (tickerQueue_) {
tickerToken_ = tickerQueue_->AddTask(tickerTask_, interval);
} else {
CORE_LOG_E("Invalid queue given for running ITickables: %s", BASE_NS::to_string(tickQueueId_).c_str());
}
}
}
bool StartableObjectController::SetTickableQueueuId(const BASE_NS::Uid& queueId)
{
if (queueId != tickQueueId_) {
tickQueueId_ = queueId;
UpdateTicker();
}
return true;
}
void StartableObjectController::TickAll(const TimeSpan& time)
{
const auto tickables = GetTickables();
if (!tickables.empty()) {
const auto sinceLast = lastTick_ != TimeSpan::Infinite() ? time - lastTick_ : TimeSpan::Zero();
for (auto&& tickable : tickables) {
bool shouldTick = true;
if (const auto st = interface_cast(tickable)) {
shouldTick = GetValue(st->StartableState()) == StartableState::STARTED;
}
if (shouldTick) {
tickable->Tick(time, sinceLast);
}
}
}
lastTick_ = time;
}
META_END_NAMESPACE()