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