2323#include " architecture/utilities/simDefinitions.h"
2424#include " architecture/utilities/macroDefinitions.h"
2525#include " architecture/utilities/rigidBodyKinematics.h"
26+ #include " spiceInterface.h"
27+
28+ namespace {
29+ /* *
30+ * RAII guard for SPICE error mode.
31+ *
32+ * Sets SPICE error action to RETURN while the guard is alive so that
33+ * calls report failures via failed_c() instead of aborting. Restores
34+ * the previous error action and print settings on destruction.
35+ */
36+ struct SpiceErrorModeGuard
37+ {
38+ char oldAction[32 ];
39+ char oldPrint[32 ];
40+
41+ SpiceErrorModeGuard ()
42+ {
43+ erract_c (" GET" , sizeof (oldAction), oldAction);
44+ errprt_c (" GET" , sizeof (oldPrint), oldPrint);
45+
46+ // Only override the abort behavior
47+ erract_c (" SET" , 0 , const_cast <char *>(" RETURN" ));
48+ // DO NOT suppress printing: errprt is left untouched
49+ }
50+
51+ ~SpiceErrorModeGuard ()
52+ {
53+ erract_c (" SET" , 0 , oldAction);
54+ errprt_c (" SET" , 0 , oldPrint);
55+ }
56+ };
57+
58+ /* *
59+ * Normalize a file system path to a canonical absolute string.
60+ *
61+ * Used to key kernels so that one physical file maps to a single
62+ * cache entry even if referenced through different relative paths.
63+ */
64+ std::string absolutize (const std::filesystem::path& path)
65+ {
66+ return std::filesystem::absolute (path).lexically_normal ().string ();
67+ }
68+ }
2669
2770/* ! This constructor initializes the variables that spice uses. Most of them are
2871 not intended to be changed, but a couple are user configurable.
@@ -426,63 +469,46 @@ void SpiceInterface::pullSpiceData(std::vector<SpicePlanetStateMsgPayload> *spic
426469 }
427470}
428471
429- /* ! This method loads a requested SPICE kernel into the system memory. It is
430- its own method because we have to load several SPICE kernels in for our
431- application. Note that they are stored in the SPICE library and are not
432- held locally in this object.
433- @return int Zero for success one for failure
434- @param kernelName The name of the kernel we are loading
435- @param dataPath The path to the data area on the filesystem
472+ /* *
473+ * Load a SPICE kernel for use by this interface.
474+ *
475+ * This function takes a kernel file name and a base directory and
476+ * ensures that the corresponding SPICE kernel is available to the
477+ * simulation. Internally the module keeps track of which kernels it
478+ * has already loaded so that the same file is not loaded multiple
479+ * times.
480+ *
481+ * @param kernelName File name of the kernel inside dataPath.
482+ * @param dataPath Directory where the kernel is located.
483+ * @return 0 on success, 1 if loading the kernel failed.
436484 */
437485int SpiceInterface::loadSpiceKernel (char *kernelName, const char *dataPath)
438486{
439- char *fileName = new char [this ->charBufferSize ];
440- SpiceChar *name = new SpiceChar[this ->charBufferSize ];
441-
442- // ! - The required calls come from the SPICE documentation.
443- // ! - The most critical call is furnsh_c
444- strcpy (name, " REPORT" );
445- erract_c (" SET" , this ->charBufferSize , name);
446- strcpy (fileName, dataPath);
447- strcat (fileName, kernelName);
448- furnsh_c (fileName);
449-
450- // ! - Check to see if we had trouble loading a kernel and alert user if so
451- strcpy (name, " DEFAULT" );
452- erract_c (" SET" , this ->charBufferSize , name);
453- delete[] fileName;
454- delete[] name;
455- if (failed_c ()) {
456- return 1 ;
457- }
487+ std::filesystem::path base (dataPath);
488+ std::filesystem::path fullPath = base / kernelName;
489+ auto kernel = SpiceKernel::request (fullPath.string ());
490+ if (!kernel->wasLoadSuccesful ()) return 1 ;
491+ this ->loadedKernels [kernel->getPath ()] = kernel;
458492 return 0 ;
459493}
460494
461- /* ! This method unloads a requested SPICE kernel into the system memory. It is
462- its own method because we have to load several SPICE kernels in for our
463- application. Note that they are stored in the SPICE library and are not
464- held locally in this object.
465- @return int Zero for success one for failure
466- @param kernelName The name of the kernel we are unloading
467- @param dataPath The path to the data area on the filesystem
495+ /* *
496+ * Tell this interface that a SPICE kernel is no longer needed.
497+ *
498+ * This function removes the kernel from the set of kernels managed
499+ * by this interface. Once no users remain, the underlying kernel is
500+ * also removed from SPICE so it no longer affects future queries.
501+ *
502+ * @param kernelName File name of the kernel inside dataPath.
503+ * @param dataPath Directory where the kernel is located.
504+ * @return always 0.
468505 */
469506int SpiceInterface::unloadSpiceKernel (char *kernelName, const char *dataPath)
470507{
471- char *fileName = new char [this ->charBufferSize ];
472- SpiceChar *name = new SpiceChar[this ->charBufferSize ];
473-
474- // ! - The required calls come from the SPICE documentation.
475- // ! - The most critical call is furnsh_c
476- strcpy (name, " REPORT" );
477- erract_c (" SET" , this ->charBufferSize , name);
478- strcpy (fileName, dataPath);
479- strcat (fileName, kernelName);
480- unload_c (fileName);
481- delete[] fileName;
482- delete[] name;
483- if (failed_c ()) {
484- return 1 ;
485- }
508+ std::filesystem::path base (dataPath);
509+ std::filesystem::path fullPath = base / kernelName;
510+ auto key = absolutize (fullPath);
511+ this ->loadedKernels .erase (key);
486512 return 0 ;
487513}
488514
@@ -506,3 +532,61 @@ std::string SpiceInterface::getCurrentTimeString()
506532 delete[] spiceOutputBuffer;
507533 return (returnTimeString);
508534}
535+
536+ std::mutex SpiceKernel::mutex;
537+ std::unordered_map<std::string, std::weak_ptr<SpiceKernel>> SpiceKernel::cache;
538+
539+ std::shared_ptr<SpiceKernel>
540+ SpiceKernel::request (const std::filesystem::path& path)
541+ {
542+ const std::string key = absolutize (path);
543+
544+ std::lock_guard<std::mutex> lock (mutex);
545+
546+ auto it = cache.find (key);
547+ if (it != cache.end ())
548+ {
549+ if (auto existing = it->second .lock ())
550+ {
551+ // Already have a live handle to this kernel
552+ return existing;
553+ }
554+ // Weak pointer expired - fall through and create a new one
555+ }
556+
557+ // First live handle for this absolute path in this process
558+ auto handle = std::shared_ptr<SpiceKernel>(new SpiceKernel (key));
559+
560+ if (handle->loadSucceeded ) cache[key] = handle;
561+
562+ return handle;
563+ }
564+
565+ SpiceKernel::~SpiceKernel () noexcept
566+ {
567+ if (!loadSucceeded) return ;
568+
569+ SpiceErrorModeGuard guard;
570+ unload_c (path.c_str ());
571+ if (failed_c ())
572+ {
573+ reset_c (); // SPICE printed its own messages already
574+ }
575+ }
576+
577+ SpiceKernel::SpiceKernel (std::string path_)
578+ : path(std::move(path_))
579+ {
580+ SpiceErrorModeGuard guard;
581+ furnsh_c (path.c_str ());
582+
583+ if (failed_c ())
584+ {
585+ reset_c (); // SPICE already printed diagnostics
586+ loadSucceeded = false ; // destructor will not unload
587+ }
588+ else
589+ {
590+ loadSucceeded = true ;
591+ }
592+ }
0 commit comments