blob: 23f6157aac675e33bb790c6790f0d6bf2fa4a041 [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/link_system.h"
#include <lib/syslog/cpp/macros.h>
#include <glm/gtc/matrix_access.hpp>
using fuchsia::ui::composition::ChildViewStatus;
using fuchsia::ui::composition::ChildViewWatcher;
using fuchsia::ui::composition::LayoutInfo;
using fuchsia::ui::composition::ParentViewportStatus;
using fuchsia::ui::composition::ParentViewportWatcher;
using fuchsia::ui::views::ViewCreationToken;
using fuchsia::ui::views::ViewportCreationToken;
namespace flatland {
namespace {
// Scale can be extracted from a matrix by finding the length of the
// column the scale is located in:
//
// a b c
// e f g
// i j k
//
// If |a| is the x scale and rotation, and |f| is the y scale and rotation, then
// we can calculate the x scale with length(vector(a,e,i)) and y scale with
// length(vector(b,f,j)).
glm::vec2 ComputeScale(const glm::mat3& matrix) {
const glm::vec3 x_column = glm::column(matrix, 0);
const glm::vec3 y_column = glm::column(matrix, 1);
return {glm::length(x_column), glm::length(y_column)};
}
} // namespace
LinkSystem::LinkSystem(TransformHandle::InstanceId instance_id)
: instance_id_(instance_id), link_graph_(instance_id_), linker_(ObjectLinker::New()) {}
LinkSystem::ChildLink LinkSystem::CreateChildLink(
std::shared_ptr<utils::DispatcherHolder> dispatcher_holder, ViewportCreationToken token,
fuchsia::ui::composition::ViewportProperties initial_properties,
fidl::InterfaceRequest<ChildViewWatcher> child_view_watcher,
TransformHandle parent_viewport_watcher_handle, LinkProtocolErrorCallback error_callback) {
FX_DCHECK(token.value.is_valid());
FX_DCHECK(initial_properties.has_logical_size());
auto impl = std::make_shared<ChildViewWatcherImpl>(dispatcher_holder,
std::move(child_view_watcher), error_callback);
const TransformHandle link_handle = CreateTransformLocked();
ObjectLinker::ImportLink importer = linker_->CreateImport(
ChildLinkInfo{.parent_viewport_watcher_handle = parent_viewport_watcher_handle,
.link_handle = link_handle,
.initial_logical_size = initial_properties.logical_size()},
std::move(token.value),
/* error_reporter */ nullptr);
auto child_view_watcher_map_key = std::make_shared<TransformHandle>();
importer.Initialize(
/* link_resolved = */
[ref = shared_from_this(), impl, child_view_watcher_map_key](ParentLinkInfo info) mutable {
*child_view_watcher_map_key = info.child_view_watcher_handle;
if (info.view_ref != nullptr) {
impl->SetViewRef({.reference = utils::CopyEventpair(info.view_ref->reference)});
}
std::scoped_lock lock(ref->mutex_);
ref->child_view_watcher_map_[*child_view_watcher_map_key] = impl;
},
/* link_invalidated = */
[ref = shared_from_this(), impl, child_view_watcher_map_key](bool on_link_destruction) {
// We expect |child_view_watcher_map_key| to be assigned by the "link_resolved" closure, but
// this might not happen if the link is being destroyed before it was resolved.
FX_DCHECK(child_view_watcher_map_key || on_link_destruction);
std::scoped_lock lock(ref->mutex_);
ref->child_view_watcher_map_.erase(*child_view_watcher_map_key);
},
dispatcher_holder);
return ChildLink({
.parent_viewport_watcher_handle = parent_viewport_watcher_handle,
.link_handle = link_handle,
.importer = std::move(importer),
});
}
LinkSystem::ParentLink LinkSystem::CreateParentLink(
std::shared_ptr<utils::DispatcherHolder> dispatcher_holder, ViewCreationToken token,
std::optional<fuchsia::ui::views::ViewIdentityOnCreation> view_identity,
fidl::InterfaceRequest<ParentViewportWatcher> parent_viewport_watcher,
TransformHandle child_view_watcher_handle, LinkProtocolErrorCallback error_callback) {
FX_DCHECK(token.value.is_valid());
std::shared_ptr<fuchsia::ui::views::ViewRef> view_ref;
std::optional<fuchsia::ui::views::ViewRefControl> view_ref_control;
if (view_identity.has_value()) {
view_ref = std::make_shared<fuchsia::ui::views::ViewRef>(std::move(view_identity->view_ref));
view_ref_control = std::move(view_identity->view_ref_control);
}
auto impl = std::make_shared<ParentViewportWatcherImpl>(
dispatcher_holder, std::move(parent_viewport_watcher), error_callback);
ObjectLinker::ExportLink exporter = linker_->CreateExport(
ParentLinkInfo{.child_view_watcher_handle = child_view_watcher_handle, .view_ref = view_ref},
std::move(token.value),
/* error_reporter */ nullptr);
auto parent_viewport_watcher_map_key = std::make_shared<TransformHandle>();
auto topology_map_key = std::make_shared<TransformHandle>();
exporter.Initialize(
/* link_resolved = */
[ref = shared_from_this(), impl, parent_viewport_watcher_map_key, topology_map_key,
child_view_watcher_handle](ChildLinkInfo info) {
*parent_viewport_watcher_map_key = info.parent_viewport_watcher_handle;
*topology_map_key = info.link_handle;
std::scoped_lock lock(ref->mutex_);
// TODO(fxbug.dev/80603): When the same parent relinks to different children, we might be
// using an outdated logical_size here. It will be corrected in UpdateLinks(), but we should
// figure out a way to set the previous ParentViewportWatcherImpl's size here.
LayoutInfo layout_info;
layout_info.set_logical_size(info.initial_logical_size);
layout_info.set_pixel_scale({1, 1});
impl->UpdateLayoutInfo(std::move(layout_info));
ref->parent_viewport_watcher_map_[*parent_viewport_watcher_map_key] =
ParentViewportWatcherData(
{.impl = impl, .child_link_origin = child_view_watcher_handle});
// The topology is constructed here, instead of in the link_resolved closure of the
// ParentLink object, so that its destruction (which depends on the link_handle) can occur
// on the same endpoint.
ref->link_topologies_[*topology_map_key] = child_view_watcher_handle;
},
/* link_invalidated = */
[ref = shared_from_this(), impl, parent_viewport_watcher_map_key,
topology_map_key](bool on_link_destruction) {
// We expect |parent_viewport_watcher_map_key| and |topology_map_key| to be assigned by the
// "link_resolved" closure, but this might not happen if the link is being destroyed before
// it was resolved.
FX_DCHECK((parent_viewport_watcher_map_key && topology_map_key) || on_link_destruction);
std::scoped_lock map_lock(ref->mutex_);
ref->parent_viewport_watcher_map_.erase(*parent_viewport_watcher_map_key);
ref->link_topologies_.erase(*topology_map_key);
ref->link_graph_.ReleaseTransform(*topology_map_key);
},
dispatcher_holder);
return ParentLink({.child_view_watcher_handle = child_view_watcher_handle,
.exporter = std::move(exporter),
.view_ref = std::move(view_ref),
.view_ref_control = std::move(view_ref_control)});
}
void LinkSystem::UpdateLinks(const GlobalTopologyData::TopologyVector& global_topology,
const std::unordered_set<TransformHandle>& live_handles,
const GlobalMatrixVector& global_matrices,
const glm::vec2& display_pixel_scale,
const UberStruct::InstanceMap& uber_structs) {
std::scoped_lock lock(mutex_);
// Since the global topology may not contain every Flatland instance, manually update the
// ParentViewportStatus of every ParentViewportWatcher.
for (auto& [graph_handle, parent_viewport_watcher] : parent_viewport_watcher_map_) {
// The child Flatland instance is connected to the display if it is present in the global
// topology.
parent_viewport_watcher.impl->UpdateLinkStatus(
live_handles.count(parent_viewport_watcher.child_link_origin) > 0
? ParentViewportStatus::CONNECTED_TO_DISPLAY
: ParentViewportStatus::DISCONNECTED_FROM_DISPLAY);
}
// ChildViewWatcher has two hanging get methods, GetStatus() and GetViewRef(), whose responses are
// generated in the loop below.
for (auto& [link_origin, child_view_watcher] : child_view_watcher_map_) {
// The ChildViewStatus changes the first time the child presents with a particular parent link.
// This is indicated by an UberStruct with the |link_origin| as its first TransformHandle in the
// snapshot.
//
// NOTE: This does not mean the child content is actually appears on-screen; it simply informs
// the parent that the child has content that is available to present on screen. This is
// intentional; for example, the parent might not want to attach the child to the global
// scene graph until it knows the child is ready to present content on screen.
//
// NOTE: The LinkSystem can technically "miss" updating the ChildViewStatus for a
// particular ChildViewWatcher if the child presents two CreateView() calls before
// UpdateLinks() is called, but in that case, the first Link is destroyed, and therefore
// its status does not need to be updated anyway.
auto uber_struct_kv = uber_structs.find(link_origin.GetInstanceId());
if (uber_struct_kv != uber_structs.end()) {
const auto& local_topology = uber_struct_kv->second->local_topology;
// If the local topology doesn't start with the |link_origin|, the child is linked to a
// different parent now, but the link_invalidated callback to remove this entry has not fired
// yet.
if (!local_topology.empty() && local_topology.front().handle == link_origin) {
child_view_watcher->UpdateLinkStatus(ChildViewStatus::CONTENT_HAS_PRESENTED);
}
}
// As soon as the child view is part of the global topology, update the watcher to send it along
// to any caller of GetViewRef(). For example, this means that by the time the watcher receives
// it, the child view will already exist in the view tree, and therefore an attempt to focus it
// will succeed.
if (live_handles.count(link_origin) > 0) {
child_view_watcher->UpdateViewRef();
}
}
std::unordered_map<std::shared_ptr<ParentViewportWatcherImpl>, LayoutInfo> layout_map;
for (size_t i = 0; i < global_topology.size(); ++i) {
const auto& handle = global_topology[i];
// For a particular Link, the ViewportProperties and ParentViewportWatcherImpl both live on the
// ChildLink's |graph_handle|. They can show up in either order (ViewportProperties before
// ParentViewportWatcherImpl if the parent Flatland calls Present() first, other way around if
// the link resolves first), so one being present without another is not a bug.
auto graph_kv = parent_viewport_watcher_map_.find(handle);
if (graph_kv != parent_viewport_watcher_map_.end()) {
auto uber_struct_kv = uber_structs.find(handle.GetInstanceId());
if (uber_struct_kv != uber_structs.end()) {
auto properties_kv = uber_struct_kv->second->link_properties.find(handle);
if (properties_kv != uber_struct_kv->second->link_properties.end() &&
properties_kv->second.has_logical_size()) {
const auto pixel_scale = display_pixel_scale * ComputeScale(global_matrices[i]);
LayoutInfo info;
info.set_logical_size(properties_kv->second.logical_size());
info.set_pixel_scale(
{static_cast<uint32_t>(pixel_scale.x), static_cast<uint32_t>(pixel_scale.y)});
// A transform handle may have multiple parents, resulting in the same handle appearing
// in the global topology vector multiple times, with multiple global matrices. We only
// want to update the LayoutInfo for the instance that has the lowest scale value.
auto watcher = graph_kv->second.impl;
if (layout_map.find(watcher) == layout_map.end()) {
layout_map[watcher] = std::move(info);
} else {
const auto& curr_info = layout_map[watcher];
if (curr_info.pixel_scale().width > info.pixel_scale().width) {
layout_map[watcher] = std::move(info);
}
}
}
}
}
}
// Now that we've determined which layout information to associate with a
// ParentViewportWatcherImpl, we can now update each one.
for (auto& [watcher, info] : layout_map) {
watcher->UpdateLayoutInfo(std::move(info));
}
}
GlobalTopologyData::LinkTopologyMap LinkSystem::GetResolvedTopologyLinks() {
GlobalTopologyData::LinkTopologyMap copy;
// Acquire the lock and copy.
{
std::scoped_lock lock(mutex_);
copy = link_topologies_;
}
return copy;
}
TransformHandle::InstanceId LinkSystem::GetInstanceId() const { return instance_id_; }
std::unordered_map<TransformHandle, TransformHandle> const
LinkSystem::GetChildViewWatcherToParentViewportWatcherMapping() {
std::unordered_map<TransformHandle, TransformHandle> mapping;
for (auto& [handle, data] : parent_viewport_watcher_map_) {
mapping.try_emplace(data.child_link_origin, handle);
}
return mapping;
}
} // namespace flatland