first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions

View File

@@ -0,0 +1,407 @@
// AircraftPerformance.cxx - compute data about planned acft performance
//
// Copyright (C) 2018 James Turner <james@flightgear.org>
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "AircraftPerformance.hxx"
#include <cassert>
#include <algorithm>
#include <simgear/constants.h>
#include <Main/fg_props.hxx>
using namespace flightgear;
double distanceForTimeAndSpeeds(double tSec, double v1, double v2)
{
return tSec * 0.5 * (v1 + v2);
}
AircraftPerformance::AircraftPerformance()
{
// read aircraft supplied performance data
if (fgGetNode("/aircraft/performance/bracket")) {
readPerformanceData();
} else {
// falls back to heuristic determination of the category,
// and a plausible default
icaoCategoryData();
}
}
double AircraftPerformance::groundSpeedForAltitudeKnots(int altitudeFt) const
{
auto bracket = bracketForAltitude(altitudeFt);
return bracket->gsForAltitude(altitudeFt);
}
int AircraftPerformance::computePreviousAltitude(double distanceM, int targetAltFt) const
{
auto bracket = bracketForAltitude(targetAltFt);
auto d = bracket->descendDistanceM(bracket->atOrBelowAltitudeFt, targetAltFt);
if (d < distanceM) {
// recurse to previous bracket
return computePreviousAltitude(distanceM - d, bracket->atOrBelowAltitudeFt+1);
}
// work out how far we travel laterally per foot change in altitude
// this value is in metres, we have to map FPM and GS in Knots to make
// everything work out
const double gsMPS = bracket->gsForAltitude(targetAltFt) * SG_KT_TO_MPS;
const double t = distanceM / gsMPS;
return targetAltFt + bracket->descentRateFPM * (t / 60.0);
}
int AircraftPerformance::computeNextAltitude(double distanceM, int initialAltFt) const
{
auto bracket = bracketForAltitude(initialAltFt);
auto d = bracket->climbDistanceM(initialAltFt, bracket->atOrBelowAltitudeFt);
if (d < distanceM) {
// recurse to next bracket
return computeNextAltitude(distanceM - d, bracket->atOrBelowAltitudeFt+1);
}
// work out how far we travel laterally per foot change in altitude
// this value is in metres, we have to map FPM and GS in Knots to make
// everything work out
const double gsMPS = bracket->gsForAltitude(initialAltFt) * SG_KT_TO_MPS;
const double t = distanceM / gsMPS;
return initialAltFt + bracket->climbRateFPM * (t / 60.0);
}
static string_list readTags()
{
string_list r;
const auto tagsNode = fgGetNode("/sim/tags");
if (!tagsNode)
return r;
for (auto t : tagsNode->getChildren("tag")) {
r.push_back(t->getStringValue());
}
return r;
}
static bool stringListContains(const string_list& t, const std::string& s)
{
auto it = std::find(t.begin(), t.end(), s);
return it != t.end();
}
std::string AircraftPerformance::heuristicCatergoryFromTags() const
{
const auto tags(readTags());
if (stringListContains(tags, "turboprop"))
return {ICAO_AIRCRAFT_CATEGORY_C};
// any way we could distuinguish fast and slow GA aircraft?
if (stringListContains(tags, "ga")) {
return {ICAO_AIRCRAFT_CATEGORY_A};
}
if (stringListContains(tags, "jet")) {
return {ICAO_AIRCRAFT_CATEGORY_E};
}
return {ICAO_AIRCRAFT_CATEGORY_C};
}
void AircraftPerformance::icaoCategoryData()
{
std::string propCat = fgGetString("/aircraft/performance/icao-category");
if (propCat.empty()) {
propCat = heuristicCatergoryFromTags();
}
const char aircraftCategory = propCat.front();
// pathTurnRate = 3.0; // 3 deg/sec = 180deg/min = standard rate turn
switch (aircraftCategory) {
case ICAO_AIRCRAFT_CATEGORY_A:
_perfData.push_back(Bracket(4000, 600, 1200, 75));
_perfData.push_back(Bracket(10000, 600, 1200, 140));
break;
case ICAO_AIRCRAFT_CATEGORY_B:
_perfData.push_back(Bracket(4000, 100, 1200, 100));
_perfData.push_back(Bracket(10000, 800, 1200, 160));
_perfData.push_back(Bracket(18000, 600, 1800, 200));
break;
case ICAO_AIRCRAFT_CATEGORY_C:
_perfData.push_back(Bracket(4000, 1800, 1800, 150));
_perfData.push_back(Bracket(10000, 1800, 1800, 200));
_perfData.push_back(Bracket(18000, 1200, 1800, 270));
_perfData.push_back(Bracket(60000, 800, 1200, 0.80, true /* is Mach */));
break;
case ICAO_AIRCRAFT_CATEGORY_D:
case ICAO_AIRCRAFT_CATEGORY_E:
default:
_perfData.push_back(Bracket(4000, 1800, 1800, 180));
_perfData.push_back(Bracket(10000, 1800, 1800, 230));
_perfData.push_back(Bracket(18000, 1200, 1800, 270));
_perfData.push_back(Bracket(60000, 800, 1200, 0.87, true /* is Mach */));
break;
}
}
void AircraftPerformance::readPerformanceData()
{
for (auto nd : fgGetNode("/aircraft/performance/")->getChildren("bracket")) {
const int atOrBelowAlt = nd->getIntValue("at-or-below-ft");
const int climbFPM = nd->getIntValue("climb-rate-fpm");
const int descentFPM = nd->getIntValue("descent-rate-fpm");
bool isMach = nd->hasChild("speed-mach");
double speed;
if (isMach) {
speed = nd->getDoubleValue("speed-mach");
} else {
speed = nd->getIntValue("speed-ias-knots");
}
Bracket b(atOrBelowAlt, climbFPM, descentFPM, speed, isMach);
_perfData.push_back(b);
}
}
auto AircraftPerformance::bracketForAltitude(int altitude) const
-> PerformanceVec::const_iterator
{
assert(!_perfData.empty());
if (_perfData.front().atOrBelowAltitudeFt >= altitude)
return _perfData.begin();
for (auto it = _perfData.begin(); it != _perfData.end(); ++it) {
if (it->atOrBelowAltitudeFt > altitude) {
return it;
}
}
return _perfData.end() - 1;
}
auto AircraftPerformance::rangeForAltitude(int lowAltitude, int highAltitude) const
-> BracketRange
{
return {bracketForAltitude(lowAltitude), bracketForAltitude(highAltitude)};
}
void AircraftPerformance::traverseAltitudeRange(int initialElevationFt, int targetElevationFt,
TraversalFunc tf) const
{
auto r = rangeForAltitude(initialElevationFt, targetElevationFt);
if (r.first == r.second) {
tf(*r.first, initialElevationFt, targetElevationFt);
return;
}
if (initialElevationFt < targetElevationFt) {
tf(*r.first, initialElevationFt, r.first->atOrBelowAltitudeFt);
int previousBracketCapAltitude = r.first->atOrBelowAltitudeFt;
for (auto bracket = r.first + 1; bracket != r.second; ++bracket) {
tf(*bracket, previousBracketCapAltitude, bracket->atOrBelowAltitudeFt);
previousBracketCapAltitude = bracket->atOrBelowAltitudeFt;
}
tf(*r.second, previousBracketCapAltitude, targetElevationFt);
} else {
int nextBracketCapAlt = (r.first - 1)->atOrBelowAltitudeFt;
tf(*r.first, initialElevationFt, nextBracketCapAlt);
for (auto bracket = r.first - 1; bracket != r.second; --bracket) {
nextBracketCapAlt = (r.first - 1)->atOrBelowAltitudeFt;
tf(*bracket, bracket->atOrBelowAltitudeFt, nextBracketCapAlt);
}
tf(*r.second, nextBracketCapAlt, targetElevationFt);
}
}
double AircraftPerformance::distanceNmBetween(int initialElevationFt, int targetElevationFt) const
{
double result = 0.0;
TraversalFunc tf = [&result](const Bracket& bk, int alt1, int alt2) {
result += (alt1 > alt2) ? bk.descendDistanceM(alt1, alt2) : bk.climbDistanceM(alt1, alt2);
};
traverseAltitudeRange(initialElevationFt, targetElevationFt, tf);
return result * SG_METER_TO_NM;
}
double AircraftPerformance::timeBetween(int initialElevationFt, int targetElevationFt) const
{
double result = 0.0;
TraversalFunc tf = [&result](const Bracket& bk, int alt1, int alt2) {
SG_LOG(SG_GENERAL, SG_INFO, "Range:" << alt1 << " " << alt2);
result += (alt1 > alt2) ? bk.descendTime(alt1, alt2) : bk.climbTime(alt1, alt2);
};
traverseAltitudeRange(initialElevationFt, targetElevationFt, tf);
return result;
}
double AircraftPerformance::timeToCruise(double cruiseDistanceNm, int cruiseAltitudeFt) const
{
auto b = bracketForAltitude(cruiseAltitudeFt);
return (cruiseDistanceNm / b->gsForAltitude(cruiseAltitudeFt)) * 3600.0;
}
double oatCForAltitudeFt(int altitudeFt)
{
if (altitudeFt > 36089)
return -56.5;
// lapse rate in C per ft
const double T_r = .0019812;
return 15.0 - (altitudeFt * T_r);
}
double oatKForAltitudeFt(int altitudeFt)
{
return oatCForAltitudeFt(altitudeFt) + 273.15;
}
double pressureAtAltitude(int altitude)
{
/*
p= P_0*(1-6.8755856*10^-6 h)^5.2558797 h<36,089.24ft
p_Tr= 0.2233609*P_0
p=p_Tr*exp(-4.806346*10^-5(h-36089.24)) h>36,089.24ft
magic numbers
6.8755856*10^-6 = T'/T_0, where T' is the standard temperature lapse rate and T_0 is the standard sea-level temperature.
5.2558797 = Mg/RT', where M is the (average) molecular weight of air, g is the acceleration of gravity and R is the gas constant.
4.806346*10^-5 = Mg/RT_tr, where T_tr is the temperature at the tropopause.
*/
const double k = 6.8755856e-6;
const double MgRT = 5.2558797;
const double MgRT_tr = 4.806346e-5;
const double P_0 = 29.92126; // (standard) sea-level pressure
if (altitude > 36089) {
const double P_Tr = 0.2233609 * P_0;
const double altAboveTr = altitude - 36089;
return P_Tr * exp(MgRT_tr * altAboveTr);
} else {
return P_0 * pow(1.0 - (k * altitude), MgRT);
}
}
double computeMachFromIAS(int iasKnots, int altitudeFt)
{
#if 0
// from the aviation formulary
DP=P_0*((1 + 0.2*(IAS/CS_0)^2)^3.5 -1)
M=(5*( (DP/P + 1)^(2/7) -1) )^0.5 (*)
#endif
const double Cs_0 = 661.4786; // speed of sound at sea level, knots
const double P_0 = 29.92126; // (standard) sea-level pressure
const double iasCsRatio = iasKnots / Cs_0;
const double P = pressureAtAltitude(altitudeFt);
// differential pressure
const double DP = P_0 * (pow(1.0 + 0.2 * pow(iasCsRatio, 2.0), 3.5) - 1.0);
const double pressureRatio = DP / P + 1.0;
const double M = pow(5.0 * (pow(pressureRatio, 2.0 / 7.0) - 1.0), 0.5);
if (M > 1.0) {
SG_LOG(SG_GENERAL, SG_INFO, "computeMachFromIAS: computed Mach is supersonic, fix for shock wave");
}
return M;
}
double AircraftPerformance::machForCAS(int altitudeFt, double cas)
{
return computeMachFromIAS(static_cast<int>(cas), altitudeFt);
}
double AircraftPerformance::groundSpeedForCAS(int altitudeFt, double cas)
{
return groundSpeedForMach(altitudeFt, computeMachFromIAS(cas, altitudeFt));
}
double AircraftPerformance::groundSpeedForMach(int altitudeFt, double mach)
{
// CS = sound speed= 38.967854*sqrt(T+273.15) where T is the OAT in celsius.
const double CS = 38.967854 * sqrt(oatKForAltitudeFt(altitudeFt));
const double TAS = mach * CS;
return TAS;
}
int AircraftPerformance::Bracket::gsForAltitude(int altitude) const
{
double M = 0.0;
if (speedIsMach) {
M = speedIASOrMach; // simple
} else {
M = computeMachFromIAS(speedIASOrMach, altitude);
}
return groundSpeedForMach(altitude, M);
}
double AircraftPerformance::Bracket::climbTime(int alt1, int alt2) const
{
return (alt2 - alt1) / static_cast<double>(climbRateFPM) * 60.0;
}
double AircraftPerformance::Bracket::climbDistanceM(int alt1, int alt2) const
{
const double t = climbTime(alt1, alt2);
return distanceForTimeAndSpeeds(t,
SG_KT_TO_MPS * gsForAltitude(alt1),
SG_KT_TO_MPS * gsForAltitude(alt2));
}
double AircraftPerformance::Bracket::descendTime(int alt1, int alt2) const
{
return (alt1 - alt2) / static_cast<double>(descentRateFPM) * 60.0;
}
double AircraftPerformance::Bracket::descendDistanceM(int alt1, int alt2) const
{
const double t = descendTime(alt1, alt2);
return distanceForTimeAndSpeeds(t,
SG_KT_TO_MPS * gsForAltitude(alt1),
SG_KT_TO_MPS * gsForAltitude(alt2));
}
double AircraftPerformance::turnRadiusMForAltitude(int altitudeFt) const
{
#if 0
From the aviation formulary again
In a steady turn, in no wind, with bank angle, b at an airspeed v
tan(b)= v^2/(R g)
With R in feet, v in knots, b in degrees and w in degrees/sec (inconsistent units!), numerical constants are introduced:
R =v^2/(11.23*tan(0.01745*b))
(Example) At 100 knots, with a 45 degree bank, the radius of turn is 100^2/(11.23*tan(0.01745*45))= 891 feet.
The bank angle b_s for a standard rate turn is given by:
b_s = 57.3*atan(v/362.1)
(Example) for 100 knots, b_s = 57.3*atan(100/362.1) = 15.4 degrees
Working in meter-per-second and radians removes a bunch of constants again.
#endif
const double gsKts = groundSpeedForAltitudeKnots(altitudeFt);
const double gs = gsKts * SG_KT_TO_MPS;
const double bankAngleRad = atan(gsKts/362.1);
const double r = (gs * gs)/(SG_g0_m_p_s2 * tan(bankAngleRad));
return r;
}

View File

@@ -0,0 +1,117 @@
// AircraftPerformance.hxx - compute data about planned acft performance
//
// Copyright (C) 2018 James Turner <james@flightgear.org>
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef AIRCRAFTPERFORMANCE_HXX
#define AIRCRAFTPERFORMANCE_HXX
#include <string>
#include <vector>
#include <functional>
namespace flightgear
{
const char ICAO_AIRCRAFT_CATEGORY_A = 'A';
const char ICAO_AIRCRAFT_CATEGORY_B = 'B';
const char ICAO_AIRCRAFT_CATEGORY_C = 'C';
const char ICAO_AIRCRAFT_CATEGORY_D = 'D';
const char ICAO_AIRCRAFT_CATEGORY_E = 'E';
/**
* Calculate flight parameter based on aircraft performance data.
* This is based on simple rules: it does not (yet) include data
* such as winds aloft, payload or temperature impact on engine
* performance. */
class AircraftPerformance
{
public:
AircraftPerformance();
double turnRateDegSec() const;
double turnRadiusMForAltitude(int altitudeFt) const;
double groundSpeedForAltitudeKnots(int altitudeFt) const;
int computePreviousAltitude(double distanceM, int targetAltFt) const;
int computeNextAltitude(double distanceM, int initialAltFt) const;
double distanceNmBetween(int initialElevationFt, int targetElevationFt) const;
double timeBetween(int initialElevationFt, int targetElevationFt) const;
double timeToCruise(double cruiseDistanceNm, int cruiseAltitudeFt) const;
static double groundSpeedForCAS(int altitudeFt, double cas);
static double machForCAS(int altitudeFt, double cas);
static double groundSpeedForMach(int altitudeFt, double mach);
private:
void readPerformanceData();
void icaoCategoryData();
/**
* @brief heuristicCatergoryFromTags - based on the aircraft tags, figure
* out a plausible ICAO category. Returns cat A if nothing better could
* be determined.
* @return a string containing a single ICAO category character A..E
*/
std::string heuristicCatergoryFromTags() const;
class Bracket
{
public:
Bracket(int atOrBelow, int climb, int descent, double speed, bool isMach = false) :
atOrBelowAltitudeFt(atOrBelow),
climbRateFPM(climb),
descentRateFPM(descent),
speedIASOrMach(speed),
speedIsMach(isMach)
{ }
int gsForAltitude(int altitude) const;
double climbTime(int alt1, int alt2) const;
double climbDistanceM(int alt1, int alt2) const;
double descendTime(int alt1, int alt2) const;
double descendDistanceM(int alt1, int alt2) const;
int atOrBelowAltitudeFt;
int climbRateFPM;
int descentRateFPM;
double speedIASOrMach;
bool speedIsMach = false;
};
using PerformanceVec = std::vector<Bracket>;
using BracketRange = std::pair<PerformanceVec::const_iterator, PerformanceVec::const_iterator>;
PerformanceVec::const_iterator bracketForAltitude(int altitude) const;
BracketRange rangeForAltitude(int lowAltitude, int highAltitude) const;
using TraversalFunc = std::function<void(const Bracket& bk, int alt1, int alt2)>;
void traverseAltitudeRange(int initialElevationFt, int targetElevationFt, TraversalFunc tf) const;
PerformanceVec _perfData;
};
}
#endif // AIRCRAFTPERFORMANCE_HXX

View File

@@ -0,0 +1,26 @@
include(FlightGearComponent)
set(SOURCES
controls.cxx
replay.cxx
flightrecorder.cxx
FlightHistory.cxx
initialstate.cxx
AircraftPerformance.cxx
replay-internal.cxx
continuous.cxx
)
set(HEADERS
controls.hxx
replay.hxx
flightrecorder.hxx
FlightHistory.hxx
initialstate.hxx
AircraftPerformance.hxx
continuous.hxx
replay-internal.hxx
)
flightgear_component(Aircraft "${SOURCES}" "${HEADERS}")

View File

@@ -0,0 +1,229 @@
// FlightHistory
//
// Written by James Turner, started December 2012.
//
// Copyright (C) 2012 James Turner - zakalawe (at) mac com
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
///////////////////////////////////////////////////////////////////////////////
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "FlightHistory.hxx"
#include <algorithm>
#include <simgear/sg_inlines.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/math/SGMath.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
FGFlightHistory::FGFlightHistory() :
m_sampleInterval(5.0),
m_validSampleCount(SAMPLE_BUCKET_WIDTH)
{
}
FGFlightHistory::~FGFlightHistory()
{
}
void FGFlightHistory::init()
{
m_enabled = fgGetNode("/sim/history/enabled", true);
m_sampleInterval = fgGetDouble("/sim/history/sample-interval-sec", 1.0);
if (m_sampleInterval <= 0.0) { // would be bad
SG_LOG(SG_FLIGHT, SG_INFO, "invalid flight-history sample interval:" << m_sampleInterval
<< ", defaulting to " << m_sampleInterval);
m_sampleInterval = 1.0;
}
// cap memory use at 4MB
m_maxMemoryUseBytes = fgGetInt("/sim/history/max-memory-use-bytes", 1024 * 1024 * 4);
m_weightOnWheels = NULL;
// reset the history when we detect a take-off
if (fgGetBool("/sim/history/clear-on-takeoff", true)) {
m_weightOnWheels = fgGetNode("/gear/gear[1]/wow", 0, true);
m_lastWoW = m_weightOnWheels->getBoolValue();
}
// force bucket re-allocation
m_validSampleCount = SAMPLE_BUCKET_WIDTH;
m_lastCaptureTime = globals->get_sim_time_sec();
}
void FGFlightHistory::shutdown()
{
clear();
}
void FGFlightHistory::reinit()
{
shutdown();
init();
}
void FGFlightHistory::update(double dt)
{
if ((dt == 0.0) || !m_enabled->getBoolValue()) {
return; // paused or disabled
}
if (m_weightOnWheels) {
if (m_lastWoW && !m_weightOnWheels->getBoolValue()) {
SG_LOG(SG_FLIGHT, SG_INFO, "history: detected main-gear takeoff, clearing history");
clear();
}
} // of rest-on-takeoff enabled
// spatial check - moved at least 1m since last capture
if (!m_buckets.empty()) {
SGVec3d lastCaptureCart(SGVec3d::fromGeod(m_buckets.back()->samples[m_validSampleCount - 1].position));
double d2 = distSqr(lastCaptureCart, globals->get_aircraft_position_cart());
if (d2 <= 1.0) {
return;
}
}
double elapsed = globals->get_sim_time_sec() - m_lastCaptureTime;
if (elapsed > m_sampleInterval) {
capture();
}
}
void FGFlightHistory::allocateNewBucket()
{
SampleBucket* bucket = NULL;
if (!m_buckets.empty() && (currentMemoryUseBytes() > m_maxMemoryUseBytes)) {
bucket = m_buckets.front();
m_buckets.erase(m_buckets.begin());
} else {
bucket = new SampleBucket;
}
m_buckets.push_back(bucket);
m_validSampleCount = 0;
}
void FGFlightHistory::capture()
{
if (m_validSampleCount == SAMPLE_BUCKET_WIDTH) {
// bucket is full, allocate a new one
allocateNewBucket();
}
m_lastCaptureTime = globals->get_sim_time_sec();
Sample* sample = m_buckets.back()->samples + m_validSampleCount;
sample->simTimeMSec = static_cast<size_t>(m_lastCaptureTime * 1000.0);
sample->position = globals->get_aircraft_position();
double heading, pitch, roll;
globals->get_aircraft_orientation(heading, pitch, roll);
sample->heading = static_cast<float>(heading);
sample->pitch = static_cast<float>(pitch);
sample->roll = static_cast<float>(roll);
++m_validSampleCount;
}
PagedPathForHistory_ptr FGFlightHistory::pagedPathForHistory(size_t max_entries, size_t newerThan ) const
{
PagedPathForHistory_ptr result = new PagedPathForHistory();
if (m_buckets.empty()) {
return result;
}
for (auto bucket : m_buckets) {
unsigned int count = (bucket == m_buckets.back() ? m_validSampleCount : SAMPLE_BUCKET_WIDTH);
// iterate over all the valid samples in the bucket
for (unsigned int index = 0; index < count; ++index) {
// skip older entries
// TODO: bisect!
if( bucket->samples[index].simTimeMSec <= newerThan )
continue;
if( max_entries ) {
max_entries--;
SGGeod g = bucket->samples[index].position;
result->path.push_back(g);
result->last_seen = bucket->samples[index].simTimeMSec;
} else {
goto exit;
}
} // of samples iteration
} // of buckets iteration
exit:
return result;
}
SGGeodVec FGFlightHistory::pathForHistory(double minEdgeLengthM) const
{
SGGeodVec result;
if (m_buckets.empty()) {
return result;
}
result.push_back(m_buckets.front()->samples[0].position);
SGVec3d lastOutputCart = SGVec3d::fromGeod(result.back());
double minLengthSqr = minEdgeLengthM * minEdgeLengthM;
for (auto bucket : m_buckets) {
unsigned int count = (bucket == m_buckets.back() ? m_validSampleCount : SAMPLE_BUCKET_WIDTH);
// iterate over all the valid samples in the bucket
for (unsigned int index = 0; index < count; ++index) {
SGGeod g = bucket->samples[index].position;
SGVec3d cart(SGVec3d::fromGeod(g));
if (distSqr(cart, lastOutputCart) > minLengthSqr) {
lastOutputCart = cart;
result.push_back(g);
}
} // of samples iteration
} // of buckets iteration
return result;
}
void FGFlightHistory::clear()
{
for (auto ptr : m_buckets) {
delete ptr;
}
m_buckets.clear();
m_validSampleCount = SAMPLE_BUCKET_WIDTH;
}
size_t FGFlightHistory::currentMemoryUseBytes() const
{
return sizeof(SampleBucket) * m_buckets.size();
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGFlightHistory> registrantFGFlightHistory;

View File

@@ -0,0 +1,132 @@
// FlightHistory
//
// Written by James Turner, started December 2012.
//
// Copyright (C) 2012 James Turner - zakalawe (at) mac com
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef FG_AIRCRAFT_FLIGHT_HISTORY_HXX
#define FG_AIRCRAFT_FLIGHT_HISTORY_HXX
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/props.hxx>
#include <simgear/math/SGMath.hxx>
#include <vector>
typedef std::vector<SGGeod> SGGeodVec;
class PagedPathForHistory : public SGReferenced
{
public:
PagedPathForHistory() : last_seen(0) {}
virtual ~PagedPathForHistory() {}
SGGeodVec path;
time_t last_seen;
};
typedef SGSharedPtr<PagedPathForHistory> PagedPathForHistory_ptr;
const unsigned int SAMPLE_BUCKET_WIDTH = 1024;
/**
* record the history of the aircraft's movements, making it available
* as a contiguous block. This can be used to show the historical flight-path
* over a long period of time (unlike the replay system), but only a small,
* fixed set of properties are recorded. (Positioned and orientation, but
* not velocity, acceleration, control inputs, or so on)
*/
class FGFlightHistory : public SGSubsystem
{
public:
FGFlightHistory();
virtual ~FGFlightHistory();
// Subsystem API.
void init() override;
void reinit() override;
void shutdown() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "history"; }
PagedPathForHistory_ptr pagedPathForHistory(size_t max_entries, size_t newerThan = 0) const;
/**
* retrieve the path, collapsing segments shorter than
* the specified minimum length
*/
SGGeodVec pathForHistory(double minEdgeLengthM = 50.0) const;
/**
* clear the history
*/
void clear();
private:
/**
* @class A single data sample in the history system.
*/
class Sample
{
public:
SGGeod position;
/// heading, pitch and roll can be recorded at lower precision
/// than a double - actually 16 bits might be sufficient
float heading, pitch, roll;
size_t simTimeMSec;
};
/**
* Bucket is a fixed-size container of samples. This is a crude slab
* allocation of samples, in chunks defined by the width constant above.
* Keep in mind that even with a 1Hz sample frequency, we use less than
* 200kbytes per hour - avoiding continous malloc traffic, or expensive
* std::vector reallocations, is the key factor here.
*/
class SampleBucket
{
public:
Sample samples[SAMPLE_BUCKET_WIDTH];
};
double m_lastCaptureTime;
double m_sampleInterval; ///< sample interval in seconds
/// our store of samples (in buckets). The last bucket is partially full,
/// with the number of valid samples indicated by m_validSampleCount
std::vector<SampleBucket*> m_buckets;
/// number of valid samples in the final bucket
unsigned int m_validSampleCount;
SGPropertyNode_ptr m_weightOnWheels;
SGPropertyNode_ptr m_enabled;
bool m_lastWoW;
size_t m_maxMemoryUseBytes;
void allocateNewBucket();
void capture();
size_t currentMemoryUseBytes() const;
};
#endif

987
src/Aircraft/continuous.cxx Normal file
View File

@@ -0,0 +1,987 @@
#include "continuous.hxx"
#include <Aircraft/flightrecorder.hxx>
#include <Main/fg_props.hxx>
#include <MultiPlayer/mpmessages.hxx>
#include <Viewer/FGEventHandler.hxx>
#include <Viewer/renderer.hxx>
#include <Viewer/viewmgr.hxx>
#include <simgear/io/iostreams/zlibstream.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/structure/commands.hxx>
#include <osgViewer/ViewerBase>
#include <assert.h>
#include <string.h>
Continuous::Continuous(std::shared_ptr<FGFlightRecorder> flight_recorder)
:
m_flight_recorder(flight_recorder)
{
SGPropertyNode* record_continuous = fgGetNode("/sim/replay/record-continuous", true);
SGPropertyNode* fdm_initialized = fgGetNode("/sim/signals/fdm-initialized", true);
record_continuous->addChangeListener(this, true /*initial*/);
fdm_initialized->addChangeListener(this, true /*initial*/);
}
// Reads binary data from a stream into an instance of a type.
template<typename T>
static void readRaw(std::istream& in, T& data)
{
in.read(reinterpret_cast<char*>(&data), sizeof(data));
}
// Writes instance of a type as binary data to a stream.
template<typename T>
static void writeRaw(std::ostream& out, const T& data)
{
out.write(reinterpret_cast<const char*>(&data), sizeof(data));
}
// Reads uncompressed vector<char> from file. Throws if length field is longer
// than <max_length>.
template<typename SizeType>
static SizeType VectorRead(std::istream& in, std::vector<char>& out, uint32_t max_length=(1u << 31))
{
SizeType length;
readRaw(in, length);
if (sizeof(length) + length > max_length)
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "recording data vector too long."
<< " max_length=" << max_length
<< " sizeof(length)=" << sizeof(length)
<< " length=" << length
);
throw std::runtime_error("Failed to read vector in recording");
}
out.resize(length);
in.read(&out.front(), length);
return sizeof(length) + length;
}
static int16_t read_int16(std::istream& in, size_t& pos)
{
int16_t a;
readRaw(in, a);
pos += sizeof(a);
return a;
}
static std::string read_string(std::istream& in, size_t& pos)
{
int16_t length = read_int16(in, pos);
std::vector<char> path(length);
in.read(&path[0], length);
pos += length;
std::string ret(&path[0], length);
return ret;
}
static int PropertiesWrite(SGPropertyNode* root, std::ostream& out)
{
stringstream buffer;
writeProperties(buffer, root, true /*write_all*/);
uint32_t buffer_len = buffer.str().size() + 1;
writeRaw(out, buffer_len);
out.write(buffer.str().c_str(), buffer_len);
return 0;
}
// Reads extra-property change items in next <length> bytes. Throws if we don't
// exactly read <length> bytes.
static void ReadFGReplayDataExtraProperties(std::istream& in, FGReplayData* replay_data, uint32_t length)
{
SG_LOG(SG_SYSTEMS, SG_BULK, "reading extra-properties. length=" << length);
size_t pos=0;
for(;;)
{
if (pos == length)
{
break;
}
if (pos > length)
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "Overrun while reading extra-properties:"
" length=" << length << ": pos=" << pos);
in.setstate(std::ios_base::failbit);
break;
}
SG_LOG(SG_SYSTEMS, SG_BULK, "length=" << length<< " pos=" << pos);
std::string path = read_string(in, pos);
if (path == "")
{
path = read_string(in, pos);
SG_LOG(SG_SYSTEMS, SG_DEBUG, "property deleted: " << path);
replay_data->replay_extra_property_removals.push_back(path);
}
else
{
std::string value = read_string(in, pos);
SG_LOG(SG_SYSTEMS, SG_DEBUG, "property changed: " << path << "=" << value);
replay_data->replay_extra_property_changes[path] = value;
}
}
}
static bool ReadFGReplayData2(
std::istream& in,
SGPropertyNode* config,
bool load_signals,
bool load_multiplayer,
bool load_extra_properties,
FGReplayData* ret
)
{
ret->raw_data.resize(0);
for (auto data: config->getChildren("data"))
{
std::string data_type = data->getStringValue();
SG_LOG(SG_SYSTEMS, SG_BULK, "in.tellg()=" << in.tellg() << " data_type=" << data_type);
uint32_t length;
readRaw(in, length);
SG_LOG(SG_SYSTEMS, SG_DEBUG, "length=" << length);
if (!in) break;
if (load_signals && data_type == "signals")
{
ret->raw_data.resize(length);
in.read(&ret->raw_data.front(), ret->raw_data.size());
}
else if (load_multiplayer && data_type == "multiplayer")
{
/* Multiplayer information is a vector of vectors. */
ret->multiplayer_messages.clear();
uint32_t pos = 0;
for(;;)
{
assert(pos <= length);
if (pos == length) break;
std::shared_ptr<std::vector<char>> v(new std::vector<char>);
ret->multiplayer_messages.push_back(v);
pos += VectorRead<uint16_t>(in, *ret->multiplayer_messages.back(), length - pos);
SG_LOG(SG_SYSTEMS, SG_BULK, "replaying multiplayer data"
<< " ret->sim_time=" << ret->sim_time
<< " length=" << length
<< " pos=" << pos
<< " callsign=" << ((T_MsgHdr*) &v->front())->Callsign
);
}
}
else if (load_extra_properties && data_type == "extra-properties")
{
ReadFGReplayDataExtraProperties(in, ret, length);
}
else
{
SG_LOG(SG_GENERAL, SG_BULK, "Skipping unrecognised/unwanted data: " << data_type);
in.seekg(length, std::ios_base::cur);
}
if (!in) break;
}
if (!in)
{
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape data");
return false;
}
return true;
}
/* Removes items more than <n> away from <it>. <n> can be -ve. */
template<typename Container, typename Iterator>
static void remove_far_away(Container& container, Iterator it, int n)
{
SG_LOG(SG_GENERAL, SG_DEBUG, "container.size()=" << container.size());
if (n > 0)
{
for (int i=0; i<n; ++i)
{
if (it == container.end()) return;
++it;
}
container.erase(it, container.end());
}
else
{
for (int i=0; i<-n-1; ++i)
{
if (it == container.begin()) return;
--it;
}
container.erase(container.begin(), it);
}
SG_LOG(SG_GENERAL, SG_DEBUG, "container.size()=" << container.size());
}
/* Returns FGReplayData for frame at specified position in file. Uses
continuous.m_in_pos_to_frame as a cache, and trims this cache using
remove_far_away(). */
static std::shared_ptr<FGReplayData> ReadFGReplayData(
Continuous& continuous,
std::ifstream& in,
size_t pos,
SGPropertyNode* config,
bool load_signals,
bool load_multiplayer,
bool load_extra_properties,
int in_compression
)
{
std::shared_ptr<FGReplayData> ret;
auto it = continuous.m_in_pos_to_frame.find(pos);
if (it != continuous.m_in_pos_to_frame.end())
{
if (0
|| (load_signals && !it->second->load_signals)
|| (load_multiplayer && !it->second->load_multiplayer)
|| (load_extra_properties && !it->second->load_extra_properties)
)
{
/* This frame is in the continuous.m_in_pos_to_frame cache, but
doesn't contain all of the required items, so we need to reload. */
continuous.m_in_pos_to_frame.erase(it);
it = continuous.m_in_pos_to_frame.end();
}
}
if (it == continuous.m_in_pos_to_frame.end())
{
/* Load FGReplayData at offset <pos>.
We need to clear any eof bit, otherwise seekg() will not work (which is
pretty unhelpful). E.g. see:
https://stackoverflow.com/questions/16364301/whats-wrong-with-the-ifstream-seekg
*/
SG_LOG(SG_SYSTEMS, SG_BULK, "reading frame. pos=" << pos);
in.clear();
in.seekg(pos);
ret.reset(new FGReplayData);
readRaw(in, ret->sim_time);
if (!in)
{
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
return nullptr;
}
bool ok;
if (in_compression)
{
uint8_t flags;
uint32_t compressed_size;
in.read((char*) &flags, sizeof(flags));
in.read((char*) &compressed_size, sizeof(compressed_size));
simgear::ZlibDecompressorIStream in_decompress(in, SGPath(), simgear::ZLibCompressionFormat::ZLIB_RAW);
ok = ReadFGReplayData2(in_decompress, config, load_signals, load_multiplayer, load_extra_properties, ret.get());
}
else
{
ok = ReadFGReplayData2(in, config, load_signals, load_multiplayer, load_extra_properties, ret.get());
}
if (!ok)
{
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
return nullptr;
}
it = continuous.m_in_pos_to_frame.lower_bound(pos);
it = continuous.m_in_pos_to_frame.insert(it, std::make_pair(pos, ret));
/* Delete faraway items. */
size_t size_old = continuous.m_in_pos_to_frame.size();
int n = 2;
size_t size_max = 2*n - 1;
remove_far_away(continuous.m_in_pos_to_frame, it, n);
remove_far_away(continuous.m_in_pos_to_frame, it, -n);
size_t size_new = continuous.m_in_pos_to_frame.size();
SG_LOG(SG_GENERAL, SG_DEBUG, ""
<< " n=" << size_old
<< " size_max=" << size_max
<< " size_old=" << size_old
<< " size_new=" << size_new
);
assert(size_new <= size_max);
}
else
{
ret = it->second;
}
return ret;
}
// streambuf that compresses using deflate().
struct compression_streambuf : std::streambuf
{
compression_streambuf(
std::ostream& out,
size_t buffer_uncompressed_size,
size_t buffer_compressed_size
)
:
std::streambuf(),
out(out),
buffer_uncompressed(new char[buffer_uncompressed_size]),
buffer_uncompressed_size(buffer_uncompressed_size),
buffer_compressed(new char[buffer_compressed_size]),
buffer_compressed_size(buffer_compressed_size)
{
zstream.zalloc = nullptr;
zstream.zfree = nullptr;
zstream.opaque = nullptr;
zstream.next_in = nullptr;
zstream.avail_in = 0;
zstream.next_out = (unsigned char*) &buffer_compressed[0];
zstream.avail_out = buffer_compressed_size;
int e = deflateInit2(
&zstream,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
-15 /*windowBits*/,
8 /*memLevel*/,
Z_DEFAULT_STRATEGY
);
if (e != Z_OK)
{
throw std::runtime_error("deflateInit2() failed");
}
// We leave space for one character to simplify overflow().
setp(&buffer_uncompressed[0], &buffer_uncompressed[0] + buffer_uncompressed_size - 1);
}
// Flush compressed data to .out and reset zstream.next_out.
void _flush()
{
// Send all data in .buffer_compressed to .out.
size_t n = (char*) zstream.next_out - &buffer_compressed[0];
out.write(&buffer_compressed[0], n);
zstream.next_out = (unsigned char*) &buffer_compressed[0];
zstream.avail_out = buffer_compressed_size;
}
// Compresses specified bytes from buffer_uncompressed into
// buffer_compressed, flushing to .out as necessary. Returns true if we get
// EOF writing to .out.
bool _deflate(size_t n, bool flush)
{
assert(this->pbase() == &buffer_uncompressed[0]);
zstream.next_in = (unsigned char*) &buffer_uncompressed[0];
zstream.avail_in = n;
for(;;)
{
if (!flush && !zstream.avail_in) break;
if (!zstream.avail_out) _flush();
int e = deflate(&zstream, (!zstream.avail_in && flush) ? Z_FINISH : Z_NO_FLUSH);
if (e != Z_OK && e != Z_STREAM_END)
{
throw std::runtime_error("zip_deflate() failed");
}
if (e == Z_STREAM_END) break;
}
if (flush) _flush();
// We leave space for one character to simplify overflow().
setp(&buffer_uncompressed[0], &buffer_uncompressed[0] + buffer_uncompressed_size - 1);
if (!out) return true; // EOF.
return false;
}
int overflow(int c) override
{
// We've deliberately left space for one character, into which we write <c>.
assert(this->pptr() == &buffer_uncompressed[0] + buffer_uncompressed_size - 1);
*this->pptr() = (char) c;
if (_deflate(buffer_uncompressed_size, false /*flush*/)) return EOF;
return c;
}
int sync() override
{
_deflate(pptr() - &buffer_uncompressed[0], true /*flush*/);
return 0;
}
~compression_streambuf()
{
deflateEnd(&zstream);
}
std::ostream& out;
z_stream zstream;
std::unique_ptr<char[]> buffer_uncompressed;
size_t buffer_uncompressed_size;
std::unique_ptr<char[]> buffer_compressed;
size_t buffer_compressed_size;
};
// Accepts uncompressed data via .write(), operator<< etc, and writes
// compressed data to the supplied std::ostream.
struct compression_ostream : std::ostream
{
compression_ostream(
std::ostream& out,
size_t buffer_uncompressed_size,
size_t buffer_compressed_size
)
:
std::ostream(&streambuf),
streambuf(out, buffer_uncompressed_size, buffer_compressed_size)
{
}
compression_streambuf streambuf;
};
static void writeFrame2(FGReplayData* r, std::ostream& out, SGPropertyNode_ptr config)
{
for (auto data: config->getChildren("data"))
{
std::string data_type = data->getStringValue();
if (data_type == "signals")
{
uint32_t signals_size = r->raw_data.size();
writeRaw(out, signals_size);
out.write(&r->raw_data.front(), r->raw_data.size());
}
else if (data_type == "multiplayer")
{
uint32_t length = 0;
for (auto message: r->multiplayer_messages)
{
length += sizeof(uint16_t) + message->size();
}
SG_LOG(SG_SYSTEMS, SG_DEBUG, "data_type=" << data_type << " out.tellp()=" << out.tellp()
<< " length=" << length);
writeRaw(out, length);
for (auto message: r->multiplayer_messages)
{
uint16_t message_size = message->size();
writeRaw(out, message_size);
out.write(&message->front(), message_size);
}
}
else if (data_type == "extra-properties")
{
uint32_t length = r->extra_properties.size();
SG_LOG(SG_SYSTEMS, SG_DEBUG, "data_type=" << data_type << " out.tellp()=" << out.tellp()
<< " length=" << length);
writeRaw(out, length);
out.write(&r->extra_properties[0], length);
}
else
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "unrecognised data_type=" << data_type);
assert(0);
}
}
}
bool continuousWriteFrame(
Continuous& continuous,
FGReplayData* r,
std::ostream& out,
SGPropertyNode_ptr config,
FGTapeType tape_type
)
{
SG_LOG(SG_SYSTEMS, SG_BULK, "writing frame."
<< " out.tellp()=" << out.tellp()
<< " r->sim_time=" << r->sim_time
);
// Don't write frame if no data to write.
//bool r_has_data = false;
bool has_signals = false;
bool has_multiplayer = false;
bool has_extra_properties = false;
for (auto data: config->getChildren("data"))
{
std::string data_type = data->getStringValue();
if (data_type == "signals")
{
has_signals = true;
}
else if (data_type == "multiplayer")
{
if (!r->multiplayer_messages.empty())
{
has_multiplayer = true;
}
}
else if (data_type == "extra-properties")
{
if (!r->extra_properties.empty())
{
has_extra_properties = true;
}
}
else
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "unrecognised data_type=" << data_type);
assert(0);
}
}
if (!has_signals && !has_multiplayer && !has_extra_properties)
{
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Not writing frame because no data to write");
return true;
}
writeRaw(out, r->sim_time);
if (tape_type == FGTapeType_CONTINUOUS && continuous.m_out_compression)
{
uint8_t flags = 0;
if (has_signals) flags |= 1;
if (has_multiplayer) flags |= 2;
if (has_extra_properties) flags |= 4;
out.write((char*) &flags, sizeof(flags));
/* We need to first write the size of the compressed data so compress
to a temporary ostringstream first. */
std::ostringstream compressed;
compression_ostream out_compressing(compressed, 1024, 1024);
writeFrame2(r, out_compressing, config);
out_compressing.flush();
uint32_t compressed_size = compressed.str().size();
out.write((char*) &compressed_size, sizeof(compressed_size));
out.write((char*) compressed.str().c_str(), compressed.str().size());
}
else
{
writeFrame2(r, out, config);
}
bool ok = true;
if (!out) ok = false;
return ok;
}
SGPropertyNode_ptr continuousWriteHeader(
Continuous& continuous,
FGFlightRecorder* flight_recorder,
std::ofstream& out,
const SGPath& path,
FGTapeType tape_type
)
{
continuous.m_out_compression = fgGetInt("/sim/replay/record-continuous-compression");
SGPropertyNode_ptr config = saveSetup(NULL /*Extra*/, path, 0 /*Duration*/,
tape_type, continuous.m_out_compression);
SGPropertyNode* signals = config->getNode("signals", true /*create*/);
flight_recorder->getConfig(signals);
out.open(path.c_str(), std::ofstream::binary | std::ofstream::trunc);
out.write(FlightRecorderFileMagic, strlen(FlightRecorderFileMagic)+1);
PropertiesWrite(config, out);
if (tape_type == FGTapeType_CONTINUOUS)
{
// Ensure that all recorded properties are written in first frame.
//
flight_recorder->resetExtraProperties();
}
if (!out)
{
out.close();
config = nullptr;
}
return config;
}
/* Replays one frame from Continuous recording. <offset> and <offset_old> are
offsets in file of frames that are >= and < <time> respectively. <offset_old>
may be 0, in which case it is ignored.
We load the frame(s) from disc, omitting some data depending on
replay_signals, replay_multiplayer and replay_extra_properties. Then call
m_pRecorder->replay(), which updates the global state.
Returns true on success, otherwise we failed to read from Continuous recording.
*/
static bool replayContinuousInternal(
Continuous& continuous,
FGFlightRecorder* recorder,
double time,
size_t offset,
size_t offset_old,
bool replay_signals,
bool replay_multiplayer,
bool replay_extra_properties,
int* xpos,
int* ypos,
int* xsize,
int* ysize
)
{
std::shared_ptr<FGReplayData> replay_data = ReadFGReplayData(
continuous,
continuous.m_in,
offset,
continuous.m_in_config,
replay_signals,
replay_multiplayer,
replay_extra_properties,
continuous.m_in_compression
);
if (!replay_data)
{
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset=" << offset << " time=" << time);
return false;
}
assert(replay_data.get());
std::shared_ptr<FGReplayData> replay_data_old;
if (offset_old)
{
replay_data_old = ReadFGReplayData(
continuous,
continuous.m_in,
offset_old,
continuous.m_in_config,
replay_signals,
replay_multiplayer,
replay_extra_properties,
continuous.m_in_compression
);
}
if (replay_extra_properties) SG_LOG(SG_SYSTEMS, SG_DEBUG,
"replay():"
<< " time=" << time
<< " offset=" << offset
<< " offset_old=" << offset_old
<< " replay_data_old=" << replay_data_old
<< " replay_data->raw_data.size()=" << replay_data->raw_data.size()
<< " replay_data->multiplayer_messages.size()=" << replay_data->multiplayer_messages.size()
<< " replay_data->extra_properties.size()=" << replay_data->extra_properties.size()
<< " replay_data->replay_extra_property_changes.size()=" << replay_data->replay_extra_property_changes.size()
);
recorder->replay(time, replay_data.get(), replay_data_old.get(), xpos, ypos, xsize, ysize);
return true;
}
// fixme: this is duplicated in replay.cxx.
static void popupTip(const char* message, int delay)
{
SGPropertyNode_ptr args(new SGPropertyNode);
args->setStringValue("label", message);
args->setIntValue("delay", delay);
globals->get_commands()->execute("show-message", args);
}
void continuous_replay_video_end(Continuous& continuous)
{
if (continuous.m_replay_create_video)
{
SG_LOG(SG_GENERAL, SG_ALERT, "Stopping replay create-video");
auto view_mgr = globals->get_subsystem<FGViewMgr>();
if (view_mgr)
{
view_mgr->video_stop();
}
continuous.m_replay_create_video = false;
}
if (continuous.m_replay_fixed_dt_prev != -1)
{
SG_LOG(SG_GENERAL, SG_ALERT, "Resetting fixed-dt to" << continuous.m_replay_fixed_dt_prev);
fgSetDouble("/sim/time/fixed-dt", continuous.m_replay_fixed_dt_prev);
continuous.m_replay_fixed_dt_prev = -1;
}
}
bool replayContinuous(FGReplayInternal& self, double time)
{
// We need to detect whether replay() updates the values for the main
// window's position and size.
int xpos0 = self.m_sim_startup_xpos->getIntValue();
int ypos0 = self.m_sim_startup_xpos->getIntValue();
int xsize0 = self.m_sim_startup_xpos->getIntValue();
int ysize0 = self.m_sim_startup_xpos->getIntValue();
int xpos = xpos0;
int ypos = ypos0;
int xsize = xsize0;
int ysize = ysize0;
double multiplayer_recent = 3;
// We replay all frames from just after the previously-replayed frame,
// in order to replay extra properties and multiplayer aircraft
// correctly.
//
double t_begin = self.m_continuous->m_in_frame_time_last;
if (time < self.m_continuous->m_in_time_last)
{
// We have gone backwards, e.g. user has clicked on the back
// buttons in the Replay dialogue.
//
if (self.m_continuous->m_in_multiplayer)
{
// Continuous recording has multiplayer data, so replay recent
// ones.
//
t_begin = time - multiplayer_recent;
}
if (self.m_continuous->m_in_extra_properties)
{
// Continuous recording has property changes. we need to replay
// all property changes from the beginning.
//
t_begin = -1;
}
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Have gone backwards."
<< " m_in_time_last=" << self.m_continuous->m_in_time_last
<< " time=" << time
<< " t_begin=" << t_begin
<< " m_in_extra_properties=" << self.m_continuous->m_in_extra_properties
);
}
// Prepare to replay signals from Continuoue recording file. We want
// to find a pair of frames that straddle the requested <time> so that
// we can interpolate.
//
auto p = self.m_continuous->m_in_time_to_frameinfo.lower_bound(time);
bool ret = false;
size_t offset;
size_t offset_prev = 0;
if (p == self.m_continuous->m_in_time_to_frameinfo.end())
{
// We are at end of recording; replay last frame.
continuous_replay_video_end(*self.m_continuous);
--p;
offset = p->second.offset;
ret = true;
}
else if (p->first > time)
{
// Look for preceding item.
if (p == self.m_continuous->m_in_time_to_frameinfo.begin())
{
// <time> is before beginning of recording.
offset = p->second.offset;
}
else
{
// Interpolate between pair of items that straddle <time>.
auto prev = p;
--prev;
offset_prev = prev->second.offset;
offset = p->second.offset;
}
}
else
{
// Exact match.
offset = p->second.offset;
}
// Before interpolating signals, we replay all property changes from
// all frame times t satisfying t_prop_begin < t < time. We also replay
// all recent multiplayer packets in this range, i.e. for which t >
// time - multiplayer_recent.
//
// todo: figure out how to interpolate view position/direction, to
// smooth things out if replay fps is different from record fps e.g.
// with new fixed dt support.
//
for (auto p_before = self.m_continuous->m_in_time_to_frameinfo.upper_bound(t_begin);
p_before != self.m_continuous->m_in_time_to_frameinfo.end();
++p_before)
{
if (p_before->first >= p->first)
{
break;
}
// Replaying a frame is expensive because we read frame data
// from disc each time. So we only replay this frame if it has
// extra_properties, or if it has multiplayer packets and we are
// within <multiplayer_recent> seconds of current time.
//
bool replay_this_frame = p_before->second.has_extra_properties;
if (p_before->second.has_multiplayer && p_before->first > time - multiplayer_recent)
{
replay_this_frame = true;
}
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Looking at extra property changes."
<< " replay_this_frame=" << replay_this_frame
<< " m_continuous->m_in_time_last=" << self.m_continuous->m_in_time_last
<< " m_continuous->m_in_frame_time_last=" << self.m_continuous->m_in_frame_time_last
<< " time=" << time
<< " t_begin=" << t_begin
<< " p_before->first=" << p_before->first
<< " p_before->second=" << p_before->second
);
if (replay_this_frame)
{
size_t pos_prev = 0;
if (p_before != self.m_continuous->m_in_time_to_frameinfo.begin())
{
auto p_before_prev = p_before;
--p_before_prev;
pos_prev = p_before_prev->second.offset;
}
bool ok = replayContinuousInternal(
*self.m_continuous,
self.m_flight_recorder.get(),
p_before->first,
p_before->second.offset,
pos_prev /*offset_old*/,
false /*replay_signals*/,
p_before->first > time - multiplayer_recent /*replay_multiplayer*/,
true /*replay_extra_properties*/,
&xpos,
&ypos,
&xsize,
&ysize
);
if (!ok)
{
if (!self.m_replay_error->getBoolValue())
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "Replay failed: cannot read fgtape data");
popupTip("Replay failed: cannot read fgtape data", 10);
self.m_replay_error->setBoolValue(true);
}
return true;
}
}
}
/* Now replay signals, interpolating between frames atoffset_prev and
offset. */
bool ok = replayContinuousInternal(
*self.m_continuous,
self.m_flight_recorder.get(),
time,
offset,
offset_prev /*offset_old*/,
true /*replay_signals*/,
true /*replay_multiplayer*/,
true /*replay_extra_properties*/,
&xpos,
&ypos,
&xsize,
&ysize
);
if (!ok)
{
if (!self.m_replay_error->getBoolValue())
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "Replay failed: cannot read fgtape data");
popupTip("Replay failed: cannot read fgtape data", 10);
self.m_replay_error->setBoolValue(true);
}
return true;
}
if (0
|| xpos != xpos0
|| ypos != ypos0
|| xsize != xsize0
|| ysize != ysize0
)
{
// Move/resize the main window to reflect the updated values.
globals->get_props()->setIntValue("/sim/startup/xpos", xpos);
globals->get_props()->setIntValue("/sim/startup/ypos", ypos);
globals->get_props()->setIntValue("/sim/startup/xsize", xsize);
globals->get_props()->setIntValue("/sim/startup/ysize", ysize);
osgViewer::ViewerBase* viewer_base = globals->get_renderer()->getViewerBase();
if (viewer_base)
{
std::vector<osgViewer::GraphicsWindow*> windows;
viewer_base->getWindows(windows);
osgViewer::GraphicsWindow* window = windows[0];
// We use FGEventHandler::setWindowRectangle() to move the
// window, because it knows how to convert from window work-area
// coordinates to window-including-furniture coordinates.
//
flightgear::FGEventHandler* event_handler = globals->get_renderer()->getEventHandler();
event_handler->setWindowRectangleInteriorWithCorrection(window, xpos, ypos, xsize, ysize);
}
}
self.m_continuous->m_in_time_last = time;
self.m_continuous->m_in_frame_time_last = p->first;
return ret;
}
/* SGPropertyChangeListener callback for detecing when FDM is initialised and
for when continuous recording is started or stopped. */
void Continuous::valueChanged(SGPropertyNode * node)
{
bool prop_continuous = fgGetBool("/sim/replay/record-continuous");
bool prop_fdm = fgGetBool("/sim/signals/fdm-initialized");
bool continuous = prop_continuous && prop_fdm;
if (continuous == (m_out.is_open() ? true : false))
{
// No change.
return;
}
if (m_out.is_open())
{
// Stop existing continuous recording.
SG_LOG(SG_SYSTEMS, SG_ALERT, "Stopping continuous recording");
m_out.close();
popupTip("Continuous record to file stopped", 5 /*delay*/);
}
if (continuous)
{
// Start continuous recording.
SGPath path_timeless;
SGPath path = makeSavePath(FGTapeType_CONTINUOUS, &path_timeless);
m_out_config = continuousWriteHeader(
*this,
m_flight_recorder.get(),
m_out,
path,
FGTapeType_CONTINUOUS
);
if (!m_out_config)
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to start continuous recording");
popupTip("Continuous record to file failed to start", 5 /*delay*/);
return;
}
SG_LOG(SG_SYSTEMS, SG_ALERT, "Starting continuous recording");
/* Make a convenience link to the recording. E.g.
harrier-gr3-continuous.fgtape -> harrier-gr3-20201224-005034-continuous.fgtape.
Link destination is in same directory as link so we use leafname
path.file(). */
path_timeless.remove();
bool ok = path_timeless.makeLink(path.file());
if (!ok)
{
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to create link " << path_timeless.c_str() << " => " << path.file());
}
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Starting continuous recording to " << path);
if (m_out_compression)
{
popupTip("Continuous+compressed record to file started", 5 /*delay*/);
}
else
{
popupTip("Continuous record to file started", 5 /*delay*/);
}
}
}

View File

@@ -0,0 +1,97 @@
#pragma once
#include "replay-internal.hxx"
#include <simgear/props/props.hxx>
#include <fstream>
#include <mutex>
#include <thread>
struct Continuous : SGPropertyChangeListener
{
Continuous(std::shared_ptr<FGFlightRecorder> flight_recorder);
/* Callback for SGPropertyChangeListener. */
void valueChanged(SGPropertyNode * node) override;
std::shared_ptr<FGFlightRecorder> m_flight_recorder;
std::ifstream m_in;
bool m_in_multiplayer = false;
bool m_in_extra_properties = false;
std::mutex m_in_time_to_frameinfo_lock;
std::map<double, FGFrameInfo> m_in_time_to_frameinfo;
SGPropertyNode_ptr m_in_config;
double m_in_time_last = 0;
double m_in_frame_time_last = 0;
std::map<size_t, std::shared_ptr<FGReplayData>>
m_in_pos_to_frame;
std::ifstream m_indexing_in;
std::streampos m_indexing_pos;
bool m_replay_create_video = false;
double m_replay_fixed_dt = -1;
double m_replay_fixed_dt_prev = -1;
// Only used for gathering statistics that are then written into
// properties.
//
int m_num_frames_extra_properties = 0;
int m_num_frames_multiplayer = 0;
// For writing Continuous fgtape file.
SGPropertyNode_ptr m_out_config;
std::ofstream m_out;
int m_out_compression = 0;
int m_in_compression = 0;
};
/* Attempts to load Continuous recording header properties into
<properties>. If in is null we use internal std::fstream, otherwise we use *in.
Returns 0 on success, +1 if we may succeed after further download, or -1 if
recording is not a Continuous recording. */
int loadContinuousHeader(const std::string& path, std::istream* in, SGPropertyNode* properties);
/* Writes one frame of continuous record information. */
bool continuousWriteFrame(
Continuous& continuous,
FGReplayData* r,
std::ostream& out,
SGPropertyNode_ptr config,
FGTapeType tape_type
);
/* Opens continuous recording file and writes header.
If MetaData is unset, we initialise it by calling saveSetup(). Otherwise should
be already set up.
If Config is unset, we make it point to a new node populated by
m_pRecorder->getConfig(). Otherwise it should be already set up to point to
such information.
If path_override is not "", we use it as the path (instead of the path
determined by saveSetup(). */
SGPropertyNode_ptr continuousWriteHeader(
Continuous& continuous,
FGFlightRecorder* m_pRecorder,
std::ofstream& out,
const SGPath& path,
FGTapeType tape_type
);
/* Replays one frame from Continuous recording.
Returns true on success, otherwise we failed to read from Continuous recording.
*/
bool replayContinuous(FGReplayInternal& self, double time);
/* Stops any video recording that was started because of
continuous->m_replay_create_video. */
void continuous_replay_video_end(Continuous& continuous);

1840
src/Aircraft/controls.cxx Normal file

File diff suppressed because it is too large Load Diff

686
src/Aircraft/controls.hxx Normal file
View File

@@ -0,0 +1,686 @@
// controls.hxx -- defines a standard interface to all flight sim controls
//
// Written by Curtis Olson, started May 1997.
//
// Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _CONTROLS_HXX
#define _CONTROLS_HXX
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/tiedpropertylist.hxx>
// Define a structure containing the control parameters
class FGControls : public SGSubsystem
{
public:
enum {
ALL_ENGINES = -1,
MAX_ENGINES = 12
};
enum {
ALL_WHEELS = -1,
MAX_WHEELS = 3
};
enum {
ALL_TANKS = -1,
MAX_TANKS = 8
};
enum {
ALL_BOOSTPUMPS = -1,
MAX_BOOSTPUMPS = 2
};
enum {
ALL_HYD_SYSTEMS = -1,
MAX_HYD_SYSTEMS = 4
};
enum {
ALL_PACKS = -1,
MAX_PACKS = 4
};
enum {
ALL_LIGHTS = -1,
MAX_LIGHTS = 4
};
enum {
ALL_STATIONS = -1,
MAX_STATIONS = 12
};
enum {
ALL_AUTOPILOTS = -1,
MAX_AUTOPILOTS = 3
};
enum {
ALL_EJECTION_SEATS = -1,
MAX_EJECTION_SEATS = 10
};
enum {
SEAT_SAFED = -1,
SEAT_ARMED = 0,
SEAT_FAIL = 1
};
enum {
CMD_SEL_NORM = -1,
CMD_SEL_AFT = 0,
CMD_SEL_SOLO = 1
};
private:
// controls/flight/
double aileron;
double aileron_trim;
double elevator;
double elevator_trim;
double rudder;
double rudder_trim;
double flaps;
double slats;
bool BLC; // Boundary Layer Control
double spoilers;
double speedbrake;
double wing_sweep;
bool wing_fold;
bool drag_chute;
// controls/engines/
bool throttle_idle;
// controls/engines/engine[n]/
double throttle[MAX_ENGINES];
bool starter[MAX_ENGINES];
bool fuel_pump[MAX_ENGINES];
bool fire_switch[MAX_ENGINES];
bool fire_bottle_discharge[MAX_ENGINES];
bool cutoff[MAX_ENGINES];
double mixture[MAX_ENGINES];
double prop_advance[MAX_ENGINES];
int magnetos[MAX_ENGINES];
int feed_tank[MAX_ENGINES];
bool nitrous_injection[MAX_ENGINES]; // War Emergency Power
double cowl_flaps_norm[MAX_ENGINES];
bool feather[MAX_ENGINES];
int ignition[MAX_ENGINES];
bool augmentation[MAX_ENGINES];
bool reverser[MAX_ENGINES];
bool water_injection[MAX_ENGINES];
double condition[MAX_ENGINES]; // turboprop speed select
// controls/fuel/
bool dump_valve;
// controls/fuel/tank[n]/
bool fuel_selector[MAX_TANKS];
int to_engine[MAX_TANKS];
int to_tank[MAX_TANKS];
// controls/fuel/tank[n]/pump[p]/
bool boost_pump[MAX_TANKS * MAX_BOOSTPUMPS];
// controls/gear/
double brake_left;
double brake_right;
double copilot_brake_left;
double copilot_brake_right;
double brake_parking;
double steering;
bool nose_wheel_steering;
bool gear_down;
bool antiskid;
bool tailhook;
bool launchbar;
bool catapult_launch_cmd;
bool tailwheel_lock;
// controls/gear/wheel[n]/
bool alternate_extension[MAX_WHEELS];
// controls/anti-ice/
bool wing_heat;
bool pitot_heat;
int wiper;
bool window_heat;
// controls/anti-ice/engine[n]/
bool carb_heat[MAX_ENGINES];
bool inlet_heat[MAX_ENGINES];
// controls/hydraulic/system[n]/
bool engine_pump[MAX_HYD_SYSTEMS];
bool electric_pump[MAX_HYD_SYSTEMS];
// controls/electric/
bool battery_switch;
bool external_power;
bool APU_generator;
// controls/electric/engine[n]/
bool generator_breaker[MAX_ENGINES];
bool bus_tie[MAX_ENGINES];
// controls/pneumatic/
bool APU_bleed;
// controls/pneumatic/engine[n]/
bool engine_bleed[MAX_ENGINES];
// controls/pressurization/
int mode;
bool dump;
double outflow_valve;
// controls/pressurization/pack[n]/
bool pack_on[MAX_PACKS];
// controls/lighting/
bool landing_lights;
bool turn_off_lights;
bool taxi_light;
bool logo_lights;
bool nav_lights;
bool beacon;
bool strobe;
double panel_norm;
double instruments_norm;
double dome_norm;
// controls/armament/
bool master_arm;
int station_select;
bool release_ALL;
// controls/armament/station[n]/
int stick_size[MAX_STATIONS];
bool release_stick[MAX_STATIONS];
bool release_all[MAX_STATIONS];
bool jettison_all[MAX_STATIONS];
// controls/seat/
double vertical_adjust;
double fore_aft_adjust;
bool eject[MAX_EJECTION_SEATS];
int eseat_status[MAX_EJECTION_SEATS];
int cmd_selector_valve;
// controls/APU/
int off_start_run;
bool APU_fire_switch;
// controls/autoflight/autopilot[n]/
bool autopilot_engage[MAX_AUTOPILOTS];
// controls/autoflight/
bool autothrottle_arm;
bool autothrottle_engage;
double heading_select;
double altitude_select;
double bank_angle_select;
double vertical_speed_select;
double speed_select;
double mach_select;
int vertical_mode;
int lateral_mode;
SGPropertyNode_ptr auto_coordination;
SGPropertyNode_ptr auto_coordination_factor;
simgear::TiedPropertyList _tiedProperties;
// we need to node pointers as well, so we can manually
// fire valueChanged for these
SGPropertyNode_ptr _aileronNode;
SGPropertyNode_ptr _elevatorNode;
SGPropertyNode_ptr _aileronTrimNode;
SGPropertyNode_ptr _elevatorTrimNode;
SGPropertyNode_ptr _rudderNode;
simgear::PropertyList _engineThrottleNodes;
simgear::PropertyList _engineMixtureNodes;
simgear::PropertyList _engineStarterNodes;
simgear::PropertyList _engineCutoffNodes;
simgear::PropertyList _engineReverserNodes;
simgear::PropertyList _engineWaterInjectionNodes;
simgear::PropertyList _engineMagnetoNodes;
simgear::PropertyList _engineAugmentationNodes;
public:
FGControls();
~FGControls();
// Subsystem API.
void bind() override;
void init() override;
void reinit() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "controls"; }
// Reset function
void reset_all(void);
// Query functions
// controls/flight/
inline double get_aileron() const { return aileron; }
inline double get_aileron_trim() const { return aileron_trim; }
inline double get_elevator() const { return elevator; }
inline double get_elevator_trim() const { return elevator_trim; }
inline double get_rudder() const { return rudder; }
inline double get_rudder_trim() const { return rudder_trim; }
inline double get_flaps() const { return flaps; }
inline double get_slats() const { return slats; }
inline bool get_BLC() const { return BLC; }
inline double get_spoilers() const { return spoilers; }
inline double get_speedbrake() const { return speedbrake; }
inline double get_wing_sweep() const { return wing_sweep; }
inline bool get_wing_fold() const { return wing_fold; }
inline bool get_drag_chute() const { return drag_chute; }
// controls/engines/
inline bool get_throttle_idle() const { return throttle_idle; }
// controls/engines/engine[n]/
inline double get_throttle(int engine) const { return throttle[engine]; }
inline bool get_starter(int engine) const { return starter[engine]; }
inline bool get_fuel_pump(int engine) const { return fuel_pump[engine]; }
inline bool get_fire_switch(int engine) const { return fire_switch[engine]; }
inline bool get_fire_bottle_discharge(int engine) const {
return fire_bottle_discharge[engine];
}
inline bool get_cutoff(int engine) const { return cutoff[engine]; }
inline double get_mixture(int engine) const { return mixture[engine]; }
inline double get_prop_advance(int engine) const {
return prop_advance[engine];
}
inline int get_magnetos(int engine) const { return magnetos[engine]; }
inline int get_feed_tank(int engine) const { return feed_tank[engine]; }
inline bool get_nitrous_injection(int engine) const {
return nitrous_injection[engine];
}
inline double get_cowl_flaps_norm(int engine) const {
return cowl_flaps_norm[engine];
}
inline bool get_feather(int engine) const { return feather[engine]; }
inline int get_ignition(int engine) const { return ignition[engine]; }
inline bool get_augmentation(int engine) const { return augmentation[engine]; }
inline bool get_reverser(int engine) const { return reverser[engine]; }
inline bool get_water_injection(int engine) const {
return water_injection[engine];
}
inline double get_condition(int engine) const { return condition[engine]; }
// controls/fuel/
inline bool get_dump_valve() const { return dump_valve; }
// controls/fuel/tank[n]/
inline bool get_fuel_selector(int tank) const {
return fuel_selector[tank];
}
inline int get_to_engine(int tank) const { return to_engine[tank]; }
inline int get_to_tank(int tank) const { return to_tank[tank]; }
// controls/fuel/tank[n]/pump[p]/
inline bool get_boost_pump(int index) const {
return boost_pump[index];
}
// controls/gear/
inline double get_brake_left() const { return brake_left; }
inline double get_brake_right() const { return brake_right; }
inline double get_copilot_brake_left() const { return copilot_brake_left; }
inline double get_copilot_brake_right() const { return copilot_brake_right; }
inline double get_brake_parking() const { return brake_parking; }
inline double get_steering() const { return steering; }
inline bool get_nose_wheel_steering() const { return nose_wheel_steering; }
inline bool get_gear_down() const { return gear_down; }
inline bool get_antiskid() const { return antiskid; }
inline bool get_tailhook() const { return tailhook; }
inline bool get_launchbar() const { return launchbar; }
inline bool get_catapult_launch_cmd() const { return catapult_launch_cmd; }
inline bool get_tailwheel_lock() const { return tailwheel_lock; }
// controls/gear/wheel[n]/
inline bool get_alternate_extension(int wheel) const {
return alternate_extension[wheel];
}
// controls/anti-ice/
inline bool get_wing_heat() const { return wing_heat; }
inline bool get_pitot_heat() const { return pitot_heat; }
inline int get_wiper() const { return wiper; }
inline bool get_window_heat() const { return window_heat; }
// controls/anti-ice/engine[n]/
inline bool get_carb_heat(int engine) const { return carb_heat[engine]; }
inline bool get_inlet_heat(int engine) const { return inlet_heat[engine]; }
// controls/hydraulic/system[n]/
inline bool get_engine_pump(int system) const { return engine_pump[system]; }
inline bool get_electric_pump(int system) const { return electric_pump[system]; }
// controls/electric/
inline bool get_battery_switch() const { return battery_switch; }
inline bool get_external_power() const { return external_power; }
inline bool get_APU_generator() const { return APU_generator; }
// controls/electric/engine[n]/
inline bool get_generator_breaker(int engine) const {
return generator_breaker[engine];
}
inline bool get_bus_tie(int engine) const { return bus_tie[engine]; }
// controls/pneumatic/
inline bool get_APU_bleed() const { return APU_bleed; }
// controls/pneumatic/engine[n]/
inline bool get_engine_bleed(int engine) const { return engine_bleed[engine]; }
// controls/pressurization/
inline int get_mode() const { return mode; }
inline double get_outflow_valve() const { return outflow_valve; }
inline bool get_dump() const { return dump; }
// controls/pressurization/pack[n]/
inline bool get_pack_on(int pack) const { return pack_on[pack]; }
// controls/lighting/
inline bool get_landing_lights() const { return landing_lights; }
inline bool get_turn_off_lights() const { return turn_off_lights; }
inline bool get_taxi_light() const { return taxi_light; }
inline bool get_logo_lights() const { return logo_lights; }
inline bool get_nav_lights() const { return nav_lights; }
inline bool get_beacon() const { return beacon; }
inline bool get_strobe() const { return strobe; }
inline double get_panel_norm() const { return panel_norm; }
inline double get_instruments_norm() const { return instruments_norm; }
inline double get_dome_norm() const { return dome_norm; }
// controls/armament/
inline bool get_master_arm() const { return master_arm; }
inline int get_station_select() const { return station_select; }
inline bool get_release_ALL() const { return release_ALL; }
// controls/armament/station[n]/
inline int get_stick_size(int station) const { return stick_size[station]; }
inline bool get_release_stick(int station) const { return release_stick[station]; }
inline bool get_release_all(int station) const { return release_all[station]; }
inline bool get_jettison_all(int station) const { return jettison_all[station]; }
// controls/seat/
inline double get_vertical_adjust() const { return vertical_adjust; }
inline double get_fore_aft_adjust() const { return fore_aft_adjust; }
inline bool get_ejection_seat( int which_seat ) const {
return eject[which_seat];
}
inline int get_eseat_status( int which_seat ) const {
return eseat_status[which_seat];
}
inline int get_cmd_selector_valve() const { return cmd_selector_valve; }
// controls/APU/
inline int get_off_start_run() const { return off_start_run; }
inline bool get_APU_fire_switch() const { return APU_fire_switch; }
// controls/autoflight/
inline bool get_autothrottle_arm() const { return autothrottle_arm; }
inline bool get_autothrottle_engage() const { return autothrottle_engage; }
inline double get_heading_select() const { return heading_select; }
inline double get_altitude_select() const { return altitude_select; }
inline double get_bank_angle_select() const { return bank_angle_select; }
inline double get_vertical_speed_select() const {
return vertical_speed_select;
}
inline double get_speed_select() const { return speed_select; }
inline double get_mach_select() const { return mach_select; }
inline int get_vertical_mode() const { return vertical_mode; }
inline int get_lateral_mode() const { return lateral_mode; }
// controls/autoflight/autopilot[n]/
inline bool get_autopilot_engage(int ap) const {
return autopilot_engage[ap];
}
void set_elevator( double pos );
void set_aileron_trim( double pos );
void set_elevator_trim( double pos );
void set_aileron( double pos );
void set_rudder( double pos );
void set_throttle( int engine, double pos );
void set_cutoff( int engine, bool val );
void set_augmentation( int engine, bool val );
void set_reverser( int engine, bool val );
void set_water_injection( int engine, bool val );
void set_magnetos( int engine, int pos );
void set_starter( int engine, bool flag );
void set_mixture( int engine, double pos );
private:
// IMPORTANT: do *not* make these setters public, or you will violate
// the listener-safety of them. If you need to make an accessor public,
// make these as 'inner', and make a public wrapper which correctly calls
// valueChanged (see, set_throttle, set_elevator etc for examples)
// Update functions
// controls/flight/
void _inner_set_aileron( double pos );
void move_aileron( double amt );
void _inner_set_aileron_trim( double pos );
void move_aileron_trim( double amt );
void _inner_set_elevator( double pos );
void move_elevator( double amt );
void _inner_set_elevator_trim( double pos );
void move_elevator_trim( double amt );
void _inner_set_rudder( double pos );
void move_rudder( double amt );
void set_rudder_trim( double pos );
void move_rudder_trim( double amt );
void set_flaps( double pos );
void move_flaps( double amt );
void set_slats( double pos );
void move_slats( double amt );
void set_BLC( bool val );
void set_spoilers( double pos );
void move_spoilers( double amt );
void set_speedbrake( double pos );
void move_speedbrake( double amt );
void set_wing_sweep( double pos );
void move_wing_sweep( double amt );
void set_wing_fold( bool val );
void set_drag_chute( bool val );
// controls/engines/
void set_throttle_idle( bool val );
// controls/engines/engine[n]/
void _inner_set_throttle( int engine, double pos );
void move_throttle( int engine, double amt );
void _inner_set_starter( int engine, bool flag );
void set_fuel_pump( int engine, bool val );
void set_fire_switch( int engine, bool val );
void set_fire_bottle_discharge( int engine, bool val );
void _inner_set_cutoff( int engine, bool val );
void _inner_set_mixture( int engine, double pos );
void move_mixture( int engine, double amt );
void set_prop_advance( int engine, double pos );
void move_prop_advance( int engine, double amt );
void _inner_set_magnetos( int engine, int pos );
void move_magnetos( int engine, int amt );
void set_feed_tank( int engine, int tank );
void set_nitrous_injection( int engine, bool val );
void set_cowl_flaps_norm( int engine, double pos );
void move_cowl_flaps_norm( int engine, double amt );
void set_feather( int engine, bool val );
void set_ignition( int engine, int val );
void _inner_set_augmentation( int engine, bool val );
void _inner_set_reverser( int engine, bool val );
void _inner_set_water_injection( int engine, bool val );
void set_condition( int engine, double val );
// controls/fuel
void set_dump_valve( bool val );
// controls/fuel/tank[n]/
void set_fuel_selector( int tank, bool pos );
void set_to_engine( int tank, int engine );
void set_to_tank( int tank, int dest_tank );
// controls/fuel/tank[n]/pump[p]
void set_boost_pump( int index, bool val );
// controls/gear/
void set_brake_left( double pos );
void move_brake_left( double amt );
void set_brake_right( double pos );
void move_brake_right( double amt );
void set_copilot_brake_left( double pos );
void set_copilot_brake_right( double pos );
void set_brake_parking( double pos );
void set_steering( double pos );
void move_steering( double amt );
void set_nose_wheel_steering( bool nws );
void set_gear_down( bool gear );
void set_antiskid( bool val );
void set_tailhook( bool val );
void set_launchbar( bool val );
void set_catapult_launch_cmd( bool val );
void set_tailwheel_lock( bool val );
// controls/gear/wheel[n]/
void set_alternate_extension( int wheel, bool val );
// controls/anti-ice/
void set_wing_heat( bool val );
void set_pitot_heat( bool val );
void set_wiper( int speed );
void set_window_heat( bool val );
// controls/anti-ice/engine[n]/
void set_carb_heat( int engine, bool val );
void set_inlet_heat( int engine, bool val );
// controls/hydraulic/system[n]/
void set_engine_pump( int system, bool val );
void set_electric_pump( int system, bool val );
// controls/electric/
void set_battery_switch( bool val );
void set_external_power( bool val );
void set_APU_generator( bool val );
// controls/electric/engine[n]/
void set_generator_breaker( int engine, bool val );
void set_bus_tie( int engine, bool val );
// controls/pneumatic/
void set_APU_bleed( bool val );
// controls/pneumatic/engine[n]/
void set_engine_bleed( int engine, bool val );
// controls/pressurization/
void set_mode( int mode );
void set_outflow_valve( double pos );
void move_outflow_valve( double amt );
void set_dump( bool val );
// controls/pressurization/pack[n]/
void set_pack_on( int pack, bool val );
// controls/lighting/
void set_landing_lights( bool val );
void set_turn_off_lights( bool val );
void set_taxi_light( bool val );
void set_logo_lights( bool val );
void set_nav_lights( bool val );
void set_beacon( bool val );
void set_strobe( bool val );
void set_panel_norm( double intensity );
void move_panel_norm( double amt );
void set_instruments_norm( double intensity );
void move_instruments_norm( double amt );
void set_dome_norm( double intensity );
void move_dome_norm( double amt );
// controls/armament/
void set_master_arm( bool val );
void set_station_select( int station );
void set_release_ALL( bool val );
// controls/armament/station[n]/
void set_stick_size( int station, int size );
void set_release_stick( int station, bool val );
void set_release_all( int station, bool val );
void set_jettison_all( int station, bool val );
// controls/seat/
void set_vertical_adjust( double pos );
void move_vertical_adjust( double amt );
void set_fore_aft_adjust( double pos );
void move_fore_aft_adjust( double amt );
void set_ejection_seat( int which_seat, bool val );
void set_eseat_status( int which_seat, int val );
void set_cmd_selector_valve( int val );
// controls/APU/
void set_off_start_run( int pos );
void set_APU_fire_switch( bool val );
// controls/autoflight/
void set_autothrottle_arm( bool val );
void set_autothrottle_engage( bool val );
void set_heading_select( double heading );
void move_heading_select( double amt );
void set_altitude_select( double altitude );
void move_altitude_select( double amt );
void set_bank_angle_select( double angle );
void move_bank_angle_select( double amt );
void set_vertical_speed_select( double vs );
void move_vertical_speed_select( double amt );
void set_speed_select( double speed );
void move_speed_select( double amt );
void set_mach_select( double mach );
void move_mach_select( double amt );
void set_vertical_mode( int mode );
void set_lateral_mode( int mode );
// controls/autoflight/autopilot[n]/
void set_autopilot_engage( int ap, bool val );
void do_autocoordination();
void fireEngineValueChanged(int index, simgear::PropertyList& props);
};
#endif // _CONTROLS_HXX

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,118 @@
// flightrecorder.hxx
//
// Written by Thorsten Brehm, started August 2011.
//
// Copyright (C) 2011 Thorsten Brehm - brehmt (at) gmail com
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef FLIGHTRECORDER_HXX_
#define FLIGHTRECORDER_HXX_
#include <simgear/props/props.hxx>
#include <MultiPlayer/multiplaymgr.hxx>
#include "replay-internal.hxx"
namespace FlightRecorder
{
typedef enum
{
discrete = 0, // no interpolation
linear = 1, // linear interpolation
angular_rad = 2, // angular interpolation, value in radians
angular_deg = 3 // angular interpolation, value in degrees
} TInterpolation;
typedef struct
{
SGPropertyNode_ptr Signal;
TInterpolation Interpolation;
} TCapture;
typedef std::vector<TCapture> TSignalList;
}
class FGFlightRecorder
{
public:
FGFlightRecorder(const char* pConfigName);
virtual ~FGFlightRecorder();
void reinit (void);
void reinit (SGPropertyNode_ptr ConfigNode);
FGReplayData* capture (double SimTime, FGReplayData* pRecycledBuffer);
// Updates main_window_* out-params if we find window move/resize events
// and replay of such events is enabled.
void replay (double SimTime, const FGReplayData* pNextBuffer,
const FGReplayData* pLastBuffer,
int* main_window_xpos,
int* main_window_ypos,
int* main_window_xsize,
int* main_window_ysize
);
int getRecordSize (void) { return m_TotalRecordSize;}
void getConfig (SGPropertyNode* root);
void resetExtraProperties();
private:
SGPropertyNode_ptr getDefault(void);
void initSignalList(const char* pSignalType, FlightRecorder::TSignalList& SignalList,
SGPropertyNode_ptr BaseNode);
void processSignalList(const char* pSignalType, FlightRecorder::TSignalList& SignalList,
SGPropertyNode_ptr SignalListNode,
std::string PropPrefix="", int Count = 1);
bool haveProperty(FlightRecorder::TSignalList& Capture,SGPropertyNode* pProperty);
bool haveProperty(SGPropertyNode* pProperty);
int getConfig(SGPropertyNode* root, const char* typeStr, const FlightRecorder::TSignalList& SignalList);
SGPropertyNode_ptr m_RecorderNode;
SGPropertyNode_ptr m_ConfigNode;
SGPropertyNode_ptr m_ReplayMultiplayer;
SGPropertyNode_ptr m_ReplayExtraProperties;
SGPropertyNode_ptr m_ReplayMainView;
SGPropertyNode_ptr m_ReplayMainWindowPosition;
SGPropertyNode_ptr m_ReplayMainWindowSize;
SGPropertyNode_ptr m_RecordContinuous;
SGPropertyNode_ptr m_RecordExtraProperties;
SGPropertyNode_ptr m_LogRawSpeed;
// This contains copy of all properties that we are recording, so that we
// can send only differences.
//
SGPropertyNode_ptr m_RecordExtraPropertiesReference;
FlightRecorder::TSignalList m_CaptureDouble;
FlightRecorder::TSignalList m_CaptureFloat;
FlightRecorder::TSignalList m_CaptureInteger;
FlightRecorder::TSignalList m_CaptureInt16;
FlightRecorder::TSignalList m_CaptureInt8;
FlightRecorder::TSignalList m_CaptureBool;
unsigned m_TotalRecordSize;
std::string m_ConfigName;
bool m_usingDefaultConfig;
FGMultiplayMgr* m_MultiplayMgr;
};
#endif /* FLIGHTRECORDER_HXX_ */

View File

@@ -0,0 +1,100 @@
// initialstate.cxx -- setup initial state of the aircraft
//
// Written by James Turner
//
// Copyright (C) 2016 James Turner <zakalawe@mac.com>
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include "config.h"
#include "initialstate.hxx"
#include <algorithm>
#include <simgear/debug/logstream.hxx>
#include <simgear/props/props_io.hxx>
#include <Main/fg_props.hxx>
#include <GUI/MessageBox.hxx>
using namespace simgear;
namespace {
class NodeValue
{
public:
NodeValue(const std::string& s) : v(s) {}
bool operator()(const SGPropertyNode_ptr n) const
{
return (v == n->getStringValue());
}
private:
std::string v;
};
SGPropertyNode_ptr nodeForState(const std::string& nm)
{
SGPropertyNode_ptr sim = fgGetNode("/sim");
const PropertyList& states = sim->getChildren("state");
PropertyList::const_iterator it;
for (it = states.begin(); it != states.end(); ++it) {
const PropertyList& names = (*it)->getChildren("name");
if (std::find_if(names.begin(), names.end(), NodeValue(nm)) != names.end()) {
return *it;
}
}
return SGPropertyNode_ptr();
}
} // of anonymous namespace
namespace flightgear
{
bool isInitialStateName(const std::string& name)
{
SGPropertyNode_ptr n = nodeForState(name);
return n.valid();
}
void applyInitialState()
{
std::string nm = fgGetString("/sim/aircraft-state");
if (nm.empty()) {
return;
}
SGPropertyNode_ptr stateNode = nodeForState(nm);
if (!stateNode) {
SG_LOG(SG_AIRCRAFT, SG_WARN, "missing state node for:" << nm);
std::string aircraft = fgGetString("/sim/aircraft");
modalMessageBox("Unknown aircraft state",
"The selected aircraft (" + aircraft + ") does not have a state '" + nm + "'");
return;
}
SG_LOG(SG_AIRCRAFT, SG_INFO, "Applying aircraft state:" << nm);
// copy all overlay properties to the tree
copyProperties(stateNode->getChild("overlay"), globals->get_props());
}
} // of namespace flightgear

View File

@@ -0,0 +1,41 @@
// initialstate.hxx -- setup initial state of the aircraft
//
// Written by James Turner
//
// Copyright (C) 2016 James Turner <zakalawe@mac.com>
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef FG_AIRCRAFT_INITIAL_STATE_HXX
#define FG_AIRCRAFT_INITIAL_STATE_HXX
#include <string>
namespace flightgear
{
/**
* @brief is the supplied name a defined initial-state, or alias of one
*/
bool isInitialStateName(const std::string& name);
void applyInitialState();
} // of namespace flightgear
#endif // FG_AIRCRAFT_INITIAL_STATE_HXX

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,251 @@
// replay.hxx - a system to record and replay FlightGear flights
//
// Written by Curtis Olson, started July 2003.
//
// Copyright (C) 2003 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#pragma once
#include <mutex>
#include <simgear/compiler.h>
#include <simgear/math/sg_types.hxx>
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/io/iostreams/gzcontainerfile.hxx>
#include <simgear/io/HTTPFileRequest.hxx>
#include <MultiPlayer/multiplaymgr.hxx>
#include <deque>
#include <vector>
class FGFlightRecorder;
/** Magic string to verify valid FG flight recorder tapes. */
extern const char* const FlightRecorderFileMagic;
/* Data for a single frame. */
struct FGReplayData
{
bool load_signals;
bool load_multiplayer;
bool load_extra_properties;
double sim_time;
// Our aircraft state.
std::vector<char> raw_data;
// Incoming multiplayer messages, if any.
std::vector<std::shared_ptr<std::vector<char>>> multiplayer_messages;
// Serialised information about extra property changes, only used when
// making a Continuous recording - we write this raw data into frame data
// in the Continuous recording file.
std::vector<char> extra_properties;
// Information about extra property changes, only used when replaying. We
// populate these when loading a frame.
std::map<std::string, std::string> replay_extra_property_changes;
std::vector<std::string> replay_extra_property_removals;
// Updates static statistics defined below.
void UpdateStats();
// Resets out static property nodes; to be called by fgStartNewReset().
static void resetStatisticsProperties();
FGReplayData();
~FGReplayData();
size_t m_bytes_raw_data = 0;
size_t m_bytes_multiplayer_messages = 0;
size_t m_num_multiplayer_messages = 0;
// Statistics about replay data, also properties /sim/replay/datastats_*.
static size_t s_num;
static size_t s_bytes_raw_data;
static size_t s_bytes_multiplayer_messages;
static size_t s_num_multiplayer_messages;
static SGPropertyNode_ptr s_prop_num;
static SGPropertyNode_ptr s_prop_bytes_raw_data;
static SGPropertyNode_ptr s_prop_bytes_multiplayer_messages;
static SGPropertyNode_ptr s_prop_num_multiplayer_messages;
};
typedef struct
{
double sim_time;
std::string message;
std::string speaker;
} FGReplayMessages;
enum FGTapeType
{
FGTapeType_NORMAL,
FGTapeType_CONTINUOUS,
FGTapeType_RECOVERY,
};
/* Index entry when replaying Continuous recording. */
struct FGFrameInfo
{
size_t offset;
bool has_signals = false;
bool has_multiplayer = false;
bool has_extra_properties = false;
};
std::ostream& operator << (std::ostream& out, const FGFrameInfo& frame_info);
struct FGReplayInternal
{
FGReplayInternal();
virtual ~FGReplayInternal();
/* Methods that implement the FGReplay API. */
void bind();
void init();
void reinit() ;
void unbind();
void update(double dt);
static const char* staticSubsystemClassId() { return "replay"; }
bool start(bool NewTape=false);
bool saveTape(const SGPropertyNode* ConfigData);
bool loadTape(const SGPropertyNode* ConfigData);
static int loadContinuousHeader(
const std::string& path,
std::istream* in,
SGPropertyNode* properties
);
bool loadTape(
const SGPath& filename,
bool preview,
bool create_video,
double fixed_dt,
SGPropertyNode& meta_meta,
simgear::HTTP::FileRequestRef file_request=nullptr
);
static std::string makeTapePath(const std::string& tape_name);
/* Callback for SGPropertyChangeListener. */
//void valueChanged(SGPropertyNode * node) override;
/* Internal state. */
double m_sim_time;
double m_last_mt_time;
double m_last_lt_time;
double m_last_msg_time;
int m_last_replay_state;
bool m_was_finished_already;
std::deque<FGReplayData*> m_short_term;
std::deque<FGReplayData*> m_medium_term;
std::deque<FGReplayData*> m_long_term;
std::deque<FGReplayData*> m_recycler;
std::vector<FGReplayMessages> m_replay_messages;
std::vector<FGReplayMessages>::iterator m_current_msg;
SGPropertyNode_ptr m_disable_replay;
SGPropertyNode_ptr m_replay_master;
SGPropertyNode_ptr m_replay_master_eof;
SGPropertyNode_ptr m_replay_time;
SGPropertyNode_ptr m_replay_time_str;
SGPropertyNode_ptr m_replay_looped;
SGPropertyNode_ptr m_replay_duration_act;
SGPropertyNode_ptr m_speed_up;
SGPropertyNode_ptr m_replay_multiplayer;
SGPropertyNode_ptr m_recovery_period;
SGPropertyNode_ptr m_replay_error;
SGPropertyNode_ptr m_record_normal_begin; // Time of first in-memory recorded frame.
SGPropertyNode_ptr m_record_normal_end;
SGPropertyNode_ptr m_log_frame_times;
SGPropertyNode_ptr m_sim_startup_xpos;
SGPropertyNode_ptr m_sim_startup_ypos;
SGPropertyNode_ptr m_sim_startup_xsize;
SGPropertyNode_ptr m_sim_startup_ysize;
SGPropertyNode_ptr m_simple_time_enabled;
double m_replay_time_prev; // Used to detect jumps while replaying.
/* short term sample rate is as every frame. */
double m_high_res_time; // default: 60 secs of high res data
double m_medium_res_time; // default: 10 mins of 1 fps data
double m_low_res_time; // default: 1 hr of 10 spf data
double m_medium_sample_rate; // medium term sample rate (sec)
double m_long_sample_rate; // long term sample rate (sec)
std::shared_ptr<FGFlightRecorder> m_flight_recorder;
/* Things for Continuous recording/replay support. */
std::unique_ptr<struct Continuous> m_continuous;
FGMultiplayMgr* m_MultiplayMgr;
};
/* Sets things up for writing to a normal or continuous fgtape file.
extra:
NULL or extra information when we are called from fgdata gui, e.g. with
the flight description entered by the user in the save dialogue.
path:
Path of fgtape file. We return nullptr if this file already exists.
duration:
Duration of recording. Zero if we are starting a continuous recording.
tape_type:
.
continuous_compression:
Whether to use compression if tape_type is FGTapeType_CONTINUOUS.
Returns:
A new SGPropertyNode suitable as prefix of recording. If
extra:user-data exists, it will appear as meta/user-data.
*/
SGPropertyNode_ptr saveSetup(
const SGPropertyNode* extra,
const SGPath& path,
double duration,
FGTapeType tape_type,
int continuous_compression=0
);
/* Returns a path using different formats depending on <type>:
FGTapeType_NORMAL: <tape-directory>/<aircraft-type>-<date>-<time>.fgtape
FGTapeType_CONTINUOUS: <tape-directory>/<aircraft-type>-<date>-<time>-continuous.fgtape
FGTapeType_RECOVERY: <tape-directory>/<aircraft-type>-recovery.fgtape
*/
SGPath makeSavePath(FGTapeType type, SGPath* path_timeless=nullptr);

126
src/Aircraft/replay.cxx Normal file
View File

@@ -0,0 +1,126 @@
// replay.cxx - a system to record and replay FlightGear flights
//
// Written by Curtis Olson, started July 2003.
// Updated by Thorsten Brehm, September 2011 and November 2012.
//
// Copyright (C) 2003 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include "replay.hxx"
#include "replay-internal.hxx"
FGReplay::FGReplay()
:
m_internal(new FGReplayInternal)
{
}
FGReplay::~FGReplay()
{
}
void
FGReplay::init()
{
m_internal->init();
}
void
FGReplay::reinit()
{
m_internal->reinit();
}
void
FGReplay::bind()
{
m_internal->bind();
}
void
FGReplay::unbind()
{
m_internal->unbind();
}
/** Start replay session
*/
bool
FGReplay::start(bool NewTape)
{
return m_internal->start(NewTape);
}
void
FGReplay::update( double dt )
{
timingInfo.clear();
stamp("begin");
m_internal->update(dt);
}
/** Write flight recorder tape to disk. User/script command. */
bool
FGReplay::saveTape(const SGPropertyNode* Extra)
{
return m_internal->saveTape(Extra);
}
bool
FGReplay::loadTape(
const SGPath& filename,
bool preview,
bool create_video,
double fixed_dt,
SGPropertyNode& meta_meta,
simgear::HTTP::FileRequestRef file_request
)
{
return m_internal->loadTape(filename, preview, create_video, fixed_dt, meta_meta, file_request);
}
std::string FGReplay::makeTapePath(const std::string& tape_name)
{
return FGReplayInternal::makeTapePath(tape_name);
}
int FGReplay::loadContinuousHeader(const std::string& path, std::istream* in, SGPropertyNode* properties)
{
return FGReplayInternal::loadContinuousHeader(path, in, properties);
}
/** Load a flight recorder tape from disk. User/script command. */
bool
FGReplay::loadTape(const SGPropertyNode* ConfigData)
{
return m_internal->loadTape(ConfigData);
}
void FGReplay::resetStatisticsProperties()
{
FGReplayData::resetStatisticsProperties();
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGReplay> registrantFGReplay;

106
src/Aircraft/replay.hxx Normal file
View File

@@ -0,0 +1,106 @@
// replay.hxx - a system to record and replay FlightGear flights
//
// Written by Curtis Olson, started July 2003.
//
// Copyright (C) 2003 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#pragma once
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/io/iostreams/gzcontainerfile.hxx>
#include <simgear/io/HTTPFileRequest.hxx>
#include <MultiPlayer/multiplaymgr.hxx>
/* A recording/replay module for FlightGear flights. */
struct FGReplay : SGSubsystem
{
FGReplay ();
virtual ~FGReplay();
/* Subsystem API. */
void bind() override;
void init() override;
void reinit() override;
void unbind() override;
void update(double dt) override;
/* Subsystem identification. */
static const char* staticSubsystemClassId() { return "replay"; }
/* For built-in 'replay' command - replay using in-memory Normal recording.
new_tape: If true, we start at beginning of tape, otherwise we start at
loop interval.
*/
bool start(bool new_tape=false);
/* For save and load tape operations from Flightgear GUI. */
bool saveTape(const SGPropertyNode* ConfigData);
bool loadTape(const SGPropertyNode* ConfigData);
/* Attempts to load Continuous recording header properties into
<properties>. If in is null we use internal std::fstream, otherwise we use
*in.
Returns 0 on success, +1 if we may succeed after further download, or -1 if
recording is not a Continuous recording.
For command line --load-tape=...
*/
static int loadContinuousHeader(const std::string& path, std::istream* in, SGPropertyNode* properties);
/* Start replaying a flight recorder tape from disk.
filename
Path of recording.
preview
If true we read the header (and return it in <meta_meta> but do not
start replaying.
create_video
If true we automatically encode a video while replaying.
fixed_dt
If non-zero we set /sim/time/fixed-dt while replaying.
meta_meta
Filled in with contents of recording header's "meta" tree.
filerequest
If not null we use this to get called back as download of file
progresses, so that we can index the recording. Only useful for
Continuous recordings.
*/
bool loadTape(
const SGPath& filename,
bool preview,
bool create_video,
double fixed_dt,
SGPropertyNode& meta_meta,
simgear::HTTP::FileRequestRef file_request=nullptr
);
/* Prepends /sim/replay/tape-directory and/or appends .fgtape etc.
For command line --load-tape=... */
static std::string makeTapePath(const std::string& tape_name);
/* Resets out static property nodes; to be called by fgStartNewReset(). */
static void resetStatisticsProperties();
std::unique_ptr<struct FGReplayInternal> m_internal;
};