Fix FlightPlan procedure transition handling
Overhaul how transitions are stored in FlightPlan XML, and how they’re exposed to Nasal. Simplify the Nasal access by making ‘sid_trans’ and ‘star_trans’ writeable. Extend the unit-tests a lot to cover this, both from C++ and also from Nasal As part of this, overhaul the ownership of FlightPlan delegate factories, to make it safer (use ref-counting of the factories, and allow the factory to customise delegate clean-up behaviour)
This commit is contained in:
parent
5f5a9d2a5e
commit
fffcd14362
14 changed files with 846 additions and 148 deletions
|
@ -998,10 +998,13 @@ flightgear::Transition* FGAirport::selectSIDByEnrouteTransition(FGPositioned* en
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
Transition *FGAirport::selectSIDByTransition(const string &aIdent) const
|
||||
Transition *FGAirport::selectSIDByTransition(const FGRunway* runway, const string &aIdent) const
|
||||
{
|
||||
loadProcedures();
|
||||
for (auto sid : mSIDs) {
|
||||
if (runway && !sid->isForRunway(runway))
|
||||
continue;
|
||||
|
||||
auto trans = sid->findTransitionByName(aIdent);
|
||||
if (trans) {
|
||||
return trans;
|
||||
|
@ -1022,10 +1025,13 @@ flightgear::Transition* FGAirport::selectSTARByEnrouteTransition(FGPositioned* e
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
Transition *FGAirport::selectSTARByTransition(const string &aIdent) const
|
||||
Transition *FGAirport::selectSTARByTransition(const FGRunway* runway, const string &aIdent) const
|
||||
{
|
||||
loadProcedures();
|
||||
for (auto star : mSTARs) {
|
||||
if (runway && !star->isForRunway(runway))
|
||||
continue;
|
||||
|
||||
auto trans = star->findTransitionByName(aIdent);
|
||||
if (trans) {
|
||||
return trans;
|
||||
|
|
|
@ -249,7 +249,7 @@ class FGAirport : public FGPositioned
|
|||
flightgear::SIDList getSIDs() const;
|
||||
|
||||
flightgear::Transition* selectSIDByEnrouteTransition(FGPositioned* enroute) const;
|
||||
flightgear::Transition* selectSIDByTransition(const std::string& aIdent) const;
|
||||
flightgear::Transition* selectSIDByTransition(const FGRunway* runway, const std::string& aIdent) const;
|
||||
|
||||
unsigned int numSTARs() const;
|
||||
flightgear::STAR* getSTARByIndex(unsigned int aIndex) const;
|
||||
|
@ -257,7 +257,7 @@ class FGAirport : public FGPositioned
|
|||
flightgear::STARList getSTARs() const;
|
||||
|
||||
flightgear::Transition* selectSTARByEnrouteTransition(FGPositioned* enroute) const;
|
||||
flightgear::Transition* selectSTARByTransition(const std::string& aIdent) const;
|
||||
flightgear::Transition* selectSTARByTransition(const FGRunway* runway, const std::string& aIdent) const;
|
||||
|
||||
unsigned int numApproaches() const;
|
||||
flightgear::Approach* getApproachByIndex(unsigned int aIndex) const;
|
||||
|
|
|
@ -78,7 +78,7 @@ namespace flightgear {
|
|||
// implemented in route.cxx
|
||||
const char* restrictionToString(RouteRestriction aRestrict);
|
||||
|
||||
typedef std::vector<FlightPlan::DelegateFactory*> FPDelegateFactoryVec;
|
||||
typedef std::vector<FlightPlan::DelegateFactoryRef> FPDelegateFactoryVec;
|
||||
static FPDelegateFactoryVec static_delegateFactories;
|
||||
|
||||
FlightPlan::FlightPlan() :
|
||||
|
@ -98,7 +98,7 @@ FlightPlan::FlightPlan() :
|
|||
for (auto factory : static_delegateFactories) {
|
||||
Delegate* d = factory->createFlightPlanDelegate(this);
|
||||
if (d) { // factory might not always create a delegate
|
||||
d->_deleteWithPlan = true;
|
||||
d->_factory = factory; // record for clean-up purposes
|
||||
addDelegate(d);
|
||||
}
|
||||
}
|
||||
|
@ -106,10 +106,11 @@ FlightPlan::FlightPlan() :
|
|||
|
||||
FlightPlan::~FlightPlan()
|
||||
{
|
||||
// delete all delegates which we own.
|
||||
// clean up delegates
|
||||
for (auto d : _delegates) {
|
||||
if (d->_deleteWithPlan) {
|
||||
delete d;
|
||||
if (d->_factory) {
|
||||
auto f = d->_factory;
|
||||
f->destroyFlightPlanDelegate(this, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -334,7 +335,6 @@ void FlightPlan::setCurrentIndex(int index)
|
|||
lockDelegates();
|
||||
_currentIndex = index;
|
||||
_currentWaypointChanged = true;
|
||||
_didLoadFP = true;
|
||||
unlockDelegates();
|
||||
}
|
||||
|
||||
|
@ -474,7 +474,7 @@ void FlightPlan::clearDeparture()
|
|||
|
||||
void FlightPlan::setSID(SID* sid, const std::string& transition)
|
||||
{
|
||||
if (sid == _sid) {
|
||||
if ((sid == _sid) && (_sidTransition == transition)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -574,7 +574,7 @@ void FlightPlan::setAlternate(FGAirportRef alt)
|
|||
|
||||
void FlightPlan::setSTAR(STAR* star, const std::string& transition)
|
||||
{
|
||||
if (_star == star) {
|
||||
if ((_star == star) && (_starTransition == transition)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -706,11 +706,9 @@ void FlightPlan::saveToProperties(SGPropertyNode* d) const
|
|||
if (_departure) {
|
||||
d->setStringValue("departure/airport", _departure->ident());
|
||||
if (_sid) {
|
||||
if (!_sidTransition.empty()) {
|
||||
d->setStringValue("departure/sid", _sidTransition);
|
||||
} else {
|
||||
d->setStringValue("departure/sid", _sid->ident());
|
||||
}
|
||||
d->setStringValue("departure/sid", _sid->ident());
|
||||
if (!_sidTransition.empty())
|
||||
d->setStringValue("departure/sid_trans", _sidTransition);
|
||||
}
|
||||
|
||||
if (_departureRunway) {
|
||||
|
@ -721,11 +719,9 @@ void FlightPlan::saveToProperties(SGPropertyNode* d) const
|
|||
if (_destination) {
|
||||
d->setStringValue("destination/airport", _destination->ident());
|
||||
if (_star) {
|
||||
if (!_starTransition.empty()) {
|
||||
d->setStringValue("destination/star", _starTransition);
|
||||
} else {
|
||||
d->setStringValue("destination/star", _star->ident());
|
||||
}
|
||||
d->setStringValue("destination/star", _star->ident());
|
||||
if (!_starTransition.empty())
|
||||
d->setStringValue("destination/star_trans", _starTransition);
|
||||
}
|
||||
|
||||
if (_approach) {
|
||||
|
@ -769,10 +765,12 @@ static bool anyWaypointsWithFlag(FlightPlan* plan, WayptFlag flag)
|
|||
{
|
||||
bool r = false;
|
||||
plan->forEachLeg([&r, flag](FlightPlan::Leg* l) {
|
||||
if (l->waypoint()->flags() && flag) {
|
||||
if (l->waypoint()->flags() & flag) {
|
||||
r = true;
|
||||
}
|
||||
});
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
bool FlightPlan::load(const SGPath& path)
|
||||
|
@ -818,6 +816,8 @@ bool FlightPlan::load(const SGPath& path)
|
|||
|
||||
_cruiseDataChanged = true;
|
||||
_waypointsChanged = true;
|
||||
_didLoadFP = true;
|
||||
|
||||
unlockDelegates();
|
||||
|
||||
return Status;
|
||||
|
@ -853,10 +853,22 @@ bool FlightPlan::load(std::istream &stream)
|
|||
Status = false;
|
||||
}
|
||||
|
||||
_arrivalChanged = false;
|
||||
_departureChanged = false;
|
||||
// we don't want to re-compute the arrival / departure after
|
||||
// a load, since we assume the flight-plan had it specified already
|
||||
// especially, the XML might have a SID/STAR embedded, which we don't
|
||||
// want to lose
|
||||
|
||||
// however, we do want to run the normal delegate if no procedure was
|
||||
// defined. We'll use the presence of waypoints tagged to decide
|
||||
const bool hasArrival = anyWaypointsWithFlag(this, WPT_ARRIVAL);
|
||||
const bool hasDeparture = anyWaypointsWithFlag(this, WPT_DEPARTURE);
|
||||
_arrivalChanged = !hasArrival;
|
||||
_departureChanged = !hasDeparture;
|
||||
|
||||
_cruiseDataChanged = true;
|
||||
_waypointsChanged = true;
|
||||
_didLoadFP = true;
|
||||
|
||||
unlockDelegates();
|
||||
|
||||
return Status;
|
||||
|
@ -1052,13 +1064,13 @@ void FlightPlan::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
|
|||
}
|
||||
|
||||
if (dep->hasChild("sid")) {
|
||||
// previously, we would write a transition id for 'SID' if set,
|
||||
// but this is ambigous. Starting with 2020.2, we only every try
|
||||
// to parse this value as a SID, and look for a seperate sid_trans
|
||||
// value
|
||||
const string trans = dep->getStringValue("sid_trans");
|
||||
const auto sid = dep->getStringValue("sid");
|
||||
auto trans = _departure->selectSIDByTransition(sid);
|
||||
if (trans) {
|
||||
setSID(trans);
|
||||
} else {
|
||||
setSID(_departure->findSIDWithIdent(sid));
|
||||
}
|
||||
setSID(_departure->findSIDWithIdent(sid), trans);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1074,14 +1086,12 @@ void FlightPlan::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
|
|||
}
|
||||
|
||||
if (dst->hasChild("star")) {
|
||||
// prior to 2020.2 we would attempt to treat 'star' as a
|
||||
// transiiton ID, but this is ambiguous. Look for a seperate value now
|
||||
const auto star = dst->getStringValue("star");
|
||||
auto trans = _destination->selectSTARByTransition(star);
|
||||
if (trans) {
|
||||
setSTAR(trans);
|
||||
} else {
|
||||
setSTAR(_destination->findSTARWithIdent(star));
|
||||
}
|
||||
}
|
||||
const string trans = dst->getStringValue("star_trans");
|
||||
setSTAR(_destination->findSTARWithIdent(star), trans);
|
||||
} // of STAR processing
|
||||
|
||||
if (dst->hasChild("approach")) {
|
||||
setApproach(_destination->findApproachWithIdent(dst->getStringValue("approach")));
|
||||
|
@ -1758,7 +1768,7 @@ void FlightPlan::unlockDelegates()
|
|||
--_delegateLock;
|
||||
}
|
||||
|
||||
void FlightPlan::registerDelegateFactory(DelegateFactory* df)
|
||||
void FlightPlan::registerDelegateFactory(DelegateFactoryRef df)
|
||||
{
|
||||
auto it = std::find(static_delegateFactories.begin(), static_delegateFactories.end(), df);
|
||||
if (it != static_delegateFactories.end()) {
|
||||
|
@ -1768,7 +1778,7 @@ void FlightPlan::registerDelegateFactory(DelegateFactory* df)
|
|||
static_delegateFactories.push_back(df);
|
||||
}
|
||||
|
||||
void FlightPlan::unregisterDelegateFactory(DelegateFactory* df)
|
||||
void FlightPlan::unregisterDelegateFactory(DelegateFactoryRef df)
|
||||
{
|
||||
auto it = std::find(static_delegateFactories.begin(), static_delegateFactories.end(), df);
|
||||
if (it == static_delegateFactories.end()) {
|
||||
|
@ -2143,4 +2153,65 @@ void FlightPlan::forEachLeg(const LegVisitor& lv)
|
|||
std::for_each(_legs.begin(), _legs.end(), lv);
|
||||
}
|
||||
|
||||
|
||||
int FlightPlan::indexOfFirstNonDepartureWaypoint() const
|
||||
{
|
||||
const auto numLegs = _legs.size();
|
||||
for (int i = 0; i < numLegs; ++i) {
|
||||
if (!(_legs.at(i)->waypoint()->flags() & WPT_DEPARTURE))
|
||||
return i ;
|
||||
}
|
||||
|
||||
// all waypoints are marked as departure
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FlightPlan::indexOfFirstArrivalWaypoint() const
|
||||
{
|
||||
const auto numLegs = _legs.size();
|
||||
for (int i = 0; i < numLegs; ++i) {
|
||||
if (_legs.at(i)->waypoint()->flags() & WPT_ARRIVAL)
|
||||
return i;
|
||||
}
|
||||
|
||||
// no waypoints are marked as arrival
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FlightPlan::indexOfFirstApproachWaypoint() const
|
||||
{
|
||||
const auto numLegs = _legs.size();
|
||||
for (int i = 0; i < numLegs; ++i) {
|
||||
if (_legs.at(i)->waypoint()->flags() & WPT_APPROACH)
|
||||
return i;
|
||||
}
|
||||
|
||||
// no waypoints are marked as arrival
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FlightPlan::indexOfDestinationRunwayWaypoint() const
|
||||
{
|
||||
if (!_destinationRunway)
|
||||
return -1;
|
||||
|
||||
// work backwards in case the departure and destination match
|
||||
// this way we'll find the one we want
|
||||
for (int i = numLegs() - 1; i >= 0; i--) {
|
||||
if (_legs.at(i)->waypoint()->source() == _destinationRunway) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void FlightPlan::DelegateFactory::destroyFlightPlanDelegate(FlightPlan* fp, Delegate* d)
|
||||
{
|
||||
// mimic old behaviour before destroyFlightPlanDelegate was added
|
||||
SG_UNUSED(fp);
|
||||
delete d;
|
||||
}
|
||||
|
||||
|
||||
} // of namespace flightgear
|
||||
|
|
|
@ -164,6 +164,9 @@ public:
|
|||
|
||||
using LegRef = SGSharedPtr<Leg>;
|
||||
|
||||
class DelegateFactory;
|
||||
using DelegateFactoryRef = std::shared_ptr<DelegateFactory>;
|
||||
|
||||
class Delegate
|
||||
{
|
||||
public:
|
||||
|
@ -195,8 +198,9 @@ public:
|
|||
|
||||
private:
|
||||
friend class FlightPlan;
|
||||
|
||||
bool _deleteWithPlan = false;
|
||||
|
||||
// record the factory which created us, so we have the option to clean up
|
||||
DelegateFactoryRef _factory;
|
||||
};
|
||||
|
||||
LegRef insertWayptAtIndex(Waypt* aWpt, int aIndex);
|
||||
|
@ -231,6 +235,11 @@ public:
|
|||
int findWayptIndex(const SGGeod& aPos) const;
|
||||
int findWayptIndex(const FGPositionedRef aPos) const;
|
||||
|
||||
int indexOfFirstNonDepartureWaypoint() const;
|
||||
int indexOfFirstArrivalWaypoint() const;
|
||||
int indexOfFirstApproachWaypoint() const;
|
||||
int indexOfDestinationRunwayWaypoint() const;
|
||||
|
||||
bool load(const SGPath& p);
|
||||
bool save(const SGPath& p) const;
|
||||
|
||||
|
@ -370,10 +379,11 @@ public:
|
|||
{
|
||||
public:
|
||||
virtual Delegate* createFlightPlanDelegate(FlightPlan* fp) = 0;
|
||||
virtual void destroyFlightPlanDelegate(FlightPlan* fp, Delegate* d);
|
||||
};
|
||||
|
||||
static void registerDelegateFactory(DelegateFactory* df);
|
||||
static void unregisterDelegateFactory(DelegateFactory* df);
|
||||
|
||||
static void registerDelegateFactory(DelegateFactoryRef df);
|
||||
static void unregisterDelegateFactory(DelegateFactoryRef df);
|
||||
|
||||
void addDelegate(Delegate* d);
|
||||
void removeDelegate(Delegate* d);
|
||||
|
|
|
@ -916,12 +916,12 @@ static void flightplanGhostSetMember(naContext c, void* g, naRef field, naRef va
|
|||
if (naIsString(value)) {
|
||||
const std::string s(naStr_data(value));
|
||||
FGAirport* apt = fp->departureAirport();
|
||||
auto trans = apt->selectSIDByTransition(s);
|
||||
if (trans) {
|
||||
fp->setSID(trans);
|
||||
} else {
|
||||
fp->setSID(apt->findSIDWithIdent(s));
|
||||
auto sid = apt->findSIDWithIdent(s);
|
||||
if (!sid) {
|
||||
naRuntimeError(c, "Unknown SID %s at %s", s.c_str(), apt->ident().c_str());
|
||||
}
|
||||
|
||||
fp->setSID(sid);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -930,7 +930,43 @@ static void flightplanGhostSetMember(naContext c, void* g, naRef field, naRef va
|
|||
return;
|
||||
}
|
||||
|
||||
naRuntimeError(c, "bad argument type setting SID");
|
||||
naRuntimeError(c, "bad argument type setting SID");
|
||||
} else if (!strcmp(fieldName, "sid_trans")) {
|
||||
Procedure* proc = procedureGhost(value);
|
||||
if (proc && (proc->type() == PROCEDURE_TRANSITION)) {
|
||||
fp->setSID((Transition*) proc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (naIsString(value)) {
|
||||
const std::string s(naStr_data(value));
|
||||
Transition* trans = nullptr;
|
||||
|
||||
if (fp->sid()) {
|
||||
trans = fp->sid()->findTransitionByName(s);
|
||||
if (!trans) {
|
||||
naRuntimeError(c, "No such transition %s for SID %s at %s",
|
||||
s.c_str(),
|
||||
fp->sid()->ident().c_str(),
|
||||
fp->departureAirport()->ident().c_str());
|
||||
}
|
||||
} else {
|
||||
trans = fp->departureAirport()->selectSIDByTransition(fp->departureRunway(), s);
|
||||
if (!trans) {
|
||||
naRuntimeError(c, "Couldn't find SID transition to %s at %s",
|
||||
s.c_str(),
|
||||
fp->departureAirport()->ident().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (trans) {
|
||||
fp->setSID(trans);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
naRuntimeError(c, "bad argument type setting sid_trans");
|
||||
} else if (!strcmp(fieldName, "star")) {
|
||||
Procedure* proc = procedureGhost(value);
|
||||
if (proc && (proc->type() == PROCEDURE_STAR)) {
|
||||
|
@ -946,12 +982,11 @@ static void flightplanGhostSetMember(naContext c, void* g, naRef field, naRef va
|
|||
if (naIsString(value)) {
|
||||
const std::string s(naStr_data(value));
|
||||
FGAirport* apt = fp->destinationAirport();
|
||||
auto trans = apt->selectSTARByTransition(s);
|
||||
if (trans) {
|
||||
fp->setSTAR(trans);
|
||||
} else {
|
||||
fp->setSTAR(apt->findSTARWithIdent(s));
|
||||
}
|
||||
auto star = apt->findSTARWithIdent(s);
|
||||
if (!star) {
|
||||
naRuntimeError(c, "Unknown SID %s at %s", s.c_str(), apt->ident().c_str());
|
||||
}
|
||||
fp->setSTAR(star);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -961,6 +996,42 @@ static void flightplanGhostSetMember(naContext c, void* g, naRef field, naRef va
|
|||
}
|
||||
|
||||
naRuntimeError(c, "bad argument type setting STAR");
|
||||
} else if (!strcmp(fieldName, "star_trans")) {
|
||||
Procedure* proc = procedureGhost(value);
|
||||
if (proc && (proc->type() == PROCEDURE_TRANSITION)) {
|
||||
fp->setSTAR((Transition*) proc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (naIsString(value)) {
|
||||
const std::string s(naStr_data(value));
|
||||
Transition* trans = nullptr;
|
||||
|
||||
if (fp->star()) {
|
||||
trans = fp->star()->findTransitionByName(s);
|
||||
if (!trans) {
|
||||
naRuntimeError(c, "No such transition %s for STAR %s at %s",
|
||||
s.c_str(),
|
||||
fp->star()->ident().c_str(),
|
||||
fp->destinationAirport()->ident().c_str());
|
||||
}
|
||||
} else {
|
||||
trans = fp->destinationAirport()->selectSTARByTransition(fp->destinationRunway(), s);
|
||||
if (!trans) {
|
||||
naRuntimeError(c, "Couldn't find STAR transition to %s at %s",
|
||||
s.c_str(),
|
||||
fp->destinationAirport()->ident().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (trans) {
|
||||
fp->setSTAR(trans);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
naRuntimeError(c, "bad argument type setting star_trans");
|
||||
} else if (!strcmp(fieldName, "approach")) {
|
||||
Procedure* proc = procedureGhost(value);
|
||||
if (proc && Approach::isApproach(proc->type())) {
|
||||
|
@ -2487,6 +2558,11 @@ public:
|
|||
return result;
|
||||
}
|
||||
|
||||
void destroyFlightPlanDelegate(FlightPlan* fp, FlightPlan::Delegate* d) override
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
const std::string& id() const
|
||||
{ return _id; }
|
||||
private:
|
||||
|
@ -2496,16 +2572,12 @@ private:
|
|||
int _gcSaveKey;
|
||||
};
|
||||
|
||||
static std::vector<NasalFPDelegateFactory*> static_nasalDelegateFactories;
|
||||
static std::vector<FlightPlan::DelegateFactoryRef> static_nasalDelegateFactories;
|
||||
|
||||
void shutdownNasalPositioned()
|
||||
{
|
||||
std::vector<NasalFPDelegateFactory*>::iterator it;
|
||||
for (it = static_nasalDelegateFactories.begin();
|
||||
it != static_nasalDelegateFactories.end(); ++it)
|
||||
{
|
||||
FlightPlan::unregisterDelegateFactory(*it);
|
||||
delete (*it);
|
||||
for (auto f : static_nasalDelegateFactories) {
|
||||
FlightPlan::unregisterDelegateFactory(f);
|
||||
}
|
||||
static_nasalDelegateFactories.clear();
|
||||
}
|
||||
|
@ -2519,15 +2591,17 @@ static naRef f_registerFPDelegate(naContext c, naRef me, int argc, naRef* args)
|
|||
const std::string delegateId = (argc > 1) ? naStr_data(args[1]) : std::string{};
|
||||
if (!delegateId.empty()) {
|
||||
auto it = std::find_if(static_nasalDelegateFactories.begin(), static_nasalDelegateFactories.end(),
|
||||
[delegateId](NasalFPDelegateFactory* delegate) {
|
||||
return delegate->id() == delegateId;
|
||||
[delegateId](FlightPlan::DelegateFactoryRef factory)
|
||||
{
|
||||
auto nfpd = static_cast<NasalFPDelegateFactory*>(factory.get());
|
||||
return nfpd->id() == delegateId;
|
||||
});
|
||||
if (it != static_nasalDelegateFactories.end()) {
|
||||
naRuntimeError(c, "duplicate delegate ID at registerFlightPlanDelegate: %s", delegateId.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
NasalFPDelegateFactory* factory = new NasalFPDelegateFactory(args[0], delegateId);
|
||||
auto factory = std::make_shared<NasalFPDelegateFactory>(args[0], delegateId);
|
||||
FlightPlan::registerDelegateFactory(factory);
|
||||
static_nasalDelegateFactories.push_back(factory);
|
||||
return naNil();
|
||||
|
@ -2541,12 +2615,13 @@ static naRef f_unregisterFPDelegate(naContext c, naRef me, int argc, naRef* args
|
|||
|
||||
const std::string delegateId = naStr_data(args[0]);
|
||||
auto it = std::find_if(static_nasalDelegateFactories.begin(), static_nasalDelegateFactories.end(),
|
||||
[delegateId](NasalFPDelegateFactory* delegate) {
|
||||
return delegate->id() == delegateId;
|
||||
[delegateId](FlightPlan::DelegateFactoryRef factory) {
|
||||
auto nfpd = static_cast<NasalFPDelegateFactory*>(factory.get());
|
||||
return nfpd->id() == delegateId;
|
||||
});
|
||||
|
||||
if (it == static_nasalDelegateFactories.end()) {
|
||||
SG_LOG(SG_NASAL, SG_DEV_WARN, "f_unregisterFPDelegate: no de;egate with ID:" << delegateId);
|
||||
SG_LOG(SG_NASAL, SG_DEV_WARN, "f_unregisterFPDelegate: no delegate with ID:" << delegateId);
|
||||
return naNil();
|
||||
}
|
||||
|
||||
|
|
|
@ -180,9 +180,6 @@ naRef initNasalUnitTestInSim(naRef nasalGlobals, naContext c)
|
|||
unitTest.set("assert_equal", f_assert_equal);
|
||||
unitTest.set("assert_doubles_equal", f_assert_doubles_equal);
|
||||
|
||||
// http.set("save", f_http_save);
|
||||
// http.set("load", f_http_load);
|
||||
|
||||
globals->get_commands()->addCommand("nasal-test", &command_executeNasalTest);
|
||||
globals->get_commands()->addCommand("nasal-test-dir", &command_executeNasalTestDir);
|
||||
|
||||
|
|
|
@ -30,76 +30,85 @@
|
|||
#include <Main/globals.hxx>
|
||||
#include <Main/util.hxx>
|
||||
|
||||
#include <cppunit/TestAssert.h>
|
||||
|
||||
#include <simgear/nasal/cppbind/from_nasal.hxx>
|
||||
#include <simgear/nasal/cppbind/to_nasal.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalHash.hxx>
|
||||
#include <simgear/nasal/cppbind/Ghost.hxx>
|
||||
|
||||
#if 0
|
||||
typedef nasal::Ghost<simgear::HTTP::Request_ptr> NasalRequest;
|
||||
typedef nasal::Ghost<simgear::HTTP::FileRequestRef> NasalFileRequest;
|
||||
typedef nasal::Ghost<simgear::HTTP::MemoryRequestRef> NasalMemoryRequest;
|
||||
|
||||
FGHTTPClient& requireHTTPClient(const nasal::ContextWrapper& ctx)
|
||||
static naRef f_assert(const nasal::CallContext& ctx )
|
||||
{
|
||||
FGHTTPClient* http = globals->get_subsystem<FGHTTPClient>();
|
||||
if( !http )
|
||||
ctx.runtimeError("Failed to get HTTP subsystem");
|
||||
|
||||
return *http;
|
||||
}
|
||||
|
||||
/**
|
||||
* http.save(url, filename)
|
||||
*/
|
||||
static naRef f_http_save(const nasal::CallContext& ctx)
|
||||
{
|
||||
const std::string url = ctx.requireArg<std::string>(0);
|
||||
|
||||
// Check for write access to target file
|
||||
const std::string filename = ctx.requireArg<std::string>(1);
|
||||
const SGPath validated_path = fgValidatePath(filename, true);
|
||||
|
||||
if( validated_path.isNull() )
|
||||
ctx.runtimeError("Access denied: can not write to %s", filename.c_str());
|
||||
|
||||
return ctx.to_nasal
|
||||
(
|
||||
requireHTTPClient(ctx).client()->save(url, validated_path.utf8Str())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* http.load(url)
|
||||
*/
|
||||
static naRef f_http_load(const nasal::CallContext& ctx)
|
||||
{
|
||||
const std::string url = ctx.requireArg<std::string>(0);
|
||||
return ctx.to_nasal( requireHTTPClient(ctx).client()->load(url) );
|
||||
}
|
||||
|
||||
static naRef f_request_abort( simgear::HTTP::Request&,
|
||||
const nasal::CallContext& ctx )
|
||||
{
|
||||
// we need a request_ptr for cancel, not a reference. So extract
|
||||
// the me object from the context directly.
|
||||
simgear::HTTP::Request_ptr req = ctx.from_nasal<simgear::HTTP::Request_ptr>(ctx.me);
|
||||
requireHTTPClient(ctx).client()->cancelRequest(req);
|
||||
bool pass = ctx.requireArg<bool>(0);
|
||||
auto msg = ctx.getArg<string>(1, "assert failed:");
|
||||
|
||||
CppUnit::Asserter::failIf(!pass, "assertion failed:" + msg,
|
||||
CppUnit::SourceLine{"Nasal source line", 0});
|
||||
if (!pass) {
|
||||
ctx.runtimeError(msg.c_str());
|
||||
}
|
||||
return naNil();
|
||||
}
|
||||
|
||||
#endif
|
||||
static naRef f_fail(const nasal::CallContext& ctx )
|
||||
{
|
||||
auto msg = ctx.getArg<string>(0);
|
||||
|
||||
CppUnit::Asserter::fail("assertion failed:" + msg,
|
||||
CppUnit::SourceLine{"Nasal source line", 0});
|
||||
|
||||
ctx.runtimeError("Test failed: %s", msg.c_str());
|
||||
return naNil();
|
||||
}
|
||||
|
||||
static naRef f_assert_equal(const nasal::CallContext& ctx )
|
||||
{
|
||||
naRef argA = ctx.requireArg<naRef>(0);
|
||||
naRef argB = ctx.requireArg<naRef>(1);
|
||||
auto msg = ctx.getArg<string>(2, "assert_equal failed");
|
||||
|
||||
bool same = naEqual(argA, argB);
|
||||
if (!same) {
|
||||
|
||||
string aStr = ctx.from_nasal<string>(argA);
|
||||
string bStr = ctx.from_nasal<string>(argB);
|
||||
msg += "; expected:" + aStr + ", actual:" + bStr;
|
||||
|
||||
ctx.runtimeError(msg.c_str());
|
||||
}
|
||||
|
||||
return naNil();
|
||||
}
|
||||
|
||||
static naRef f_assert_doubles_equal(const nasal::CallContext& ctx )
|
||||
{
|
||||
double argA = ctx.requireArg<double>(0);
|
||||
double argB = ctx.requireArg<double>(1);
|
||||
double tolerance = ctx.requireArg<double>(2);
|
||||
|
||||
auto msg = ctx.getArg<string>(3, "assert_doubles_equal failed");
|
||||
|
||||
const bool same = fabs(argA - argB) < tolerance;
|
||||
if (!same) {
|
||||
msg += "; expected:" + std::to_string(argA) + ", actual:" + std::to_string(argB);
|
||||
// static_activeTest->failure = true;
|
||||
// static_activeTest->failureMessage = msg;
|
||||
ctx.runtimeError(msg.c_str());
|
||||
}
|
||||
|
||||
return naNil();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
naRef initNasalUnitTestCppUnit(naRef globals, naContext c)
|
||||
naRef initNasalUnitTestCppUnit(naRef nasalGlobals, naContext c)
|
||||
{
|
||||
|
||||
|
||||
nasal::Hash globals_module(globals, c),
|
||||
unitTest = globals_module.createHash("unitTest");
|
||||
|
||||
// http.set("save", f_http_save);
|
||||
// http.set("load", f_http_load);
|
||||
nasal::Hash globals_module(nasalGlobals, c),
|
||||
unitTest = globals_module.createHash("unitTest");
|
||||
|
||||
unitTest.set("assert", f_assert);
|
||||
unitTest.set("fail", f_fail);
|
||||
unitTest.set("assert_equal", f_assert_equal);
|
||||
unitTest.set("assert_doubles_equal", f_assert_doubles_equal);
|
||||
|
||||
return naNil();
|
||||
}
|
||||
|
|
|
@ -80,15 +80,18 @@ public:
|
|||
// insert waypt for the dpearture runway
|
||||
auto dr = new RunwayWaypt(thePlan->departureRunway(), thePlan);
|
||||
dr->setFlag(WPT_DEPARTURE);
|
||||
dr->setFlag(WPT_GENERATED);
|
||||
thePlan->insertWayptAtIndex(dr, 0);
|
||||
|
||||
if (thePlan->sid()) {
|
||||
WayptVec sidRoute;
|
||||
bool ok = thePlan->sid()->route(thePlan->departureRunway(), nullptr, sidRoute);
|
||||
bool ok = thePlan->sid()->route(thePlan->departureRunway(), thePlan->sidTransition(), sidRoute);
|
||||
if (!ok)
|
||||
throw sg_exception("failed to route via SID");
|
||||
int insertIndex = 1;
|
||||
for (auto w : sidRoute) {
|
||||
w->setFlag(WPT_DEPARTURE);
|
||||
w->setFlag(WPT_GENERATED);
|
||||
thePlan->insertWayptAtIndex(w, insertIndex++);
|
||||
}
|
||||
}
|
||||
|
@ -104,15 +107,18 @@ public:
|
|||
// insert waypt for the destination runway
|
||||
auto dr = new RunwayWaypt(thePlan->destinationRunway(), thePlan);
|
||||
dr->setFlag(WPT_ARRIVAL);
|
||||
thePlan->insertWayptAtIndex(dr, 0);
|
||||
dr->setFlag(WPT_GENERATED);
|
||||
auto leg = thePlan->insertWayptAtIndex(dr, -1);
|
||||
|
||||
if (thePlan->star()) {
|
||||
WayptVec starRoute;
|
||||
bool ok = thePlan->star()->route(thePlan->destinationRunway(), nullptr, starRoute);
|
||||
bool ok = thePlan->star()->route(thePlan->destinationRunway(), thePlan->starTransition(), starRoute);
|
||||
if (!ok)
|
||||
throw sg_exception("failed to route via STAR");
|
||||
int insertIndex = 1;
|
||||
int insertIndex = leg->index();
|
||||
for (auto w : starRoute) {
|
||||
w->setFlag(WPT_ARRIVAL);
|
||||
w->setFlag(WPT_GENERATED);
|
||||
thePlan->insertWayptAtIndex(w, insertIndex++);
|
||||
}
|
||||
}
|
||||
|
@ -201,7 +207,7 @@ void RNAVProcedureTests::testBasic()
|
|||
void RNAVProcedureTests::testHeadingToAlt()
|
||||
{
|
||||
auto vhhh = FGAirport::findByIdent("VHHH");
|
||||
// FGTestApi::setUp::logPositionToKML("heading_to_alt");
|
||||
// FGTestApi::setUp::logPositionToKML("heading_to_alt");
|
||||
|
||||
auto rm = globals->get_subsystem<FGRouteMgr>();
|
||||
auto fp = new FlightPlan;
|
||||
|
@ -219,7 +225,7 @@ void RNAVProcedureTests::testHeadingToAlt()
|
|||
wp->setAltitude(4000, RESTRICT_ABOVE);
|
||||
fp->insertWayptAtIndex(wp, 1); // between the runway WP and HAZEL
|
||||
|
||||
// FGTestApi::writeFlightPlanToKML(fp);
|
||||
// FGTestApi::writeFlightPlanToKML(fp);
|
||||
|
||||
auto depRwy = fp->departureRunway();
|
||||
|
||||
|
@ -237,8 +243,10 @@ void RNAVProcedureTests::testHeadingToAlt()
|
|||
pilot->setSpeedKts(200);
|
||||
pilot->flyGPSCourse(m_gps);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(fp->currentIndex(), 0);
|
||||
|
||||
// check we sequence to the heading-to-alt wp
|
||||
bool ok = FGTestApi::runForTimeWithCheck(240.0, [fp] () {
|
||||
bool ok = FGTestApi::runForTimeWithCheck(300.0, [fp] () {
|
||||
if (fp->currentIndex() == 1) {
|
||||
return true;
|
||||
}
|
||||
|
@ -410,7 +418,11 @@ void RNAVProcedureTests::testEGPH_TLA6C()
|
|||
CPPUNIT_ASSERT_EQUAL(std::string{"D242H"}, d242Wpt->ident());
|
||||
CPPUNIT_ASSERT_EQUAL(true, d242Wpt->flag(WPT_OVERFLIGHT));
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"D346T"}, fp->legAtIndex(3)->waypoint()->ident());
|
||||
const auto wp3Ident = fp->legAtIndex(3)->waypoint()->ident();
|
||||
|
||||
// depeding which versino fo the procedures we loaded, we can find
|
||||
// one ID or the other
|
||||
CPPUNIT_ASSERT((wp3Ident == "D346T") || (wp3Ident == "D345T"));
|
||||
|
||||
// FGTestApi::writeFlightPlanToKML(fp);
|
||||
|
||||
|
@ -547,10 +559,10 @@ void RNAVProcedureTests::testLFKC_AJO1R()
|
|||
void RNAVProcedureTests::testTransitionsSID()
|
||||
{
|
||||
auto kjfk = FGAirport::findByIdent("kjfk");
|
||||
//auto sid = kjfk->findSIDWithIdent("DEEZZ5.13L");
|
||||
// the method used by nasal to search for a transition only accepts transition ID as argument
|
||||
// - not the associated SID. I believe this is an issue. This code will try to load DEEZZ5.04L!
|
||||
auto sid = kjfk->selectSIDByTransition("CANDR");
|
||||
auto runway = kjfk->getRunwayByIdent("13L");
|
||||
|
||||
|
||||
auto sid = kjfk->selectSIDByTransition(runway, "CANDR");
|
||||
// procedures not loaded, abandon test
|
||||
if (!sid)
|
||||
return;
|
||||
|
@ -566,7 +578,7 @@ void RNAVProcedureTests::testTransitionsSID()
|
|||
FGTestApi::setUp::populateFPWithNasal(fp, "KJFK", "13L", "KCLE", "24R", "");
|
||||
|
||||
fp->setSID(sid);
|
||||
CPPUNIT_ASSERT_EQUAL(7, fp->numLegs());
|
||||
CPPUNIT_ASSERT_EQUAL(8, fp->numLegs());
|
||||
auto wp = fp->legAtIndex(6);
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"CANDR"}, wp->waypoint()->ident());
|
||||
CPPUNIT_ASSERT(rm->activate());
|
||||
|
@ -575,8 +587,8 @@ void RNAVProcedureTests::testTransitionsSID()
|
|||
void RNAVProcedureTests::testTransitionsSTAR()
|
||||
{
|
||||
auto kjfk = FGAirport::findByIdent("kjfk");
|
||||
//auto star = kjfk->findSIDWithIdent("DEEZZ5.13L");
|
||||
auto star = kjfk->selectSTARByTransition("SEY");
|
||||
auto runway = kjfk->getRunwayByIdent("22L");
|
||||
auto star = kjfk->selectSTARByTransition(runway, "SEY");
|
||||
// procedures not loaded, abandon test
|
||||
if (!star)
|
||||
return;
|
||||
|
@ -592,7 +604,7 @@ void RNAVProcedureTests::testTransitionsSTAR()
|
|||
FGTestApi::setUp::populateFPWithNasal(fp, "KBOS", "22R", "KJFK", "22L", "");
|
||||
|
||||
fp->setSTAR(star);
|
||||
CPPUNIT_ASSERT_EQUAL(8, fp->numLegs());
|
||||
CPPUNIT_ASSERT_EQUAL(9, fp->numLegs());
|
||||
auto wp = fp->legAtIndex(1);
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"SEY"}, wp->waypoint()->ident());
|
||||
CPPUNIT_ASSERT(rm->activate());
|
||||
|
@ -682,4 +694,4 @@ void RNAVProcedureTests::testIndexOf()
|
|||
auto SAM = fp->legAtIndex(6)->waypoint();
|
||||
CPPUNIT_ASSERT_EQUAL(southamptonVOR->ident(), SAM->ident());
|
||||
CPPUNIT_ASSERT_EQUAL(6, fp->findWayptIndex(southamptonVOR));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include "test_suite/FGTestApi/NavDataCache.hxx"
|
||||
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/misc/sg_dir.hxx>
|
||||
#include <simgear/io/iostreams/sgstream.hxx>
|
||||
|
||||
#include <Navaids/FlightPlan.hxx>
|
||||
#include <Navaids/routePath.hxx>
|
||||
|
@ -18,6 +20,102 @@
|
|||
|
||||
using namespace flightgear;
|
||||
|
||||
static bool static_haveProcedures = false;
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
class TestFPDelegate : public FlightPlan::Delegate
|
||||
{
|
||||
public:
|
||||
TestFPDelegate(FlightPlan* plan) :
|
||||
_plan(plan)
|
||||
{}
|
||||
|
||||
virtual ~TestFPDelegate()
|
||||
{
|
||||
}
|
||||
|
||||
void sequence() override
|
||||
{
|
||||
}
|
||||
|
||||
void currentWaypointChanged() override
|
||||
{
|
||||
}
|
||||
|
||||
void departureChanged() override
|
||||
{
|
||||
sawDepartureChange = true;
|
||||
}
|
||||
|
||||
void arrivalChanged() override
|
||||
{
|
||||
sawArrivalChange = true;
|
||||
}
|
||||
|
||||
void loaded() override
|
||||
{
|
||||
didLoad = true;
|
||||
}
|
||||
|
||||
FlightPlan* _plan = nullptr;
|
||||
bool didLoad = false;
|
||||
bool sawDepartureChange = false;
|
||||
bool sawArrivalChange = false;
|
||||
};
|
||||
|
||||
class TestFPDelegateFactory : public FlightPlan::DelegateFactory
|
||||
{
|
||||
public:
|
||||
TestFPDelegateFactory() = default;
|
||||
virtual ~TestFPDelegateFactory() = default;
|
||||
|
||||
FlightPlan::Delegate* createFlightPlanDelegate(FlightPlan* fp) override
|
||||
{
|
||||
auto d = new TestFPDelegate(fp);
|
||||
_instances.push_back(d);
|
||||
return d;
|
||||
}
|
||||
|
||||
void destroyFlightPlanDelegate(FlightPlan* fp, FlightPlan::Delegate* d) override
|
||||
{
|
||||
auto it = std::find_if(_instances.begin(), _instances.end(), [d] (TestFPDelegate* fpd) {
|
||||
return fpd == d;
|
||||
});
|
||||
|
||||
CPPUNIT_ASSERT(it != _instances.end());
|
||||
|
||||
_instances.erase(it);
|
||||
delete d;
|
||||
}
|
||||
|
||||
static TestFPDelegate* delegateForPlan(FlightPlan* fp);
|
||||
|
||||
std::vector<TestFPDelegate*> _instances;
|
||||
};
|
||||
|
||||
std::shared_ptr<TestFPDelegateFactory> static_factory;
|
||||
|
||||
TestFPDelegate* TestFPDelegateFactory::delegateForPlan(FlightPlan* fp)
|
||||
{
|
||||
auto it = std::find_if(static_factory->_instances.begin(),
|
||||
static_factory->_instances.end(),
|
||||
[fp] (TestFPDelegate* del) {
|
||||
return del->_plan == fp;
|
||||
});
|
||||
|
||||
if (it == static_factory->_instances.end())
|
||||
return nullptr;
|
||||
|
||||
return *it;
|
||||
}
|
||||
|
||||
} // of anonymous namespace
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// Set up function for each test.
|
||||
void FlightplanTests::setUp()
|
||||
|
@ -25,6 +123,12 @@ void FlightplanTests::setUp()
|
|||
FGTestApi::setUp::initTestGlobals("flightplan");
|
||||
FGTestApi::setUp::initNavDataCache();
|
||||
|
||||
SGPath proceduresPath = SGPath::fromEnv("FG_PROCEDURES_PATH");
|
||||
if (proceduresPath.exists()) {
|
||||
static_haveProcedures = true;
|
||||
globals->append_fg_scenery(proceduresPath);
|
||||
}
|
||||
|
||||
globals->get_subsystem_mgr()->bind();
|
||||
globals->get_subsystem_mgr()->init();
|
||||
globals->get_subsystem_mgr()->postinit();
|
||||
|
@ -35,6 +139,11 @@ void FlightplanTests::setUp()
|
|||
void FlightplanTests::tearDown()
|
||||
{
|
||||
FGTestApi::tearDown::shutdownTestGlobals();
|
||||
|
||||
if (static_factory) {
|
||||
FlightPlan::unregisterDelegateFactory(static_factory);
|
||||
static_factory.reset();
|
||||
}
|
||||
}
|
||||
|
||||
static FlightPlanRef makeTestFP(const std::string& depICAO, const std::string& depRunway,
|
||||
|
@ -506,3 +615,237 @@ void FlightplanTests::testRadialIntercept()
|
|||
RoutePath rtepath(f);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(232, f->legAtIndex(3)->courseDeg(), 1.0);
|
||||
}
|
||||
|
||||
void FlightplanTests::loadFGFPWithoutDepartureArrival()
|
||||
{
|
||||
static_factory = std::make_shared<TestFPDelegateFactory>();
|
||||
FlightPlan::registerDelegateFactory(static_factory);
|
||||
|
||||
FlightPlanRef f = new FlightPlan;
|
||||
|
||||
SGPath fgfpPath = simgear::Dir::current().path() / "test_fgfp_without_dep_arr.fgfp";
|
||||
{
|
||||
sg_ofstream s(fgfpPath);
|
||||
s << R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||
<PropertyList>
|
||||
<version type="int">2</version>
|
||||
<departure>
|
||||
<airport type="string">EDDM</airport>
|
||||
<runway type="string">08R</runway>
|
||||
</departure>
|
||||
<destination>
|
||||
<airport type="string">EDDF</airport>
|
||||
</destination>
|
||||
<route>
|
||||
<wp n="1">
|
||||
<type type="string">navaid</type>
|
||||
<ident type="string">GIVMI</ident>
|
||||
<lon type="double">11.364700</lon>
|
||||
<lat type="double">48.701100</lat>
|
||||
</wp>
|
||||
<wp n="2">
|
||||
<type type="string">navaid</type>
|
||||
<ident type="string">ERNAS</ident>
|
||||
<lon type="double">11.219400</lon>
|
||||
<lat type="double">48.844700</lat>
|
||||
</wp>
|
||||
<wp n="3">
|
||||
<type type="string">navaid</type>
|
||||
<ident type="string">TALAL</ident>
|
||||
<lon type="double">11.085300</lon>
|
||||
<lat type="double">49.108300</lat>
|
||||
</wp>
|
||||
<wp n="4">
|
||||
<type type="string">navaid</type>
|
||||
<ident type="string">ERMEL</ident>
|
||||
<lon type="double">11.044700</lon>
|
||||
<lat type="double">49.187800</lat>
|
||||
</wp>
|
||||
<wp n="5">
|
||||
<type type="string">navaid</type>
|
||||
<ident type="string">PSA</ident>
|
||||
<lon type="double">9.348300</lon>
|
||||
<lat type="double">49.862200</lat>
|
||||
</wp>
|
||||
</route>
|
||||
</PropertyList>
|
||||
)";
|
||||
}
|
||||
|
||||
auto ourDelegate = TestFPDelegateFactory::delegateForPlan(f);
|
||||
CPPUNIT_ASSERT(!ourDelegate->didLoad);
|
||||
|
||||
CPPUNIT_ASSERT(f->load(fgfpPath));
|
||||
|
||||
CPPUNIT_ASSERT(ourDelegate->didLoad);
|
||||
CPPUNIT_ASSERT(ourDelegate->sawArrivalChange);
|
||||
CPPUNIT_ASSERT(ourDelegate->sawDepartureChange);
|
||||
}
|
||||
|
||||
void FlightplanTests::loadFGFPWithEmbeddedProcedures()
|
||||
{
|
||||
static_factory = std::make_shared<TestFPDelegateFactory>();
|
||||
FlightPlan::registerDelegateFactory(static_factory);
|
||||
|
||||
FlightPlanRef f = new FlightPlan;
|
||||
|
||||
SGPath fgfpPath = simgear::Dir::current().path() / "test_fgfp_with_dep_arr.fgfp";
|
||||
{
|
||||
sg_ofstream s(fgfpPath);
|
||||
s << R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||
<PropertyList>
|
||||
<version type="int">2</version>
|
||||
<departure>
|
||||
<airport type="string">EDDM</airport>
|
||||
<runway type="string">08R</runway>
|
||||
</departure>
|
||||
<destination>
|
||||
<airport type="string">EDDF</airport>
|
||||
</destination>
|
||||
<route>
|
||||
<wp>
|
||||
<type type="string">runway</type>
|
||||
<departure type="bool">true</departure>
|
||||
<ident type="string">08R</ident>
|
||||
<icao type="string">EDDM</icao>
|
||||
</wp>
|
||||
<wp n="1">
|
||||
<type type="string">navaid</type>
|
||||
<ident type="string">GIVMI</ident>
|
||||
<lon type="double">11.364700</lon>
|
||||
<lat type="double">48.701100</lat>
|
||||
</wp>
|
||||
<wp n="2">
|
||||
<type type="string">navaid</type>
|
||||
<ident type="string">ERNAS</ident>
|
||||
<lon type="double">11.219400</lon>
|
||||
<lat type="double">48.844700</lat>
|
||||
</wp>
|
||||
<wp n="3">
|
||||
<type type="string">navaid</type>
|
||||
<ident type="string">TALAL</ident>
|
||||
<lon type="double">11.085300</lon>
|
||||
<lat type="double">49.108300</lat>
|
||||
</wp>
|
||||
<wp n="4">
|
||||
<type type="string">navaid</type>
|
||||
<ident type="string">ERMEL</ident>
|
||||
<lon type="double">11.044700</lon>
|
||||
<lat type="double">49.187800</lat>
|
||||
</wp>
|
||||
<wp n="5">
|
||||
<type type="string">navaid</type>
|
||||
<ident type="string">PSA</ident>
|
||||
<lon type="double">9.348300</lon>
|
||||
<lat type="double">49.862200</lat>
|
||||
</wp>
|
||||
|
||||
<wp>
|
||||
<type type="string">runway</type>
|
||||
<arrival type="bool">true</arrival>
|
||||
<ident type="string">36</ident>
|
||||
<icao type="string">EDDF</icao>
|
||||
</wp>
|
||||
</route>
|
||||
</PropertyList>
|
||||
)";
|
||||
}
|
||||
|
||||
auto ourDelegate = TestFPDelegateFactory::delegateForPlan(f);
|
||||
CPPUNIT_ASSERT(!ourDelegate->didLoad);
|
||||
|
||||
CPPUNIT_ASSERT(f->load(fgfpPath));
|
||||
|
||||
CPPUNIT_ASSERT(ourDelegate->didLoad);
|
||||
CPPUNIT_ASSERT(!ourDelegate->sawArrivalChange);
|
||||
CPPUNIT_ASSERT(!ourDelegate->sawDepartureChange);
|
||||
}
|
||||
|
||||
void FlightplanTests::loadFGFPWithOldProcedures()
|
||||
{
|
||||
if (!static_haveProcedures)
|
||||
return;
|
||||
|
||||
FlightPlanRef f = new FlightPlan;
|
||||
|
||||
SGPath fgfpPath = simgear::Dir::current().path() / "test_fgfp_old_procedure_idents.fgfp";
|
||||
{
|
||||
sg_ofstream s(fgfpPath);
|
||||
s << R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||
<PropertyList>
|
||||
<version type="int">2</version>
|
||||
<departure>
|
||||
<airport type="string">KJFK</airport>
|
||||
<runway type="string">13L</runway>
|
||||
<sid type="string">CANDR</sid>
|
||||
</departure>
|
||||
<destination>
|
||||
<airport type="string">EHAM</airport>
|
||||
<star type="string">BEDUM</star>
|
||||
</destination>
|
||||
<route>
|
||||
<wp>
|
||||
<type type="string">runway</type>
|
||||
<arrival type="bool">true</arrival>
|
||||
<ident type="string">36</ident>
|
||||
<icao type="string">EDDF</icao>
|
||||
</wp>
|
||||
</route>
|
||||
</PropertyList>
|
||||
)";
|
||||
}
|
||||
|
||||
auto kjfk = FGAirport::findByIdent("KJFK");
|
||||
auto eham = FGAirport::findByIdent("EHAM");
|
||||
CPPUNIT_ASSERT(f->load(fgfpPath));
|
||||
|
||||
CPPUNIT_ASSERT(f->sid() == nullptr);
|
||||
CPPUNIT_ASSERT(f->star() == nullptr);
|
||||
}
|
||||
|
||||
void FlightplanTests::loadFGFPWithProcedureIdents()
|
||||
{
|
||||
if (!static_haveProcedures)
|
||||
return;
|
||||
|
||||
FlightPlanRef f = new FlightPlan;
|
||||
|
||||
SGPath fgfpPath = simgear::Dir::current().path() / "test_fgfp_procedure_idents.fgfp";
|
||||
{
|
||||
sg_ofstream s(fgfpPath);
|
||||
s << R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||
<PropertyList>
|
||||
<version type="int">2</version>
|
||||
<departure>
|
||||
<airport type="string">KJFK</airport>
|
||||
<runway type="string">13L</runway>
|
||||
<sid type="string">DEEZZ5.13L</sid>
|
||||
<sid_trans type="string">CANDR</sid_trans>
|
||||
</departure>
|
||||
<destination>
|
||||
<airport type="string">EHAM</airport>
|
||||
<star type="string">EEL1A</star>
|
||||
<star_trans type="string">KUBAT</star_trans>
|
||||
</destination>
|
||||
<route>
|
||||
<wp>
|
||||
<type type="string">runway</type>
|
||||
<arrival type="bool">true</arrival>
|
||||
<ident type="string">36</ident>
|
||||
<icao type="string">EDDF</icao>
|
||||
</wp>
|
||||
</route>
|
||||
</PropertyList>
|
||||
)";
|
||||
}
|
||||
|
||||
auto kjfk = FGAirport::findByIdent("KJFK");
|
||||
auto eham = FGAirport::findByIdent("EHAM");
|
||||
CPPUNIT_ASSERT(f->load(fgfpPath));
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(f->sid()->ident(), string{"DEEZZ5.13L"});
|
||||
CPPUNIT_ASSERT_EQUAL(f->sidTransition()->ident(), string{"CANDR"});
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(f->star()->ident(), string{"EEL1A"});
|
||||
CPPUNIT_ASSERT_EQUAL(f->starTransition()->ident(), string{"KUBAT"});
|
||||
}
|
||||
|
|
|
@ -45,6 +45,10 @@ class FlightplanTests : public CppUnit::TestFixture
|
|||
CPPUNIT_TEST(testBasicDiscontinuity);
|
||||
CPPUNIT_TEST(testLeadingWPDynamic);
|
||||
CPPUNIT_TEST(testRadialIntercept);
|
||||
CPPUNIT_TEST(loadFGFPWithoutDepartureArrival);
|
||||
CPPUNIT_TEST(loadFGFPWithEmbeddedProcedures);
|
||||
CPPUNIT_TEST(loadFGFPWithOldProcedures);
|
||||
CPPUNIT_TEST(loadFGFPWithProcedureIdents);
|
||||
|
||||
// CPPUNIT_TEST(testParseICAORoute);
|
||||
// CPPUNIT_TEST(testParseICANLowLevelRoute);
|
||||
|
@ -74,6 +78,10 @@ public:
|
|||
void testOnlyDiscontinuityRoute();
|
||||
void testLeadingWPDynamic();
|
||||
void testRadialIntercept();
|
||||
void loadFGFPWithoutDepartureArrival();
|
||||
void loadFGFPWithEmbeddedProcedures();
|
||||
void loadFGFPWithOldProcedures();
|
||||
void loadFGFPWithProcedureIdents();
|
||||
};
|
||||
|
||||
#endif // FG_FLIGHTPLAN_UNIT_TESTS_HXX
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
using namespace flightgear;
|
||||
|
||||
static bool static_haveProcedures = false;
|
||||
|
||||
// Set up function for each test.
|
||||
void FPNasalTests::setUp()
|
||||
|
@ -26,6 +27,12 @@ void FPNasalTests::setUp()
|
|||
FGTestApi::setUp::initTestGlobals("flightplan");
|
||||
FGTestApi::setUp::initNavDataCache();
|
||||
|
||||
SGPath proceduresPath = SGPath::fromEnv("FG_PROCEDURES_PATH");
|
||||
if (proceduresPath.exists()) {
|
||||
static_haveProcedures = true;
|
||||
globals->append_fg_scenery(proceduresPath);
|
||||
}
|
||||
|
||||
// flightplan() acces needs the route manager
|
||||
globals->add_new_subsystem<FGRouteMgr>();
|
||||
|
||||
|
@ -103,3 +110,101 @@ void FPNasalTests::testSegfaultWaypointGhost()
|
|||
)");
|
||||
CPPUNIT_ASSERT(ok);
|
||||
}
|
||||
|
||||
void FPNasalTests::testSIDTransitionAPI()
|
||||
{
|
||||
if (!static_haveProcedures) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto rm = globals->get_subsystem<FGRouteMgr>();
|
||||
|
||||
bool ok = FGTestApi::executeNasal(R"(
|
||||
var fp = flightplan();
|
||||
fp.departure = airportinfo("KJFK");
|
||||
fp.destination = airportinfo("EGLL");
|
||||
|
||||
var sid = fp.departure.getSid("DEEZZ5.13L");
|
||||
|
||||
unitTest.assert(sid != nil, "SID not found");
|
||||
unitTest.assert_equal(sid.id, "DEEZZ5.13L", "Incorrect SID loaded");
|
||||
|
||||
var trans = sid.transition('CANDR');
|
||||
unitTest.assert_equal(trans.id, "CANDR", "Couldn't find transition");
|
||||
|
||||
fp.sid = trans;
|
||||
fp.departure_runway = fp.departure.runway('13L')
|
||||
)");
|
||||
|
||||
CPPUNIT_ASSERT(ok);
|
||||
|
||||
auto fp = rm->flightPlan();
|
||||
|
||||
CPPUNIT_ASSERT(fp->departureRunway());
|
||||
CPPUNIT_ASSERT(fp->sid());
|
||||
CPPUNIT_ASSERT(fp->sidTransition());
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(fp->departureRunway()->ident(), string{"13L"});
|
||||
CPPUNIT_ASSERT_EQUAL(fp->sid()->ident(), string{"DEEZZ5.13L"});
|
||||
CPPUNIT_ASSERT_EQUAL(fp->sidTransition()->ident(), string{"CANDR"});
|
||||
|
||||
// test specify SID via transition in Nasal
|
||||
rm->setFlightPlan(new FlightPlan{});
|
||||
|
||||
ok = FGTestApi::executeNasal(R"(
|
||||
var fp = flightplan();
|
||||
fp.departure = airportinfo("KJFK");
|
||||
fp.destination = airportinfo("EGLL");
|
||||
fp.departure_runway = airportinfo("KJFK").runways["13L"];
|
||||
fp.sid = airportinfo("KJFK").getSid("DEEZZ5.13L");
|
||||
fp.sid_trans = "CANDR";
|
||||
|
||||
)");
|
||||
|
||||
CPPUNIT_ASSERT(ok);
|
||||
|
||||
fp = rm->flightPlan();
|
||||
|
||||
CPPUNIT_ASSERT(fp->departureRunway());
|
||||
CPPUNIT_ASSERT(fp->sid());
|
||||
CPPUNIT_ASSERT(fp->sidTransition());
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(fp->departureRunway()->ident(), string{"13L"});
|
||||
CPPUNIT_ASSERT_EQUAL(fp->sid()->ident(), string{"DEEZZ5.13L"});
|
||||
CPPUNIT_ASSERT_EQUAL(fp->sidTransition()->ident(), string{"CANDR"});
|
||||
|
||||
|
||||
}
|
||||
|
||||
void FPNasalTests::testSTARTransitionAPI()
|
||||
{
|
||||
if (!static_haveProcedures) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto rm = globals->get_subsystem<FGRouteMgr>();
|
||||
|
||||
bool ok = FGTestApi::executeNasal(R"(
|
||||
var fp = flightplan();
|
||||
fp.departure = airportinfo("EGLL");
|
||||
fp.destination = airportinfo("EDDM");
|
||||
|
||||
var star = fp.destination.getStar("RIXE3A.26L");
|
||||
|
||||
unitTest.assert(star != nil, "STAR not found");
|
||||
unitTest.assert_equal(star.id, "RIXE3A.26L", "Incorrect STAR loaded");
|
||||
|
||||
|
||||
fp.star = star;
|
||||
fp.destination_runway = fp.destination.runway('26L')
|
||||
)");
|
||||
|
||||
CPPUNIT_ASSERT(ok);
|
||||
|
||||
auto fp = rm->flightPlan();
|
||||
|
||||
CPPUNIT_ASSERT(fp->star());
|
||||
CPPUNIT_ASSERT(fp->starTransition() == nullptr);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(fp->star()->ident(), string{"RIXE3A.26L"});
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ class FPNasalTests : public CppUnit::TestFixture
|
|||
CPPUNIT_TEST_SUITE(FPNasalTests);
|
||||
CPPUNIT_TEST(testBasic);
|
||||
CPPUNIT_TEST(testSegfaultWaypointGhost);
|
||||
CPPUNIT_TEST(testSIDTransitionAPI);
|
||||
CPPUNIT_TEST(testSTARTransitionAPI);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
|
@ -43,4 +45,6 @@ public:
|
|||
// The tests.
|
||||
void testBasic();
|
||||
void testSegfaultWaypointGhost();
|
||||
void testSIDTransitionAPI();
|
||||
void testSTARTransitionAPI();
|
||||
};
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
|
||||
using namespace flightgear;
|
||||
|
||||
static bool static_haveProcedures = false;
|
||||
|
||||
static FlightPlanRef makeTestFP(const std::string& depICAO, const std::string& depRunway,
|
||||
const std::string& destICAO, const std::string& destRunway,
|
||||
const std::string& waypoints)
|
||||
|
@ -39,6 +41,12 @@ void RouteManagerTests::setUp()
|
|||
FGTestApi::setUp::initTestGlobals("routemanager");
|
||||
FGTestApi::setUp::initNavDataCache();
|
||||
|
||||
SGPath proceduresPath = SGPath::fromEnv("FG_PROCEDURES_PATH");
|
||||
if (proceduresPath.exists()) {
|
||||
static_haveProcedures = true;
|
||||
globals->append_fg_scenery(proceduresPath);
|
||||
}
|
||||
|
||||
globals->add_new_subsystem<FGRouteMgr>();
|
||||
|
||||
// setup the default GPS, which is needed for waypoint
|
||||
|
@ -676,3 +684,50 @@ void RouteManagerTests::loadFGFP()
|
|||
CPPUNIT_ASSERT_EQUAL(std::string{"EDDF"}, wp2->waypoint()->ident());
|
||||
|
||||
}
|
||||
|
||||
void RouteManagerTests::testRouteWithProcedures()
|
||||
{
|
||||
if (!static_haveProcedures)
|
||||
return;
|
||||
|
||||
auto rm = globals->get_subsystem<FGRouteMgr>();
|
||||
|
||||
FlightPlanRef f = new FlightPlan;
|
||||
rm->setFlightPlan(f);
|
||||
|
||||
auto kjfk = FGAirport::findByIdent("KJFK");
|
||||
auto eham = FGAirport::findByIdent("EHAM");
|
||||
f->setDeparture(kjfk->getRunwayByIdent("13L"));
|
||||
f->setSID(kjfk->findSIDWithIdent("DEEZZ5.13L"), "CANDR");
|
||||
|
||||
f->setDestination(eham->getRunwayByIdent("18R"));
|
||||
f->setSTAR(eham->findSTARWithIdent("EEL1A"), "BEDUM");
|
||||
|
||||
f->setApproach(eham->findApproachWithIdent("VDM18R"));
|
||||
|
||||
auto w = f->waypointFromString("TOMYE");
|
||||
f->insertWayptAtIndex(w, f->indexOfFirstNonDepartureWaypoint());
|
||||
|
||||
auto w2 = f->waypointFromString("DEVOL");
|
||||
f->insertWayptAtIndex(w2, f->indexOfFirstArrivalWaypoint());
|
||||
|
||||
// let's check what we got
|
||||
|
||||
auto endOfSID = f->legAtIndex(f->indexOfFirstNonDepartureWaypoint() - 1);
|
||||
CPPUNIT_ASSERT_EQUAL(endOfSID->waypoint()->ident(), string{"CANDR"});
|
||||
|
||||
auto startOfSTAR = f->legAtIndex(f->indexOfFirstArrivalWaypoint());
|
||||
CPPUNIT_ASSERT_EQUAL(startOfSTAR->waypoint()->ident(), string{"BEDUM"});
|
||||
|
||||
auto endOfSTAR = f->legAtIndex(f->indexOfFirstApproachWaypoint() - 1);
|
||||
CPPUNIT_ASSERT_EQUAL(endOfSTAR->waypoint()->ident(), string{"ARTIP"});
|
||||
|
||||
auto startOfApproach = f->legAtIndex(f->indexOfFirstApproachWaypoint());
|
||||
CPPUNIT_ASSERT_EQUAL(startOfApproach->waypoint()->ident(), string{"D070O"});
|
||||
|
||||
auto landingRunway = f->legAtIndex(f->indexOfDestinationRunwayWaypoint());
|
||||
CPPUNIT_ASSERT(landingRunway->waypoint()->source() == f->destinationRunway());
|
||||
|
||||
auto firstMiss = f->legAtIndex(f->indexOfDestinationRunwayWaypoint() + 1);
|
||||
CPPUNIT_ASSERT_EQUAL(firstMiss->waypoint()->ident(), string{"(461)"});
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ class RouteManagerTests : public CppUnit::TestFixture
|
|||
CPPUNIT_TEST(testHiddenWaypoints);
|
||||
CPPUNIT_TEST(loadGPX);
|
||||
CPPUNIT_TEST(loadFGFP);
|
||||
CPPUNIT_TEST(testRouteWithProcedures);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
|
@ -66,6 +67,8 @@ public:
|
|||
void testHiddenWaypoints();
|
||||
void loadGPX();
|
||||
void loadFGFP();
|
||||
void testRouteWithProcedures();
|
||||
|
||||
private:
|
||||
GPS* m_gps = nullptr;
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue