| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "peridot/bin/sessionmgr/story/model/story_model_owner.h" |
| |
| #include <lib/fit/bridge.h> |
| #include <lib/fit/defer.h> |
| #include <lib/fxl/logging.h> |
| #include "peridot/bin/sessionmgr/story/model/apply_mutations.h" |
| #include "peridot/bin/sessionmgr/story/model/story_model_storage.h" |
| #include "peridot/bin/sessionmgr/story/model/story_mutator.h" |
| |
| using fuchsia::modular::storymodel::ModuleModel; |
| using fuchsia::modular::storymodel::StoryModel; |
| using fuchsia::modular::storymodel::StoryModelMutation; |
| |
| namespace modular { |
| |
| namespace { |
| // Sets default values for all fields of a new StoryModel. Defaults are |
| // documented in |
| // peridot/lib/fidl/public/fuchsia.modular.storymodel/story_model.fidl. |
| void InitializeModelDefaults(StoryModel* model) { |
| model->set_runtime_state(fuchsia::modular::StoryState::STOPPED); |
| model->set_visibility_state(fuchsia::modular::StoryVisibilityState::DEFAULT); |
| model->set_modules({}); |
| } |
| } // namespace |
| |
| // Delegates Execute() to the StoryModelOwner. |
| class StoryModelOwner::Mutator : public StoryMutator { |
| public: |
| Mutator(fxl::WeakPtr<StoryModelOwner> weak_owner) : weak_owner_(weak_owner) {} |
| ~Mutator() override = default; |
| |
| private: |
| // |StoryMutator| |
| fit::consumer<> ExecuteInternal( |
| std::vector<StoryModelMutation> commands) override { |
| if (!weak_owner_) { |
| fit::bridge<> bridge; |
| bridge.completer.complete_error(); |
| return std::move(bridge.consumer); |
| } |
| return weak_owner_->ExecuteCommands(std::move(commands)); |
| } |
| |
| fxl::WeakPtr<StoryModelOwner> weak_owner_; |
| }; |
| |
| // Manages the lifecycle of multiple listener callbacks. When Observer dies, |
| // all callbacks registered with RegisterListener() are unregistered from the |
| // backing StoryModelOwner. |
| class StoryModelOwner::Observer : public StoryObserver { |
| public: |
| Observer(fxl::WeakPtr<StoryModelOwner> weak_owner) |
| : weak_owner_(weak_owner) {} |
| ~Observer() { |
| // If our owner is gone, all of the listener functions have already been |
| // cleaned up. We need to cancel all the deferred actions since they |
| // capture and make a call on our owner. |
| if (!weak_owner_) { |
| for (auto& i : deferred_cleanup_) { |
| i.cancel(); |
| } |
| } |
| } |
| |
| private: |
| void RegisterListener( |
| fit::function<void(const StoryModel&)> listener) override { |
| if (!weak_owner_) { |
| return; |
| // |listener| is destroyed. |
| } |
| |
| deferred_cleanup_.push_back( |
| weak_owner_->RegisterListener(std::move(listener))); |
| } |
| |
| const StoryModel& model() override { |
| FXL_CHECK(weak_owner_); |
| return weak_owner_->model_; |
| } |
| |
| fxl::WeakPtr<StoryModelOwner> weak_owner_; |
| // When we are destroyed, we want to clean up any listeners we've added to |
| // |shared_state_->owner|. |
| std::vector<fit::deferred_action<fit::function<void()>>> deferred_cleanup_; |
| }; |
| |
| StoryModelOwner::StoryModelOwner( |
| const std::string& story_name, fit::executor* executor, |
| std::unique_ptr<StoryModelStorage> model_storage) |
| : model_storage_(std::move(model_storage)), |
| weak_ptr_factory_(this), |
| executor_(executor) { |
| FXL_CHECK(model_storage_ != nullptr); |
| model_.mutable_name()->assign(story_name); |
| InitializeModelDefaults(&model_); |
| model_storage_->SetObserveCallback( |
| [this](std::vector<StoryModelMutation> commands) { |
| HandleObservedMutations(std::move(commands)); |
| }); |
| } |
| |
| StoryModelOwner::~StoryModelOwner() = default; |
| |
| std::unique_ptr<StoryMutator> StoryModelOwner::NewMutator() { |
| return std::make_unique<Mutator>(weak_ptr_factory_.GetWeakPtr()); |
| } |
| |
| std::unique_ptr<StoryObserver> StoryModelOwner::NewObserver() { |
| return std::make_unique<Observer>(weak_ptr_factory_.GetWeakPtr()); |
| } |
| |
| void StoryModelOwner::LoadStorage() { |
| FXL_CHECK(!seen_any_requests_to_execute_) |
| << "Must call LoadStorage() before any calls to StoryMutator.Execute();"; |
| executor_->schedule_task(model_storage_->Load()); |
| } |
| |
| fit::consumer<> StoryModelOwner::FlushStorage() { |
| fit::bridge<> bridge; |
| executor_->schedule_task(model_storage_->Flush().then( |
| [completer = std::move(bridge.completer)](fit::result<>& result) mutable { |
| if (result.is_ok()) { |
| completer.complete_ok(); |
| } else { |
| completer.complete_error(); |
| } |
| })); |
| return std::move(bridge.consumer); |
| } |
| |
| fit::deferred_action<fit::function<void()>> StoryModelOwner::RegisterListener( |
| fit::function<void(const StoryModel&)> listener) { |
| auto it = listeners_.insert(listeners_.end(), std::move(listener)); |
| return fit::defer( |
| fit::function<void()>([this, it] { listeners_.erase(it); })); |
| } |
| |
| fit::consumer<> StoryModelOwner::ExecuteCommands( |
| std::vector<StoryModelMutation> commands) { |
| seen_any_requests_to_execute_ = true; |
| // fit::bridge allows this function to return (and eventually complete) a |
| // promise that is owned by the caller and still schedule a promise as a task |
| // to execute the model update locally. |
| // |
| // If the caller chooses to ignore the result, our local promise will still be |
| // scheduled and executed. |
| fit::bridge<> bridge; |
| auto promise = model_storage_->Execute(std::move(commands)) |
| .then([completer = std::move(bridge.completer)]( |
| fit::result<>& result) mutable { |
| if (result.is_ok()) { |
| completer.complete_ok(); |
| } else { |
| completer.complete_error(); |
| } |
| }); |
| |
| executor_->schedule_task(std::move(promise)); |
| return std::move(bridge.consumer); |
| } |
| |
| void StoryModelOwner::HandleObservedMutations( |
| std::vector<StoryModelMutation> commands) { |
| // This is not thread-safe. We rely on the fact that |
| // HandleObservedMutations() will only be called on a single thread. |
| StoryModel old_model; |
| FXL_CHECK(fidl::Clone(model_, &old_model) == ZX_OK); |
| model_ = ApplyMutations(old_model, std::move(commands)); |
| |
| // Don't notify anyone if the model didn't change. |
| if (model_ == old_model) { |
| return; |
| } |
| |
| executor_->schedule_task(fit::make_promise([this] { |
| for (auto& listener : listeners_) { |
| listener(model_); |
| } |
| return fit::ok(); |
| }).wrap_with(scope_)); |
| } |
| |
| } // namespace modular |