Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/source/Support/bskReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Version |release|
- Refactored the CI build system scripts
- Removed deprecated use of ``Basilisk.simulation.planetEphemeris.ClassicElementsMsgPayload``.
Users need to use ``ClassicalElements()`` defined in ``orbitalMotion``.
- Fixed bug with recording message payload entries that are 2D arrays. This bug was introduced with the faster recording
strategy added in version 2.78.0.


Version 2.78.0 (August 30, 2025)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,7 @@ def parseSwigXml(xmlPath: str, targetStructName: str, cpp: bool, recorderPropert
if typeWithPointerRef in C_TYPE_TO_NPY_ENUM and len(typeArrayParts) < 3:
npyType = C_TYPE_TO_NPY_ENUM[typeWithPointerRef]
macroName = f"RECORDER_PROPERTY_NUMERIC_{len(typeArrayParts)}"
dimensions = "".join(f", {dim}" for dim in typeArrayParts)
result += f"{macroName}({targetStructName}, {fieldName}, {typeWithPointerRef}, {npyType} {dimensions});\n"
result += f"{macroName}({targetStructName}, {fieldName}, {typeWithPointerRef}, {npyType});\n"
elif not recorderPropertyRollback:
fullType = f"{typeWithPointerRef}{''.join(f'[{i}]' for i in typeArrayParts)}"
result += f"RECORDER_PROPERTY({targetStructName}, {fieldName}, ({fullType}));\n"
Expand Down
55 changes: 32 additions & 23 deletions src/architecture/messaging/newMessaging.ih
Original file line number Diff line number Diff line change
Expand Up @@ -333,9 +333,11 @@ typedef struct messageType;
} else {
npy_intp dims[1] = {static_cast<npy_intp>(N)};
$result = PyArray_SimpleNew(1, dims, npyTypeNum);
for (size_t recordIndex = 0; recordIndex < N; ++recordIndex ) {
void* ptr = PyArray_GETPTR1((PyArrayObject*)$result, recordIndex);
*reinterpret_cast<fieldType*>(ptr) = $1->record().at(recordIndex). ## fieldName;
if (!$result) SWIG_fail;

for (size_t i = 0; i < N; ++i ) {
void* ptr = PyArray_GETPTR1((PyArrayObject*)$result, i);
*reinterpret_cast<fieldType*>(ptr) = $1->record().at(i). ## fieldName;
}
}
if (PyErr_Occurred()) SWIG_fail;
Expand All @@ -355,27 +357,29 @@ typedef struct messageType;

%enddef

%define RECORDER_PROPERTY_NUMERIC_1(payloadType, fieldName, fieldType, npyTypeNum, dim1)
/*
* Exposes a fixed-size 1D numeric array field (e.g. float[3]) as a NumPy array with shape [N, dim1].
* Uses contiguous memcpy per record for efficiency.
*/
%define RECORDER_PROPERTY_NUMERIC_1(payloadType, fieldName, fieldType, npyTypeNum)
%typemap(out) Recorder<payloadType>* Recorder<payloadType>::_ ## fieldName ## _array
{
size_t N = $1->record().size();
if (N == 0) {
npy_intp dims[1] = {0};
$result = PyArray_SimpleNew(1, dims, NPY_FLOAT64);
} else {
npy_intp dims[2] = {static_cast<npy_intp>(N), (npy_intp) dim1};
/* Derive dimensions from compiled type */
auto &first = $1->record().at(0).fieldName;
const size_t d1 = sizeof(first) / sizeof(first[0]);
const size_t elemsPerRecord = d1;
const size_t bytesPerRecord = elemsPerRecord * sizeof(fieldType);

npy_intp dims[2] = {static_cast<npy_intp>(N), (npy_intp) d1};
$result = PyArray_SimpleNew(2, dims, npyTypeNum);
if (!$result) SWIG_fail;

fieldType* dest = static_cast<fieldType*>(PyArray_DATA((PyArrayObject*)$result));
size_t recordStride = dim1;

for (size_t recordIndex = 0; recordIndex < N; ++recordIndex) {
const fieldType* src = &($1->record().at(recordIndex).fieldName[0]);
std::memcpy(dest + recordIndex * recordStride, src, recordStride * sizeof(fieldType));
for (size_t i = 0; i < N; ++i) {
const fieldType* src = &($1->record().at(i).fieldName[0]);
std::memcpy(dest + i * elemsPerRecord, src, bytesPerRecord);
}
}
if (PyErr_Occurred()) SWIG_fail;
Expand All @@ -395,29 +399,34 @@ typedef struct messageType;

%enddef

%define RECORDER_PROPERTY_NUMERIC_2(payloadType, fieldName, fieldType, npyTypeNum, dim1, dim2)
/*
* Exposes a fixed-size 2D numeric array field (e.g. float[3][3]) as a NumPy array with shape [N, dim1, dim2].
*/
%define RECORDER_PROPERTY_NUMERIC_2(payloadType, fieldName, fieldType, npyTypeNum)
%typemap(out) Recorder<payloadType>* Recorder<payloadType>::_ ## fieldName ## _array
{
size_t N = $1->record().size();
if (N == 0) {
npy_intp dims[1] = {0};
$result = PyArray_SimpleNew(1, dims, NPY_FLOAT64);
} else {
npy_intp dims[3] = {static_cast<npy_intp>(N), (npy_intp) dim1, (npy_intp) dim2};
/* Derive dimensions from compiled type */
auto &first = $1->record().at(0).fieldName;
const size_t d1 = sizeof(first) / sizeof(first[0]);
const size_t d2 = sizeof(first[0]) / sizeof(first[0][0]);
const size_t elemsPerRecord = d1 * d2;
const size_t bytesPerRecord = elemsPerRecord * sizeof(fieldType);

npy_intp dims[3] = {static_cast<npy_intp>(N), (npy_intp) d1, (npy_intp) d2};
$result = PyArray_SimpleNew(3, dims, npyTypeNum);
if (!$result) SWIG_fail;

fieldType* dest = static_cast<fieldType*>(PyArray_DATA((PyArrayObject*)$result));
size_t recordStride = dim1 * dim2;

for (size_t recordIndex = 0; recordIndex < N; ++recordIndex) {
const fieldType* src = &($1->record().at(recordIndex).fieldName[0][0]);
std::memcpy(dest + recordIndex * recordStride, src, recordStride * sizeof(fieldType));
for (size_t i = 0; i < N; ++i) {
const fieldType* src = &($1->record().at(i).fieldName[0][0]);
std::memcpy(dest + i * elemsPerRecord, src, bytesPerRecord);
}

if (PyErr_Occurred()) SWIG_fail;
}
if (PyErr_Occurred()) SWIG_fail;
}

%extend Recorder<payloadType> {
Expand Down
Loading