first commit
This commit is contained in:
407
src/Aircraft/AircraftPerformance.cxx
Normal file
407
src/Aircraft/AircraftPerformance.cxx
Normal 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;
|
||||
}
|
||||
|
||||
117
src/Aircraft/AircraftPerformance.hxx
Normal file
117
src/Aircraft/AircraftPerformance.hxx
Normal 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
|
||||
26
src/Aircraft/CMakeLists.txt
Normal file
26
src/Aircraft/CMakeLists.txt
Normal 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}")
|
||||
229
src/Aircraft/FlightHistory.cxx
Normal file
229
src/Aircraft/FlightHistory.cxx
Normal 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;
|
||||
132
src/Aircraft/FlightHistory.hxx
Normal file
132
src/Aircraft/FlightHistory.hxx
Normal 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
987
src/Aircraft/continuous.cxx
Normal 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*/);
|
||||
}
|
||||
}
|
||||
}
|
||||
97
src/Aircraft/continuous.hxx
Normal file
97
src/Aircraft/continuous.hxx
Normal 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
1840
src/Aircraft/controls.cxx
Normal file
File diff suppressed because it is too large
Load Diff
686
src/Aircraft/controls.hxx
Normal file
686
src/Aircraft/controls.hxx
Normal 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
|
||||
|
||||
|
||||
1119
src/Aircraft/flightrecorder.cxx
Normal file
1119
src/Aircraft/flightrecorder.cxx
Normal file
File diff suppressed because it is too large
Load Diff
118
src/Aircraft/flightrecorder.hxx
Normal file
118
src/Aircraft/flightrecorder.hxx
Normal 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_ */
|
||||
100
src/Aircraft/initialstate.cxx
Normal file
100
src/Aircraft/initialstate.cxx
Normal 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
|
||||
41
src/Aircraft/initialstate.hxx
Normal file
41
src/Aircraft/initialstate.hxx
Normal 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
|
||||
2168
src/Aircraft/replay-internal.cxx
Normal file
2168
src/Aircraft/replay-internal.cxx
Normal file
File diff suppressed because it is too large
Load Diff
251
src/Aircraft/replay-internal.hxx
Normal file
251
src/Aircraft/replay-internal.hxx
Normal 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
126
src/Aircraft/replay.cxx
Normal 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
106
src/Aircraft/replay.hxx
Normal 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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user