Skip to content

Commit 258edc5

Browse files
Merge pull request #1106 from AVSLab/fix/1097-2d-recorder
Fix recorders for 2D arrays not working
2 parents 54a5ec2 + e01eb8f commit 258edc5

File tree

3 files changed

+35
-25
lines changed

3 files changed

+35
-25
lines changed

docs/source/Support/bskReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Version |release|
3434
- Refactored the CI build system scripts
3535
- Removed deprecated use of ``Basilisk.simulation.planetEphemeris.ClassicElementsMsgPayload``.
3636
Users need to use ``ClassicalElements()`` defined in ``orbitalMotion``.
37+
- Fixed bug with recording message payload entries that are 2D arrays. This bug was introduced with the faster recording
38+
strategy added in version 2.78.0.
3739

3840

3941
Version 2.78.0 (August 30, 2025)

src/architecture/messaging/msgAutoSource/generateSWIGModules.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,8 +308,7 @@ def parseSwigXml(xmlPath: str, targetStructName: str, cpp: bool, recorderPropert
308308
if typeWithPointerRef in C_TYPE_TO_NPY_ENUM and len(typeArrayParts) < 3:
309309
npyType = C_TYPE_TO_NPY_ENUM[typeWithPointerRef]
310310
macroName = f"RECORDER_PROPERTY_NUMERIC_{len(typeArrayParts)}"
311-
dimensions = "".join(f", {dim}" for dim in typeArrayParts)
312-
result += f"{macroName}({targetStructName}, {fieldName}, {typeWithPointerRef}, {npyType} {dimensions});\n"
311+
result += f"{macroName}({targetStructName}, {fieldName}, {typeWithPointerRef}, {npyType});\n"
313312
elif not recorderPropertyRollback:
314313
fullType = f"{typeWithPointerRef}{''.join(f'[{i}]' for i in typeArrayParts)}"
315314
result += f"RECORDER_PROPERTY({targetStructName}, {fieldName}, ({fullType}));\n"

src/architecture/messaging/newMessaging.ih

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -333,9 +333,11 @@ typedef struct messageType;
333333
} else {
334334
npy_intp dims[1] = {static_cast<npy_intp>(N)};
335335
$result = PyArray_SimpleNew(1, dims, npyTypeNum);
336-
for (size_t recordIndex = 0; recordIndex < N; ++recordIndex ) {
337-
void* ptr = PyArray_GETPTR1((PyArrayObject*)$result, recordIndex);
338-
*reinterpret_cast<fieldType*>(ptr) = $1->record().at(recordIndex). ## fieldName;
336+
if (!$result) SWIG_fail;
337+
338+
for (size_t i = 0; i < N; ++i ) {
339+
void* ptr = PyArray_GETPTR1((PyArrayObject*)$result, i);
340+
*reinterpret_cast<fieldType*>(ptr) = $1->record().at(i). ## fieldName;
339341
}
340342
}
341343
if (PyErr_Occurred()) SWIG_fail;
@@ -355,27 +357,29 @@ typedef struct messageType;
355357

356358
%enddef
357359

358-
%define RECORDER_PROPERTY_NUMERIC_1(payloadType, fieldName, fieldType, npyTypeNum, dim1)
359-
/*
360-
* Exposes a fixed-size 1D numeric array field (e.g. float[3]) as a NumPy array with shape [N, dim1].
361-
* Uses contiguous memcpy per record for efficiency.
362-
*/
360+
%define RECORDER_PROPERTY_NUMERIC_1(payloadType, fieldName, fieldType, npyTypeNum)
363361
%typemap(out) Recorder<payloadType>* Recorder<payloadType>::_ ## fieldName ## _array
364362
{
365363
size_t N = $1->record().size();
366364
if (N == 0) {
367365
npy_intp dims[1] = {0};
368366
$result = PyArray_SimpleNew(1, dims, NPY_FLOAT64);
369367
} else {
370-
npy_intp dims[2] = {static_cast<npy_intp>(N), (npy_intp) dim1};
368+
/* Derive dimensions from compiled type */
369+
auto &first = $1->record().at(0).fieldName;
370+
const size_t d1 = sizeof(first) / sizeof(first[0]);
371+
const size_t elemsPerRecord = d1;
372+
const size_t bytesPerRecord = elemsPerRecord * sizeof(fieldType);
373+
374+
npy_intp dims[2] = {static_cast<npy_intp>(N), (npy_intp) d1};
371375
$result = PyArray_SimpleNew(2, dims, npyTypeNum);
376+
if (!$result) SWIG_fail;
372377

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

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

396400
%enddef
397401

398-
%define RECORDER_PROPERTY_NUMERIC_2(payloadType, fieldName, fieldType, npyTypeNum, dim1, dim2)
399-
/*
400-
* Exposes a fixed-size 2D numeric array field (e.g. float[3][3]) as a NumPy array with shape [N, dim1, dim2].
401-
*/
402+
%define RECORDER_PROPERTY_NUMERIC_2(payloadType, fieldName, fieldType, npyTypeNum)
402403
%typemap(out) Recorder<payloadType>* Recorder<payloadType>::_ ## fieldName ## _array
403404
{
404405
size_t N = $1->record().size();
405406
if (N == 0) {
406407
npy_intp dims[1] = {0};
407408
$result = PyArray_SimpleNew(1, dims, NPY_FLOAT64);
408409
} else {
409-
npy_intp dims[3] = {static_cast<npy_intp>(N), (npy_intp) dim1, (npy_intp) dim2};
410+
/* Derive dimensions from compiled type */
411+
auto &first = $1->record().at(0).fieldName;
412+
const size_t d1 = sizeof(first) / sizeof(first[0]);
413+
const size_t d2 = sizeof(first[0]) / sizeof(first[0][0]);
414+
const size_t elemsPerRecord = d1 * d2;
415+
const size_t bytesPerRecord = elemsPerRecord * sizeof(fieldType);
416+
417+
npy_intp dims[3] = {static_cast<npy_intp>(N), (npy_intp) d1, (npy_intp) d2};
410418
$result = PyArray_SimpleNew(3, dims, npyTypeNum);
419+
if (!$result) SWIG_fail;
411420

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

415-
for (size_t recordIndex = 0; recordIndex < N; ++recordIndex) {
416-
const fieldType* src = &($1->record().at(recordIndex).fieldName[0][0]);
417-
std::memcpy(dest + recordIndex * recordStride, src, recordStride * sizeof(fieldType));
423+
for (size_t i = 0; i < N; ++i) {
424+
const fieldType* src = &($1->record().at(i).fieldName[0][0]);
425+
std::memcpy(dest + i * elemsPerRecord, src, bytesPerRecord);
418426
}
427+
428+
if (PyErr_Occurred()) SWIG_fail;
419429
}
420-
if (PyErr_Occurred()) SWIG_fail;
421430
}
422431

423432
%extend Recorder<payloadType> {

0 commit comments

Comments
 (0)