• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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