blob: d818c9498c15735889920739dcf8007fb4643d7f [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.
#ifndef SRC_UI_SCENIC_LIB_FLATLAND_LINK_SYSTEM_H_
#define SRC_UI_SCENIC_LIB_FLATLAND_LINK_SYSTEM_H_
#include <fidl/fuchsia.ui.composition/cpp/fidl.h>
#include <fidl/fuchsia.ui.composition/cpp/hlcpp_conversion.h>
#include <fuchsia/ui/composition/cpp/fidl.h>
#include <lib/async/default.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fidl/cpp/interface_request.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/errors.h>
#include <functional>
#include <unordered_map>
#include <unordered_set>
#include "src/lib/fxl/synchronization/thread_annotations.h"
#include "src/ui/scenic/lib/flatland/global_matrix_data.h"
#include "src/ui/scenic/lib/flatland/global_topology_data.h"
#include "src/ui/scenic/lib/flatland/hanging_get_helper.h"
#include "src/ui/scenic/lib/flatland/transform_graph.h"
#include "src/ui/scenic/lib/flatland/transform_handle.h"
#include "src/ui/scenic/lib/flatland/uber_struct.h"
#include "src/ui/scenic/lib/scenic/util/error_reporter.h"
#include "src/ui/scenic/lib/utils/dispatcher_holder.h"
#include "src/ui/scenic/lib/utils/object_linker.h"
#include <glm/glm.hpp>
#include <glm/mat3x3.hpp>
namespace flatland {
// Used by to communicate back to `LinkSystem` callers that a `ParentViewportWatcher` or
// `ChildViewWatcher` client performed an illegal action. For example, this is used by Flatland
// to close down the associated Flatland session with an error.
using LinkProtocolErrorCallback = std::function<void(const std::string&)>;
// An implementation of the ParentViewportWatcher protocol, consisting of hanging gets for various
// updateable pieces of information.
class ParentViewportWatcherImpl
: public fidl::Server<fuchsia_ui_composition::ParentViewportWatcher> {
public:
ParentViewportWatcherImpl(
std::shared_ptr<utils::DispatcherHolder> dispatcher_holder,
fidl::InterfaceRequest<fuchsia::ui::composition::ParentViewportWatcher> request,
LinkProtocolErrorCallback error_callback)
:
#ifndef NDEBUG
dispatcher_holder_(dispatcher_holder),
#endif
error_callback_(std::move(error_callback)),
binding_(dispatcher_holder->dispatcher(), fidl::HLCPPToNatural(request), this,
&ParentViewportWatcherImpl::OnClose) {
}
ParentViewportWatcherImpl(ParentViewportWatcherImpl&&) = delete;
ParentViewportWatcherImpl(const ParentViewportWatcherImpl&) = delete;
ParentViewportWatcherImpl& operator=(const ParentViewportWatcherImpl&) = delete;
~ParentViewportWatcherImpl() override {
#ifndef NDEBUG
auto dispatcher_holder = dispatcher_holder_.lock();
FX_CHECK(!dispatcher_holder ||
(dispatcher_holder->dispatcher() == async_get_default_dispatcher()));
#endif // NDEBUG
}
void UpdateLayoutInfo(fuchsia::ui::composition::LayoutInfo info) {
last_layout_info_ = fidl::Clone(info);
layout_helper_.Update(std::move(info));
}
void UpdateDevicePixelRatio(const fuchsia::math::VecF& device_pixel_ratio) {
auto info = fidl::Clone(last_layout_info_);
info.set_device_pixel_ratio(device_pixel_ratio);
UpdateLayoutInfo(std::move(info));
}
void UpdateLinkStatus(fuchsia::ui::composition::ParentViewportStatus status) {
status_helper_.Update(std::move(status));
}
// |fuchsia::ui::composition::ParentViewportWatcher|
void GetLayout(GetLayoutCompleter::Sync& sync_completer) override {
if (layout_helper_.HasPendingCallback()) {
FX_DCHECK(error_callback_);
error_callback_(
"GetLayout() called when there is a pending GetLayout() call. Flatland connection "
"will be closed because of broken flow control.");
sync_completer.Close(ZX_ERR_SHOULD_WAIT);
return;
}
layout_helper_.SetCallback([completer = sync_completer.ToAsync()](
fuchsia::ui::composition::LayoutInfo layout_info) mutable {
completer.Reply({fidl::HLCPPToNatural(layout_info)});
});
}
// |fuchsia_ui_composition::ParentViewportWatcher|
void GetStatus(GetStatusCompleter::Sync& sync_completer) override {
if (status_helper_.HasPendingCallback()) {
FX_DCHECK(error_callback_);
error_callback_(
"GetStatus() called when there is a pending GetStatus() call. Flatland connection "
"will be closed because of broken flow control.");
sync_completer.Close(ZX_ERR_SHOULD_WAIT);
return;
}
status_helper_.SetCallback([completer = sync_completer.ToAsync()](
fuchsia::ui::composition::ParentViewportStatus status) mutable {
completer.Reply({fidl::HLCPPToNatural(status)});
});
}
private:
// Called when the connection is torn down.
static void OnClose(fidl::UnbindInfo info) {
if (info.is_peer_closed()) {
FX_LOGS(DEBUG) << "ParentViewportWatcherImpl::OnUnbound() Client disconnected";
} else if (!info.is_user_initiated()) {
FX_LOGS(WARNING) << "ParentViewportWatcherImpl::OnUnbound() server error: " << info;
}
}
#ifndef NDEBUG
// Only used to verify that destruction occurs on the correct thread.
std::weak_ptr<utils::DispatcherHolder> dispatcher_holder_;
#endif
fuchsia::ui::composition::LayoutInfo last_layout_info_;
LinkProtocolErrorCallback error_callback_;
HangingGetHelper<fuchsia::ui::composition::LayoutInfo> layout_helper_;
HangingGetHelper<fuchsia::ui::composition::ParentViewportStatus> status_helper_;
// Safety: destroy binding first, then destroy hanging-get helpers.
// This confirms that we are shutting down instead of simply forgot to reply
// to the completers captured in the hanging-get helpers.
fidl::ServerBinding<fuchsia_ui_composition::ParentViewportWatcher> binding_;
};
// An implementation of the ChildViewWatcher protocol, consisting of hanging gets for various
// updateable pieces of information.
class ChildViewWatcherImpl : public fidl::Server<fuchsia_ui_composition::ChildViewWatcher> {
public:
ChildViewWatcherImpl(std::shared_ptr<utils::DispatcherHolder> dispatcher_holder,
fidl::InterfaceRequest<fuchsia::ui::composition::ChildViewWatcher> request,
LinkProtocolErrorCallback error_callback)
:
#ifndef NDEBUG
dispatcher_holder_(dispatcher_holder),
#endif
error_callback_(std::move(error_callback)),
binding_(dispatcher_holder->dispatcher(), fidl::HLCPPToNatural(request), this,
&ChildViewWatcherImpl::OnClose) {
}
ChildViewWatcherImpl(ChildViewWatcherImpl&&) = delete;
ChildViewWatcherImpl(const ChildViewWatcherImpl&) = delete;
ChildViewWatcherImpl& operator=(const ChildViewWatcherImpl&) = delete;
~ChildViewWatcherImpl() override {
#ifndef NDEBUG
auto dispatcher_holder = dispatcher_holder_.lock();
FX_CHECK(!dispatcher_holder ||
(dispatcher_holder->dispatcher() == async_get_default_dispatcher()));
#endif // NDEBUG
}
void UpdateLinkStatus(fuchsia::ui::composition::ChildViewStatus status) {
status_helper_.Update(status);
if (viewref_) {
// At the time of writing, CONTENT_HAS_PRESENTED is the only possible value. DCHECK just in
// case this changes.
FX_DCHECK(status == fuchsia::ui::composition::ChildViewStatus::CONTENT_HAS_PRESENTED);
}
}
// If ViewRef hasn't yet been pushed to the hanging get helper, do so.
void UpdateViewRef() {
if (viewref_) {
viewref_helper_.Update(std::move(viewref_.value()));
viewref_.reset();
}
}
void SetViewRef(fuchsia::ui::views::ViewRef viewref) {
FX_CHECK(viewref.reference);
viewref_ = std::move(viewref);
}
// |fuchsia_ui_composition::ChildViewWatcher|
void GetStatus(GetStatusCompleter::Sync& sync_completer) override {
if (status_helper_.HasPendingCallback()) {
FX_DCHECK(error_callback_);
error_callback_(
"GetStatus() called when there is a pending GetStatus() call. Flatland connection "
"will be closed because of broken flow control.");
sync_completer.Close(ZX_ERR_SHOULD_WAIT);
return;
}
status_helper_.SetCallback([completer = sync_completer.ToAsync()](
fuchsia::ui::composition::ChildViewStatus status) mutable {
completer.Reply({fidl::HLCPPToNatural(status)});
});
}
// |fuchsia_ui_composition::ChildViewWatcher|
void GetViewRef(GetViewRefCompleter::Sync& sync_completer) override {
if (viewref_helper_.HasPendingCallback()) {
FX_DCHECK(error_callback_);
error_callback_(
"GetViewRef() called when there is a pending GetViewRef() call. Flatland connection "
"will be closed because of broken flow control.");
sync_completer.Close(ZX_ERR_SHOULD_WAIT);
return;
}
viewref_helper_.SetCallback(
[completer = sync_completer.ToAsync()](fuchsia::ui::views::ViewRef viewref) mutable {
completer.Reply({fidl::HLCPPToNatural(viewref)});
});
}
private:
// Called when the connection is torn down.
static void OnClose(fidl::UnbindInfo info) {
if (info.is_peer_closed()) {
FX_LOGS(DEBUG) << "ChildViewWatcherImpl::OnUnbound() Client disconnected";
} else if (!info.is_user_initiated()) {
FX_LOGS(WARNING) << "ChildViewWatcherImpl::OnUnbound() server error: " << info;
}
}
#ifndef NDEBUG
// Only used to verify that destruction occurs on the correct thread.
std::weak_ptr<utils::DispatcherHolder> dispatcher_holder_;
#endif
LinkProtocolErrorCallback error_callback_;
HangingGetHelper<fuchsia::ui::composition::ChildViewStatus> status_helper_;
HangingGetHelper<fuchsia::ui::views::ViewRef> viewref_helper_;
// Safety: destroy binding first, then destroy hanging-get helpers.
// This confirms that we are shutting down instead of simply forgot to reply
// to the completers captured in the hanging-get helpers.
fidl::ServerBinding<fuchsia_ui_composition::ChildViewWatcher> binding_;
// Temporarily held when SetViewRef() is called. Instead of immediately notifying any pending
// hanging get requests, we wait until the child view first appears in the global topology.
std::optional<fuchsia::ui::views::ViewRef> viewref_;
};
// A system for managing links between Flatland instances. Each Flatland instance creates Links
// using tokens provided by Flatland clients. Each end of a Link consists of:
// - An implementation of the FIDL protocol for communicating with the other end of the link.
// - A TransformHandle which serves as the "attachment point" for that end of the link.
// - The ObjectLinker link which serves as the actual implementation of the link.
//
// The LinkSystem is only responsible for connecting the "attachment point" TransformHandles
// returned in the Link structs. Flatland instances must attach these handles to their own
// transform hierarchy and notify the TopologySystem in order for the link to actually be
// established.
class LinkSystem : public std::enable_shared_from_this<LinkSystem> {
public:
explicit LinkSystem(TransformHandle::InstanceId instance_id);
// Because this object captures its "this" pointer in internal closures, it is unsafe to copy or
// move it. Disable all copy and move operations.
LinkSystem(const LinkSystem&) = delete;
LinkSystem& operator=(const LinkSystem&) = delete;
LinkSystem(LinkSystem&&) = delete;
LinkSystem& operator=(LinkSystem&&) = delete;
// In addition to supplying an interface request via the ObjectLinker, the "ToChild" end of a link
// also supplies its attachment point so that the LinkSystem can create an edge between the two
// when the link resolves. This allows creation and destruction logic to be paired within a single
// ObjectLinker endpoint, instead of being spread out between the two endpoints.
struct LinkToChildInfo {
TransformHandle parent_transform_handle;
TransformHandle internal_link_handle;
};
struct LinkToParentInfo {
TransformHandle child_transform_handle;
std::shared_ptr<const fuchsia::ui::views::ViewRef> view_ref;
};
// Linked Flatland instances only implement a small piece of link functionality. For now, directly
// sharing link requests is a clean way to implement that functionality. This will become more
// complicated as the Flatland API evolves.
using ObjectLinker = utils::ObjectLinker<LinkToParentInfo, LinkToChildInfo>;
// Destruction of a LinkToChild object will trigger deregistration with the LinkSystem.
// Deregistration is thread safe, but the user of the Link object should be confident (e.g., by
// tracking release fences) that no other systems will try to reference the Link.
struct LinkToChild {
// The handle on which the ParentViewportWatcherImpl will live.
TransformHandle parent_transform_handle;
// The LinkSystem-owned handle that will be a key in the LinkTopologyMap when the link resolves.
// These handles will never be in calculated global topologies; they are primarily used to
// signal when to look for a link in GlobalTopologyData::ComputeGlobalTopologyData().
TransformHandle internal_link_handle;
ObjectLinker::ImportLink importer;
};
// Destruction of a LinkToParent object will trigger deregistration with the LinkSystem.
// Deregistration is thread safe, but the user of the Link object should be confident (e.g., by
// tracking release fences) that no other systems will try to reference the Link.
struct LinkToParent {
// The handle that the ChildViewWatcherImpl will live on and will be a value in the
// LinkTopologyMap when the link resolves.
TransformHandle child_transform_handle;
ObjectLinker::ExportLink exporter;
// Tracks the ViewRef for this View and is the reference for the lifetime of the ViewRef by
// uniquely holding |view_ref_control| until going out of scope.
std::shared_ptr<const fuchsia::ui::views::ViewRef> view_ref;
// |view_ref_control| and |view_ref| are set when there is a valid ViewIdentityOnCreation.
// Otherwise both are kept empty.
std::optional<fuchsia::ui::views::ViewRefControl> view_ref_control;
};
// Creates the parent end of a link. The LinkToChild's |internal_link_handle| serves as the
// attachment point for the caller's transform hierarchy. |initial_properties| is immediately
// dispatched to the LinkToParent when the Link is resolved, regardless of whether the parent or
// the child has called |Flatland::Present()|.
//
// Link handles are excluded from global topologies, so the |parent_transform_handle| is
// provided by the parent as the attachment point for the ChildViewWatcherImpl.
//
// |dispatcher_holder| allows hanging-get response-callbacks to be invoked from the appropriate
// Flatland session thread.
LinkToChild CreateLinkToChild(
std::shared_ptr<utils::DispatcherHolder> dispatcher_holder,
fuchsia::ui::views::ViewportCreationToken token,
fuchsia::ui::composition::ViewportProperties initial_properties,
fidl::InterfaceRequest<fuchsia::ui::composition::ChildViewWatcher> child_view_watcher,
TransformHandle parent_transform_handle, LinkProtocolErrorCallback error_callback);
// Creates the child end of a link. Once both ends of a Link have been created, the LinkSystem
// will create a local topology that connects the internal Link to the LinkToParent's
// |child_transform_handle|.
//
// |dispatcher_holder| allows hanging-get response-callbacks to be invoked from the appropriate
// Flatland session thread.
LinkToParent CreateLinkToParent(
std::shared_ptr<utils::DispatcherHolder> dispatcher_holder,
fuchsia::ui::views::ViewCreationToken token,
std::optional<fuchsia::ui::views::ViewIdentityOnCreation> view_identity,
fidl::InterfaceRequest<fuchsia::ui::composition::ParentViewportWatcher>
parent_viewport_watcher,
TransformHandle child_transform_handle, LinkProtocolErrorCallback error_callback);
// Returns a snapshot of the current set of links, represented as a map from LinkSystem-owned
// TransformHandles to TransformHandles in LinkToParents. The LinkSystem generates Keys for this
// map in CreateLinkToChild() and returns them to callers in a LinkToChild's
// |internal_link_handle|. The values in this map are arguments to CreateLinkToParent() and become
// the LinkToParent's |child_transform_handle|. The LinkSystem places entries in the map when a
// link resolves and removes them when a link is invalidated.
GlobalTopologyData::LinkTopologyMap GetResolvedTopologyLinks();
// Returns the instance ID used for LinkSystem-authored handles.
TransformHandle::InstanceId GetInstanceId() const;
// For use by the core processing loop, this function consumes global information, processes it,
// and sends all necessary updates to active ParentViewportWatcher and ChildViewWatcher channels.
//
// This data passed into this function is generated by merging information from multiple Flatland
// instances. |global_topology| is the TopologyVector of all nodes visible from the (currently
// single) display. |live_handles| is the set of nodes in that vector. |global_matrices| is the
// list of global matrices, one per handle in |global_topology|. |uber_structs| is the set of
// UberStructs used to generate the global topology.
void 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);
// Returns the mapping from the child_transform_handle of each LinkToParent to the corresponding
// parent_transform_handle from each LinkToChild.
std::unordered_map<TransformHandle, TransformHandle> const GetLinkChildToParentTransformMap();
// Updates |device_pixel_ratio_| for the View with parent |handle|. If the value changed it sends
// updates to all waiting clients, otherwise it does nothing.
// |handle| should have been previously used as |parent_transform_handle| in CreateLinkToChild().
void UpdateViewportPropertiesFor(TransformHandle handle,
const fuchsia::ui::composition::ViewportProperties& properties);
void set_device_pixel_ratio(const glm::vec2& initial_device_pixel_ratio) {
std::scoped_lock lock(mutex_);
UpdateDevicePixelRatio({initial_device_pixel_ratio.x, initial_device_pixel_ratio.y});
}
private:
// Updates |device_pixel_ratio_| and, if the value changed, sends updates to all waiting clients.
void UpdateDevicePixelRatio(const fuchsia::math::VecF& device_pixel_raito)
FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
TransformHandle CreateTransformLocked() {
TransformHandle transform;
{
std::scoped_lock lock(mutex_);
transform = link_graph_.CreateTransform();
}
return transform;
}
TransformHandle::InstanceId instance_id_;
// |link_graph_|, an instance of a TransformGraph, is not thread safe, as it is designed to be
// used by individual Flatland instances. However, this class is shared across all Flatland
// instances, and therefore different threads. Therefore, access to |link_graph_| should be
// guarded by |mutex_|.
TransformGraph link_graph_ FXL_GUARDED_BY(mutex_);
std::shared_ptr<ObjectLinker> linker_;
// |mutex_| guards access to |link_graph_| and |link_topologies_|.
//
// TODO(https://fxbug.dev/42120738): These maps are modified at Link creation and destruction time
// (within the ObjectLinker closures) as well as within UpdateLinks, which is called by the core
// render loop. This produces a possible priority inversion between the Flatland instance threads
// and the (possibly deadline scheduled) render thread.
std::mutex mutex_;
// Structs representing the child and parent ends of a link.
struct ChildEnd {
std::shared_ptr<ParentViewportWatcherImpl> parent_viewport_watcher;
TransformHandle child_transform_handle;
};
struct ParentEnd {
std::shared_ptr<ChildViewWatcherImpl> child_view_watcher;
};
// Keyed by LinkToChild::parent_transform_handle. Access is managed by |mutex_|.
std::unordered_map<TransformHandle, ChildEnd> parent_to_child_map_ FXL_GUARDED_BY(mutex_);
// Keyed by LinkToParent::child_transform_handle. Access is managed by |mutex_|.
std::unordered_map<TransformHandle, ParentEnd> child_to_parent_map_ FXL_GUARDED_BY(mutex_);
// The set of current link topologies. Access is managed by |mutex_|.
GlobalTopologyData::LinkTopologyMap link_topologies_ FXL_GUARDED_BY(mutex_);
// A map of the most recent LayoutInfo generated for each link that hasn't resolved yet.
std::unordered_map<TransformHandle, fuchsia::ui::composition::LayoutInfo> initial_layout_infos_
FXL_GUARDED_BY(mutex_);
// The starting DPR used by the link system. The actual DPR used on subsequent calls to
// UpdateLinks() may be different from this value.
// TODO(https://fxbug.dev/42059985): This will need to be updated once we have multidisplay setup.
std::atomic<fuchsia::math::VecF> device_pixel_ratio_;
};
} // namespace flatland
#endif // SRC_UI_SCENIC_LIB_FLATLAND_LINK_SYSTEM_H_