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