Class ThreadSafeEntityCache
Defined in File thread_safe_entity_cache.hpp
Nested Relationships
Nested Types
Class Documentation
-
class ThreadSafeEntityCache
Thread-safe, index-optimized cache for SOVD entities.
Design principles:
Primary storage in vectors (cache-friendly, predictable memory)
Hash indexes for O(1) lookup by ID
Relationship indexes for O(1) aggregation queries
Reader-writer lock for concurrent access
Batch updates to minimize lock contention
Thread Safety:
Multiple readers can access concurrently (shared lock)
Writers get exclusive access (unique lock)
Readers never block each other
Writers block all readers and other writers
Usage:
Discovery thread calls update_*() methods (writer)
HTTP handlers call get_*() methods (reader)
Public Functions
-
ThreadSafeEntityCache() = default
-
void update_all(std::vector<Area> areas, std::vector<Component> components, std::vector<App> apps, std::vector<Function> functions, std::unordered_map<std::string, std::string> node_to_app = {})
Atomic batch update of all entities.
This is the preferred update method as it:
Minimizes lock hold time
Ensures readers never see partial state
Rebuilds all indexes in one pass
- Parameters:
areas – All discovered areas
components – All discovered components
apps – All discovered apps
functions – All discovered functions
node_to_app – Node FQN to app entity ID mapping from linking result (optional)
-
void update_areas(std::vector<Area> areas)
Incremental update for single entity type.
Use sparingly - prefer update_all() for full refresh. Each call acquires exclusive lock and rebuilds relevant indexes.
-
void update_topic_types(std::unordered_map<std::string, std::string> topic_types)
Update cached topic type mapping.
Called during cache refresh to cache topic name -> message type mapping. This avoids expensive ROS graph queries on every /data request.
- Parameters:
topic_types – Map of topic name to message type
-
AppLinksSnapshot get_app_with_links(const std::string &id) const
-
AppDependenciesSnapshot get_app_with_dependencies(const std::string &id) const
-
bool has_area(const std::string &id) const
-
bool has_component(const std::string &id) const
-
bool has_app(const std::string &id) const
-
bool has_function(const std::string &id) const
-
std::string get_topic_type(const std::string &topic_name) const
Get cached message type for a topic.
- Parameters:
topic_name – Full topic path (e.g., “/sensor/temperature”)
- Returns:
Message type string, or empty if not cached
-
std::unordered_map<std::string, std::string> get_node_to_app() const
Get the node FQN to app entity ID mapping.
Used by trigger subscribers to resolve ROS 2 node FQNs to manifest entity IDs. The mapping is populated from the linking result during cache refresh.
- Returns:
Map of node FQN -> app entity ID (empty if no linking available)
-
std::string resolve_node_to_app(const std::string &node_fqn) const
Look up a single node FQN to app entity ID mapping (shared lock, no map copy)
-
SovdEntityType get_entity_type(const std::string &id) const
-
std::vector<std::string> get_apps_for_component(const std::string &component_id) const
Get Apps hosted on a Component.
- Returns:
Vector of App IDs (empty if component not found)
-
std::vector<std::string> get_components_for_area(const std::string &area_id) const
Get Components in an Area.
- Returns:
Vector of Component IDs (empty if area not found)
-
std::vector<std::string> get_apps_for_function(const std::string &function_id) const
Get Apps implementing a Function.
- Returns:
Vector of App IDs (empty if function not found)
-
std::vector<std::string> get_subareas(const std::string &area_id) const
Get subareas of an Area.
- Returns:
Vector of Area IDs (empty if area not found or no subareas)
-
AggregatedOperations get_app_operations(const std::string &app_id) const
Aggregate operations for an App (no aggregation, returns own ops)
-
AggregatedOperations get_component_operations(const std::string &component_id) const
Aggregate operations for a Component.
Returns: Component’s own operations + all operations from hosted Apps. Deduplicates by operation full_path.
-
AggregatedOperations get_area_operations(const std::string &area_id) const
Aggregate operations for an Area (x-medkit extension)
Returns: All operations from all Components in the Area. Recursive through Component→App hierarchy.
Note: This is a ros2_medkit extension. SOVD spec doesn’t allow Area to have resource collections.
-
AggregatedOperations get_function_operations(const std::string &function_id) const
Aggregate operations for a Function.
Returns: All operations from all Apps implementing this Function.
-
AggregatedData get_entity_data(const std::string &entity_id) const
Aggregate data (topics) for any entity by ID.
Unified method that works for all entity types. Aggregates topics from the entity and its children.
- Parameters:
entity_id – Entity ID to get data for
- Returns:
AggregatedData with topics, or empty if entity not found
-
AggregatedData get_app_data(const std::string &app_id) const
Aggregate data (topics) for an App.
-
AggregatedData get_component_data(const std::string &component_id) const
Aggregate data (topics) for a Component.
Returns: Component’s own topics + all topics from hosted Apps.
-
AggregatedData get_area_data(const std::string &area_id) const
Aggregate data (topics) for an Area.
Returns: All topics from all Components in the Area.
-
AggregatedData get_function_data(const std::string &function_id) const
Aggregate data (topics) for a Function.
Returns: All topics from all Apps implementing this Function.
-
AggregatedConfigurations get_entity_configurations(const std::string &entity_id) const
Aggregate configurations (node FQNs) for any entity by ID.
Unified method that works for all entity types. For Apps, returns the single bound node. For Components/Areas/Functions, aggregates all child app nodes.
- Parameters:
entity_id – Entity ID to get configurations for
- Returns:
AggregatedConfigurations with node FQNs, or empty if entity not found
-
AggregatedConfigurations get_app_configurations(const std::string &app_id) const
Get configurations for an App (returns its single bound node)
-
AggregatedConfigurations get_component_configurations(const std::string &component_id) const
Aggregate configurations for a Component.
Returns: All node FQNs from hosted Apps.
-
AggregatedConfigurations get_area_configurations(const std::string &area_id) const
Aggregate configurations for an Area.
Returns: All node FQNs from all Components in the Area.
-
AggregatedConfigurations get_function_configurations(const std::string &function_id) const
Aggregate configurations for a Function.
Returns: All node FQNs from all Apps implementing this Function.
-
std::optional<EntityRef> find_operation_owner(const std::string &operation_path) const
Find which entity owns an operation by full path.
- Parameters:
operation_path – e.g., “/nav2/navigate_to_pose”
- Returns:
EntityRef if found
-
EntityCacheStats get_stats() const
Get cache statistics.
-
std::string validate() const
Validate internal consistency.
Checks:
All indexes point to valid vector entries
Relationship indexes are consistent
No duplicate IDs within entity type
- Returns:
Empty string if valid, error message otherwise
-
std::chrono::system_clock::time_point get_last_update() const
Get last update timestamp.
-
uint64_t generation() const
Get the current generation counter.
The generation counter is incremented on every cache mutation (update_all, update_areas, update_components, update_apps, update_functions). Consumers can use this to detect when cached derived data (e.g., OpenAPI specs) is stale and needs regeneration.
-
struct AppDependenciesSnapshot
Atomic snapshot of an App together with its declared
depends_onapps.handle_app_depends_oniteratesapp.depends_onand resolves each id viaget_app()- one shared_lock per dependency. A writer refresh can land between the app fetch and any of the per-dependency fetches, so a 5-dependency app can return 5 apps from 5 different cache generations. This helper takes a single shared_lock and returns the app together with every dependency resolved in the same generation.appis empty ifapp_idis not in the cache.dependencieslists every entry inapp->depends_onin declaration order; the optional is empty when the referenced dependency cannot be resolved (broken ref).
-
struct AppLinksSnapshot
Atomic snapshot of an App together with its parent Component and Area.
Three sequential get_app/get_component/get_area calls each acquire a fresh shared_lock and a writer refresh can advance the generation between them, so handlers that traverse App -> Component -> Area can observe a mixed-generation view (e.g. app from gen N, component from N+1, area from N). This helper resolves the chain under a single shared_lock so the result is internally consistent.
appis empty ifapp_idis not in the cache.componentis empty if the app has nocomponent_idor the referenced component is missing.areais empty if the component has noareaor the referenced area is missing. The two latter cases are distinguishable from “no parent” becauseapp.component_id/component.arearemain set on the returned models.