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