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.
@@ -74,11 +117,6 @@ SpiceInterface::~SpiceInterface()
74117 delete this ->transRefStateOutMsgs .at (c);
75118 }
76119 delete [] this ->spiceBuffer ;
77- // if(this->SPICELoaded)
78- // {
79- // this->clearKeeper();
80- // }
81- return ;
82120}
83121
84122void SpiceInterface::clearKeeper ()
@@ -426,63 +464,46 @@ void SpiceInterface::pullSpiceData(std::vector<SpicePlanetStateMsgPayload> *spic
426464 }
427465}
428466
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
467+ /* *
468+ * Load a SPICE kernel for use by this interface.
469+ *
470+ * This function takes a kernel file name and a base directory and
471+ * ensures that the corresponding SPICE kernel is available to the
472+ * simulation. Internally the module keeps track of which kernels it
473+ * has already loaded so that the same file is not loaded multiple
474+ * times.
475+ *
476+ * @param kernelName File name of the kernel inside dataPath.
477+ * @param dataPath Directory where the kernel is located.
478+ * @return 0 on success, 1 if loading the kernel failed.
436479 */
437480int SpiceInterface::loadSpiceKernel (char *kernelName, const char *dataPath)
438481{
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- }
482+ std::filesystem::path base (dataPath);
483+ std::filesystem::path fullPath = base / kernelName;
484+ auto kernel = SpiceKernel::request (fullPath.string ());
485+ if (!kernel->wasLoadSuccesful ()) return 1 ;
486+ this ->loadedKernels [kernel->getPath ()] = kernel;
458487 return 0 ;
459488}
460489
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
490+ /* *
491+ * Tell this interface that a SPICE kernel is no longer needed.
492+ *
493+ * This function removes the kernel from the set of kernels managed
494+ * by this interface. Once no users remain, the underlying kernel is
495+ * also removed from SPICE so it no longer affects future queries.
496+ *
497+ * @param kernelName File name of the kernel inside dataPath.
498+ * @param dataPath Directory where the kernel is located.
499+ * @return always 0.
468500 */
469501int SpiceInterface::unloadSpiceKernel (char *kernelName, const char *dataPath)
470502{
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- }
503+ std::filesystem::path base (dataPath);
504+ std::filesystem::path fullPath = base / kernelName;
505+ auto key = absolutize (fullPath);
506+ this ->loadedKernels .erase (key);
486507 return 0 ;
487508}
488509
@@ -506,3 +527,61 @@ std::string SpiceInterface::getCurrentTimeString()
506527 delete[] spiceOutputBuffer;
507528 return (returnTimeString);
508529}
530+
531+ std::mutex SpiceKernel::mutex;
532+ std::unordered_map<std::string, std::weak_ptr<SpiceKernel>> SpiceKernel::cache;
533+
534+ std::shared_ptr<SpiceKernel>
535+ SpiceKernel::request (const std::filesystem::path& path)
536+ {
537+ const std::string key = absolutize (path);
538+
539+ std::lock_guard<std::mutex> lock (mutex);
540+
541+ auto it = cache.find (key);
542+ if (it != cache.end ())
543+ {
544+ if (auto existing = it->second .lock ())
545+ {
546+ // Already have a live handle to this kernel
547+ return existing;
548+ }
549+ // Weak pointer expired - fall through and create a new one
550+ }
551+
552+ // First live handle for this absolute path in this process
553+ auto handle = std::shared_ptr<SpiceKernel>(new SpiceKernel (key));
554+
555+ if (handle->loadSucceeded ) cache[key] = handle;
556+
557+ return handle;
558+ }
559+
560+ SpiceKernel::~SpiceKernel () noexcept
561+ {
562+ if (!loadSucceeded) return ;
563+
564+ SpiceErrorModeGuard guard;
565+ unload_c (path.c_str ());
566+ if (failed_c ())
567+ {
568+ reset_c (); // SPICE printed its own messages already
569+ }
570+ }
571+
572+ SpiceKernel::SpiceKernel (std::string path_)
573+ : path(std::move(path_))
574+ {
575+ SpiceErrorModeGuard guard;
576+ furnsh_c (path.c_str ());
577+
578+ if (failed_c ())
579+ {
580+ reset_c (); // SPICE already printed diagnostics
581+ loadSucceeded = false ; // destructor will not unload
582+ }
583+ else
584+ {
585+ loadSucceeded = true ;
586+ }
587+ }
0 commit comments