diff --git a/docs/source/Support/bskReleaseNotes.rst b/docs/source/Support/bskReleaseNotes.rst index a8d3b0cf1ac..48c2da41c4e 100644 --- a/docs/source/Support/bskReleaseNotes.rst +++ b/docs/source/Support/bskReleaseNotes.rst @@ -31,7 +31,6 @@ Version |release| - Demo video was added to :ref:`scenarioQuadMaps` documentation - Pinned python dependencies to avoid issues with new package versions. - Added a new github workflow job ``canary`` to routinely check the compatibility of latest python dependencies with python 3.13 on the latest mac-os. -- Fixed a bug in :ref:`spiceInterface` where multiple instances of the module were not properly managing SPICE kernel references, leading to potential conflicts and data corruption. - Deprecated :ref:`SpacecraftSystem`. It was never completed and we have other ways to connect spacecraft components - Allow event conditions and effects to be defined by functions. This is preferred over the old string-based method, as it enables the use of arbitrary packages and objects in events and allows for event code to be parsed by IDE tools. @@ -43,7 +42,6 @@ Version |release| - Enhance how ``uint64_t`` values are converted to doubles. BSK now warns if the time value is large enough such that the conversion method has a loss of precision in this process. - Support including an eclipse message in :ref:`SpacecraftLocation` to more accurately determine illumination. -- Fixed a bug in :ref:`spiceInterface` where required kernels were being unloaded before they were no longer needed. - Fixed an issue where the :ref:`spaceToGroundTransmitter` would check for the amount of data remaining in a different partition than the one being downlinked. - Fixed an issue where a high baud rate prevented the :ref:`spaceToGroundTransmitter` from downlinking data from the :ref:`simpleStorageUnit` or :ref:`partitionedStorageUnit`. diff --git a/src/simulation/environment/spiceInterface/_UnitTest/test_spiceThreadSafety.py b/src/simulation/environment/spiceInterface/_UnitTest/test_spiceThreadSafety.py deleted file mode 100644 index 477fc3da694..00000000000 --- a/src/simulation/environment/spiceInterface/_UnitTest/test_spiceThreadSafety.py +++ /dev/null @@ -1,331 +0,0 @@ -# -# ISC License -# -# Copyright (c) 2025, Autonomous Vehicle Systems Lab, University of Colorado at Boulder -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# - -import os -import sys -import time -import hashlib -import multiprocessing as mp -import pytest -import inspect -import traceback - -# Import Basilisk modules -from Basilisk import __path__ -from Basilisk.simulation import spiceInterface - -r""" -Unit Test for SPICE Interface Thread Safety -=========================================== - -This script tests the thread safety of the SPICE interface, specifically addressing -`GitHub issue #220 `_ where parallel simulations using SPICE were causing deadlocks and -data corruption. - -The test creates multiple SpiceInterface instances in parallel and forces them to -load/unload kernels simultaneously, creating contention that would previously lead -to deadlocks or corruption. -""" - -def get_file_hash(file_path): - """Calculate SHA-1 hash of a file to check for corruption.""" - hash_sha1 = hashlib.sha1() - with open(file_path, "rb") as f: - for chunk in iter(lambda: f.read(4096), b""): - hash_sha1.update(chunk) - return hash_sha1.hexdigest() - -def create_load_destroy_spice(worker_id, iterations, dataPath): - """ - Repeatedly create, load, and destroy SpiceInterface objects. - This function will be run in parallel by multiple processes. - - Args: - worker_id: ID of this worker process - iterations: Number of iterations to perform - dataPath: Path to SPICE data directory - """ - print(f"Worker {worker_id} starting with {iterations} iterations") - - success_count = 0 - failure_count = 0 - exceptions = [] - - try: - for i in range(iterations): - try: - # Create a new SpiceInterface object - spice = spiceInterface.SpiceInterface() - - # Use a fixed set of planets to avoid issues with random sampling - planets = ['earth', 'sun'] # Removed 'mars' which was causing errors - spice.addPlanetNames(planets) - - # Set data path - spice.SPICEDataPath = dataPath - - # This will trigger loading of SPICE kernels - spice.Reset(0) - - # Sleep for a short time to increase chances of contention - time.sleep(0.001) # Reduced from 0.01 to speed up test - - # Delete the object, which will trigger kernel unloading - del spice - - success_count += 1 - - # Report progress for each iteration - print(f"Worker {worker_id} completed iteration {i+1}/{iterations}") - except Exception as e: - failure_count += 1 - error_info = { - "worker_id": worker_id, - "iteration": i, - "error": str(e), - "traceback": traceback.format_exc() - } - exceptions.append(error_info) - print(f"Worker {worker_id} failed at iteration {i} with error: {str(e)}") - - # Continue with next iteration - continue - - except Exception as e: - # This catches any exceptions outside the iteration loop - failure_count += 1 - error_info = { - "worker_id": worker_id, - "iteration": -1, # Outside the loop - "error": str(e), - "traceback": traceback.format_exc() - } - exceptions.append(error_info) - print(f"Worker {worker_id} failed with error outside iteration loop: {str(e)}") - - return { - "worker_id": worker_id, - "success_count": success_count, - "failure_count": failure_count, - "exceptions": exceptions - } - -def run_thread_safety_test(num_workers=2, iterations_per_worker=5): - """ - Run the SPICE thread safety test. - - Args: - num_workers: Number of parallel workers - iterations_per_worker: Number of iterations per worker - - Returns: - results: Dictionary with test results - success: True if all operations completed successfully - """ - print(f"Starting SPICE Thread Safety Test with {num_workers} workers") - print(f"Each worker will perform {iterations_per_worker} iterations") - - # Get SPICE data path using the same approach as the other SPICE tests - bskPath = __path__[0] - dataPath = bskPath + '/supportData/EphemerisData/' - - # Start timer - start_time = time.time() - - # Prepare worker arguments - worker_args = [(i, iterations_per_worker, dataPath) for i in range(num_workers)] - - # Run workers in parallel - with mp.Pool(processes=num_workers) as pool: - worker_results = list(pool.starmap(create_load_destroy_spice, worker_args)) - - # End timer - end_time = time.time() - execution_time = end_time - start_time - - # Analyze results - total_success = sum(r["success_count"] for r in worker_results) - total_failure = sum(r["failure_count"] for r in worker_results) - all_exceptions = [e for r in worker_results for e in r["exceptions"]] - - # Prepare test results - results = { - "execution_time": execution_time, - "total_iterations": num_workers * iterations_per_worker, - "successful_iterations": total_success, - "failed_iterations": total_failure, - "exceptions": all_exceptions, - "corruption_detected": False # We're not checking for corruption in this simplified version - } - - # Print summary - print("\n--- SPICE Thread Safety Test Report ---") - print(f"Total execution time: {execution_time:.2f} seconds") - print(f"Total iterations: {num_workers * iterations_per_worker}") - print(f"Successful iterations: {total_success}") - print(f"Failed iterations: {total_failure}") - print(f"Exceptions encountered: {len(all_exceptions)}") - print("--------------------------------------\n") - - # Determine overall success - if total_success == 0: - # If no successful iterations at all, that's a test failure - print("TEST FAILED: No successful iterations completed") - if len(all_exceptions) > 0: - print("\nFirst exception details:") - print(all_exceptions[0]["traceback"]) - success = False - else: - # If some iterations succeeded, check for partial failures - success = (total_failure == 0) - - if success: - print("TEST PASSED: SPICE interface thread safety implementation is robust!") - else: - print("TEST FAILED: Issues detected with SPICE interface thread safety") - if len(all_exceptions) > 0: - print("\nFirst exception details:") - print(all_exceptions[0]["traceback"]) - - return results, success - -# Define the function outside of any test functions so it can be pickled -def _run_test_with_timeout(result_queue, num_workers, iterations): - try: - results, success = run_thread_safety_test(num_workers, iterations) - result_queue.put((results, success)) - except Exception as e: - import traceback - result_queue.put(({"error": str(e), "traceback": traceback.format_exc()}, False)) - -# Pytest test function -@pytest.mark.parametrize( - "num_workers, iterations", - [ - (2, 2), # Using 2 workers with 2 iterations each to test thread safety - ] -) -def test_spice_thread_safety(show_plots, num_workers, iterations): - """ - Test the thread safety of SPICE interface - - Args: - show_plots: Pytest fixture, not used but required - num_workers: Number of parallel workers to use - iterations: Number of kernel load/unload iterations per worker - """ - # Use multiprocessing with a timeout instead of signals for better platform compatibility - from multiprocessing import Process, Queue - import queue - - # Create a queue for results - result_queue = Queue() - - # Create and start process - test_process = Process(target=_run_test_with_timeout, args=(result_queue, num_workers, iterations)) - test_process.start() - - # Wait for result with timeout - timeout = 60 # 60 seconds timeout - test_process.join(timeout) - - # Check if process is still alive (timeout occurred) - if test_process.is_alive(): - # Kill the process if it's still running - test_process.terminate() - test_process.join(1) # Give it 1 second to terminate - - # If still alive after terminate, force kill - if test_process.is_alive(): - import os - import signal - os.kill(test_process.pid, signal.SIGKILL) - - pytest.fail(f"Thread safety test timed out after {timeout} seconds") - - # Get result from queue - try: - results, success = result_queue.get(block=False) - - # Handle error case - if isinstance(results, dict) and "error" in results: - pytest.fail(f"Thread safety test failed with error: {results['error']}\n{results.get('traceback')}") - - # Assert that the test passed - assert success, "Thread safety test failed with thread safety issues" - assert results["failed_iterations"] == 0, "Some iterations failed" - except queue.Empty: - pytest.fail("Thread safety test completed but did not return any results") - - -if __name__ == "__main__": - # Run the test directly with minimal parameters - num_workers = 2 - iterations_per_worker = 2 - - # Allow command line overrides - if len(sys.argv) > 1: - num_workers = int(sys.argv[1]) - if len(sys.argv) > 2: - iterations_per_worker = int(sys.argv[2]) - - # Use multiprocessing with a timeout - from multiprocessing import Process, Queue - import queue - - # Create a queue for results - result_queue = Queue() - - # Create and start process - test_process = Process(target=_run_test_with_timeout, args=(result_queue, num_workers, iterations_per_worker)) - test_process.start() - - # Wait for result with timeout - timeout = 60 # 60 seconds timeout - test_process.join(timeout) - - # Check if process is still alive (timeout occurred) - if test_process.is_alive(): - # Kill the process if it's still running - test_process.terminate() - test_process.join(1) # Give it 1 second to terminate - - # If still alive after terminate, force kill - if test_process.is_alive(): - import os - import signal - os.kill(test_process.pid, signal.SIGKILL) - - print(f"ERROR: Thread safety test timed out after {timeout} seconds") - sys.exit(2) - - # Get result from queue - try: - results, success = result_queue.get(block=False) - - # Handle error case - if isinstance(results, dict) and "error" in results: - print(f"ERROR: Thread safety test failed with error: {results['error']}") - print(results.get('traceback')) - sys.exit(1) - - # Exit with appropriate status code - sys.exit(0 if success else 1) - except queue.Empty: - print("ERROR: Thread safety test completed but did not return any results") - sys.exit(1) diff --git a/src/simulation/environment/spiceInterface/spiceInterface.cpp b/src/simulation/environment/spiceInterface/spiceInterface.cpp index a7d6793656b..bdcff1241cd 100755 --- a/src/simulation/environment/spiceInterface/spiceInterface.cpp +++ b/src/simulation/environment/spiceInterface/spiceInterface.cpp @@ -19,27 +19,15 @@ #include "simulation/environment/spiceInterface/spiceInterface.h" #include #include "SpiceUsr.h" -#include +#include #include "architecture/utilities/simDefinitions.h" #include "architecture/utilities/macroDefinitions.h" #include "architecture/utilities/rigidBodyKinematics.h" -// Initialize static members -std::mutex SpiceInterface::kernelManipulationMutex; -std::unordered_map SpiceInterface::kernelReferenceCounter; -int SpiceInterface::requiredKernelsRefCount = 0; // Global counter for REQUIRED_KERNELS - -// Static constant member initialization -const std::vector SpiceInterface::REQUIRED_KERNELS = { - "naif0012.tls", - "pck00010.tpc", - "de-403-masses.tpc", - "de430.bsp" -}; - /*! This constructor initializes the variables that spice uses. Most of them are not intended to be changed, but a couple are user configurable. */ + SpiceInterface::SpiceInterface() { SPICEDataPath = ""; @@ -59,17 +47,18 @@ SpiceInterface::SpiceInterface() referenceBase = "j2000"; zeroBase = "SSB"; - timeOutPicture = "MON DD,YYYY HR:MN:SC.#### (UTC) ::UTC"; + timeOutPicture = "MON DD,YYYY HR:MN:SC.#### (UTC) ::UTC"; //! - set default epoch time information char string[255]; snprintf(string, 255, "%4d/%02d/%02d, %02d:%02d:%04.1f (UTC)", EPOCH_YEAR, EPOCH_MONTH, EPOCH_DAY, EPOCH_HOUR, EPOCH_MIN, EPOCH_SEC); this->UTCCalInit = string; + + return; } /*! The only needed activity in the destructor is to delete the spice I/O buffer - * and unload any loaded SPICE kernels. - */ + that was allocated in the constructor*/ SpiceInterface::~SpiceInterface() { for (long unsigned int c=0; cplanetStateOutMsgs.size(); c++) { @@ -85,30 +74,22 @@ SpiceInterface::~SpiceInterface() delete this->transRefStateOutMsgs.at(c); } delete [] this->spiceBuffer; - - // Properly unload kernels if they were loaded - if(this->SPICELoaded) - { - // Unload the SPICE kernels in reverse order of loading - for (const auto& kernelName : REQUIRED_KERNELS) { - unloadSpiceKernel(kernelName.c_str(), this->SPICEDataPath.c_str()); - } - } - +// if(this->SPICELoaded) +// { +// this->clearKeeper(); +// } return; } -/*! This method clears the SPICE kernel pool using the SPICE kclear_c function. - * It is protected by a mutex to ensure thread safety. - */ void SpiceInterface::clearKeeper() { - std::lock_guard lock(kernelManipulationMutex); kclear_c(); } + /*! Reset the module to origina configuration values. -*/ + + */ void SpiceInterface::Reset(uint64_t CurrenSimNanos) { //! - Bail if the SPICEDataPath is not present @@ -120,12 +101,17 @@ void SpiceInterface::Reset(uint64_t CurrenSimNanos) //!- Load the SPICE kernels if they haven't already been loaded if(!this->SPICELoaded) { - // Load the required SPICE kernels - they will only be loaded once per kernel - // across all threads due to our reference counting mechanism - for (const auto& kernelName : REQUIRED_KERNELS) { - if(loadSpiceKernel(kernelName.c_str(), this->SPICEDataPath.c_str())) { - bskLogger.bskLog(BSK_ERROR, "Unable to load %s", kernelName.c_str()); - } + if(loadSpiceKernel((char *)"naif0012.tls", this->SPICEDataPath.c_str())) { + bskLogger.bskLog(BSK_ERROR, "Unable to load %s", "naif0012.tls"); + } + if(loadSpiceKernel((char *)"pck00010.tpc", this->SPICEDataPath.c_str())) { + bskLogger.bskLog(BSK_ERROR, "Unable to load %s", "pck00010.tpc"); + } + if(loadSpiceKernel((char *)"de-403-masses.tpc", this->SPICEDataPath.c_str())) { + bskLogger.bskLog(BSK_ERROR, "Unable to load %s", "de-403-masses.tpc"); + } + if(loadSpiceKernel((char *)"de430.bsp", this->SPICEDataPath.c_str())) { + bskLogger.bskLog(BSK_ERROR, "Unable to load %s", "de430.tpc"); } this->SPICELoaded = true; } @@ -166,9 +152,11 @@ void SpiceInterface::Reset(uint64_t CurrenSimNanos) this->UpdateState(CurrenSimNanos); } + /*! This method is used to initialize the zero-time that will be used to calculate all system time values in the Update method. It also creates the output message for time data + */ void SpiceInterface::initTimeData() { @@ -198,9 +186,10 @@ void SpiceInterface::initTimeData() } -/*! This method computes the GPS time data for the current elapsed time. -It uses the total elapsed times at both the GPS epoch time and the current time to +/*! This method computes the GPS time data for the current elapsed time. It uses + the total elapsed times at both the GPS epoch time and the current time to compute the GPS time (week, seconds, rollovers) + */ void SpiceInterface::computeGPSData() { @@ -222,7 +211,7 @@ void SpiceInterface::computeGPSData() It packages up the internal variables into the output structure definitions and puts them out on the messaging system -@param CurrentClock The current simulation time (used for time stamping) + @param CurrentClock The current simulation time (used for time stamping) */ void SpiceInterface::writeOutputMessages(uint64_t CurrentClock) { @@ -266,25 +255,13 @@ void SpiceInterface::writeOutputMessages(uint64_t CurrentClock) } /*! This method is the interface point between the upper level simulation and -the SPICE interface at runtime. It calls all of the necessary lower level -methods. + the SPICE interface at runtime. It calls all of the necessary lower level + methods. @param CurrentSimNanos The current clock time for the simulation */ void SpiceInterface::UpdateState(uint64_t CurrentSimNanos) { - // Ensure required kernels are loaded if we have a data path - if (!this->SPICELoaded && !this->SPICEDataPath.empty()) { - bool allLoaded = true; - for (const auto& kernelName : REQUIRED_KERNELS) { - if(loadSpiceKernel(kernelName.c_str(), this->SPICEDataPath.c_str())) { - bskLogger.bskLog(BSK_ERROR, "Unable to load %s", kernelName.c_str()); - allLoaded = false; - } - } - this->SPICELoaded = allLoaded; - } - //! - Increment the J2000 elapsed time based on init value and Current sim this->J2000Current = this->J2000ETInit + CurrentSimNanos*NANO2SEC; @@ -301,7 +278,7 @@ void SpiceInterface::UpdateState(uint64_t CurrentSimNanos) } /*! take a vector of planet name strings and create the vector of -planet state output messages and the vector of planet state message payloads */ + planet state output messages and the vector of planet state message payloads */ void SpiceInterface::addPlanetNames(std::vector planetNames) { std::vector::iterator it; @@ -333,8 +310,7 @@ void SpiceInterface::addPlanetNames(std::vector planetNames) { } /*! take a vector of spacecraft name strings and create the vectors of -spacecraft state output messages and the vector of spacecraft state -message payloads */ + spacecraft state output messages and the vector of spacecraft state message payloads */ void SpiceInterface::addSpacecraftNames(std::vector spacecraftNames) { std::vector::iterator it; SpiceChar *name = new SpiceChar[this->charBufferSize]; @@ -391,7 +367,7 @@ void SpiceInterface::addSpacecraftNames(std::vector spacecraftNames /*! This method gets the state of each spice item that has been added to the module -and saves the information off into the array. + and saves the information off into the array. */ void SpiceInterface::pullSpiceData(std::vector *spiceData) @@ -458,49 +434,27 @@ void SpiceInterface::pullSpiceData(std::vector *spic @param kernelName The name of the kernel we are loading @param dataPath The path to the data area on the filesystem */ -int SpiceInterface::loadSpiceKernel(const char *kernelName, const char *dataPath) +int SpiceInterface::loadSpiceKernel(char *kernelName, const char *dataPath) { - // Create the full filepath using string_view for internal handling - std::string filepath = std::string{dataPath} + std::string{kernelName}; - - // Acquire the mutex to protect kernel operations - std::lock_guard lock(kernelManipulationMutex); - - // Initialize the reference counter for this kernel if it doesn't exist - kernelReferenceCounter.try_emplace(filepath, 0); - - // Check if this is a required kernel by exact filename match - bool isRequiredKernel = false; - for (const auto& reqKernel : REQUIRED_KERNELS) { - if (kernelName == reqKernel) { - isRequiredKernel = true; - break; - } - } - - // Only load the kernel if it hasn't been loaded yet - if (kernelReferenceCounter.at(filepath) <= 0) { - // The required calls come from the SPICE documentation. - // The most critical call is furnsh_c - erract_c("SET", this->charBufferSize, (SpiceChar*)"REPORT"); - furnsh_c(filepath.c_str()); - - // Check to see if we had trouble loading a kernel - erract_c("SET", this->charBufferSize, (SpiceChar*)"DEFAULT"); - - if(failed_c()) { - return 1; - } - } - - // Increment the reference counter for this kernel - kernelReferenceCounter[filepath]++; + char *fileName = new char[this->charBufferSize]; + SpiceChar *name = new SpiceChar[this->charBufferSize]; - // If this is a required kernel, increment the global counter - if (isRequiredKernel) { - requiredKernelsRefCount++; + //! - The required calls come from the SPICE documentation. + //! - The most critical call is furnsh_c + strcpy(name, "REPORT"); + erract_c("SET", this->charBufferSize, name); + strcpy(fileName, dataPath); + strcat(fileName, kernelName); + furnsh_c(fileName); + + //! - Check to see if we had trouble loading a kernel and alert user if so + strcpy(name, "DEFAULT"); + erract_c("SET", this->charBufferSize, name); + delete[] fileName; + delete[] name; + if(failed_c()) { + return 1; } - return 0; } @@ -512,63 +466,43 @@ int SpiceInterface::loadSpiceKernel(const char *kernelName, const char *dataPath @param kernelName The name of the kernel we are unloading @param dataPath The path to the data area on the filesystem */ -int SpiceInterface::unloadSpiceKernel(const char *kernelName, const char *dataPath) +int SpiceInterface::unloadSpiceKernel(char *kernelName, const char *dataPath) { - // Create the full filepath using string_view for internal handling - std::string filepath = std::string{dataPath} + std::string{kernelName}; - - // Acquire the mutex to protect kernel operations - std::lock_guard lock(kernelManipulationMutex); - - // Check if this is a required kernel by exact filename match - bool isRequiredKernel = false; - for (const auto& reqKernel : REQUIRED_KERNELS) { - if (kernelName == reqKernel) { - isRequiredKernel = true; - break; - } - } - - // Check if the kernel exists in our reference counter - auto it = kernelReferenceCounter.find(filepath); - if (it == kernelReferenceCounter.end() || it->second <= 0) { - // Kernel was never loaded or already unloaded - silently succeed - return 0; - } - - // Decrement the reference counter - it->second--; - - // If this is a required kernel, decrement the global counter - if (isRequiredKernel) { - requiredKernelsRefCount--; - } - - // Only unload if no more references to this kernel - // For required kernels, also check the global counter - if (it->second <= 0 && (!isRequiredKernel || requiredKernelsRefCount <= 0)) { - // The required calls come from the SPICE documentation. - erract_c("SET", this->charBufferSize, (SpiceChar*)"REPORT"); - unload_c(filepath.c_str()); - - erract_c("SET", this->charBufferSize, (SpiceChar*)"DEFAULT"); - - if(failed_c()) { - return 1; - } + char *fileName = new char[this->charBufferSize]; + SpiceChar *name = new SpiceChar[this->charBufferSize]; - // Remove the kernel from our reference counter - kernelReferenceCounter.erase(it); + //! - The required calls come from the SPICE documentation. + //! - The most critical call is furnsh_c + strcpy(name, "REPORT"); + erract_c("SET", this->charBufferSize, name); + strcpy(fileName, dataPath); + strcat(fileName, kernelName); + unload_c(fileName); + delete[] fileName; + delete[] name; + if(failed_c()) { + return 1; } - return 0; } std::string SpiceInterface::getCurrentTimeString() { - constexpr size_t allowedOutputLength = 255; // Reasonable fixed size for time string - char spiceOutputBuffer[allowedOutputLength]; - - timout_c(this->J2000Current, this->timeOutPicture.c_str(), (SpiceInt) allowedOutputLength, spiceOutputBuffer); - return std::string(spiceOutputBuffer); + char *spiceOutputBuffer; + int64_t allowedOutputLength; + + allowedOutputLength = (int64_t)this->timeOutPicture.size() - 5; + + if (allowedOutputLength < 0) + { + bskLogger.bskLog(BSK_ERROR, "The output format string is not long enough. It should be much larger than 5 characters. It is currently: %s", this->timeOutPicture.c_str()); + return(""); + } + + spiceOutputBuffer = new char[allowedOutputLength]; + timout_c(this->J2000Current, this->timeOutPicture.c_str(), (SpiceInt) allowedOutputLength, + spiceOutputBuffer); + std::string returnTimeString = spiceOutputBuffer; + delete[] spiceOutputBuffer; + return(returnTimeString); } diff --git a/src/simulation/environment/spiceInterface/spiceInterface.h b/src/simulation/environment/spiceInterface/spiceInterface.h index 8f4922176c2..e162ae1f6bc 100755 --- a/src/simulation/environment/spiceInterface/spiceInterface.h +++ b/src/simulation/environment/spiceInterface/spiceInterface.h @@ -22,7 +22,6 @@ #include #include -#include #include "architecture/_GeneralModuleFiles/sys_model.h" #include "architecture/utilities/linearAlgebra.h" #include "architecture/utilities/bskLogging.h" @@ -43,17 +42,17 @@ class SpiceInterface: public SysModel { ~SpiceInterface(); void UpdateState(uint64_t CurrentSimNanos); - int loadSpiceKernel(const char *kernelName, const char *dataPath); //!< Load a SPICE kernel with thread-safe reference counting - int unloadSpiceKernel(const char *kernelName, const char *dataPath); //!< Unload a SPICE kernel with thread-safe reference counting - std::string getCurrentTimeString(); //!< class method + int loadSpiceKernel(char *kernelName, const char *dataPath); + int unloadSpiceKernel(char *kernelName, const char *dataPath); + std::string getCurrentTimeString(); //!< class method void Reset(uint64_t CurrentSimNanos); void initTimeData(); void computeGPSData(); void pullSpiceData(std::vector *spiceData); void writeOutputMessages(uint64_t CurrentClock); void clearKeeper(); //!< class method - void addPlanetNames(std::vector planetNames); //!< Adds a list of planet names to track in SPICE - void addSpacecraftNames(std::vector spacecraftNames); //!< Adds a list of spacecraft names to track in SPICE + void addPlanetNames(std::vector planetNames); + void addSpacecraftNames(std::vector spacecraftNames); public: Message spiceTimeOutMsg; //!< spice time sampling output message @@ -66,7 +65,7 @@ class SpiceInterface: public SysModel { std::string SPICEDataPath; //!< -- Path on file to SPICE data std::string referenceBase; //!< -- Base reference frame to use std::string zeroBase; //!< -- Base zero point to use for states - std::string timeOutPicture; //!< -- Optional parameter used to extract time strings + std::string timeOutPicture; //!< -- Optional parameter used to extract time strings bool SPICELoaded; //!< -- Boolean indicating to reload spice int charBufferSize; //!< -- avert your eyes we're getting SPICE uint8_t *spiceBuffer; //!< -- General buffer to pass down to spice @@ -91,10 +90,7 @@ class SpiceInterface: public SysModel { std::vector planetData; std::vector scData; - static std::mutex kernelManipulationMutex; //!< -- Mutex to protect SPICE kernel loading/unloading operations - static std::unordered_map kernelReferenceCounter; //!< -- Reference counter for SPICE kernels - static int requiredKernelsRefCount; // Global counter for REQUIRED_KERNELS - static const std::vector REQUIRED_KERNELS; //!< -- List of required SPICE kernels }; + #endif