blob: 362bd1d4276833b5184e4fa67217ccb91ff1c360 [file] [log] [blame]
// 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