flightgear/test_suite/FGTestApi/TestDataLogger.cxx
2022-10-20 20:29:11 +08:00

171 lines
4.1 KiB
C++

/*
* Copyright (C) 2020 James Turner
*
* This file is part of the program FlightGear.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TestDataLogger.hxx"
#include <algorithm>
#include <cmath>
#include <limits>
#include <vector>
#include <simgear/io/iostreams/sgstream.hxx>
#include "Main/globals.hxx"
namespace FGTestApi {
using DoubleVec = std::vector<double>;
static std::unique_ptr<DataLogger> static_instance;
class DataLogger::DataLoggerPrivate
{
public:
sg_ofstream _stream;
struct SampleInfo {
int column;
std::string name;
// range / units info, later
SGPropertyNode_ptr property;
};
double _currentTimeBase;
std::vector<SampleInfo> _samples;
DoubleVec _openRow;
bool _didHeader = false;
void writeCurrentRow()
{
if (!_didHeader) {
writeHeader();
_didHeader = true;
}
// capture property values into the open row data
std::for_each(_samples.begin(), _samples.end(), [this](const SampleInfo& info) {
if (info.property) {
_openRow[info.column] = info.property->getDoubleValue();
}
});
// write time base
_stream << globals->get_sim_time_sec() << ",";
for (const auto v : _openRow) {
if (std::isnan(v)) {
_stream << ","; // skip this data point
} else {
_stream << v << ",";
}
}
_stream << "\n";
std::fill(_openRow.begin(), _openRow.end(), std::numeric_limits<double>::quiet_NaN());
}
void writeHeader()
{
_stream << "sim-time, ";
std::for_each(_samples.begin(), _samples.end(), [this](const SampleInfo& info) {
_stream << info.name << ", ";
});
_stream << "\n";
}
};
DataLogger::DataLogger()
{
d.reset(new DataLoggerPrivate);
}
DataLogger::~DataLogger()
{
d->_stream.close();
}
bool DataLogger::isActive()
{
return static_instance != nullptr;
}
DataLogger* DataLogger::instance()
{
if (!static_instance) {
static_instance.reset(new DataLogger);
}
return static_instance.get();
}
void DataLogger::initTest(const std::string& testName)
{
d->_stream = sg_ofstream(testName + "_trace.csv");
}
void DataLogger::tearDown()
{
if (static_instance) {
static_instance.reset();
}
}
void DataLogger::writeRecord()
{
d->writeCurrentRow();
}
void DataLogger::recordProperty(const std::string& name, SGPropertyNode_ptr prop)
{
int index = static_cast<int>(d->_samples.size());
DataLoggerPrivate::SampleInfo info{index, name, prop};
d->_samples.push_back(info);
if (d->_openRow.size() <= index) {
d->_openRow.resize(index + 1, std::numeric_limits<double>::quiet_NaN());
}
}
void DataLogger::recordSamplePoint(const std::string& name, double value)
{
auto it = std::find_if(d->_samples.begin(), d->_samples.end(), [&name](const DataLoggerPrivate::SampleInfo& sample) {
return name == sample.name;
});
int index = 0;
if (it == d->_samples.end()) {
index = static_cast<int>(d->_samples.size());
DataLoggerPrivate::SampleInfo info{index, name};
d->_samples.push_back(info);
} else {
index = it->column;
}
// grow _openRow as required
if (d->_openRow.size() <= index) {
d->_openRow.resize(index + 1, std::numeric_limits<double>::quiet_NaN());
}
d->_openRow[index] = value;
}
} // namespace FGTestApi