blob: 6145a7ba10c5a708c4eb30c9e08f3d4b8ecb959a [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 <lib/trace/event.h>
#include "src/ui/scenic/lib/utils/dispatcher_holder.h"
#include "src/ui/scenic/lib/utils/task_utils.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 {
LinkSystem::LinkSystem(TransformHandle::InstanceId instance_id)
: instance_id_(instance_id), link_graph_(instance_id_), linker_(ObjectLinker::New()) {}
LinkSystem::LinkToChild LinkSystem::CreateLinkToChild(
std::shared_ptr<utils::DispatcherHolder> dispatcher_holder, ViewportCreationToken token,
fuchsia::ui::composition::ViewportProperties initial_properties,
fidl::InterfaceRequest<ChildViewWatcher> child_view_watcher,
TransformHandle parent_transform_handle, LinkProtocolErrorCallback error_callback) {
FX_DCHECK(token.value.is_valid());
{ // Put the initial layout in a map. This lets us update the layout before the link resolves if
// necessary.
LayoutInfo info;
info.set_logical_size(initial_properties.logical_size());
info.set_inset(initial_properties.inset());
info.set_device_pixel_ratio(device_pixel_ratio_.load());
std::scoped_lock lock(mutex_);
initial_layout_infos_.emplace(parent_transform_handle, std::move(info));
}
auto impl = std::make_shared<ChildViewWatcherImpl>(dispatcher_holder,
std::move(child_view_watcher), error_callback);
const TransformHandle internal_link_handle = CreateTransformLocked();
ObjectLinker::ImportLink importer =
linker_->CreateImport(LinkToChildInfo{.parent_transform_handle = parent_transform_handle,
.internal_link_handle = internal_link_handle},
std::move(token.value),
/* error_reporter */ nullptr);
auto child_transform_handle = std::make_shared<TransformHandle>(); // Uninitialized.
importer.Initialize(
/* link_resolved = */
[ref = shared_from_this(), impl, child_transform_handle](LinkToParentInfo info) mutable {
if (info.view_ref != nullptr) {
impl->SetViewRef({.reference = utils::CopyEventpair(info.view_ref->reference)});
}
*child_transform_handle = info.child_transform_handle;
{
std::scoped_lock lock(ref->mutex_);
ref->child_to_parent_map_[*child_transform_handle] =
ParentEnd{.child_view_watcher = impl};
}
},
/* link_invalidated = */
[ref = shared_from_this(), impl, child_transform_handle, parent_transform_handle,
weak_dispatcher_holder = std::weak_ptr<utils::DispatcherHolder>(dispatcher_holder)](
bool on_link_destruction) mutable {
// We expect |child_transform_handle| 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_transform_handle || on_link_destruction);
{
std::scoped_lock lock(ref->mutex_);
ref->child_to_parent_map_.erase(*child_transform_handle);
ref->initial_layout_infos_.erase(parent_transform_handle);
}
// Avoid race conditions by destroying ChildViewWatcher on its "own" thread. For example,
// if not destroyed on its "own" thread, it might concurrently be handling a FIDL message.
if (auto dispatcher_holder = weak_dispatcher_holder.lock()) {
utils::ExecuteOrPostTaskOnDispatcher(
dispatcher_holder->dispatcher(), [impl = std::move(impl)]() mutable {
TRACE_DURATION("gfx", "LinkSystem::CreateLinkToChild[destroy impl]");
impl.reset();
});
// The point of moving |impl| into the task above is to destroy it on the correct thread.
// Verify that we did actually move it (previously, there was a subtle bug where this
// closure wasn't declared as mutable, so we copied the shared_ptr instead of moving it).
FX_DCHECK(!impl);
}
});
return LinkToChild({
.parent_transform_handle = parent_transform_handle,
.internal_link_handle = internal_link_handle,
.importer = std::move(importer),
});
}
LinkSystem::LinkToParent LinkSystem::CreateLinkToParent(
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_transform_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(
LinkToParentInfo{.child_transform_handle = child_transform_handle, .view_ref = view_ref},
std::move(token.value),
/* error_reporter */ nullptr);
auto parent_transform_handle = std::make_shared<TransformHandle>(); // Uninitialized.
auto topology_map_key = std::make_shared<TransformHandle>(); // Uninitialized.
exporter.Initialize(
/* link_resolved = */
[ref = shared_from_this(), impl, parent_transform_handle, topology_map_key,
child_transform_handle](LinkToChildInfo info) {
*parent_transform_handle = info.parent_transform_handle;
*topology_map_key = info.internal_link_handle;
{
std::scoped_lock lock(ref->mutex_);
const auto layout_kv = ref->initial_layout_infos_.find(*parent_transform_handle);
FX_DCHECK(layout_kv != ref->initial_layout_infos_.end())
<< "initial_layout_infos_ for |parent_transform_handle| should exist until the link resolves or is destroyed.";
impl->UpdateLayoutInfo(std::move(layout_kv->second));
ref->initial_layout_infos_.erase(layout_kv);
ref->parent_to_child_map_[*parent_transform_handle] = ChildEnd{
.parent_viewport_watcher = impl, .child_transform_handle = child_transform_handle};
// The topology is constructed here, instead of in the link_resolved closure of the
// LinkToParent object, so that its destruction (which depends on the
// internal_link_handle) can occur on the same endpoint.
ref->link_topologies_[*topology_map_key] = child_transform_handle;
}
},
/* link_invalidated = */
[ref = shared_from_this(), impl, parent_transform_handle, topology_map_key,
weak_dispatcher_holder = std::weak_ptr<utils::DispatcherHolder>(dispatcher_holder)](
bool on_link_destruction) mutable {
// We expect |parent_transform_handle| 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_transform_handle && topology_map_key) || on_link_destruction);
{
std::scoped_lock map_lock(ref->mutex_);
ref->parent_to_child_map_.erase(*parent_transform_handle);
ref->link_topologies_.erase(*topology_map_key);
ref->link_graph_.ReleaseTransform(*topology_map_key);
}
// Avoid race conditions by destroying ParentViewportWatcher on its "own" thread. For
// example, if not destroyed on its "own" thread, it might concurrently be handling a FIDL
// message.
if (auto dispatcher_holder = weak_dispatcher_holder.lock()) {
utils::ExecuteOrPostTaskOnDispatcher(
dispatcher_holder->dispatcher(), [impl = std::move(impl)]() mutable {
TRACE_DURATION("gfx", "LinkSystem::CreateLinkToParent[destroy impl]");
impl.reset();
});
// The point of moving |impl| into the task above is to destroy it on the correct thread.
// Verify that we did actually move it (previously, there was a subtle bug where this
// closure wasn't declared as mutable, so we copied the shared_ptr instead of moving it).
FX_DCHECK(!impl);
}
});
return LinkToParent({.child_transform_handle = child_transform_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& device_pixel_ratio,
const UberStruct::InstanceMap& uber_structs) {
TRACE_DURATION("gfx", "LinkSystem::UpdateLinks");
std::scoped_lock lock(mutex_);
// Since the global topology may not contain every Flatland instance, manually update the
// ParentViewportStatus of every ParentViewportWatcher.
for (auto& [_, child_end] : parent_to_child_map_) {
// The child Flatland instance is connected to the display if it is present in the global
// topology.
child_end.parent_viewport_watcher->UpdateLinkStatus(
live_handles.count(child_end.child_transform_handle) > 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& [child_transform_handle, parent_end] : child_to_parent_map_) {
auto& child_view_watcher = parent_end.child_view_watcher;
// The ChildViewStatus changes the first time the child presents with a particular parent link.
// This is indicated by an UberStruct with the |child_transform_handle| as its first
// TransformHandle in the snapshot.
//
// NOTE: This does not mean the child content 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(child_transform_handle.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 |child_transform_handle|, 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 == child_transform_handle) {
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(child_transform_handle) > 0) {
child_view_watcher->UpdateViewRef();
}
}
UpdateDevicePixelRatio({device_pixel_ratio.x, device_pixel_ratio.y});
}
void LinkSystem::UpdateDevicePixelRatio(const fuchsia::math::VecF& device_pixel_ratio) {
if (fidl::Equals(device_pixel_ratio_.exchange(device_pixel_ratio), device_pixel_ratio)) {
// The new value is the same as the old.
return;
}
// We update DPR info for every single View in the scene graph.
// TODO(https://fxbug.dev/42059985): This assumes the same DPR for every client. Need to fix it
// for multi-display.
for (const auto& [handle, child_end] : parent_to_child_map_) {
child_end.parent_viewport_watcher->UpdateDevicePixelRatio(device_pixel_ratio);
}
for (auto& [_, layout] : initial_layout_infos_) {
layout.set_device_pixel_ratio(device_pixel_ratio);
}
}
void LinkSystem::UpdateViewportPropertiesFor(
const TransformHandle handle, const fuchsia::ui::composition::ViewportProperties& properties) {
// May be called from main thread or from a Flatland thread.
FX_DCHECK(properties.has_logical_size());
FX_DCHECK(properties.has_inset());
LayoutInfo info;
info.set_logical_size(properties.logical_size());
info.set_inset(properties.inset());
info.set_device_pixel_ratio(device_pixel_ratio_.load());
// |handle| should always be a valid parent TransformHandle. But since the caller may be a
// Flatland instance thread, which may not know about the destruction of a link, we can be in
// one of three states:
// 1. Unresolved link -> Layout stored in |initial_layout_infos_|.
// 2. Resolved link -> Layout stored in |parent_to_child_map_|.
// 3. Dead link -> Layout stored nowhere.
std::scoped_lock lock(mutex_);
FX_DCHECK((initial_layout_infos_.empty() && parent_to_child_map_.empty()) ||
(initial_layout_infos_.count(handle) != parent_to_child_map_.count(handle)))
<< "Layout should only exist in at most one map at a time.";
if (auto initial_layout_it = initial_layout_infos_.find(handle);
initial_layout_it != initial_layout_infos_.end()) {
// The link hasn't resolved yet; just update the initial layout that will be sent to the child
// once it resolves.
initial_layout_it->second = std::move(info);
} else if (const auto child_it = parent_to_child_map_.find(handle);
child_it != parent_to_child_map_.end()) {
child_it->second.parent_viewport_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::GetLinkChildToParentTransformMap() {
std::unordered_map<TransformHandle, TransformHandle> child_to_parent_map;
std::scoped_lock lock(mutex_);
for (const auto& [parent_transform_handle, child_end] : parent_to_child_map_) {
child_to_parent_map.try_emplace(child_end.child_transform_handle, parent_transform_handle);
}
return child_to_parent_map;
}
} // namespace flatland