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