/** * Entity cache for rclnodejs publishers, subscribers, and service clients. * * Lazily creates and caches DDS entities so repeated publishes to the same * topic reuse a single publisher, etc. Same pattern as the reference agents' * `_pubs`, `_subs`, `_srv_clients` maps. */ import type { Subscription } from "../types.js"; import { loadMessageClass, fromRosMessage } from "./conversion.js"; type Node = any; type MessageHandler = (msg: Record) => void; export class EntityCache { private publishers = new Map(); private subscriptions = new Map }>(); private serviceClients = new Map(); /** * Get or create a cached publisher for the given topic and type. */ getPublisher(node: Node, topic: string, typeStr: string): any { const key = `${topic}::${typeStr}`; const cached = this.publishers.get(key); if (cached) return cached; const MessageClass = loadMessageClass(typeStr); const publisher = node.createPublisher(MessageClass, topic); this.publishers.set(key, publisher); return publisher; } /** * Subscribe to a topic. If already subscribed, adds the handler to the * existing subscription's handler set. Returns a Subscription handle. */ addSubscription( node: Node, topic: string, typeStr: string, handler: MessageHandler, ): Subscription { const key = `${topic}::${typeStr}`; let entry = this.subscriptions.get(key); if (!entry) { const MessageClass = loadMessageClass(typeStr); const handlers = new Set(); const handle = node.createSubscription( MessageClass, topic, (msg: any) => { const plain = fromRosMessage(msg, typeStr); for (const h of handlers) { h(plain); } }, ); entry = { handle, handlers }; this.subscriptions.set(key, entry); } entry.handlers.add(handler); return { unsubscribe: () => { entry!.handlers.delete(handler); // If no more handlers, destroy the subscription if (entry!.handlers.size === 0) { try { node.destroySubscription(entry!.handle); } catch { // Already destroyed } this.subscriptions.delete(key); } }, }; } /** * Get or create a cached service client for the given service and type. */ getServiceClient(node: Node, service: string, typeStr: string): any { const key = `${service}::${typeStr}`; const cached = this.serviceClients.get(key); if (cached) return cached; const ServiceClass = loadMessageClass(typeStr); const client = node.createClient(ServiceClass, service); this.serviceClients.set(key, client); return client; } /** * Destroy all cached entities. Called during transport shutdown. */ destroyAll(node: Node): void { for (const pub of this.publishers.values()) { try { node.destroyPublisher(pub); } catch { // Best-effort cleanup } } this.publishers.clear(); for (const { handle } of this.subscriptions.values()) { try { node.destroySubscription(handle); } catch { // Best-effort cleanup } } this.subscriptions.clear(); for (const client of this.serviceClients.values()) { try { node.destroyClient(client); } catch { // Best-effort cleanup } } this.serviceClients.clear(); } }