From 04ddd3bfa52df9091638c4c4cc98d6cb827d831e Mon Sep 17 00:00:00 2001 From: Mathew Sutcliffe Date: Mon, 6 Feb 2017 19:24:39 +0000 Subject: [PATCH] refs #863 Move interpolator logging into a separate class. A single logger instance can be shared between multiple interpolator instances. --- src/blackcore/simulatorcommon.cpp | 10 +- src/blackcore/simulatorcommon.h | 2 + .../simulation/interpolationlogger.cpp | 256 ++++++++++++++++++ .../simulation/interpolationlogger.h | 118 ++++++++ src/blackmisc/simulation/interpolator.cpp | 239 +--------------- src/blackmisc/simulation/interpolator.h | 71 +---- .../simulation/interpolatorlinear.cpp | 2 +- src/blackmisc/simulation/interpolatorlinear.h | 3 +- .../simulation/interpolatorspline.cpp | 2 +- src/blackmisc/simulation/interpolatorspline.h | 3 +- src/plugins/simulator/fs9/fs9client.cpp | 5 +- src/plugins/simulator/fs9/fs9client.h | 3 +- src/plugins/simulator/fs9/simulatorfs9.cpp | 2 +- .../simulator/fsx/simconnectobject.cpp | 7 +- src/plugins/simulator/fsx/simconnectobject.h | 12 +- src/plugins/simulator/fsx/simulatorfsx.cpp | 2 +- 16 files changed, 419 insertions(+), 318 deletions(-) create mode 100644 src/blackmisc/simulation/interpolationlogger.cpp create mode 100644 src/blackmisc/simulation/interpolationlogger.h diff --git a/src/blackcore/simulatorcommon.cpp b/src/blackcore/simulatorcommon.cpp index c04368aeb..c428b417a 100644 --- a/src/blackcore/simulatorcommon.cpp +++ b/src/blackcore/simulatorcommon.cpp @@ -363,9 +363,8 @@ namespace BlackCore } if (p == "clear" || p == "clr") { - //! \todo refactoring broken by rebase - //this->m_interpolator->clearLog(); - //CStatusMessage(this).info("Cleared interpolation logging"); + this->m_interpolationLogger.clearLog(); + CStatusMessage(this).info("Cleared interpolation logging"); return true; } if (p == "write" || p == "save") @@ -374,9 +373,8 @@ namespace BlackCore this->m_interpolationRenderingSetup.clearInterpolatorLogCallsigns(); // write - //! \todo refactoring broken by rebase - //this->m_interpolator->writeLogInBackground(); - //CLogMessage(this).info("Started writing interpolation log"); + this->m_interpolationLogger.writeLogInBackground(); + CLogMessage(this).info("Started writing interpolation log"); return true; } diff --git a/src/blackcore/simulatorcommon.h b/src/blackcore/simulatorcommon.h index 62e7c6c08..9e550af36 100644 --- a/src/blackcore/simulatorcommon.h +++ b/src/blackcore/simulatorcommon.h @@ -31,6 +31,7 @@ #include "blackmisc/simulation/simulatorsettings.h" #include "blackmisc/simulation/interpolationrenderingsetup.h" #include "blackmisc/simulation/interpolationhints.h" +#include "blackmisc/simulation/interpolationlogger.h" #include "blackmisc/weather/weathergridprovider.h" #include "blackmisc/pq/length.h" #include "blackmisc/pq/time.h" @@ -184,6 +185,7 @@ namespace BlackCore BlackMisc::Simulation::CSimulatorInternals m_simulatorInternals; //!< setup object BlackMisc::Simulation::CInterpolationAndRenderingSetup m_interpolationRenderingSetup; //!< logging, rendering etc. mutable QReadWriteLock m_interpolationRenderingSetupMutex; //!< mutex protecting setup object + BlackMisc::Simulation::CInterpolationLogger m_interpolationLogger; //!< log interpolation // some optional functionality which can be used by the sims as needed BlackMisc::Simulation::CSimulatedAircraftList m_aircraftToAddAgainWhenRemoved; //!< add this model again when removed, normally used to change model diff --git a/src/blackmisc/simulation/interpolationlogger.cpp b/src/blackmisc/simulation/interpolationlogger.cpp new file mode 100644 index 000000000..97cefc619 --- /dev/null +++ b/src/blackmisc/simulation/interpolationlogger.cpp @@ -0,0 +1,256 @@ +/* Copyright (C) 2015 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#include "interpolationlogger.h" +#include "blackconfig/buildconfig.h" +#include "blackmisc/aviation/callsign.h" +#include "blackmisc/aviation/heading.h" +#include "blackmisc/pq/angle.h" +#include "blackmisc/pq/speed.h" +#include "blackmisc/pq/units.h" +#include "blackmisc/pq/length.h" +#include "blackmisc/logmessage.h" +#include "blackmisc/worker.h" +#include "blackmisc/directoryutils.h" +#include +#include + +using namespace BlackConfig; +using namespace BlackMisc; +using namespace BlackMisc::Aviation; +using namespace BlackMisc::Geo; +using namespace BlackMisc::Math; +using namespace BlackMisc::PhysicalQuantities; +using namespace BlackMisc::Simulation; + +namespace BlackMisc +{ + namespace Simulation + { + CInterpolationLogger::CInterpolationLogger(QObject *parent) : + QObject(parent) + { + this->setObjectName("CInterpolationLogger"); + } + + CWorker *CInterpolationLogger::writeLogInBackground() + { + QList interpolation; + QList parts; + { + QReadLocker l(&m_lockLogs); + interpolation = m_situationLogs; + parts = m_partsLogs; + } + + CWorker *worker = CWorker::fromTask(this, "WriteInterpolationLog", [interpolation, parts]() + { + const CStatusMessageList msg = CInterpolationLogger::writeLogFile(interpolation, parts); + CLogMessage::preformatted(msg); + }); + return worker; + } + + QStringList CInterpolationLogger::getLatestLogFiles() + { + QStringList files({ "", ""}); + const QString logDir = CDirectoryUtils::getLogDirectory(); + QDir logs(logDir); + if (!logs.exists()) { return files; } + logs.setNameFilters(QStringList() << "*interpolation.html" << "*parts.html"); + const QStringList interpolations = logs.entryList(QStringList({"*interpolation.html"}), QDir::NoFilter, QDir::Time); + if (!interpolations.isEmpty()) + { + files[0] = CFileUtils::appendFilePaths(logDir, interpolations.first()); + } + const QStringList parts = logs.entryList(QStringList({"*parts.html"}), QDir::NoFilter, QDir::Time); + if (!parts.isEmpty()) + { + files[1] = CFileUtils::appendFilePaths(logDir, parts.first()); + } + return files; + } + + CStatusMessageList CInterpolationLogger::writeLogFile(const QList &interpolation, const QList &parts) + { + if (parts.isEmpty() && interpolation.isEmpty()) { return CStatusMessage(static_cast(nullptr)).warning("No data for log"); } + static const QString html = QStringLiteral("Entries: %1\n\n%2"); + const QString htmlTemplate = CFileUtils::readFileToString(CBuildConfig::getHtmlTemplateFileName()); + + CStatusMessageList msgs; + const QString ts = QDateTime::currentDateTimeUtc().toString("yyyyMMddhhmmss"); + const QString htmlInterpolation = CInterpolationLogger::getHtmlInterpolationLog(interpolation); + if (!htmlInterpolation.isEmpty()) + { + const QString fn = CFileUtils::appendFilePaths(CDirectoryUtils::getLogDirectory(), QString("%1 interpolation.html").arg(ts)); + const bool s = CFileUtils::writeStringToFile(htmlTemplate.arg(html.arg(interpolation.size()).arg(htmlInterpolation)), fn); + msgs.push_back(CInterpolationLogger::logStatusFileWriting(s, fn)); + } + + const QString htmlParts = CInterpolationLogger::getHtmlPartsLog(parts); + if (!htmlParts.isEmpty()) + { + const QString fn = CFileUtils::appendFilePaths(CDirectoryUtils::getLogDirectory(), QString("%1 parts.html").arg(ts)); + const bool s = CFileUtils::writeStringToFile(htmlTemplate.arg(html.arg(parts.size()).arg(htmlParts)), fn); + msgs.push_back(CInterpolationLogger::logStatusFileWriting(s, fn)); + } + return msgs; + } + + CStatusMessage CInterpolationLogger::logStatusFileWriting(bool success, const QString &fileName) + { + if (success) + { + return CStatusMessage(static_cast(nullptr)).info("Written log file '%1'") << fileName; + } + else + { + return CStatusMessage(static_cast(nullptr)).error("Failed to write log file '%1'") << fileName; + } + } + + void CInterpolationLogger::logInterpolation(const CInterpolationLogger::SituationLog &log) + { + QWriteLocker l(&m_lockLogs); + m_situationLogs.append(log); + } + + void CInterpolationLogger::logParts(const CInterpolationLogger::PartsLog &parts) + { + QWriteLocker l(&m_lockLogs); + m_partsLogs.append(parts); + } + + QString CInterpolationLogger::getHtmlInterpolationLog(const QList &logs) + { + if (logs.isEmpty()) { return {}; } + const QString tableHeader = + QLatin1String("") % + QLatin1String("c.CSVTOLtimestampsince") % + QLatin1String("ts oldts newts cur") % + QLatin1String("ΔtΔt fr.fraction") % + QLatin1String("lat.oldlat.newlat.cur") % + QLatin1String("lng.oldlng.newlng.cur") % + QLatin1String("alt.oldalt.newalt.cur") % + QLatin1String("elv.oldelv.newelv.cur") % + QLatin1String("gnd.factor") % + QLatin1String("onGnd.oldonGnd.newonGnd.cur") % + QLatin1String("partsc.parts details") % + QLatin1String("\n"); + + static const CLengthUnit ft = CLengthUnit::ft(); + const SituationLog firstLog = logs.first(); + qint64 newPosTs = firstLog.newSituation.getMSecsSinceEpoch(); + CAircraftParts lastParts; // default, so shown if parts are different from default + + QString tableRows("\n"); + for (const SituationLog &log : logs) + { + const bool changedNewPosition = newPosTs != log.newSituation.getMSecsSinceEpoch(); + const bool changedParts = lastParts != log.parts; + newPosTs = log.newSituation.getMSecsSinceEpoch(); + lastParts = log.parts; + + // concatenating in multiple steps, otherwise C4503 warnings + tableRows += + QLatin1String("") % + (changedNewPosition ? QLatin1String("*") : QLatin1String("")) % + QLatin1String("") % log.callsign.asString() % QLatin1String("") % + QLatin1String("") % boolToYesNo(log.vtolAircraft) % QLatin1String("") % + QLatin1String("") % msSinceEpochToTime(log.timestamp) % QLatin1String("") % + QLatin1String("") % QString::number(log.timestamp - firstLog.timestamp) % QLatin1String("") % + + QLatin1String("") % msSinceEpochToTime(log.oldSituation.getAdjustedMSecsSinceEpoch()) % QLatin1Char('-') % QString::number(log.oldSituation.getTimeOffsetMs()) % QLatin1String("") % + QLatin1String("") % msSinceEpochToTime(log.newSituation.getAdjustedMSecsSinceEpoch()) % QLatin1Char('-') % QString::number(log.newSituation.getTimeOffsetMs()) % QLatin1String("") % + QLatin1String("") % msSinceEpochToTime(log.currentSituation.getAdjustedMSecsSinceEpoch()) % QLatin1Char('-') % QString::number(log.currentSituation.getTimeOffsetMs()) % QLatin1String("") % + + QLatin1String("") % QString::number(log.deltaTimeMs) % QLatin1String("") % + QLatin1String("") % QString::number(log.deltaTimeFractionMs) % QLatin1String("") % + QLatin1String("") % QString::number(log.simulationTimeFraction) % QLatin1String(""); + + tableRows += + QLatin1String("") % log.oldSituation.latitudeAsString() % QLatin1String("") % + QLatin1String("") % log.newSituation.latitudeAsString() % QLatin1String("") % + QLatin1String("") % log.currentSituation.latitudeAsString() % QLatin1String("") % + + QLatin1String("") % log.oldSituation.longitudeAsString() % QLatin1String("") % + QLatin1String("") % log.newSituation.longitudeAsString() % QLatin1String("") % + QLatin1String("") % log.currentSituation.longitudeAsString() % QLatin1String(""); + + tableRows += + QLatin1String("") % log.oldSituation.getAltitude().valueRoundedWithUnit(ft, 1) % QLatin1String("") % + QLatin1String("") % log.newSituation.getAltitude().valueRoundedWithUnit(ft, 1) % QLatin1String("") % + QLatin1String("") % log.currentSituation.getAltitude().valueRoundedWithUnit(ft, 1) % QLatin1String("") % + + QLatin1String("") % log.oldSituation.getGroundElevation().valueRoundedWithUnit(ft, 1) % QLatin1String("") % + QLatin1String("") % log.newSituation.getGroundElevation().valueRoundedWithUnit(ft, 1) % QLatin1String("") % + QLatin1String("") % log.currentSituation.getGroundElevation().valueRoundedWithUnit(ft, 1) % QLatin1String("") % + + QLatin1String("") % QString::number(log.groundFactor) % QLatin1String("") % + QLatin1String("") % log.oldSituation.getOnGroundInfo() % QLatin1String("") % + QLatin1String("") % log.newSituation.getOnGroundInfo() % QLatin1String("") % + QLatin1String("") % log.currentSituation.getOnGroundInfo() % QLatin1String(""); + + tableRows += + QLatin1String("") % boolToYesNo(log.useParts) % QLatin1String("") % + (changedParts ? QLatin1String("*") : QLatin1String("")) % + QLatin1String("") % (log.useParts ? log.parts.toQString(true) : QLatin1String("")) % QLatin1String("") % + QLatin1String("\n"); + } + tableRows += QLatin1String("\n"); + return QLatin1String("\n") % tableHeader % tableRows % QLatin1String("
\n"); + } + + QString CInterpolationLogger::getHtmlPartsLog(const QList &logs) + { + if (logs.isEmpty()) { return {}; } + const QString tableHeader = + QLatin1String("") % + QLatin1String("CStimestamp") % + QLatin1String("c.") % + QLatin1String("parts") % + QLatin1String("\n"); + + CAircraftParts lastParts; // default, so shown if parts are different from default + QString tableRows("\n"); + for (const PartsLog &log : logs) + { + const bool changedParts = lastParts != log.parts; + lastParts = log.parts; + tableRows += + QLatin1String("") % + QLatin1String("") % log.callsign.asString() % QLatin1String("") % + QLatin1String("") % msSinceEpochToTime(log.timestamp) % QLatin1String("") % + (changedParts ? QLatin1String("*") : QLatin1String("")) % + QLatin1String("") % log.parts.toQString() % QLatin1String(""); + } + tableRows += QLatin1String("\n"); + return QLatin1String("\n") % tableHeader % tableRows % QLatin1String("
\n"); + } + + void CInterpolationLogger::clearLog() + { + QWriteLocker l(&m_lockLogs); + this->m_partsLogs.clear(); + this->m_situationLogs.clear(); + } + + QString CInterpolationLogger::msSinceEpochToTime(qint64 ms) + { + static const QString dateFormat("hh:mm:ss.zzz"); + return QDateTime::fromMSecsSinceEpoch(ms).toString(dateFormat); + } + + QString CInterpolationLogger::msSinceEpochToTime(qint64 t1, qint64 t2, qint64 t3) + { + if (t3 < 0) return QString("%1 %2").arg(msSinceEpochToTime(t1), msSinceEpochToTime(t2)); + return QString("%1 %2 %3").arg(msSinceEpochToTime(t1), msSinceEpochToTime(t2), msSinceEpochToTime(t3)); + } + } // namespace +} // namespace diff --git a/src/blackmisc/simulation/interpolationlogger.h b/src/blackmisc/simulation/interpolationlogger.h new file mode 100644 index 000000000..9f1094f8d --- /dev/null +++ b/src/blackmisc/simulation/interpolationlogger.h @@ -0,0 +1,118 @@ +/* Copyright (C) 2014 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKMISC_SIMULATION_INTERPOLATIONLOGGER_H +#define BLACKMISC_SIMULATION_INTERPOLATIONLOGGER_H + +#include "interpolationrenderingsetup.h" +#include "blackmisc/aviation/aircraftpartslist.h" +#include "blackmisc/aviation/aircraftsituation.h" +#include "blackmisc/aviation/aircraftpartslist.h" +#include "blackmisc/simulation/remoteaircraftprovider.h" + +#include +#include +#include + +namespace BlackMisc +{ + class CWorker; + namespace Aviation { class CCallsign; } + namespace Simulation + { + class CInterpolationHints; + class CInterpolatorLinear; + class CInterpolatorSpline; + struct CInterpolationStatus; + struct CPartsStatus; + + //! Record internal state of interpolator for debugging + class BLACKMISC_EXPORT CInterpolationLogger : public QObject + { + Q_OBJECT + + public: + //! Constructor + CInterpolationLogger(QObject *parent = nullptr); + + //! Log category + static QString getLogCategory() { return "swift.interpolationlogger"; } + + //! Write a log in background + BlackMisc::CWorker *writeLogInBackground(); + + //! Clear log file + void clearLog(); + + //! Latest log files: 0: Interpolation / 1: Parts + static QStringList getLatestLogFiles(); + + //! Log entry for situation interpolation + struct SituationLog + { + qint64 timestamp = -1; //!< current timestamp + double groundFactor = -1; //!< current ground factor + double vtolAircraft = false; //!< VTOL aircraft + double deltaTimeMs = 0; //!< delta time to last situation + double simulationTimeFraction = -1; //!< time fraction, normally 0..1 + double deltaTimeFractionMs = -1; //!< delta time fraction + bool useParts = false; //!< supporting aircraft parts + BlackMisc::Aviation::CCallsign callsign; //!< current callsign + BlackMisc::Aviation::CAircraftParts parts; //!< corresponding parts used in interpolator + BlackMisc::Aviation::CAircraftSituation oldSituation; //!< old situation + BlackMisc::Aviation::CAircraftSituation newSituation; //!< new situation + BlackMisc::Aviation::CAircraftSituation currentSituation; //!< interpolated situation + }; + + //! Log entry for parts interpolation + struct PartsLog + { + qint64 timestamp = -1; //!< current timestamp + BlackMisc::Aviation::CCallsign callsign; //!< current callsign + BlackMisc::Aviation::CAircraftParts parts; //!< parts to be logged + }; + + //! Log current interpolation cycle, only stores in memory, for performance reasons + //! \remark const to allow const interpolator functions + //! \threadsafe + void logInterpolation(const SituationLog &log); + + //! Log current parts cycle, only stores in memory, for performance reasons + //! \remark const to allow const interpolator functions + //! \threadsafe + void logParts(const PartsLog &parts); + + private: + //! Get log as HTML table + static QString getHtmlInterpolationLog(const QList &logs); + + //! Get log as HTML table + static QString getHtmlPartsLog(const QList &logs); + + //! Write log to file + static CStatusMessageList writeLogFile(const QList &interpolation, const QList &parts); + + //! Status of file operation + static CStatusMessage logStatusFileWriting(bool success, const QString &fileName); + + //! Create readable time + static QString msSinceEpochToTime(qint64 ms); + + //! Create readable time + static QString msSinceEpochToTime(qint64 t1, qint64 t2, qint64 t3 = -1); + + mutable QReadWriteLock m_lockLogs; //!< lock logging + QList m_partsLogs; //!< logs of parts + QList m_situationLogs; //!< logs of interpolation + }; + } // namespace +} // namespace +#endif // guard diff --git a/src/blackmisc/simulation/interpolator.cpp b/src/blackmisc/simulation/interpolator.cpp index 5ee08da72..0f34db691 100644 --- a/src/blackmisc/simulation/interpolator.cpp +++ b/src/blackmisc/simulation/interpolator.cpp @@ -10,6 +10,7 @@ #include "interpolator.h" #include "blackconfig/buildconfig.h" #include "blackmisc/simulation/interpolationhints.h" +#include "blackmisc/simulation/interpolationlogger.h" #include "blackmisc/simulation/interpolatorlinear.h" #include "blackmisc/simulation/interpolatorspline.h" #include "blackmisc/aviation/callsign.h" @@ -19,8 +20,6 @@ #include "blackmisc/pq/units.h" #include "blackmisc/pq/length.h" #include "blackmisc/logmessage.h" -#include "blackmisc/worker.h" -#include "blackmisc/directoryutils.h" #include #include @@ -49,7 +48,7 @@ namespace BlackMisc const CInterpolationAndRenderingSetup &setup, const CInterpolationHints &hints, CInterpolationStatus &status) { status.reset(); - InterpolationLog log; + CInterpolationLogger::SituationLog log; // any data at all? if (m_aircraftSituations.isEmpty()) { return {}; } @@ -111,7 +110,7 @@ namespace BlackMisc status.setChangedPosition(true); } status.setInterpolationSucceeded(true); - if (hints.isLoggingInterpolation()) + if (m_logger && hints.isLoggingInterpolation()) { log.timestamp = currentTimeMsSinceEpoc; log.callsign = m_callsign; @@ -119,7 +118,7 @@ namespace BlackMisc log.currentSituation = currentSituation; log.useParts = hints.hasAircraftParts(); log.parts = hints.getAircraftParts(); - this->logInterpolation(log); + m_logger->logInterpolation(log); } return currentSituation; @@ -225,13 +224,13 @@ namespace BlackMisc } while (false); - if (log) + if (m_logger && log) { - PartsLog log; + CInterpolationLogger::PartsLog log; log.callsign = m_callsign; log.timestamp = currentTimeMsSinceEpoch; log.parts = currentParts; - CInterpolator::logParts(log); + m_logger->logParts(log); } return currentParts; @@ -250,216 +249,6 @@ namespace BlackMisc IRemoteAircraftProvider::removeOutdatedParts(m_aircraftParts); } - template - CWorker *CInterpolator::writeLogInBackground() - { - QList interpolation; - QList parts; - { - QReadLocker l(&m_lockLogs); - interpolation = m_interpolationLogs; - parts = m_partsLogs; - } - - CWorker *worker = CWorker::fromTask(this, "WriteInterpolationLog", [interpolation, parts]() - { - const CStatusMessageList msg = CInterpolator::writeLogFile(interpolation, parts); - CLogMessage::preformatted(msg); - }); - return worker; - } - - QStringList IInterpolator::getLatestLogFiles() - { - QStringList files({ "", ""}); - const QString logDir = CDirectoryUtils::getLogDirectory(); - QDir logs(logDir); - if (!logs.exists()) { return files; } - logs.setNameFilters(QStringList() << "*interpolation.html" << "*parts.html"); - const QStringList interpolations = logs.entryList(QStringList({"*interpolation.html"}), QDir::NoFilter, QDir::Time); - if (!interpolations.isEmpty()) - { - files[0] = CFileUtils::appendFilePaths(logDir, interpolations.first()); - } - const QStringList parts = logs.entryList(QStringList({"*parts.html"}), QDir::NoFilter, QDir::Time); - if (!parts.isEmpty()) - { - files[1] = CFileUtils::appendFilePaths(logDir, parts.first()); - } - return files; - } - - template - CStatusMessageList CInterpolator::writeLogFile(const QList &interpolation, const QList &parts) - { - if (parts.isEmpty() && interpolation.isEmpty()) { return CStatusMessage(static_cast(nullptr)).warning("No data for log"); } - static const QString html = QLatin1String("Entries: %1\n\n%2"); - const QString htmlTemplate = CFileUtils::readFileToString(CBuildConfig::getHtmlTemplateFileName()); - - CStatusMessageList msgs; - const QString ts = QDateTime::currentDateTimeUtc().toString("yyyyMMddhhmmss"); - const QString htmlInterpolation = CInterpolator::getHtmlInterpolationLog(interpolation); - if (!htmlInterpolation.isEmpty()) - { - const QString fn = CFileUtils::appendFilePaths(CDirectoryUtils::getLogDirectory(), QString("%1 interpolation.html").arg(ts)); - const bool s = CFileUtils::writeStringToFile(htmlTemplate.arg(html.arg(interpolation.size()).arg(htmlInterpolation)), fn); - msgs.push_back(CInterpolator::logStatusFileWriting(s, fn)); - } - - const QString htmlParts = CInterpolator::getHtmlPartsLog(parts); - if (!htmlParts.isEmpty()) - { - const QString fn = CFileUtils::appendFilePaths(CDirectoryUtils::getLogDirectory(), QString("%1 parts.html").arg(ts)); - const bool s = CFileUtils::writeStringToFile(htmlTemplate.arg(html.arg(parts.size()).arg(htmlParts)), fn); - msgs.push_back(CInterpolator::logStatusFileWriting(s, fn)); - } - return msgs; - } - - template - CStatusMessage CInterpolator::logStatusFileWriting(bool success, const QString &fileName) - { - if (success) - { - return CStatusMessage(static_cast(nullptr)).info("Written log file '%1'") << fileName; - } - else - { - return CStatusMessage(static_cast(nullptr)).error("Failed to write log file '%1'") << fileName; - } - } - - template - void CInterpolator::logInterpolation(const typename CInterpolator::InterpolationLog &log) const - { - QWriteLocker l(&m_lockLogs); - m_interpolationLogs.append(log); - } - - template - void CInterpolator::logParts(const typename CInterpolator::PartsLog &parts) const - { - QWriteLocker l(&m_lockLogs); - m_partsLogs.append(parts); - } - - template - QString CInterpolator::getHtmlInterpolationLog(const QList &logs) - { - if (logs.isEmpty()) { return {}; } - const QString tableHeader = - QLatin1String("") % - QLatin1String("c.CSVTOLtimestampsince") % - QLatin1String("ts oldts newts cur") % - QLatin1String("ΔtΔt fr.fraction") % - QLatin1String("lat.oldlat.newlat.cur") % - QLatin1String("lng.oldlng.newlng.cur") % - QLatin1String("alt.oldalt.newalt.cur") % - QLatin1String("elv.oldelv.newelv.cur") % - QLatin1String("gnd.factor") % - QLatin1String("onGnd.oldonGnd.newonGnd.cur") % - QLatin1String("partsc.parts details") % - QLatin1String("\n"); - - static const CLengthUnit ft = CLengthUnit::ft(); - const InterpolationLog firstLog = logs.first(); - qint64 newPosTs = firstLog.newSituation.getMSecsSinceEpoch(); - CAircraftParts lastParts; // default, so shown if parts are different from default - - QString tableRows("\n"); - for (const InterpolationLog &log : logs) - { - const bool changedNewPosition = newPosTs != log.newSituation.getMSecsSinceEpoch(); - const bool changedParts = lastParts != log.parts; - newPosTs = log.newSituation.getMSecsSinceEpoch(); - lastParts = log.parts; - - // concatenating in multiple steps, otherwise C4503 warnings - tableRows += - QLatin1String("") % - (changedNewPosition ? QLatin1String("*") : QLatin1String("")) % - QLatin1String("") % log.callsign.asString() % QLatin1String("") % - QLatin1String("") % boolToYesNo(log.vtolAircraft) % QLatin1String("") % - QLatin1String("") % msSinceEpochToTime(log.timestamp) % QLatin1String("") % - QLatin1String("") % QString::number(log.timestamp - firstLog.timestamp) % QLatin1String("") % - - QLatin1String("") % msSinceEpochToTime(log.oldSituation.getAdjustedMSecsSinceEpoch()) % QLatin1Char('-') % QString::number(log.oldSituation.getTimeOffsetMs()) % QLatin1String("") % - QLatin1String("") % msSinceEpochToTime(log.newSituation.getAdjustedMSecsSinceEpoch()) % QLatin1Char('-') % QString::number(log.newSituation.getTimeOffsetMs()) % QLatin1String("") % - QLatin1String("") % msSinceEpochToTime(log.currentSituation.getAdjustedMSecsSinceEpoch()) % QLatin1Char('-') % QString::number(log.currentSituation.getTimeOffsetMs()) % QLatin1String("") % - - QLatin1String("") % QString::number(log.deltaTimeMs) % QLatin1String("") % - QLatin1String("") % QString::number(log.deltaTimeFractionMs) % QLatin1String("") % - QLatin1String("") % QString::number(log.simulationTimeFraction) % QLatin1String(""); - - tableRows += - QLatin1String("") % log.oldSituation.latitudeAsString() % QLatin1String("") % - QLatin1String("") % log.newSituation.latitudeAsString() % QLatin1String("") % - QLatin1String("") % log.currentSituation.latitudeAsString() % QLatin1String("") % - - QLatin1String("") % log.oldSituation.longitudeAsString() % QLatin1String("") % - QLatin1String("") % log.newSituation.longitudeAsString() % QLatin1String("") % - QLatin1String("") % log.currentSituation.longitudeAsString() % QLatin1String(""); - - tableRows += - QLatin1String("") % log.oldSituation.getAltitude().valueRoundedWithUnit(ft, 1) % QLatin1String("") % - QLatin1String("") % log.newSituation.getAltitude().valueRoundedWithUnit(ft, 1) % QLatin1String("") % - QLatin1String("") % log.currentSituation.getAltitude().valueRoundedWithUnit(ft, 1) % QLatin1String("") % - - QLatin1String("") % log.oldSituation.getGroundElevation().valueRoundedWithUnit(ft, 1) % QLatin1String("") % - QLatin1String("") % log.newSituation.getGroundElevation().valueRoundedWithUnit(ft, 1) % QLatin1String("") % - QLatin1String("") % log.currentSituation.getGroundElevation().valueRoundedWithUnit(ft, 1) % QLatin1String("") % - - QLatin1String("") % QString::number(log.groundFactor) % QLatin1String("") % - QLatin1String("") % log.oldSituation.getOnGroundInfo() % QLatin1String("") % - QLatin1String("") % log.newSituation.getOnGroundInfo() % QLatin1String("") % - QLatin1String("") % log.currentSituation.getOnGroundInfo() % QLatin1String(""); - - tableRows += - QLatin1String("") % boolToYesNo(log.useParts) % QLatin1String("") % - (changedParts ? QLatin1String("*") : QLatin1String("")) % - QLatin1String("") % (log.useParts ? log.parts.toQString(true) : QLatin1String("")) % QLatin1String("") % - QLatin1String("\n"); - } - tableRows += QLatin1String("\n"); - return QLatin1String("\n") % tableHeader % tableRows % QLatin1String("
\n"); - } - - template - QString CInterpolator::getHtmlPartsLog(const QList &logs) - { - if (logs.isEmpty()) { return {}; } - const QString tableHeader = - QLatin1String("") % - QLatin1String("CStimestamp") % - QLatin1String("c.") % - QLatin1String("parts") % - QLatin1String("\n"); - - CAircraftParts lastParts; // default, so shown if parts are different from default - QString tableRows("\n"); - for (const PartsLog &log : logs) - { - const bool changedParts = lastParts != log.parts; - lastParts = log.parts; - tableRows += - QLatin1String("") % - QLatin1String("") % log.callsign.asString() % QLatin1String("") % - QLatin1String("") % msSinceEpochToTime(log.timestamp) % QLatin1String("") % - (changedParts ? QLatin1String("*") : QLatin1String("")) % - QLatin1String("") % log.parts.toQString() % QLatin1String(""); - } - tableRows += QLatin1String("\n"); - return QLatin1String("\n") % tableHeader % tableRows % QLatin1String("
\n"); - } - - template - void CInterpolator::clearLog() - { - QWriteLocker l(&m_lockLogs); - this->m_partsLogs.clear(); - this->m_interpolationLogs.clear(); - } - template void CInterpolator::setGroundElevationFromHint(const CInterpolationHints &hints, CAircraftSituation &situation, bool override) { @@ -520,20 +309,6 @@ namespace BlackMisc situation.setOnGround(CAircraftSituation::OnGround, CAircraftSituation::OnGroundByGuessing); } - template - QString CInterpolator::msSinceEpochToTime(qint64 ms) - { - static const QString dateFormat("hh:mm:ss.zzz"); - return QDateTime::fromMSecsSinceEpoch(ms).toString(dateFormat); - } - - template - QString CInterpolator::msSinceEpochToTime(qint64 t1, qint64 t2, qint64 t3) - { - if (t3 < 0) return QString("%1 %2").arg(msSinceEpochToTime(t1), msSinceEpochToTime(t2)); - return QString("%1 %2 %3").arg(msSinceEpochToTime(t1), msSinceEpochToTime(t2), msSinceEpochToTime(t3)); - } - bool CInterpolationStatus::allTrue() const { return m_interpolationSucceeded && m_changedPosition; diff --git a/src/blackmisc/simulation/interpolator.h b/src/blackmisc/simulation/interpolator.h index 156ae187f..bfebf95de 100644 --- a/src/blackmisc/simulation/interpolator.h +++ b/src/blackmisc/simulation/interpolator.h @@ -29,6 +29,7 @@ namespace BlackMisc namespace Simulation { class CInterpolationHints; + class CInterpolationLogger; class CInterpolatorLinear; class CInterpolatorSpline; struct CInterpolationStatus; @@ -56,13 +57,6 @@ namespace BlackMisc //! Add a new aircraft parts void addAircraftParts(const BlackMisc::Aviation::CAircraftParts &parts); - //! Write a log in background - //! \threadsafe - BlackMisc::CWorker *writeLogInBackground(); - - //! Clear log file - void clearLog(); - //! Takes input between 0 and 1 and returns output between 0 and 1 smoothed with an S-shaped curve. //! //! Useful for making interpolation seem smoother, efficiently as it just uses simple arithmetic. @@ -73,60 +67,17 @@ namespace BlackMisc return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); } - //! Latest log files: 0: Interpolation / 1: Parts - static QStringList getLatestLogFiles(); + //! Attach an observer to read the interpolator's state for debugging + void attachLogger(CInterpolationLogger *logger) { m_logger = logger; } protected: BlackMisc::Aviation::CAircraftSituationList m_aircraftSituations; //!< recent situations BlackMisc::Aviation::CAircraftPartsList m_aircraftParts; //!< recent parts BlackMisc::Aviation::CCallsign m_callsign; //!< callsign - //! Log for interpolation - struct InterpolationLog - { - qint64 timestamp = -1; //!< current timestamp - double groundFactor = -1; //!< current ground factor - double vtolAircraft = false; //!< VTOL aircraft - double deltaTimeMs = 0; //!< delta time to last situation - double simulationTimeFraction = -1; //!< time fraction, normally 0..1 - double deltaTimeFractionMs = -1; //!< delta time fraction - bool useParts = false; //!< supporting aircraft parts - BlackMisc::Aviation::CCallsign callsign; //!< current callsign - BlackMisc::Aviation::CAircraftParts parts; //!< corresponding parts used in interpolator - BlackMisc::Aviation::CAircraftSituation oldSituation; //!< old situation - BlackMisc::Aviation::CAircraftSituation newSituation; //!< new situation - BlackMisc::Aviation::CAircraftSituation currentSituation; //!< interpolated situation - }; - - //! Log for parts - struct PartsLog - { - qint64 timestamp = -1; //!< current timestamp - BlackMisc::Aviation::CCallsign callsign; //!< current callsign - BlackMisc::Aviation::CAircraftParts parts; //!< parts to be logged - }; - //! Constructor CInterpolator(const QString &objectName, const BlackMisc::Aviation::CCallsign &callsign, QObject *parent); - //! Log current interpolation cycle, only stores in memory, for performance reasons - //! \remark const to allow const interpolator functions - //! \threadsafe - void logInterpolation(const InterpolationLog &log) const; - - //! Log current parts cycle, only stores in memory, for performance reasons - //! \remark const to allow const interpolator functions - //! \threadsafe - void logParts(const PartsLog &parts) const; - - //! Get log as HTML table - //! \threadsafe - static QString getHtmlInterpolationLog(const QList &logs); - - //! Get log as HTML table - //! \threadsafe - static QString getHtmlPartsLog(const QList &logs); - //! Set the ground elevation from hints static void setGroundElevationFromHint(const CInterpolationHints &hints, BlackMisc::Aviation::CAircraftSituation &situation, bool override = true); @@ -134,24 +85,10 @@ namespace BlackMisc static void setGroundFlagFromInterpolator(const CInterpolationHints &hints, double groundFactor, BlackMisc::Aviation::CAircraftSituation &situation); private: - //! Write log to file - static CStatusMessageList writeLogFile(const QList &interpolation, const QList &parts); - - //! Status of file operation - static CStatusMessage logStatusFileWriting(bool success, const QString &fileName); - - //! Create readable time - static QString msSinceEpochToTime(qint64 ms); - - //! Create readable time - static QString msSinceEpochToTime(qint64 t1, qint64 t2, qint64 t3 = -1); + CInterpolationLogger *m_logger = nullptr; Derived *derived() { return static_cast(this); } const Derived *derived() const { return static_cast(this); } - - mutable QReadWriteLock m_lockLogs; //!< lock logging - mutable QList m_partsLogs; //!< logs of parts - mutable QList m_interpolationLogs; //!< logs of interpolation }; //! Simple interpolator for pitch, bank, heading, groundspeed diff --git a/src/blackmisc/simulation/interpolatorlinear.cpp b/src/blackmisc/simulation/interpolatorlinear.cpp index 06ebf9781..93d8803f9 100644 --- a/src/blackmisc/simulation/interpolatorlinear.cpp +++ b/src/blackmisc/simulation/interpolatorlinear.cpp @@ -36,7 +36,7 @@ namespace BlackMisc namespace Simulation { CInterpolatorLinear::Interpolant CInterpolatorLinear::getInterpolant(qint64 currentTimeMsSinceEpoc, - const CInterpolationAndRenderingSetup &setup, const CInterpolationHints &hints, CInterpolationStatus &status, InterpolationLog &log) const + const CInterpolationAndRenderingSetup &setup, const CInterpolationHints &hints, CInterpolationStatus &status, CInterpolationLogger::SituationLog &log) const { Q_UNUSED(setup); Q_UNUSED(hints); diff --git a/src/blackmisc/simulation/interpolatorlinear.h b/src/blackmisc/simulation/interpolatorlinear.h index 1c51e1965..d9cbb360f 100644 --- a/src/blackmisc/simulation/interpolatorlinear.h +++ b/src/blackmisc/simulation/interpolatorlinear.h @@ -15,6 +15,7 @@ #include "interpolator.h" #include "blackmisc/blackmiscexport.h" #include "blackmisc/aviation/aircraftsituation.h" +#include "blackmisc/simulation/interpolationlogger.h" #include #include @@ -66,7 +67,7 @@ namespace BlackMisc //! Get the interpolant for the given time point Interpolant getInterpolant(qint64 currentTimeMsSinceEpoc, const CInterpolationAndRenderingSetup &setup, - const CInterpolationHints &hints, CInterpolationStatus &status, InterpolationLog &log) const; + const CInterpolationHints &hints, CInterpolationStatus &status, CInterpolationLogger::SituationLog &log) const; //! Log category static QString getLogCategory() { return "swift.interpolatorlinear"; } diff --git a/src/blackmisc/simulation/interpolatorspline.cpp b/src/blackmisc/simulation/interpolatorspline.cpp index def673207..560ee92af 100644 --- a/src/blackmisc/simulation/interpolatorspline.cpp +++ b/src/blackmisc/simulation/interpolatorspline.cpp @@ -90,7 +90,7 @@ namespace BlackMisc } CInterpolatorSpline::Interpolant CInterpolatorSpline::getInterpolant(qint64 currentTimeMsSinceEpoc, - const CInterpolationAndRenderingSetup &setup, const CInterpolationHints &hints, CInterpolationStatus &status, InterpolationLog &log) + const CInterpolationAndRenderingSetup &setup, const CInterpolationHints &hints, CInterpolationStatus &status, CInterpolationLogger::SituationLog &log) { Q_UNUSED(hints); Q_UNUSED(setup); diff --git a/src/blackmisc/simulation/interpolatorspline.h b/src/blackmisc/simulation/interpolatorspline.h index 88c2eaf5c..bb76a77e4 100644 --- a/src/blackmisc/simulation/interpolatorspline.h +++ b/src/blackmisc/simulation/interpolatorspline.h @@ -15,6 +15,7 @@ #include "blackmisc/simulation/interpolator.h" #include "blackmisc/blackmiscexport.h" #include "blackmisc/aviation/aircraftsituation.h" +#include "blackmisc/simulation/interpolationlogger.h" #include #include @@ -56,7 +57,7 @@ namespace BlackMisc //! Strategy used by CInterpolator::getInterpolatedSituation Interpolant getInterpolant(qint64 currentTimeMsSinceEpoc, const CInterpolationAndRenderingSetup &setup, - const CInterpolationHints &hints, CInterpolationStatus &status, InterpolationLog &log); + const CInterpolationHints &hints, CInterpolationStatus &status, CInterpolationLogger::SituationLog &log); //! Log category static QString getLogCategory() { return "swift.interpolatorspline"; } diff --git a/src/plugins/simulator/fs9/fs9client.cpp b/src/plugins/simulator/fs9/fs9client.cpp index 91d4f24ae..12d140fc6 100644 --- a/src/plugins/simulator/fs9/fs9client.cpp +++ b/src/plugins/simulator/fs9/fs9client.cpp @@ -118,13 +118,14 @@ namespace BlackSimPlugin return positionSlewMode; } - CFs9Client::CFs9Client(const CCallsign &callsign, const QString &modelName, - const CTime &updateInterval, QObject *owner) : + CFs9Client::CFs9Client(const CCallsign &callsign, const QString &modelName, const CTime &updateInterval, + BlackMisc::Simulation::CInterpolationLogger *logger, QObject *owner) : CDirectPlayPeer(owner, callsign), m_updateInterval(updateInterval), m_interpolator(callsign), m_modelName(modelName) { + m_interpolator.attachLogger(logger); } CFs9Client::~CFs9Client() diff --git a/src/plugins/simulator/fs9/fs9client.h b/src/plugins/simulator/fs9/fs9client.h index 781019dbf..4968d34d8 100644 --- a/src/plugins/simulator/fs9/fs9client.h +++ b/src/plugins/simulator/fs9/fs9client.h @@ -41,7 +41,8 @@ namespace BlackSimPlugin //! Constructor CFs9Client(const BlackMisc::Aviation::CCallsign &callsign, const QString &modelName, - const BlackMisc::PhysicalQuantities::CTime &updateInterval, QObject *owner); + const BlackMisc::PhysicalQuantities::CTime &updateInterval, + BlackMisc::Simulation::CInterpolationLogger *logger, QObject *owner); //! Destructor virtual ~CFs9Client(); diff --git a/src/plugins/simulator/fs9/simulatorfs9.cpp b/src/plugins/simulator/fs9/simulatorfs9.cpp index 646770180..79124c475 100644 --- a/src/plugins/simulator/fs9/simulatorfs9.cpp +++ b/src/plugins/simulator/fs9/simulatorfs9.cpp @@ -171,7 +171,7 @@ namespace BlackSimPlugin bool rendered = true; updateAircraftRendered(callsign, rendered); - CFs9Client *client = new CFs9Client(callsign, newRemoteAircraft.getModelString(), CTime(25, CTimeUnit::ms()), this); + CFs9Client *client = new CFs9Client(callsign, newRemoteAircraft.getModelString(), CTime(25, CTimeUnit::ms()), &m_interpolationLogger, this); client->setHostAddress(m_fs9Host->getHostAddress()); client->setPlayerUserId(m_fs9Host->getPlayerUserId()); client->start(); diff --git a/src/plugins/simulator/fsx/simconnectobject.cpp b/src/plugins/simulator/fsx/simconnectobject.cpp index 1e515a9d6..8d19458e9 100644 --- a/src/plugins/simulator/fsx/simconnectobject.cpp +++ b/src/plugins/simulator/fsx/simconnectobject.cpp @@ -20,10 +20,13 @@ namespace BlackSimPlugin CSimConnectObject::CSimConnectObject() { } - CSimConnectObject::CSimConnectObject(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, DWORD requestId) : + CSimConnectObject::CSimConnectObject(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, DWORD requestId, + BlackMisc::Simulation::CInterpolationLogger *logger) : m_aircraft(aircraft), m_requestId(requestId), m_validRequestId(true), m_interpolator(QSharedPointer::create(aircraft.getCallsign())) - { } + { + m_interpolator->attachLogger(logger); + } bool CSimConnectObject::isPendingAdded() const { diff --git a/src/plugins/simulator/fsx/simconnectobject.h b/src/plugins/simulator/fsx/simconnectobject.h index 8b905b1ad..cd0ccf536 100644 --- a/src/plugins/simulator/fsx/simconnectobject.h +++ b/src/plugins/simulator/fsx/simconnectobject.h @@ -16,7 +16,14 @@ #include "simconnectdatadefinition.h" #include -namespace BlackMisc { namespace Simulation { class CInterpolatorLinear; } } +namespace BlackMisc +{ + namespace Simulation + { + class CInterpolatorLinear; + class CInterpolationLogger; + } +} namespace BlackSimPlugin { namespace Fsx @@ -29,7 +36,8 @@ namespace BlackSimPlugin CSimConnectObject(); //! Constructor - CSimConnectObject(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, DWORD requestId); + CSimConnectObject(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, DWORD requestId, + BlackMisc::Simulation::CInterpolationLogger *logger); //! Destructor ~CSimConnectObject() {} diff --git a/src/plugins/simulator/fsx/simulatorfsx.cpp b/src/plugins/simulator/fsx/simulatorfsx.cpp index 73e50fb4d..d4deeb125 100644 --- a/src/plugins/simulator/fsx/simulatorfsx.cpp +++ b/src/plugins/simulator/fsx/simulatorfsx.cpp @@ -174,7 +174,7 @@ namespace BlackSimPlugin // we will request a new aircraft by request ID, later we will receive its object id // so far this object id is -1 addedAircraft.setRendered(false); - const CSimConnectObject simObject(addedAircraft, requestId); + const CSimConnectObject simObject(addedAircraft, requestId, &m_interpolationLogger); m_simConnectObjects.insert(callsign, simObject); adding = true; }