blob: 4a5b3d3ad6ce02364c17fce8d6b350243bb6253a [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.
#ifndef GARNET_LIB_UI_GFX_ENGINE_OBJECT_LINKER_H_
#define GARNET_LIB_UI_GFX_ENGINE_OBJECT_LINKER_H_
#include <lib/async/cpp/wait.h>
#include <lib/fit/function.h>
#include <lib/zx/handle.h>
#include <lib/zx/object_traits.h>
#include <zircon/types.h>
#include <memory>
#include <type_traits>
#include <unordered_map>
#include "garnet/lib/ui/scenic/util/error_reporter.h"
#include "lib/fxl/macros.h"
#include "lib/fxl/memory/weak_ptr.h"
namespace scenic_impl {
namespace gfx {
// Contains common linking functionality that operates on type-erased objects.
// Use ObjectLinker to link objects of concrete types together.
class ObjectLinkerBase {
public:
~ObjectLinkerBase() = default;
// Returns the corresponding import's client object.
void* GetImport(zx_koid_t endpoint_id) {
auto it = imports_.find(endpoint_id);
return it != imports_.end() ? it->second.object : nullptr;
}
size_t ExportCount() { return exports_.size(); }
size_t UnresolvedExportCount() { return unresolved_exports_.size(); }
size_t ImportCount() { return imports_.size(); }
size_t UnresolvedImportCount() { return unresolved_imports_.size(); }
protected:
// Information for one end of a Link registered with the linker.
struct Endpoint {
zx_koid_t peer_endpoint_id = ZX_KOID_INVALID;
void* object = nullptr; // Opaque pointer to client object
fit::function<void(void* linked_object)> link_resolved;
fit::closure link_failed; // TODO(SCN-769): How to multiple imports?
};
// Information used to match one end of a link with its peer(s) on the
// other end.
struct UnresolvedEndpoint {
zx::handle token; // Token for initial matching to peer endpoint.
std::unique_ptr<async::Wait> peer_death_waiter;
};
// Only concrete ObjectLinker types should instantiate these.
ObjectLinkerBase() = default;
// Creates a new Endpoint for linking and reports any errors in creation
// using |error_reporter|.
//
// Returns a zx_koid_t that uniquely identifies the registered Endpoint, or
// ZX_KOID_INVALID if creation failed.
zx_koid_t CreateEndpoint(zx::handle token, ErrorReporter* error_reporter,
bool is_import);
// Destroys the Endpoint pointed to by |endpoint_id| and removes all traces
// of it from the linker. If the Endpoint is linked to a peer, the peer
// will be notified of the Endpoint's destruction.
void DestroyEndpoint(zx_koid_t endpoint_id, bool is_import);
// Puts the Endpoint pointed to by |endpoint_id| into an initialized state
// by supplying it with an object and connection callbacks. The Endpoint
// will not be linked until its peer is also initialized.
void InitializeEndpoint(
zx_koid_t endpoint_id, void* object,
fit::function<void(void* linked_object)> link_resolved,
fit::closure link_failed, bool is_import);
// Attempts linking of the endpoints associated with |endpoint_id| and
// |peer_endpoint_id|.
//
// The operation will only succeed if both endpoints have been initialized
// first.
void AttemptLinking(zx_koid_t endpoint_id, zx_koid_t peer_endpoint_id,
bool is_import);
// Sets up an async::Wait on |Endpoint| that will fire a callback if the
// Endpoint peer's token is destroyed before a link has been established.
std::unique_ptr<async::Wait> WaitForPeerDeath(zx_handle_t endpoint_handle,
zx_koid_t endpoint_id,
bool is_import);
std::unordered_map<zx_koid_t, Endpoint> exports_;
std::unordered_map<zx_koid_t, Endpoint> imports_;
std::unordered_map<zx_koid_t, UnresolvedEndpoint> unresolved_exports_;
std::unordered_map<zx_koid_t, UnresolvedEndpoint> unresolved_imports_;
FXL_DISALLOW_COPY_AND_ASSIGN(ObjectLinkerBase);
};
// Allows direct linking of peer objects, regardless of which session(s) they
// exist in. Once the objects are linked, they have direct references to each
// other.
//
// This linking is accomplished via lookup between pairable kernel objects.
// zx::eventpair objects are a natural fit for this purpose and are commonly
// used.
//
// To create a Link, provide a handle to one half of a pairable kernel object
// (zx::object_traits:supports_duplication is true) to the |CreateExport| or
// |CreateImport| methods. It can be connected with its peer by providing a
// concrete object to link along with callbacks for both successful and
// unsuccessful resolution.
//
// When the other half of the kernel object is registered with the
// ObjectLinker, and Initialize() is called on the corresponding Link, the
// provided resolution callbacks in both Links will be fired. The callback
// associated with the Export will always fire first.
//
// If either Link endpoint is destroyed, this will cause the provided
// disconnection callback on its peer endpoint to be fired. If the peer
// endpoint has not been provided any callbacks yet via Initialize(), the
// disconnection callback will be fired later when Initialize() is first called
// on it.
//
// Attempts to register either half of the kernel object multiple times, even
// through cloned handles, will result in an error.
// TODO(SCN-769): Allow multiple Imports.
//
// This class is thread-hostile. It requires the owning thread to have a
// default async loop.
template <typename Export, typename Import>
class ObjectLinker : public ObjectLinkerBase {
public:
// Represents one endpoint of a Link between two objects in different
// |Session|s.
//
// Links can be moved, but not copied. Valid Links can only be constructed by
// the CreateExport and CreateImport methods.
template <bool is_import>
class Link {
public:
using Obj = typename std::conditional<is_import, Import, Export>::type;
using PeerObj = typename std::conditional<is_import, Export, Import>::type;
Link() {}
~Link() { Invalidate(); }
Link(Link&& other) { *this = std::move(other); }
Link& operator=(nullptr_t) { Invalidate(); }
Link& operator=(Link&& other);
bool valid() const { return linker_ && endpoint_id_ != ZX_KOID_INVALID; }
bool initialized() const { return valid() && initialized_; }
PeerObj* peer() { return peer_object_; }
// Initialize the Link with an |object| and callbacks for |link_resolved|
// and |link_failed| events, making it ready for connection to its
// peer.
void Initialize(
Obj* object,
fit::function<void(PeerObj* peer_object)> link_resolved =
[](PeerObj*) {},
fit::closure link_failed = []() {});
private:
// Kept private so only an ObjectLinker can construct a valid Link.
Link(zx_koid_t endpoint_id, fxl::WeakPtr<ObjectLinker> linker)
: linker_(std::move(linker)), endpoint_id_(endpoint_id) {}
void Invalidate(bool destroy_endpoint = true);
fxl::WeakPtr<ObjectLinker> linker_;
zx_koid_t endpoint_id_ = ZX_KOID_INVALID;
PeerObj* peer_object_ = nullptr;
bool initialized_ = false;
friend class ObjectLinker;
};
using ExportLink = Link<false>;
using ImportLink = Link<true>;
ObjectLinker() : weak_factory_(this) {}
~ObjectLinker() = default;
// Creates an outgoing cross-session ExportLink between two objects, which
// can be used to initiate and close the connection between them.
//
// The ObjectLinker uses the provided |token| to locate the paired ImportLink.
// |token| must reference a pairable kernel object type such as |zx::channel|
// or |zx::eventpair|. |token| may not reference a kernel object that is in
// use by this ObjectLinker.
//
// If a link cannot be created, |error_reporter| will be used to flag an
// error.
//
// The objects are linked as soon as the |Initialize()| method is called on
// the links for both objects.
template <typename T,
typename = std::enable_if_t<zx::object_traits<T>::has_peer_handle>>
ExportLink CreateExport(T token, ErrorReporter* error_reporter) {
const zx_koid_t endpoint_id =
CreateEndpoint(std::move(token), error_reporter, false);
return ExportLink(endpoint_id, weak_factory_.GetWeakPtr());
}
// Creates an incoming cross-session ImportLink between two objects, which
// can be used to initiate and close the connection between them.
//
// The ObjectLinker uses the provided |token| to locate the paired ExportLink.
// |token| must reference a pairable kernel object type such as |zx::channel|
// or |zx::eventpair|. |token| may not reference a kernel object that is in
// use by this ObjectLinker.
//
// If a link cannot be created, |error_reporter| will be used to flag an
// the links for both objects.
template <typename T,
typename = std::enable_if_t<zx::object_traits<T>::has_peer_handle>>
ImportLink CreateImport(T token, ErrorReporter* error_reporter) {
const zx_koid_t endpoint_id =
CreateEndpoint(std::move(token), error_reporter, true);
return ImportLink(endpoint_id, weak_factory_.GetWeakPtr());
}
private:
// Should be last. See weak_ptr.h.
fxl::WeakPtrFactory<ObjectLinker> weak_factory_;
};
template <typename Export, typename Import>
template <bool is_import>
auto ObjectLinker<Export, Import>::Link<is_import>::operator=(Link&& other)
-> Link& {
// Invalidate the existing Link if its still valid.
Invalidate();
// Move data from the other Link and invalidate it, so it won't destroy
// its endpoint when it dies.
linker_ = other.linker_;
peer_object_ = other.peer_object_;
endpoint_id_ = other.endpoint_id_;
initialized_ = other.initialized_;
other.Invalidate(false /* destroy_endpoint */);
return *this;
}
template <typename Export, typename Import>
template <bool is_import>
void ObjectLinker<Export, Import>::Link<is_import>::Initialize(
Obj* object, fit::function<void(PeerObj* peer_object)> link_resolved,
fit::closure link_failed) {
FXL_DCHECK(valid());
FXL_DCHECK(!initialized());
FXL_DCHECK(!peer());
FXL_DCHECK(object);
FXL_DCHECK(link_resolved);
FXL_DCHECK(link_failed);
linker_->InitializeEndpoint(
endpoint_id_, object,
[this, resolved_cb = std::move(link_resolved)](void* object) {
peer_object_ = static_cast<PeerObj*>(object);
resolved_cb(peer_object_);
},
[this, disconnected_cb = std::move(link_failed)]() {
Invalidate();
disconnected_cb();
},
is_import);
initialized_ = true;
}
template <typename Export, typename Import>
template <bool is_import>
void ObjectLinker<Export, Import>::Link<is_import>::Invalidate(
bool destroy_endpoint) {
if (valid() && destroy_endpoint) {
linker_->DestroyEndpoint(endpoint_id_, is_import);
}
linker_.reset();
peer_object_ = nullptr;
endpoint_id_ = ZX_KOID_INVALID;
initialized_ = false;
}
} // namespace gfx
} // namespace scenic_impl
#endif // GARNET_LIB_UI_GFX_ENGINE_OBJECT_LINKER_H_