blob: 04c1f6c360bf114b4fd7e264179d7488cf3bd395 [file] [log] [blame]
// Copyright 2019 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 "src/ui/scenic/lib/flatland/flatland.h"
#include <lib/zx/eventpair.h>
#include <memory>
#include "src/lib/fxl/logging.h"
using fuchsia::ui::scenic::internal::ContentLink;
using fuchsia::ui::scenic::internal::ContentLinkStatus;
using fuchsia::ui::scenic::internal::ContentLinkToken;
using fuchsia::ui::scenic::internal::Error;
using fuchsia::ui::scenic::internal::GraphLink;
using fuchsia::ui::scenic::internal::GraphLinkToken;
using fuchsia::ui::scenic::internal::LinkProperties;
using fuchsia::ui::scenic::internal::Vec2;
namespace flatland {
Flatland::Flatland(const std::shared_ptr<LinkSystem>& link_system,
const std::shared_ptr<UberStructSystem>& uber_struct_system)
: link_system_(link_system),
uber_struct_system_(uber_struct_system),
instance_id_(uber_struct_system_->GetNextInstanceId()),
transform_graph_(instance_id_),
local_root_(transform_graph_.CreateTransform()) {}
Flatland::~Flatland() { uber_struct_system_->ClearUberStruct(instance_id_); }
void Flatland::Present(PresentCallback callback) {
bool success = true;
// TODO(36161): Don't execute operations until the (yet to be added) acquire fences have been
// reached.
for (auto& operation : pending_operations_) {
if (!operation()) {
success = false;
break;
}
}
pending_operations_.clear();
auto root_handle = GetRoot();
// TODO(40818): Decide on a proper limit on compute time for topological sorting.
auto data = transform_graph_.ComputeAndCleanup(root_handle, std::numeric_limits<uint64_t>::max());
FXL_DCHECK(data.iterations != std::numeric_limits<uint64_t>::max());
// TODO(36166): Once the 2D scene graph is externalized, don't commit changes if a cycle is
// detected. Instead, kill the channel and remove the sub-graph from the global graph.
success &= data.cyclical_edges.empty();
if (success) {
FXL_DCHECK(data.sorted_transforms[0].handle == root_handle);
auto uber_struct = std::make_unique<UberStruct>();
uber_struct->local_topology = std::move(data.sorted_transforms);
uber_struct_system_->SetUberStruct(instance_id_, std::move(uber_struct));
// TODO(36161): Once present operations can be pipelined, this variable will change state based
// on the number of outstanding Present calls. Until then, this call is synchronous, and we can
// always return 1 as the number of remaining presents.
callback(fit::ok(num_presents_remaining_));
} else {
callback(fit::error(Error::BAD_OPERATION));
}
}
void Flatland::LinkToParent(GraphLinkToken token, fidl::InterfaceRequest<GraphLink> graph_link) {
// Attempting to link with an invalid token will never succeed, so its better to fail early and
// immediately close the link connection.
if (!token.value.is_valid()) {
pending_operations_.push_back([]() {
FXL_LOG(ERROR) << "LinkToParent failed, GraphLinkToken was invalid";
return false;
});
return;
}
FXL_DCHECK(link_system_);
// This portion of the method is not feed forward. This makes it possible for clients to receive
// layout information before this operation has been presented. By initializing the link
// immediately, parents can inform children of layout changes, and child clients can perform
// layout decisions before their first call to Present().
auto link_origin = transform_graph_.CreateTransform();
LinkSystem::ParentLink link =
link_system_->CreateParentLink(std::move(token), std::move(graph_link), link_origin);
// This portion of the method is feed-forward. Our Link should not actually be changed until
// Present() is called, so that the update to the Link is atomic with all other operations in the
// batch. The parent-child relationship between |link_origin| and |local_root_| establishes the
// transform hierarchy between the two instances.
pending_operations_.push_back([this, link = std::move(link)]() mutable {
if (parent_link_) {
bool child_removed = transform_graph_.RemoveChild(parent_link_->link_origin, local_root_);
FXL_DCHECK(child_removed);
bool transform_released = transform_graph_.ReleaseTransform(parent_link_->link_origin);
FXL_DCHECK(transform_released);
}
bool child_added = transform_graph_.AddChild(link.link_origin, local_root_);
FXL_DCHECK(child_added);
parent_link_ = std::move(link);
return true;
});
}
void Flatland::UnlinkFromParent(
fuchsia::ui::scenic::internal::Flatland::UnlinkFromParentCallback callback) {
pending_operations_.push_back([this, callback = std::move(callback)]() {
if (!parent_link_) {
FXL_LOG(ERROR) << "UnlinkFromParent failed, no existing parent link";
return false;
}
GraphLinkToken return_token;
// If the link is still valid, return the original token. If not, create an orphaned
// zx::eventpair and return it since the ObjectLinker does not retain the orphaned token.
auto link_token = parent_link_->exporter.ReleaseToken();
if (link_token.has_value()) {
return_token.value = zx::eventpair(std::move(link_token.value()));
} else {
// |peer_token| immediately falls out of scope, orphaning |return_token|.
zx::eventpair peer_token;
zx::eventpair::create(0, &return_token.value, &peer_token);
}
bool child_removed = transform_graph_.RemoveChild(parent_link_->link_origin, local_root_);
FXL_DCHECK(child_removed);
bool transform_released = transform_graph_.ReleaseTransform(parent_link_->link_origin);
FXL_DCHECK(transform_released);
parent_link_.reset();
callback(std::move(return_token));
return true;
});
}
void Flatland::ClearGraph() {
pending_operations_.push_back([=]() {
transforms_.clear();
// We always preserve the link origin when clearing the graph.
transform_graph_.ResetGraph(local_root_);
child_links_.clear();
parent_link_.reset();
return true;
});
}
void Flatland::CreateTransform(TransformId transform_id) {
pending_operations_.push_back([=]() {
if (transform_id == kInvalidId) {
FXL_LOG(ERROR) << "CreateTransform called with transform_id 0";
return false;
}
if (transforms_.count(transform_id)) {
FXL_LOG(ERROR) << "CreateTransform called with pre-existing transform_id " << transform_id;
return false;
}
TransformHandle handle = transform_graph_.CreateTransform();
transforms_.insert({transform_id, handle});
return true;
});
}
void Flatland::AddChild(TransformId parent_transform_id, TransformId child_transform_id) {
pending_operations_.push_back([=]() {
if (parent_transform_id == kInvalidId || child_transform_id == kInvalidId) {
FXL_LOG(ERROR) << "AddChild called with transform_id zero";
return false;
}
auto parent_global_kv = transforms_.find(parent_transform_id);
auto child_global_kv = transforms_.find(child_transform_id);
if (parent_global_kv == transforms_.end()) {
FXL_LOG(ERROR) << "AddChild failed, parent_transform_id " << parent_transform_id
<< " not found";
return false;
}
if (child_global_kv == transforms_.end()) {
FXL_LOG(ERROR) << "AddChild failed, child_transform_id " << child_transform_id
<< " not found";
return false;
}
bool added = transform_graph_.AddChild(parent_global_kv->second, child_global_kv->second);
if (!added) {
FXL_LOG(ERROR) << "AddChild failed, connection already exists between parent "
<< parent_transform_id << " and child " << child_transform_id;
}
return added;
});
}
void Flatland::RemoveChild(TransformId parent_transform_id, TransformId child_transform_id) {
pending_operations_.push_back([=]() {
if (parent_transform_id == kInvalidId || child_transform_id == kInvalidId) {
FXL_LOG(ERROR) << "RemoveChild failed, transform_id " << parent_transform_id << " not found";
return false;
}
auto parent_global_kv = transforms_.find(parent_transform_id);
auto child_global_kv = transforms_.find(child_transform_id);
if (parent_global_kv == transforms_.end()) {
FXL_LOG(ERROR) << "RemoveChild failed, parent_transform_id " << parent_transform_id
<< " not found";
return false;
}
if (child_global_kv == transforms_.end()) {
FXL_LOG(ERROR) << "RemoveChild failed, child_transform_id " << child_transform_id
<< " not found";
return false;
}
bool removed = transform_graph_.RemoveChild(parent_global_kv->second, child_global_kv->second);
if (!removed) {
FXL_LOG(ERROR) << "RemoveChild failed, connection between parent " << parent_transform_id
<< " and child " << child_transform_id << " not found";
}
return removed;
});
}
void Flatland::SetRootTransform(TransformId transform_id) {
pending_operations_.push_back([=]() {
transform_graph_.ClearChildren(local_root_);
// SetRootTransform(0) is special -- it only clears the existing root transform.
if (transform_id == kInvalidId) {
return true;
}
auto global_kv = transforms_.find(transform_id);
if (global_kv == transforms_.end()) {
FXL_LOG(ERROR) << "SetRootTransform failed, transform_id " << transform_id << " not found";
return false;
}
bool added = transform_graph_.AddChild(local_root_, global_kv->second);
FXL_DCHECK(added);
return true;
});
}
void Flatland::CreateLink(LinkId link_id, ContentLinkToken token, LinkProperties properties,
fidl::InterfaceRequest<ContentLink> content_link) {
// Attempting to link with an invalid token will never succeed, so its better to fail early and
// immediately close the link connection.
if (!token.value.is_valid()) {
pending_operations_.push_back([]() {
FXL_LOG(ERROR) << "CreateLink failed, ContentLinkToken was invalid";
return false;
});
return;
}
if (!properties.has_logical_size()) {
pending_operations_.push_back([]() {
FXL_LOG(ERROR) << "CreateLink must be provided with LinkProperties with a logical size.";
return false;
});
return;
}
FXL_DCHECK(link_system_);
// The LinkProperties and ContentLinkImpl live on a handle from this Flatland instance.
// TODO(44334): Make it mandatory for data to live on a handle from the Flatland instance that
// authored it.
auto graph_handle = transform_graph_.CreateTransform();
// We can initialize the link importer immediately, since no state changes actually occur before
// the feed-forward portion of this method. We also forward the initial LinkProperties through
// the LinkSystem immediately, so the child can receive them as soon as possible.
LinkProperties initial_properties;
fidl::Clone(properties, &initial_properties);
LinkSystem::ChildLink link = link_system_->CreateChildLink(
std::move(token), std::move(initial_properties), std::move(content_link), graph_handle);
// This is the feed-forward portion of the method. Here, we add the link to the map, and
// initialize its layout with the desired properties. The link will not actually result in
// additions to the transform hierarchy until it is added to a Transform.
pending_operations_.push_back(
[=, link = std::move(link), properties = std::move(properties)]() mutable {
if (link_id == 0) {
return false;
}
if (child_links_.count(link_id)) {
return false;
}
// The link system expects some parent of a ChildLink TransformHandle to have LinkProperties
// attached to it. Even though we've already sent the initial LinkProperties, we set them
// here as well to aid in LinkSystem's evaluation of the global topology.
//
// TODO(44334): Move this data into the UberStruct.
link_system_->SetLinkProperties(link.graph_handle, std::move(properties));
bool child_added = transform_graph_.AddChild(link.graph_handle, link.link_handle);
FXL_DCHECK(child_added);
child_links_[link_id] = std::move(link);
return true;
});
}
void Flatland::SetLinkOnTransform(LinkId link_id, TransformId transform_id) {
pending_operations_.push_back([=]() {
if (transform_id == kInvalidId) {
FXL_LOG(ERROR) << "SetLinkOnTransform called with transform_id zero";
return false;
}
auto transform_kv = transforms_.find(transform_id);
if (transform_kv == transforms_.end()) {
FXL_LOG(ERROR) << "ReleaseTransform failed, transform_id " << transform_id << " not found";
return false;
}
if (link_id == 0) {
transform_graph_.ClearPriorityChild(transform_kv->second);
return true;
}
auto link_kv = child_links_.find(link_id);
if (link_kv == child_links_.end()) {
FXL_LOG(ERROR) << "SetLinkOnTransform failed, link_id " << link_id << " not found";
return false;
}
transform_graph_.SetPriorityChild(transform_kv->second, link_kv->second.graph_handle);
return true;
});
}
void Flatland::SetLinkProperties(LinkId id, LinkProperties properties) {
pending_operations_.push_back([=, properties = std::move(properties)]() mutable {
if (id == 0) {
return false;
}
auto link_kv = child_links_.find(id);
if (link_kv == child_links_.end()) {
return false;
}
FXL_DCHECK(link_kv->second.importer.valid());
// TODO(44334): Replace this function call with a unified structure update for all Flatland
// instance-local data (similar to and merged with the local topology update).
link_system_->SetLinkProperties(link_kv->second.graph_handle, std::move(properties));
return true;
});
}
void Flatland::ReleaseTransform(TransformId transform_id) {
pending_operations_.push_back([=]() {
if (transform_id == kInvalidId) {
FXL_LOG(ERROR) << "ReleaseTransform called with transform_id zero";
return false;
}
auto iter = transforms_.find(transform_id);
if (iter == transforms_.end()) {
FXL_LOG(ERROR) << "ReleaseTransform failed, transform_id " << transform_id << " not found";
return false;
}
bool erased_from_graph = transform_graph_.ReleaseTransform(iter->second);
FXL_DCHECK(erased_from_graph);
transforms_.erase(iter);
return true;
});
}
void Flatland::ReleaseLink(LinkId link_id,
fuchsia::ui::scenic::internal::Flatland::ReleaseLinkCallback callback) {
pending_operations_.push_back([this, link_id, callback = std::move(callback)]() {
if (link_id == 0) {
FXL_LOG(ERROR) << "ReleaseLink called with link_id zero";
return false;
}
auto link_kv = child_links_.find(link_id);
if (link_kv == child_links_.end()) {
FXL_LOG(ERROR) << "ReleaseLink failed, link_id " << link_id << " not found";
return false;
}
ContentLinkToken return_token;
// If the link is still valid, return the original token. If not, create an orphaned
// zx::eventpair and return it since the ObjectLinker does not retain the orphaned token.
auto link_token = link_kv->second.importer.ReleaseToken();
if (link_token.has_value()) {
return_token.value = zx::eventpair(std::move(link_token.value()));
} else {
// |peer_token| immediately falls out of scope, orphaning |return_token|.
zx::eventpair peer_token;
zx::eventpair::create(0, &return_token.value, &peer_token);
}
bool child_removed =
transform_graph_.RemoveChild(link_kv->second.graph_handle, link_kv->second.link_handle);
FXL_DCHECK(child_removed);
bool content_released = transform_graph_.ReleaseTransform(link_kv->second.graph_handle);
FXL_DCHECK(content_released);
child_links_.erase(link_id);
callback(std::move(return_token));
return true;
});
}
TransformHandle Flatland::GetRoot() const {
return parent_link_ ? parent_link_->link_origin : local_root_;
}
} // namespace flatland