1
0
Fork 0
flightgear/test_suite/FGTestApi/testGlobals.cxx
James Turner c1e5cc3074 Improve creating VIA segments
Improve argument parsing for createViaTo, createViaFromTo to
handle Airway ghosts as well as strings. As part of this, allow
specification of the airway level explicitly when looking
up an airway.

SF-ID: https://sourceforge.net/p/flightgear/codetickets/2686/
2022-01-05 15:44:50 +00:00

484 lines
14 KiB
C++

#include "config.h"
#include "test_suite/dataStore.hxx"
#include <ctime>
#include "TestDataLogger.hxx"
#include "testGlobals.hxx"
#include <simgear/io/iostreams/sgstream.hxx>
#if defined(HAVE_QT)
#include <GUI/QtLauncher.hxx>
#endif
#include <Main/globals.hxx>
#include <Main/options.hxx>
#include <Main/util.hxx>
#include <Main/FGInterpolator.hxx>
#include <Main/locale.hxx>
#include <Time/TimeManager.hxx>
#include <simgear/structure/event_mgr.hxx>
#include <simgear/timing/timestamp.hxx>
#include <simgear/timing/sg_time.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/props/props_io.hxx>
#include <Airports/airport.hxx>
#include <Navaids/FlightPlan.hxx>
#include <Navaids/waypoint.hxx>
#include <Navaids/routePath.hxx>
#include <Scripting/NasalSys.hxx>
using namespace flightgear;
namespace FGTestApi {
bool global_loggingToKML = false;
sg_ofstream global_kmlStream;
bool global_lineStringOpen = false;
namespace setUp {
void initTestGlobals(const std::string& testName)
{
assert(globals == nullptr);
globals = new FGGlobals;
DataStore &data = DataStore::get();
if (!data.getFGRoot().exists()) {
data.findFGRoot("");
}
globals->set_fg_root(data.getFGRoot());
// current dir
SGPath homePath = SGPath::fromUtf8(FGBUILDDIR) / "test_home";
if (!homePath.exists()) {
(homePath / "dummyFile").create_dir(0755);
}
globals->set_fg_home(homePath);
auto props = globals->get_props();
props->setStringValue("sim/fg-home", homePath.utf8Str());
// Activate headless mode.
globals->set_headless(true);
fgSetDefaults();
auto t = globals->add_new_subsystem<TimeManager>(SGSubsystemMgr::INIT);
t->bind();
t->init(); // establish mag-var data
/**
* Both the event manager and subsystem manager are initialised by the
* FGGlobals ctor, but only the subsystem manager is destroyed by the dtor.
* Here the event manager is added to the subsystem manager so it can be
* destroyed via the subsystem manager.
*/
globals->add_subsystem("events", globals->get_event_mgr(), SGSubsystemMgr::DISPLAY);
// necessary to avoid asserts: mark FGLocale as initialized
globals->get_locale()->selectLanguage({});
}
bool logPositionToKML(const std::string& testName)
{
// clear any previous state
if (global_loggingToKML) {
global_kmlStream.close();
global_lineStringOpen = false;
}
SGPath p = SGPath::desktop() / (testName + ".kml");
global_kmlStream.open(p);
if (!global_kmlStream.is_open()) {
SG_LOG(SG_GENERAL, SG_WARN, "unable to open:" << p);
return false;
}
// pre-amble
global_kmlStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n"
"<Document>\n";
// need more precision for doubles when specifying lat/lon, see
// https://xkcd.com/2170/ :)
global_kmlStream.precision(12);
global_loggingToKML = true;
return true;
}
bool logLinestringsToKML(const std::string& testName)
{
// clear any previous state
if (global_loggingToKML) {
global_kmlStream.close();
global_lineStringOpen = false;
}
SGPath p = SGPath::desktop() / (testName + ".kml");
global_kmlStream.open(p);
if (!global_kmlStream.is_open()) {
SG_LOG(SG_GENERAL, SG_WARN, "unable to open:" << p);
return false;
}
// pre-amble
global_kmlStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n"
"<Document>\n";
// need more precision for doubles when specifying lat/lon, see
// https://xkcd.com/2170/ :)
global_kmlStream.precision(12);
global_loggingToKML = false;
return true;
}
void initStandardNasal(bool withCanvas)
{
fgInitAllowedPaths();
auto nasalNode = globals->get_props()->getNode("nasal", true);
// load loadpriority.xml, for default modules load order
auto nasalLoadPriority = globals->get_props()->getNode("/sim/nasal-load-priority",true);
readProperties(globals->get_fg_root() / "Nasal/loadpriority.xml", nasalLoadPriority);
// set various props to reduce Nasal errors
auto props = globals->get_props();
props->setStringValue("sim/flight-model", "null");
props->setStringValue("sim/aircraft", "test-suite-aircraft");
props->setDoubleValue("sim/current-view/config/default-field-of-view-deg", 90.0);
// ensure /sim/view/config exists
props->setBoolValue("sim/view/config/foo", false);
props->setBoolValue("sim/rendering/precipitation-gui-enable", false);
props->setBoolValue("sim/rendering/precipitation-aircraft-enable", false);
// disable various larger modules
nasalNode->setBoolValue("canvas/enabled", withCanvas);
nasalNode->setBoolValue("jetways/enabled", false);
nasalNode->setBoolValue("jetways_edit/enabled", false);
nasalNode->setBoolValue("local_weather/enabled", false);
// Nasal needs the interpolator running
globals->add_subsystem("prop-interpolator", new FGInterpolator, SGSubsystemMgr::INIT);
// will be inited, since we already did that
globals->add_new_subsystem<FGNasalSys>(SGSubsystemMgr::INIT);
}
void populateFPWithoutNasal(flightgear::FlightPlanRef f,
const std::string& depICAO, const std::string& depRunway,
const std::string& destICAO, const std::string& destRunway,
const std::string& waypoints)
{
FGAirportRef depApt = FGAirport::getByIdent(depICAO);
f->setDeparture(depApt->getRunwayByIdent(depRunway));
FGAirportRef destApt = FGAirport::getByIdent(destICAO);
f->setDestination(destApt->getRunwayByIdent(destRunway));
// since we don't have the Nasal route-manager delegate, insert the
// runway waypoints manually
auto depRwy = new RunwayWaypt(f->departureRunway(), f);
depRwy->setFlag(WPT_DEPARTURE);
f->insertWayptAtIndex(depRwy, -1);
for (auto ws : simgear::strutils::split(waypoints)) {
WayptRef wpt = f->waypointFromString(ws);
if (!wpt) {
SG_LOG(SG_NAVAID, SG_ALERT, "No waypoint created for:" << ws);
continue;
}
f->insertWayptAtIndex(wpt, -1);
}
auto destRwy = f->destinationRunway();
f->insertWayptAtIndex(new BasicWaypt(destRwy->pointOnCenterline(-8 * SG_NM_TO_METER),
destRwy->ident() + "-8", f), -1);
f->insertWayptAtIndex(new RunwayWaypt(destRwy, f), -1);
}
void populateFPWithNasal(flightgear::FlightPlanRef f,
const std::string& depICAO, const std::string& depRunway,
const std::string& destICAO, const std::string& destRunway,
const std::string& waypoints)
{
FGAirportRef depApt = FGAirport::getByIdent(depICAO);
f->setDeparture(depApt->getRunwayByIdent(depRunway));
FGAirportRef destApt = FGAirport::getByIdent(destICAO);
f->setDestination(destApt->getRunwayByIdent(destRunway));
// insert after the last departure waypoint
int insertIndex = 1;
for (auto ws : simgear::strutils::split(waypoints)) {
WayptRef wpt = f->waypointFromString(ws);
f->insertWayptAtIndex(wpt, insertIndex++);
}
}
} // End of namespace setUp.
void beginLineString(const std::string& ident)
{
global_lineStringOpen = true;
global_kmlStream << "<Placemark>\n";
if (!ident.empty()) {
global_kmlStream << "<name>" << ident << "</name>\n";
}
global_kmlStream << "<LineString>\n";
global_kmlStream << "<tessellate>1</tessellate>\n";
global_kmlStream << "<coordinates>\n";
}
void logCoordinate(const SGGeod& pos)
{
if (!global_lineStringOpen) {
beginLineString({});
}
global_kmlStream << pos.getLongitudeDeg() << "," << pos.getLatitudeDeg() << " " << endl;
}
void rawLogCoordinate(const SGGeod& pos)
{
global_kmlStream << pos.getLongitudeDeg() << "," << pos.getLatitudeDeg() << " " << endl;
}
void endCurrentLineString()
{
global_lineStringOpen = false;
global_kmlStream <<
"</coordinates>\n"
"</LineString>\n"
"</Placemark>\n" << endl;
}
void setPosition(const SGGeod& g)
{
if (global_loggingToKML) {
if (global_lineStringOpen) {
endCurrentLineString();
}
logCoordinate(g);
}
globals->get_props()->setDoubleValue("position/latitude-deg", g.getLatitudeDeg());
globals->get_props()->setDoubleValue("position/longitude-deg", g.getLongitudeDeg());
globals->get_props()->setDoubleValue("position/altitude-ft", g.getElevationFt());
}
const SGGeod getPosition()
{
return SGGeod::fromDegFt(
globals->get_props()->getDoubleValue("position/latitude-deg"),
globals->get_props()->getDoubleValue("position/longitude-deg"),
globals->get_props()->getDoubleValue("position/altitude-ft"));
}
void setPositionAndStabilise(const SGGeod& g)
{
setPosition(g);
for (int i=0; i<60; ++i) {
globals->get_subsystem_mgr()->update(0.015);
}
}
void runForTime(double t)
{
const int tickHz = 30;
const double tickDuration = 1.0 / tickHz;
int ticks = static_cast<int>(t * tickHz);
assert(ticks > 0);
const int logInterval = 0.5 * tickHz;
int nextLog = 0;
long startTime = globals->get_time_params()->get_cur_time();
for (int t = 0; t < ticks; ++t) {
globals->inc_sim_time_sec(tickDuration);
globals->get_time_params()->update(globals->get_view_position(), startTime, t * tickDuration);
globals->get_subsystem_mgr()->update(tickDuration);
if (nextLog == 0) {
if (global_loggingToKML) {
logCoordinate(globals->get_aircraft_position());
}
if (DataLogger::isActive()) {
DataLogger::instance()->writeRecord();
}
nextLog = logInterval;
} else {
nextLog--;
}
}
}
bool runForTimeWithCheck(double t, RunCheck check)
{
const int tickHz = 30;
const double tickDuration = 1.0 / tickHz;
int ticks = static_cast<int>(t * tickHz);
assert(ticks > 0);
const int logInterval = 0.5 * tickHz;
int nextLog = 0;
for (int t = 0; t < ticks; ++t) {
globals->inc_sim_time_sec(tickDuration);
globals->get_subsystem_mgr()->update(tickDuration);
if (nextLog == 0) {
if (global_loggingToKML) {
logCoordinate(globals->get_aircraft_position());
}
if (DataLogger::isActive()) {
DataLogger::instance()->writeRecord();
}
nextLog = logInterval;
} else {
nextLog--;
}
bool done = check();
if (done) {
if (global_loggingToKML) {
endCurrentLineString();
}
return true;
}
}
return false;
}
void adjustSimulationWorldTime(time_t desiredUnixTime)
{
int timeOffset = desiredUnixTime - time(nullptr);
globals->get_props()->setIntValue("/sim/time/cur-time-override", 0);
globals->get_props()->setIntValue("/sim/time/warp", timeOffset);
globals->get_subsystem<TimeManager>()->update(0.0);
}
void writeFlightPlanToKML(flightgear::FlightPlanRef fp)
{
if (!global_loggingToKML)
return;
RoutePath rpath(fp);
for (int i=0; i<fp->numLegs(); ++i) {
SGGeodVec legPath = rpath.pathForIndex(i);
auto wp = fp->legAtIndex(i)->waypoint();
writeGeodsToKML("FP-leg-" + wp->ident(), legPath);
SGGeod legWPPosition = wp->position();
writePointToKML("WP " + wp->ident(), legWPPosition);
}
}
void writeGeodsToKML(const std::string &label, const flightgear::SGGeodVec& geods)
{
if (global_lineStringOpen) {
endCurrentLineString();
}
beginLineString(label);
for (const auto& g : geods) {
logCoordinate(g);
}
endCurrentLineString();
}
void writePointToKML(const std::string& ident, const SGGeod& pos)
{
global_kmlStream << "<Placemark>\n";
global_kmlStream << "<name>" << ident << "</name>\n";
global_kmlStream << "<Point>\n";
global_kmlStream << "<coordinates>\n";
rawLogCoordinate(pos);
global_kmlStream << "</coordinates>\n";
global_kmlStream << "</Point>\n";
global_kmlStream << "</Placemark>\n";
}
bool executeNasal(const std::string& code)
{
auto nasal = globals->get_subsystem<FGNasalSys>();
if (!nasal) {
throw sg_exception("Nasal not available");
}
nasal->getAndClearErrorList();
std::string output, dummyErrors;
bool ok = nasal->parseAndRunWithOutput(code, output, dummyErrors);
if (!dummyErrors.empty()) {
SG_LOG(SG_NASAL, SG_ALERT, "Errors running Nasal:" << dummyErrors);
return false;
}
auto errors = nasal->getAndClearErrorList();
if (!errors.empty()) {
for (auto err : errors) {
SG_LOG(SG_NASAL, SG_ALERT, "Errors running Nasal:" << err);
}
return false;
}
return ok;
}
namespace tearDown {
void shutdownTestGlobals()
{
// The QApplication instance must be destroyed before exit() begins, see
// <https://bugreports.qt.io/browse/QTBUG-48709> (otherwise, segfault).
#if defined(HAVE_QT)
flightgear::shutdownQtApp();
#endif
delete globals;
globals = nullptr;
if (global_kmlStream) {
if (global_lineStringOpen) {
endCurrentLineString();
}
// post-amble
global_kmlStream << "</Document>\n"
"</kml>" << endl;
global_kmlStream.close();
global_loggingToKML = false;
}
}
} // End of namespace tearDown.
} // End of namespace FGTestApi.