1/* 2 * Copyright (c) 2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15#include "startable_object_controller.h" 16 17#include <meta/api/future.h> 18#include <meta/api/iteration.h> 19#include <meta/api/make_callback.h> 20#include <meta/api/task.h> 21#include <meta/api/util.h> 22#include <meta/interface/intf_content.h> 23#include <meta/interface/intf_task_queue_registry.h> 24#include <meta/interface/property/property_events.h> 25 26META_BEGIN_NAMESPACE() 27 28bool StartableObjectController::Build(const IMetadata::Ptr& data) 29{ 30 auto& reg = GetObjectRegistry(); 31 observer_ = reg.Create<IObjectHierarchyObserver>(ClassId::ObjectHierarchyObserver); 32 CORE_ASSERT(observer_); 33 clock_ = reg.Create<IClock>(ClassId::SystemClock); 34 CORE_ASSERT(clock_); 35 36 observer_->OnHierarchyChanged()->AddHandler( 37 MakeCallback<IOnHierarchyChanged>(this, &StartableObjectController::HierarchyChanged)); 38 39 META_ACCESS_PROPERTY(StartBehavior)->OnChanged()->AddHandler(MakeCallback<IOnChanged>([this]() { 40 if (META_ACCESS_PROPERTY_VALUE(StartBehavior) == StartBehavior::AUTOMATIC) { 41 // If StartBehavior changes to AUTOMATIC, start all AUTOMATIC startables 42 StartAll(ControlBehavior::CONTROL_AUTOMATIC); 43 } 44 })); 45 46 defaultTickerQueue_ = META_NS::GetObjectRegistry().Create<ITaskQueue>(ClassId::ThreadedTaskQueue); 47 CORE_ASSERT(defaultTickerQueue_); 48 tickerTask_ = META_NS::MakeCallback<ITaskQueueTask>([this]() { 49 TickAll(clock_->GetTime()); 50 return true; // Recurring 51 }); 52 CORE_ASSERT(tickerTask_); 53 54 META_ACCESS_PROPERTY(TickInterval) 55 ->OnChanged() 56 ->AddHandler(MakeCallback<IOnChanged>(this, &StartableObjectController::UpdateTicker)); 57 META_ACCESS_PROPERTY(TickOrder)->OnChanged()->AddHandler( 58 MakeCallback<IOnChanged>(this, &StartableObjectController::InvalidateTickables)); 59 UpdateTicker(); 60 return true; 61} 62 63void StartableObjectController::Destroy() 64{ 65 if (tickerQueue_ && tickerToken_) { 66 tickerQueue_->CancelTask(tickerToken_); 67 } 68 InvalidateTickables(); 69 SetTarget({}, {}); 70 observer_.reset(); 71} 72 73bool StartableObjectController::SetStartableQueueId( 74 const BASE_NS::Uid& startStartableQueueId, const BASE_NS::Uid& stopStartableQueueId) 75{ 76 startQueueId_ = startStartableQueueId; 77 stopQueueId_ = stopStartableQueueId; 78 return true; 79} 80 81void StartableObjectController::SetTarget(const IObject::Ptr& hierarchyRoot, HierarchyChangeModeValue mode) 82{ 83 if (!observer_) { 84 return; 85 } 86 InvalidateTickables(); 87 target_ = hierarchyRoot; 88 bool automatic = META_ACCESS_PROPERTY_VALUE(StartBehavior) == StartBehavior::AUTOMATIC; 89 if (automatic && !hierarchyRoot) { 90 StopAll(ControlBehavior::CONTROL_AUTOMATIC); 91 } 92 observer_->SetTarget(hierarchyRoot, mode); 93 if (automatic && hierarchyRoot) { 94 StartAll(ControlBehavior::CONTROL_AUTOMATIC); 95 } 96} 97 98IObject::Ptr StartableObjectController::GetTarget() const 99{ 100 return observer_->GetTarget(); 101} 102 103BASE_NS::vector<IObject::Ptr> StartableObjectController::GetAllObserved() const 104{ 105 return observer_->GetAllObserved(); 106} 107 108bool StartableObjectController::StartAll(ControlBehavior behavior) 109{ 110 if (const auto root = target_.lock()) { 111 return AddOperation({ StartableOperation::START, target_ }, startQueueId_); 112 } 113 return false; 114} 115 116bool StartableObjectController::StopAll(ControlBehavior behavior) 117{ 118 if (auto root = target_.lock()) { 119 return AddOperation({ StartableOperation::STOP, target_ }, stopQueueId_); 120 } 121 return false; 122} 123 124template<class T, class Callback> 125void IterateChildren(const BASE_NS::vector<T>& children, bool reverse, Callback&& callback) 126{ 127 if (reverse) { 128 for (auto it = children.rbegin(); it != children.rend(); ++it) { 129 callback(*it); 130 } 131 } else { 132 for (auto&& child : children) { 133 callback(child); 134 } 135 } 136} 137 138template<class Callback> 139void IterateHierarchy(const IObject::Ptr& root, bool reverse, Callback&& callback) 140{ 141 if (const auto container = interface_cast<IContainer>(root)) { 142 IterateChildren(container->GetAll(), reverse, callback); 143 } 144 if (const auto content = interface_cast<IContent>(root)) { 145 if (auto object = GetValue(content->Content())) { 146 callback(object); 147 } 148 } 149} 150 151template<class ObjectType, class Callback> 152void IterateAttachments(const IObject::Ptr& object, bool reverse, Callback&& callback) 153{ 154 if (const auto attach = interface_cast<IAttach>(object)) { 155 if (auto container = attach->GetAttachmentContainer(false)) { 156 IterateChildren(container->GetAll<ObjectType>(), reverse, BASE_NS::forward<Callback>(callback)); 157 } 158 } 159} 160 161template<class Callback> 162void IterateStartables(const IObject::Ptr& object, bool reverse, Callback&& callback) 163{ 164 IterateAttachments<IStartable, Callback>(object, reverse, BASE_NS::forward<Callback>(callback)); 165} 166 167template<class Callback> 168void IterateTickables(const IObject::Ptr& object, TraversalType order, Callback&& callback) 169{ 170 if (!object) { 171 return; 172 } 173 bool rootFirst = order != TraversalType::DEPTH_FIRST_POST_ORDER; 174 if (rootFirst) { 175 IterateAttachments<ITickable, Callback>(object, false, BASE_NS::forward<Callback>(callback)); 176 } 177 IterateShared( 178 object, 179 [&callback](const IObject::Ptr& object) { 180 IterateAttachments<ITickable, Callback>(object, false, callback); 181 return true; 182 }, 183 order); 184 if (!rootFirst) { 185 IterateAttachments<ITickable, Callback>(object, false, BASE_NS::forward<Callback>(callback)); 186 } 187} 188 189void StartableObjectController::HierarchyChanged(const HierarchyChangedInfo& info) 190{ 191 if (info.change == HierarchyChangeType::ADDED || info.change == HierarchyChangeType::REMOVING || 192 info.change == HierarchyChangeType::MOVED) { 193 // Any hierarchy change (add/remove/move) invalidates the tick order 194 InvalidateTickables(); 195 if (info.change == HierarchyChangeType::ADDED) { 196 AddOperation({ StartableOperation::START, info.object }, startQueueId_); 197 } else if (info.change == HierarchyChangeType::REMOVING) { 198 AddOperation({ StartableOperation::STOP, info.object }, stopQueueId_); 199 } 200 } 201} 202 203BASE_NS::vector<IStartable::Ptr> StartableObjectController::GetAllStartables() const 204{ 205 BASE_NS::vector<IStartable::Ptr> startables; 206 auto add = [&startables](const IStartable::Ptr& startable) { startables.push_back(startable); }; 207 if (const auto root = target_.lock()) { 208 IterateStartables(root, false, add); 209 IterateShared( 210 root, 211 [&add](const IObject::Ptr& object) { 212 IterateStartables(object, false, add); 213 return true; 214 }, 215 TraversalType::DEPTH_FIRST_POST_ORDER); 216 } 217 return startables; 218} 219 220void StartableObjectController::StartHierarchy(const IObject::Ptr& root, ControlBehavior behavior) 221{ 222 const auto traversal = META_ACCESS_PROPERTY_VALUE(TraversalType); 223 if (traversal != TraversalType::DEPTH_FIRST_POST_ORDER && traversal != TraversalType::FULL_HIERARCHY) { 224 CORE_LOG_E("Only DEPTH_FIRST_POST_ORDER is supported"); 225 } 226 227 if (!root) { 228 return; 229 } 230 231 IterateHierarchy(root, false, [this, behavior](const IObject::Ptr& object) { StartHierarchy(object, behavior); }); 232 233 // Don't traverse hierarchy for attachments 234 IterateStartables( 235 root, false, [this, behavior](const IStartable::Ptr& startable) { StartStartable(startable.get(), behavior); }); 236 237 StartStartable(interface_cast<IStartable>(root), behavior); 238} 239 240void StartableObjectController::StartStartable(IStartable* const startable, ControlBehavior behavior) 241{ 242 if (startable) { 243 const auto state = GetValue(startable->StartableState()); 244 if (state == StartableState::ATTACHED) { 245 const auto mode = GetValue(startable->StartableMode()); 246 if (behavior == ControlBehavior::CONTROL_ALL || mode == StartBehavior::AUTOMATIC) { 247 startable->Start(); 248 } 249 } 250 } 251} 252 253void StartableObjectController::StopHierarchy(const IObject::Ptr& root, ControlBehavior behavior) 254{ 255 const auto traversal = META_ACCESS_PROPERTY_VALUE(TraversalType); 256 if (traversal != TraversalType::DEPTH_FIRST_POST_ORDER && traversal != TraversalType::FULL_HIERARCHY) { 257 CORE_LOG_E("Only DEPTH_FIRST_POST_ORDER is supported"); 258 } 259 if (!root) { 260 return; 261 } 262 263 StopStartable(interface_cast<IStartable>(root), behavior); 264 265 IterateStartables( 266 root, true, [this, behavior](const IStartable::Ptr& startable) { StopStartable(startable.get(), behavior); }); 267 268 IterateHierarchy(root, true, [this, behavior](const IObject::Ptr& object) { StopHierarchy(object, behavior); }); 269} 270 271void StartableObjectController::StopStartable(IStartable* const startable, ControlBehavior behavior) 272{ 273 if (startable) { 274 const auto state = GetValue(startable->StartableState()); 275 if (state == StartableState::STARTED) { 276 const auto mode = GetValue(startable->StartableMode()); 277 if (behavior == ControlBehavior::CONTROL_ALL || mode == StartBehavior::AUTOMATIC) { 278 startable->Stop(); 279 } 280 } 281 } 282} 283 284bool StartableObjectController::HasTasks(const BASE_NS::Uid& queueId) const 285{ 286 std::shared_lock lock(mutex_); 287 if (auto it = operations_.find(queueId); it != operations_.end()) { 288 return !it->second.empty(); 289 } 290 return false; 291} 292 293void StartableObjectController::RunTasks(const BASE_NS::Uid& queueId) 294{ 295 BASE_NS::vector<StartableOperation> operations; 296 { 297 std::unique_lock lock(mutex_); 298 // Take tasks for the given queue id 299 if (auto it = operations_.find(queueId); it != operations_.end()) { 300 operations.swap(it->second); 301 } 302 } 303 for (auto&& op : operations) { 304 // This may potentially end up calling Start/StopHierarchy several times 305 // for the same subtrees, but we will accept that. Start/Stop will only 306 // be called once since the functions check for current state. 307 if (auto root = op.root_.lock()) { 308 switch (op.operation_) { 309 case StartableOperation::START: 310 ++executingStart_; 311 StartHierarchy(root, ControlBehavior::CONTROL_AUTOMATIC); 312 --executingStart_; 313 break; 314 case StartableOperation::STOP: 315 StopHierarchy(root, ControlBehavior::CONTROL_AUTOMATIC); 316 break; 317 default: 318 break; 319 } 320 } 321 } 322} 323 324bool StartableObjectController::ProcessOps(const BASE_NS::Uid& queueId) 325{ 326 if (!HasTasks(queueId)) { 327 // No tasks for the given queue, bail out 328 return true; 329 } 330 331 auto task = [queueId, internal = IStartableObjectControllerInternal::WeakPtr { 332 GetSelf<IStartableObjectControllerInternal>() }]() { 333 if (auto me = internal.lock()) { 334 me->RunTasks(queueId); 335 } 336 }; 337 338 if (queueId != BASE_NS::Uid {} && !executingStart_) { 339 if (auto queue = GetTaskQueueRegistry().GetTaskQueue(queueId)) { 340 queue->AddWaitableTask(CreateWaitableTask(BASE_NS::move(task))); 341 return true; 342 } 343 CORE_LOG_W("Cannot get task queue '%s'. Running the task synchronously.", BASE_NS::to_string(queueId).c_str()); 344 } 345 // Just run the task immediately if we don't have a queue to defer it to 346 task(); 347 return true; 348} 349 350bool StartableObjectController::AddOperation(StartableOperation&& operation, const BASE_NS::Uid& queueId) 351{ 352 auto object = operation.root_.lock(); 353 if (!object) { 354 return false; 355 } 356 // Note that queueId may be {}, but it is still a valid key for our queue map 357 { 358 std::unique_lock lock(mutex_); 359 auto& ops = operations_[queueId]; 360 for (auto it = ops.begin(); it != ops.end(); ++it) { 361 // If we already have an operation in queue for a given object, cancel the existing operation 362 // and just add the new one 363 if ((*it).root_.lock() == object) { 364 ops.erase(it); 365 break; 366 } 367 } 368 ops.emplace_back(BASE_NS::move(operation)); 369 } 370 return ProcessOps(queueId); 371} 372 373void StartableObjectController::InvalidateTickables() 374{ 375 std::unique_lock lock(mutex_); 376 tickables_.clear(); 377 tickablesValid_ = false; 378} 379 380BASE_NS::vector<ITickable::Ptr> StartableObjectController::GetTickables() const 381{ 382 BASE_NS::vector<ITickable::WeakPtr> weaks; 383 { 384 std::unique_lock lock(tickMutex_); 385 if (!tickablesValid_) { 386 auto add = [this](const ITickable::Ptr& tickable) { tickables_.push_back(tickable); }; 387 IterateTickables(target_.lock(), META_ACCESS_PROPERTY_VALUE(TickOrder), add); 388 tickablesValid_ = true; 389 } 390 weaks = tickables_; 391 } 392 BASE_NS::vector<ITickable::Ptr> tickables; 393 tickables.reserve(weaks.size()); 394 for (auto&& t : weaks) { 395 if (auto tick = t.lock()) { 396 tickables.emplace_back(BASE_NS::move(tick)); 397 } 398 } 399 return tickables; 400} 401 402void StartableObjectController::UpdateTicker() 403{ 404 auto queue = tickQueueId_ != BASE_NS::Uid {} ? META_NS::GetTaskQueueRegistry().GetTaskQueue(tickQueueId_) 405 : defaultTickerQueue_; 406 if (tickerQueue_ && tickerToken_) { 407 tickerQueue_->CancelTask(tickerToken_); 408 tickerToken_ = {}; 409 } 410 tickerQueue_ = queue; 411 if (const auto interval = META_ACCESS_PROPERTY_VALUE(TickInterval); interval != TimeSpan::Infinite()) { 412 if (tickerQueue_) { 413 tickerToken_ = tickerQueue_->AddTask(tickerTask_, interval); 414 } else { 415 CORE_LOG_E("Invalid queue given for running ITickables: %s", BASE_NS::to_string(tickQueueId_).c_str()); 416 } 417 } 418} 419 420bool StartableObjectController::SetTickableQueueuId(const BASE_NS::Uid& queueId) 421{ 422 if (queueId != tickQueueId_) { 423 tickQueueId_ = queueId; 424 UpdateTicker(); 425 } 426 return true; 427} 428 429void StartableObjectController::TickAll(const TimeSpan& time) 430{ 431 const auto tickables = GetTickables(); 432 if (!tickables.empty()) { 433 const auto sinceLast = lastTick_ != TimeSpan::Infinite() ? time - lastTick_ : TimeSpan::Zero(); 434 for (auto&& tickable : tickables) { 435 bool shouldTick = true; 436 if (const auto st = interface_cast<IStartable>(tickable)) { 437 shouldTick = GetValue(st->StartableState()) == StartableState::STARTED; 438 } 439 if (shouldTick) { 440 tickable->Tick(time, sinceLast); 441 } 442 } 443 } 444 lastTick_ = time; 445} 446 447META_END_NAMESPACE() 448