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 
META_BEGIN_NAMESPACE()26 META_BEGIN_NAMESPACE()
27 
28 bool 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 
Destroy()63 void StartableObjectController::Destroy()
64 {
65     if (tickerQueue_ && tickerToken_) {
66         tickerQueue_->CancelTask(tickerToken_);
67     }
68     InvalidateTickables();
69     SetTarget({}, {});
70     observer_.reset();
71 }
72 
SetStartableQueueId(const BASE_NS::Uid & startStartableQueueId,const BASE_NS::Uid & stopStartableQueueId)73 bool 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 
SetTarget(const IObject::Ptr & hierarchyRoot,HierarchyChangeModeValue mode)81 void 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 
GetTarget() const98 IObject::Ptr StartableObjectController::GetTarget() const
99 {
100     return observer_->GetTarget();
101 }
102 
GetAllObserved() const103 BASE_NS::vector<IObject::Ptr> StartableObjectController::GetAllObserved() const
104 {
105     return observer_->GetAllObserved();
106 }
107 
StartAll(ControlBehavior behavior)108 bool 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 
StopAll(ControlBehavior behavior)116 bool StartableObjectController::StopAll(ControlBehavior behavior)
117 {
118     if (auto root = target_.lock()) {
119         return AddOperation({ StartableOperation::STOP, target_ }, stopQueueId_);
120     }
121     return false;
122 }
123 
124 template<class T, class Callback>
IterateChildren(const BASE_NS::vector<T> & children,bool reverse,Callback && callback)125 void 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 
138 template<class Callback>
IterateHierarchy(const IObject::Ptr & root,bool reverse,Callback && callback)139 void 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 
151 template<class ObjectType, class Callback>
IterateAttachments(const IObject::Ptr & object,bool reverse,Callback && callback)152 void 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 
161 template<class Callback>
IterateStartables(const IObject::Ptr & object,bool reverse,Callback && callback)162 void IterateStartables(const IObject::Ptr& object, bool reverse, Callback&& callback)
163 {
164     IterateAttachments<IStartable, Callback>(object, reverse, BASE_NS::forward<Callback>(callback));
165 }
166 
167 template<class Callback>
IterateTickables(const IObject::Ptr & object,TraversalType order,Callback && callback)168 void 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 
HierarchyChanged(const HierarchyChangedInfo & info)189 void 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 
GetAllStartables() const203 BASE_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 
StartHierarchy(const IObject::Ptr & root,ControlBehavior behavior)220 void 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 
StartStartable(IStartable * const startable,ControlBehavior behavior)240 void 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 
StopHierarchy(const IObject::Ptr & root,ControlBehavior behavior)253 void 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 
StopStartable(IStartable * const startable,ControlBehavior behavior)271 void 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 
HasTasks(const BASE_NS::Uid & queueId) const284 bool 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 
RunTasks(const BASE_NS::Uid & queueId)293 void 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 
ProcessOps(const BASE_NS::Uid & queueId)324 bool 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 
AddOperation(StartableOperation && operation,const BASE_NS::Uid & queueId)350 bool 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 
InvalidateTickables()373 void StartableObjectController::InvalidateTickables()
374 {
375     std::unique_lock lock(mutex_);
376     tickables_.clear();
377     tickablesValid_ = false;
378 }
379 
GetTickables() const380 BASE_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 
UpdateTicker()402 void 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 
SetTickableQueueuId(const BASE_NS::Uid & queueId)420 bool StartableObjectController::SetTickableQueueuId(const BASE_NS::Uid& queueId)
421 {
422     if (queueId != tickQueueId_) {
423         tickQueueId_ = queueId;
424         UpdateTicker();
425     }
426     return true;
427 }
428 
TickAll(const TimeSpan & time)429 void 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 
447 META_END_NAMESPACE()
448