* 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 <meta/api/future.h>
#include <meta/api/iteration.h>
#include <meta/api/make_callback.h>
#include <meta/api/task.h>
#include <meta/api/util.h>
#include <meta/interface/intf_content.h>
#include <meta/interface/intf_task_queue_registry.h>
#include <meta/interface/property/property_events.h>
META_BEGIN_NAMESPACE()
bool StartableObjectController::Build(const IMetadata::Ptr& data)
{
auto& reg = GetObjectRegistry();
observer_ = reg.Create<IObjectHierarchyObserver>(ClassId::ObjectHierarchyObserver);
CORE_ASSERT(observer_);
clock_ = reg.Create<IClock>(ClassId::SystemClock);
CORE_ASSERT(clock_);
observer_->OnHierarchyChanged()->AddHandler(
MakeCallback<IOnHierarchyChanged>(this, &StartableObjectController::HierarchyChanged));
META_ACCESS_PROPERTY(StartBehavior)->OnChanged()->AddHandler(MakeCallback<IOnChanged>([this]() {
if (META_ACCESS_PROPERTY_VALUE(StartBehavior) == StartBehavior::AUTOMATIC) {
StartAll(ControlBehavior::CONTROL_AUTOMATIC);
}
}));
defaultTickerQueue_ = META_NS::GetObjectRegistry().Create<ITaskQueue>(ClassId::ThreadedTaskQueue);
CORE_ASSERT(defaultTickerQueue_);
tickerTask_ = META_NS::MakeCallback<ITaskQueueTask>([this]() {
TickAll(clock_->GetTime());
return true;
});
CORE_ASSERT(tickerTask_);
META_ACCESS_PROPERTY(TickInterval)
->OnChanged()
->AddHandler(MakeCallback<IOnChanged>(this, &StartableObjectController::UpdateTicker));
META_ACCESS_PROPERTY(TickOrder)->OnChanged()->AddHandler(
MakeCallback<IOnChanged>(this, &StartableObjectController::InvalidateTickables));
UpdateTicker();
return true;
}
void StartableObjectController::Destroy()
{
if (tickerQueue_ && tickerToken_) {
tickerQueue_->CancelTask(tickerToken_);
}
InvalidateTickables();
SetTarget({}, {});
observer_.reset();
Super::Destroy();
}
bool StartableObjectController::SetStartableQueueId(
const BASE_NS::Uid& startStartableQueueId, const BASE_NS::Uid& stopStartableQueueId)
{
startQueue_.reset();
stopQueue_.reset();
startQueueId_ = startStartableQueueId;
stopQueueId_ = stopStartableQueueId;
return true;
}
bool StartableObjectController::SetStartableQueue(
const META_NS::ITaskQueue::Ptr& startStartableQueue, const META_NS::ITaskQueue::Ptr& stopStartableQueue)
{
startQueue_ = startStartableQueue;
stopQueue_ = stopStartableQueue;
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<IObject::Ptr> StartableObjectController::GetAllObserved() const
{
return observer_->GetAllObserved();
}
META_NS::ITaskQueue::Ptr StartableObjectController::GetStartQueue() const
{
auto queue = startQueue_.lock();
if (!queue && startQueueId_ != BASE_NS::Uid{}) {
queue = META_NS::GetTaskQueueRegistry().GetTaskQueue(startQueueId_);
if (!queue) {
CORE_LOG_W("Cannot get task queue '%s', tasks will be run synchronously",
BASE_NS::to_string(startQueueId_).c_str());
}
startQueue_ = queue;
}
return queue;
}
META_NS::ITaskQueue::Ptr StartableObjectController::GetStopQueue() const
{
auto queue = stopQueue_.lock();
if (!queue && stopQueueId_ != BASE_NS::Uid{}) {
queue = META_NS::GetTaskQueueRegistry().GetTaskQueue(stopQueueId_);
if (!queue) {
CORE_LOG_W("Cannot get task queue '%s', tasks will be run synchronously",
BASE_NS::to_string(stopQueueId_).c_str());
}
stopQueue_ = queue;
}
return queue;
}
bool StartableObjectController::StartAll(ControlBehavior behavior)
{
if (const auto root = target_.lock()) {
return AddOperation({StartableOperation::START, target_}, GetStartQueue());
}
return false;
}
bool StartableObjectController::StopAll(ControlBehavior behavior)
{
if (auto root = target_.lock()) {
return AddOperation({StartableOperation::STOP, target_}, GetStopQueue());
}
return false;
}
template <class T, class Callback>
void IterateChildren(const BASE_NS::vector<T>& 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 <class Callback>
void IterateHierarchy(const IObject::Ptr& root, bool reverse, Callback&& callback)
{
if (const auto container = interface_cast<IContainer>(root)) {
IterateChildren(container->GetAll(), reverse, callback);
}
if (const auto content = interface_cast<IContent>(root)) {
if (auto object = GetValue(content->Content())) {
callback(object);
}
}
}
template <class ObjectType, class Callback>
void IterateAttachments(const IObject::Ptr& object, bool reverse, Callback&& callback)
{
if (const auto attach = interface_cast<IAttach>(object)) {
if (auto container = attach->GetAttachmentContainer(false)) {
IterateChildren(container->GetAll<ObjectType>(), reverse, BASE_NS::forward<Callback>(callback));
}
}
}
template <class Callback>
void IterateStartables(const IObject::Ptr& object, bool reverse, Callback&& callback)
{
IterateAttachments<IStartable, Callback>(object, reverse, BASE_NS::forward<Callback>(callback));
}
template <class Callback>
void IterateTickables(const IObject::Ptr& object, TraversalType order, Callback&& callback)
{
if (!object) {
return;
}
bool rootFirst = order != TraversalType::DEPTH_FIRST_POST_ORDER;
if (rootFirst) {
IterateAttachments<ITickable, Callback>(object, false, BASE_NS::forward<Callback>(callback));
}
IterateShared(
object,
[&callback](const IObject::Ptr& object) {
IterateAttachments<ITickable, Callback>(object, false, callback);
return true;
},
order);
if (!rootFirst) {
IterateAttachments<ITickable, Callback>(object, false, BASE_NS::forward<Callback>(callback));
}
}
void StartableObjectController::HierarchyChanged(const HierarchyChangedInfo& info)
{
if (info.change == HierarchyChangeType::ADDED || info.change == HierarchyChangeType::REMOVING ||
info.change == HierarchyChangeType::MOVED) {
if (info.objectType != HierarchyChangeObjectType::ATTACHMENT || info.object->GetInterface<IStartable>()) {
InvalidateTickables();
if (info.change == HierarchyChangeType::ADDED) {
AddOperation({StartableOperation::START, info.object}, GetStartQueue());
} else if (info.change == HierarchyChangeType::REMOVING) {
AddOperation({StartableOperation::STOP, info.object}, GetStopQueue());
}
}
}
}
BASE_NS::vector<IStartable::Ptr> StartableObjectController::GetAllStartables() const
{
BASE_NS::vector<IStartable::Ptr> 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); });
IterateStartables(
root, false, [this, behavior](const IStartable::Ptr& startable) { StartStartable(startable.get(), behavior); });
StartStartable(interface_cast<IStartable>(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<IStartable>(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 ITaskQueue::Ptr& queue) const
{
std::shared_lock lock(mutex_);
if (auto it = operations_.find(queue.get()); it != operations_.end()) {
return !it->second.empty();
}
return false;
}
void StartableObjectController::RunTasks(const ITaskQueue::Ptr& queue)
{
BASE_NS::vector<StartableOperation> operations;
{
std::unique_lock lock(mutex_);
if (auto it = operations_.find(queue.get()); it != operations_.end()) {
operations.swap(it->second);
}
}
for (auto&& op : operations) {
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 ITaskQueue::Ptr& queue)
{
if (!HasTasks(queue)) {
return true;
}
auto task = [q = ITaskQueue::WeakPtr{queue},
internal =
IStartableObjectControllerInternal::WeakPtr{GetSelf<IStartableObjectControllerInternal>()}]() {
auto me = internal.lock();
if (me) {
me->RunTasks(q.lock());
}
};
if (queue && !executingStart_) {
queue->AddWaitableTask(CreateWaitableTask(BASE_NS::move(task)));
return true;
}
task();
return true;
}
bool StartableObjectController::AddOperation(StartableOperation&& operation, const ITaskQueue::Ptr& queue)
{
auto object = operation.root_.lock();
if (!object) {
return false;
}
{
std::unique_lock lock(mutex_);
auto& ops = operations_[queue.get()];
for (auto it = ops.begin(); it != ops.end(); ++it) {
if ((*it).root_.lock() == object) {
ops.erase(it);
break;
}
}
ops.emplace_back(BASE_NS::move(operation));
}
return ProcessOps(queue);
}
void StartableObjectController::InvalidateTickables()
{
std::unique_lock lock(mutex_);
tickables_.clear();
tickablesValid_ = false;
}
BASE_NS::vector<ITickable::Ptr> StartableObjectController::GetTickables() const
{
BASE_NS::vector<ITickable::WeakPtr> 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<ITickable::Ptr> 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<IStartable>(tickable)) {
shouldTick = GetValue(st->StartableState()) == StartableState::STARTED;
}
if (shouldTick) {
tickable->Tick(time, sinceLast);
}
}
}
lastTick_ = time;
}
META_END_NAMESPACE()