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
26META_BEGIN_NAMESPACE()
27
28bool 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
63void StartableObjectController::Destroy()
64{
65    if (tickerQueue_ && tickerToken_) {
66        tickerQueue_->CancelTask(tickerToken_);
67    }
68    InvalidateTickables();
69    SetTarget({}, {});
70    observer_.reset();
71}
72
73bool 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
81void 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
98IObject::Ptr StartableObjectController::GetTarget() const
99{
100    return observer_->GetTarget();
101}
102
103BASE_NS::vector<IObject::Ptr> StartableObjectController::GetAllObserved() const
104{
105    return observer_->GetAllObserved();
106}
107
108bool 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
116bool StartableObjectController::StopAll(ControlBehavior behavior)
117{
118    if (auto root = target_.lock()) {
119        return AddOperation({ StartableOperation::STOP, target_ }, stopQueueId_);
120    }
121    return false;
122}
123
124template<class T, class Callback>
125void 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
138template<class Callback>
139void 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
151template<class ObjectType, class Callback>
152void 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
161template<class Callback>
162void IterateStartables(const IObject::Ptr& object, bool reverse, Callback&& callback)
163{
164    IterateAttachments<IStartable, Callback>(object, reverse, BASE_NS::forward<Callback>(callback));
165}
166
167template<class Callback>
168void 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
189void 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
203BASE_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
220void 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
240void 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
253void 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
271void 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
284bool 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
293void 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
324bool 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
350bool 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
373void StartableObjectController::InvalidateTickables()
374{
375    std::unique_lock lock(mutex_);
376    tickables_.clear();
377    tickablesValid_ = false;
378}
379
380BASE_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
402void 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
420bool StartableObjectController::SetTickableQueueuId(const BASE_NS::Uid& queueId)
421{
422    if (queueId != tickQueueId_) {
423        tickQueueId_ = queueId;
424        UpdateTicker();
425    }
426    return true;
427}
428
429void 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
447META_END_NAMESPACE()
448