| // 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_ |