// This file defines a typed Packet type. It fully interoperates with the older // mediapipe::Packet; creating an api::Packet that refers to an existing // Packet (or vice versa) is cheap, just like copying a Packet. Ownership of // the payload is shared. Consider this as a typed view into the same data. // // Conversion is currently done explicitly with the FromOldPacket and // ToOldPacket functions, but calculator code does not need to concern itself // with it. #ifndef MEDIAPIPE_FRAMEWORK_API2_PACKET_H_ #define MEDIAPIPE_FRAMEWORK_API2_PACKET_H_ #include #include #include "absl/meta/type_traits.h" #include "mediapipe/framework/api2/tuple.h" #include "mediapipe/framework/packet.h" #include "mediapipe/framework/port/logging.h" namespace mediapipe { namespace api2 { using Timestamp = mediapipe::Timestamp; using HolderBase = mediapipe::packet_internal::HolderBase; template class Packet; // Type-erased packet. class PacketBase { public: // Empty. PacketBase() = default; // Copy. PacketBase(const PacketBase&) = default; PacketBase& operator=(const PacketBase&) = default; // Move. PacketBase(PacketBase&&) = default; PacketBase& operator=(PacketBase&&) = default; // Get timestamp. Timestamp timestamp() const { return timestamp_; } // The original API has a Timestamp method, but it shadows the Timestamp // type within this class, which is annoying. // Timestamp Timestamp() const { return timestamp_; } PacketBase At(Timestamp timestamp) const&; PacketBase At(Timestamp timestamp) &&; bool IsEmpty() const { return payload_ == nullptr; } template Packet As() const; // Returns the reference to the object of type T if it contains // one, crashes otherwise. template const T& Get() const; // Conversion to old Packet type. operator mediapipe::Packet() const& { return ToOldPacket(*this); } operator mediapipe::Packet() && { return ToOldPacket(std::move(*this)); } // Note: Consume is included for compatibility with the old Packet; however, // it relies on shared_ptr.unique(), which is deprecated and is not guaranteed // to give exact results. template absl::StatusOr> Consume() { // Using the implementation in the old Packet for now. mediapipe::Packet old = packet_internal::Create(std::move(payload_), timestamp_); auto result = old.Consume(); if (!result.ok()) payload_ = packet_internal::GetHolderShared(std::move(old)); return result; } protected: explicit PacketBase(std::shared_ptr payload) : payload_(std::move(payload)) {} std::shared_ptr payload_; Timestamp timestamp_; template friend PacketBase PacketBaseAdopting(const T* ptr); friend PacketBase FromOldPacket(const mediapipe::Packet& op); friend PacketBase FromOldPacket(mediapipe::Packet&& op); friend mediapipe::Packet ToOldPacket(const PacketBase& p); friend mediapipe::Packet ToOldPacket(PacketBase&& p); }; PacketBase FromOldPacket(const mediapipe::Packet& op); PacketBase FromOldPacket(mediapipe::Packet&& op); mediapipe::Packet ToOldPacket(const PacketBase& p); mediapipe::Packet ToOldPacket(PacketBase&& p); template inline const T& PacketBase::Get() const { CHECK(payload_); packet_internal::Holder* typed_payload = payload_->As(); CHECK(typed_payload) << absl::StrCat( "The Packet stores \"", payload_->DebugTypeName(), "\", but \"", MediaPipeTypeStringOrDemangled(), "\" was requested."); return typed_payload->data(); } // This is used to indicate that the packet could be holding one of a set of // types, e.g. Packet>. // // A Packet> has an interface similar to std::variant. // However, we cannot use std::variant directly, since it requires that the // contained object be stored in place within the variant. // Suppose we have a stream that accepts an Image or an ImageFrame, and it // receives a Packet. To present it as a // std::variant we would have to move the ImageFrame into // the variant (or copy it), but that is not compatible with Packet's existing // ownership model. // We could have Get() return a std::variant, // std::reference_wrapper>, but that would just make user code more // convoluted. // // TODO: should we just use Packet? template struct OneOf {}; namespace internal { template inline void CheckCompatibleType(const HolderBase& holder, internal::Wrap) { const packet_internal::Holder* typed_payload = holder.As(); CHECK(typed_payload) << absl::StrCat( "The Packet stores \"", holder.DebugTypeName(), "\", but \"", MediaPipeTypeStringOrDemangled(), "\" was requested."); // CHECK(payload_->has_type()); } template inline void CheckCompatibleType(const HolderBase& holder, internal::Wrap>) { bool compatible = (holder.As() || ...); CHECK(compatible) << "The Packet stores \"" << holder.DebugTypeName() << "\", but one of " << absl::StrJoin( {absl::StrCat("\"", MediaPipeTypeStringOrDemangled(), "\"")...}, ", ") << " was requested."; } struct Generic { Generic() = delete; }; template struct IsCompatibleType : std::false_type {}; template struct IsCompatibleType : std::true_type {}; template struct IsCompatibleType : std::true_type {}; template struct IsCompatibleType> : std::integral_constant || ...)> {}; }; // namespace internal template inline Packet PacketBase::As() const { if (!payload_) return Packet().At(timestamp_); packet_internal::Holder* typed_payload = payload_->As(); internal::CheckCompatibleType(*payload_, internal::Wrap{}); return Packet(payload_).At(timestamp_); } template <> inline Packet PacketBase::As() const; template class Packet; #if __cplusplus >= 201703L // Deduction guide to silence -Wctad-maybe-unsupported. explicit Packet()->Packet; #endif // C++17 template <> class Packet : public PacketBase { public: Packet() = default; Packet At(Timestamp timestamp) const&; Packet At(Timestamp timestamp) &&; protected: explicit Packet(std::shared_ptr payload) : PacketBase(std::move(payload)) {} friend PacketBase; }; // Having Packet subclass Packet will require hiding some methods // like As. May be better not to subclass, and allow implicit conversion // instead. template class Packet : public Packet { public: Packet() = default; Packet At(Timestamp timestamp) const&; Packet At(Timestamp timestamp) &&; const T& Get() const { CHECK(payload_); packet_internal::Holder* typed_payload = payload_->As(); CHECK(typed_payload); return typed_payload->data(); } const T& operator*() const { return Get(); } template T GetOr(U&& v) const { return IsEmpty() ? static_cast(absl::forward(v)) : **this; } // Note: Consume is included for compatibility with the old Packet; however, // it relies on shared_ptr.unique(), which is deprecated and is not guaranteed // to give exact results. absl::StatusOr> Consume() { return PacketBase::Consume(); } private: explicit Packet(std::shared_ptr payload) : Packet(std::move(payload)) {} friend PacketBase; template friend Packet MakePacket(Args&&... args); template friend Packet PacketAdopting(const U* ptr); template friend Packet PacketAdopting(std::unique_ptr ptr); }; namespace internal { template struct Overload : F... { using F::operator()...; }; template explicit Overload(F...) -> Overload; template struct First { using type = T; }; template struct AddStatus { using type = StatusOr; }; template struct AddStatus> { using type = StatusOr; }; template <> struct AddStatus { using type = Status; }; template <> struct AddStatus { using type = Status; }; template struct CallAndAddStatusImpl { typename AddStatus::type operator()(const F& f, A&&... a) { return f(std::forward(a)...); } }; template struct CallAndAddStatusImpl { Status operator()(const F& f, A&&... a) { f(std::forward(a)...); return {}; } }; template auto CallAndAddStatus(const F& f, A&&... a) { return CallAndAddStatusImpl, F, A...>()( f, std::forward(a)...); } } // namespace internal template class Packet> : public PacketBase { public: Packet() = default; template using AllowedType = std::enable_if_t<(std::is_same_v || ...)>; template > Packet(const Packet& p) : PacketBase(p) {} template > Packet>& operator=(const Packet& p) { PacketBase::operator=(p); return *this; } template > Packet(Packet&& p) : PacketBase(std::move(p)) {} template > Packet>& operator=(Packet&& p) { PacketBase::operator=(std::move(p)); return *this; } Packet> At(Timestamp timestamp) const& { return Packet>(*this).At(timestamp); } Packet> At(Timestamp timestamp) && { timestamp_ = timestamp; return std::move(*this); } template > const U& Get() const { CHECK(payload_); packet_internal::Holder* typed_payload = payload_->As(); CHECK(typed_payload); return typed_payload->data(); } template > bool Has() const { return payload_ && payload_->As(); } template auto Visit(const F&... args) const { CHECK(payload_); auto f = internal::Overload{args...}; using FirstT = typename internal::First::type; using ResultType = absl::result_of_t; static_assert( (std::is_same_v> && ...), "All visitor overloads must have the same return type"); return Invoke(f); } // Note: Consume is included for compatibility with the old Packet; however, // it relies on shared_ptr.unique(), which is deprecated and is not guaranteed // to give exact results. template > absl::StatusOr> Consume() { return PacketBase::Consume(); } template auto ConsumeAndVisit(const F&... args) { CHECK(payload_); auto f = internal::Overload{args...}; using FirstT = typename internal::First::type; using VisitorResultType = absl::result_of_t)>; static_assert( (std::is_same_v)>> && ...), "All visitor overloads must have the same return type"); using ResultType = typename internal::AddStatus::type; return InvokeConsuming(f); } protected: explicit Packet(std::shared_ptr payload) : PacketBase(std::move(payload)) {} friend PacketBase; private: template auto Invoke(const F& f) const { return f(Get()); } template auto Invoke(const F& f) const { return Has() ? f(Get()) : Invoke(f); } template auto InvokeConsuming(const F& f) -> R { auto maybe_value = Consume(); if (maybe_value.ok()) return internal::CallAndAddStatus(f, std::move(maybe_value).value()); else return maybe_value.status(); } template auto InvokeConsuming(const F& f) -> R { return Has() ? InvokeConsuming(f) : InvokeConsuming(f); } }; template <> inline Packet PacketBase::As() const { if (!payload_) return Packet().At(timestamp_); return Packet(payload_).At(timestamp_); } inline PacketBase PacketBase::At(Timestamp timestamp) const& { return PacketBase(*this).At(timestamp); } inline PacketBase PacketBase::At(Timestamp timestamp) && { timestamp_ = timestamp; return std::move(*this); } template inline Packet Packet::At(Timestamp timestamp) const& { return Packet(*this).At(timestamp); } template inline Packet Packet::At(Timestamp timestamp) && { timestamp_ = timestamp; return std::move(*this); } inline Packet Packet::At( Timestamp timestamp) const& { return Packet(*this).At(timestamp); } inline Packet Packet::At( Timestamp timestamp) && { timestamp_ = timestamp; return std::move(*this); } template Packet MakePacket(Args&&... args) { return Packet(std::make_shared>( new T(std::forward(args)...))); } template Packet PacketAdopting(const T* ptr) { return Packet(std::make_shared>(ptr)); } template Packet PacketAdopting(std::unique_ptr ptr) { return Packet(std::make_shared>(ptr.release())); } } // namespace api2 } // namespace mediapipe #endif // MEDIAPIPE_FRAMEWORK_API2_PACKET_H_