What can I help you with?
NVIDIA Holoscan SDK v3.4.0

Program Listing for File fragment.hpp

Return to documentation for file (include/holoscan/core/fragment.hpp)

Copy
Copied!
            

/* * SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef HOLOSCAN_CORE_FRAGMENT_HPP #define HOLOSCAN_CORE_FRAGMENT_HPP #include <future> // for std::future #include <iostream> // for std::cout #include <memory> // for std::shared_ptr #include <set> // for std::set #include <shared_mutex> // for std::shared_mutex #include <string> // for std::string #include <string_view> // for std::string_view #include <tuple> #include <type_traits> // for std::enable_if_t, std::is_constructible #include <typeinfo> // for std::type_info #include <unordered_map> #include <unordered_set> #include <utility> // for std::pair #include <vector> #include "common.hpp" #include "config.hpp" #include "data_logger.hpp" #include "dataflow_tracker.hpp" #include "executor.hpp" #include "fragment_service_provider.hpp" #include "graph.hpp" #include "network_context.hpp" #include "resources/data_logger.hpp" #include "scheduler.hpp" namespace holoscan { namespace gxf { // Forward declarations class GXFExecutor; } // namespace gxf class ThreadPool; // Forward declare ComponentBase for the friend declaration or internal setter class ComponentBase; constexpr static const char* kStartOperatorName = "<|start|>"; // key = operator name, value = (input port names, output port names, multi-receiver names) using FragmentPortMap = std::unordered_map<std::string, std::tuple<std::unordered_set<std::string>, std::unordered_set<std::string>, std::unordered_set<std::string>>>; // Data structure containing port information for multiple fragments. Fragments are composed by // the workers and port information is sent back to the driver for addition to this map. // The keys are the fragment names. using MultipleFragmentsPortMap = std::unordered_map<std::string, FragmentPortMap>; constexpr MetadataPolicy kDefaultMetadataPolicy = MetadataPolicy::kRaise; constexpr bool kDefaultMetadataEnabled = true; class Fragment : public FragmentServiceProvider { public: Fragment() = default; ~Fragment() override; // Delete copy and move operations due to std::shared_mutex member Fragment(const Fragment&) = delete; Fragment& operator=(const Fragment&) = delete; Fragment(Fragment&&) = delete; Fragment& operator=(Fragment&&) = delete; Fragment& name(const std::string& name) &; Fragment&& name(const std::string& name) &&; const std::string& name() const; Fragment& application(Application* app); Application* application() const; void config(const std::string& config_file, [[maybe_unused]] const std::string& prefix = ""); void config(std::shared_ptr<Config>& config); Config& config(); std::shared_ptr<Config> config_shared(); OperatorGraph& graph(); std::shared_ptr<OperatorGraph> graph_shared(); Executor& executor(); std::shared_ptr<Executor> executor_shared(); std::shared_ptr<Scheduler> scheduler(); // /** // * @brief Set the scheduler used by the executor // * // * @param scheduler The scheduler to be added. // */ void scheduler(const std::shared_ptr<Scheduler>& scheduler); std::shared_ptr<NetworkContext> network_context(); void network_context(const std::shared_ptr<NetworkContext>& network_context); ArgList from_config(const std::string& key); std::unordered_set<std::string> config_keys(); template <typename OperatorT, typename StringT, typename... ArgsT, typename = std::enable_if_t<std::is_constructible_v<std::string, StringT>>> std::shared_ptr<OperatorT> make_operator(StringT name, ArgsT&&... args) { HOLOSCAN_LOG_DEBUG("Creating operator '{}'", name); auto op = std::make_shared<OperatorT>(std::forward<ArgsT>(args)...); op->name(name); setup_component_internals(op.get()); auto spec = std::make_shared<OperatorSpec>(this); op->setup(*spec.get()); op->spec(spec); // We used to initialize operator here, but now it is initialized in initialize_fragment // function after a graph of a fragment has been composed. return op; } template <typename OperatorT, typename... ArgsT> std::shared_ptr<OperatorT> make_operator(ArgsT&&... args) { HOLOSCAN_LOG_DEBUG("Creating operator"); auto op = make_operator<OperatorT>("noname_operator", std::forward<ArgsT>(args)...); return op; } template <typename ResourceT, typename StringT, typename... ArgsT, typename = std::enable_if_t<std::is_constructible_v<std::string, StringT>>> std::shared_ptr<ResourceT> make_resource(StringT name, ArgsT&&... args) { HOLOSCAN_LOG_DEBUG("Creating resource '{}'", name); auto resource = std::make_shared<ResourceT>(std::forward<ArgsT>(args)...); resource->name(name); setup_component_internals(resource.get()); auto spec = std::make_shared<ComponentSpec>(this); resource->setup(*spec.get()); resource->spec(spec); // Skip initialization. `resource->initialize()` is done in GXFOperator::initialize() return resource; } template <typename ResourceT, typename... ArgsT> std::shared_ptr<ResourceT> make_resource(ArgsT&&... args) { HOLOSCAN_LOG_DEBUG("Creating resource"); auto resource = make_resource<ResourceT>("noname_resource", std::forward<ArgsT>(args)...); return resource; } template <typename ConditionT, typename StringT, typename... ArgsT, typename = std::enable_if_t<std::is_constructible_v<std::string, StringT>>> std::shared_ptr<ConditionT> make_condition(StringT name, ArgsT&&... args) { HOLOSCAN_LOG_DEBUG("Creating condition '{}'", name); auto condition = std::make_shared<ConditionT>(std::forward<ArgsT>(args)...); condition->name(name); setup_component_internals(condition.get()); auto spec = std::make_shared<ComponentSpec>(this); condition->setup(*spec.get()); condition->spec(spec); // Skip initialization. `condition->initialize()` is done in GXFOperator::initialize() return condition; } template <typename ConditionT, typename... ArgsT> std::shared_ptr<ConditionT> make_condition(ArgsT&&... args) { HOLOSCAN_LOG_DEBUG("Creating condition"); auto condition = make_condition<ConditionT>("noname_condition", std::forward<ArgsT>(args)...); return condition; } template <typename SchedulerT, typename StringT, typename... ArgsT, typename = std::enable_if_t<std::is_constructible_v<std::string, StringT>>> std::shared_ptr<SchedulerT> make_scheduler(StringT name, ArgsT&&... args) { HOLOSCAN_LOG_DEBUG("Creating scheduler '{}'", name); auto scheduler = std::make_shared<SchedulerT>(std::forward<ArgsT>(args)...); scheduler->name(name); scheduler->fragment(this); auto spec = std::make_shared<ComponentSpec>(this); scheduler->setup(*spec.get()); scheduler->spec(spec); // Skip initialization. `scheduler->initialize()` is done in GXFExecutor::run() return scheduler; } template <typename SchedulerT, typename... ArgsT> std::shared_ptr<SchedulerT> make_scheduler(ArgsT&&... args) { HOLOSCAN_LOG_DEBUG("Creating scheduler"); auto scheduler = make_scheduler<SchedulerT>("", std::forward<ArgsT>(args)...); return scheduler; } template <typename NetworkContextT, typename StringT, typename... ArgsT, typename = std::enable_if_t<std::is_constructible_v<std::string, StringT>>> std::shared_ptr<NetworkContextT> make_network_context(StringT name, ArgsT&&... args) { HOLOSCAN_LOG_DEBUG("Creating network context '{}'", name); auto network_context = std::make_shared<NetworkContextT>(std::forward<ArgsT>(args)...); network_context->name(name); network_context->fragment(this); auto spec = std::make_shared<ComponentSpec>(this); network_context->setup(*spec.get()); network_context->spec(spec); // Skip initialization. `network_context->initialize()` is done in GXFExecutor::run() return network_context; } template <typename NetworkContextT, typename... ArgsT> std::shared_ptr<NetworkContextT> make_network_context(ArgsT&&... args) { HOLOSCAN_LOG_DEBUG("Creating network_context"); auto network_context = make_network_context<NetworkContextT>("", std::forward<ArgsT>(args)...); return network_context; } std::shared_ptr<ThreadPool> make_thread_pool(const std::string& name, int64_t initial_size = 1); std::shared_ptr<FragmentService> get_service_erased(const std::type_info& service_type, std::string_view id) const override; template <typename ServiceT> bool register_service(const std::shared_ptr<ServiceT>& svc, std::string_view id = "") { static_assert(holoscan::is_one_of_derived_v<ServiceT, Resource, FragmentService>, "ServiceT must inherit from Resource or FragmentService"); if (!svc) { HOLOSCAN_LOG_ERROR("Cannot register null pointer to fragment service"); return false; } bool is_service = true; std::shared_ptr<FragmentService> svc_to_register; std::shared_ptr<Resource> resource; if constexpr (std::is_base_of_v<Resource, ServiceT>) { resource = svc; is_service = false; } if constexpr (std::is_base_of_v<FragmentService, ServiceT>) { svc_to_register = svc; resource = svc->resource(); if constexpr (holoscan::is_one_of_derived_v<ServiceT, Resource>) { // For classes that inherit from both FragmentService and Resource, use the object itself // as the resource if no other resource has been specified. if (!resource) { resource = std::const_pointer_cast<ServiceT>(svc); svc->resource(resource); } } // When a class inherits from both Resource and FragmentService, prioritize treating it as a // service is_service = true; } // If the resource is available, we use resource's name for id and the id should be empty if (resource) { if (!id.empty()) { HOLOSCAN_LOG_ERROR( "If the Holoscan Resource is registered as a service, the id should be empty"); return false; } id = resource->name(); if (fragment_resource_services_by_name_.find(std::string(id)) != fragment_resource_services_by_name_.end()) { HOLOSCAN_LOG_ERROR( "Resource service '{}' already exists in the fragment. Please specify a unique " "name when creating a Resource instance.", id); return false; } } // If the service is a resource, we need to create a new DefaultFragmentService object with the // resource if (!is_service) { auto fragment_service = std::make_shared<DefaultFragmentService>(resource); svc_to_register = fragment_service; } std::unique_lock<std::shared_mutex> lock(fragment_service_registry_mutex_); ServiceKey key{is_service ? typeid(*svc_to_register) : typeid(DefaultFragmentService), std::string(id)}; if (resource) { fragment_resource_services_by_name_[std::string(id)] = resource; // We use 'insert_or_assign' here since ServiceKey contains a std::type_index member which // cannot be default-constructed fragment_resource_to_service_key_map_.insert_or_assign(resource, key); // Also register the service with its resource type ServiceKey resource_key{typeid(*resource), std::string(id)}; fragment_services_by_key_[resource_key] = svc_to_register; } fragment_services_by_key_[key] = std::move(svc_to_register); HOLOSCAN_LOG_DEBUG("Registered service '{}' with id '{}'", typeid(ServiceT).name(), id); return true; } template <typename ServiceT = DefaultFragmentService> std::shared_ptr<ServiceT> service(std::string_view id = "") const { static_assert(holoscan::is_one_of_derived_v<ServiceT, Resource, FragmentService>, "ServiceT must inherit from Resource or FragmentService"); // Get the base service from the service registry auto base_service = get_service_erased(typeid(ServiceT), id); if (!base_service) { HOLOSCAN_LOG_DEBUG("Fragment '{}': Service of type {} with id '{}' not found.", name(), typeid(ServiceT).name(), std::string(id)); return nullptr; } // Handle Resource-derived services if constexpr (std::is_base_of_v<Resource, ServiceT>) { auto resource_ptr = base_service->resource(); if (!resource_ptr) { HOLOSCAN_LOG_DEBUG( "Fragment '{}': No service resource is available for service with id '{}'.", name(), std::string(id)); return nullptr; } // Attempt to cast the resource to the requested type auto typed_resource = std::dynamic_pointer_cast<ServiceT>(resource_ptr); if (!typed_resource) { HOLOSCAN_LOG_DEBUG( "Fragment '{}': Service resource with id '{}' is not type-castable to type '{}'.", name(), std::string(id), typeid(ServiceT).name()); } return typed_resource; } else { // Handle FragmentService-derived services // Since DefaultFragmentService implements FragmentService, we can safely cast auto typed_service = std::dynamic_pointer_cast<ServiceT>(base_service); if (!typed_service) { HOLOSCAN_LOG_DEBUG("Fragment '{}': Service with id '{}' is not type-castable to type '{}'.", name(), std::string(id), typeid(ServiceT).name()); } return typed_service; } } std::shared_ptr<FragmentService> get_service_by_type_info(const std::type_info& service_type, std::string_view id = "") const { return get_service_erased(service_type, id); } virtual const std::shared_ptr<Operator>& start_op(); virtual void add_operator(const std::shared_ptr<Operator>& op); virtual void add_flow(const std::shared_ptr<Operator>& upstream_op, const std::shared_ptr<Operator>& downstream_op); virtual void add_flow(const std::shared_ptr<Operator>& upstream_op, const std::shared_ptr<Operator>& downstream_op, std::set<std::pair<std::string, std::string>> port_pairs); virtual void set_dynamic_flows( const std::shared_ptr<Operator>& op, const std::function<void(const std::shared_ptr<Operator>&)>& dynamic_flow_func); virtual void compose(); virtual void run(); virtual std::future<void> run_async(); DataFlowTracker& track(uint64_t num_start_messages_to_skip = kDefaultNumStartMessagesToSkip, uint64_t num_last_messages_to_discard = kDefaultNumLastMessagesToDiscard, int latency_threshold = kDefaultLatencyThreshold, bool is_limited_tracking = false); DataFlowTracker* data_flow_tracker() { return data_flow_tracker_.get(); } virtual void compose_graph(); FragmentPortMap port_info() const; virtual bool is_metadata_enabled() const; virtual void is_metadata_enabled(bool enabled); virtual void enable_metadata(bool enable); virtual MetadataPolicy metadata_policy() const; virtual void metadata_policy(MetadataPolicy policy); virtual void stop_execution(const std::string& op_name = ""); void add_data_logger(const std::shared_ptr<DataLogger>& logger); const std::vector<std::shared_ptr<DataLogger>>& data_loggers() const { return data_loggers_; } protected: friend class Application; // to access 'scheduler_' in Application friend class AppDriver; friend class gxf::GXFExecutor; friend class holoscan::ComponentBase; // Allow ComponentBase to access internal setup template <typename ConfigT, typename... ArgsT> std::shared_ptr<Config> make_config(ArgsT&&... args) { return std::make_shared<ConfigT>(std::forward<ArgsT>(args)...); } template <typename GraphT> std::shared_ptr<GraphT> make_graph() { return std::make_shared<GraphT>(); } template <typename ExecutorT> std::shared_ptr<Executor> make_executor() { return std::make_shared<ExecutorT>(this); } template <typename ExecutorT, typename... ArgsT> std::shared_ptr<Executor> make_executor(ArgsT&&... args) { return std::make_shared<ExecutorT>(std::forward<ArgsT>(args)...); } void reset_graph_entities(); virtual void reset_state(); void load_extensions_from_config(); std::vector<std::shared_ptr<ThreadPool>>& thread_pools() { return thread_pools_; } void setup_component_internals(ComponentBase* component); // Note: Maintain the order of declarations (executor_ and graph_) to ensure proper destruction // of the executor's context. std::string name_; Application* app_ = nullptr; std::shared_ptr<Config> config_; std::shared_ptr<Executor> executor_; std::shared_ptr<OperatorGraph> graph_; std::shared_ptr<Scheduler> scheduler_; std::shared_ptr<NetworkContext> network_context_; std::shared_ptr<DataFlowTracker> data_flow_tracker_; std::vector<std::shared_ptr<ThreadPool>> thread_pools_; bool is_composed_ = false; bool is_run_called_ = false; std::optional<bool> is_metadata_enabled_ = std::nullopt; std::optional<MetadataPolicy> metadata_policy_ = std::nullopt; std::shared_ptr<Operator> start_op_; std::vector<std::shared_ptr<DataLogger>> data_loggers_; // Service registry members mutable std::shared_mutex fragment_service_registry_mutex_; std::unordered_map<ServiceKey, std::shared_ptr<FragmentService>, ServiceKeyHash> fragment_services_by_key_; std::unordered_map<std::string, std::shared_ptr<Resource>> fragment_resource_services_by_name_; std::unordered_map<std::shared_ptr<Resource>, ServiceKey> fragment_resource_to_service_key_map_; }; } // namespace holoscan #endif/* HOLOSCAN_CORE_FRAGMENT_HPP */

© Copyright 2022-2025, NVIDIA. Last updated on Jul 1, 2025.