blob: 324c81ea04bfb799573a60dcc3d1a5024f1e6cd1 [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_mutator.h"
#include "peridot/bin/sessionmgr/story/model/story_model_storage.h"
using fuchsia::modular::storymodel::StoryModel;
using fuchsia::modular::storymodel::StoryModelMutation;
using fuchsia::modular::storymodel::ModuleModel;
namespace modular {
namespace {
// Sets default values for all fields of a new StoryModel.
void InitializeModelDefaults(StoryModel* model) {
model->set_visibility_state(fuchsia::modular::StoryVisibilityState::DEFAULT);
model->set_modules(fidl::VectorPtr<ModuleModel>::New(0));
}
} // 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()->reset(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());
}
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) {
// 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