55#include <simgear/compiler.h>
56#include <simgear/debug/ErrorReportingCallback.hxx>
57#include <simgear/io/iostreams/sgstream.hxx>
58#include <simgear/misc/sg_dir.hxx>
59#include <simgear/misc/sg_path.hxx>
60#include <simgear/props/props.hxx>
61#include <simgear/structure/exception.hxx>
62#include <simgear/structure/subsystem_mgr.hxx>
63#include <simgear/threads/SGThread.hxx>
64#include <simgear/timing/sg_time.hxx>
66#include <simgear/xml/easyxml.hxx>
67#include <simgear/scene/tsync/terrasync.hxx>
95 _trafficManager(traffic),
113 _cancelThread =
true;
123 _trafficDirPaths = dirs;
128 std::lock_guard<std::mutex> g(_lock);
134 for (
const auto&
p : _trafficDirPaths) {
141 std::lock_guard<std::mutex> g(_lock);
148 requiredAircraft =
"";
158 const XMLAttributes & atts)
165 attval = atts.getValue(
"include");
168 SGPath path =
globals->get_fg_root();
169 path.append(
"/Traffic/");
171 readXML(path, *
this);
173 elementValueStack.push_back(
"");
180 const string & value = elementValueStack.back();
182 if (!strcmp(
name,
"model"))
184 else if (!strcmp(
name,
"livery"))
186 else if (!strcmp(
name,
"home-port"))
188 else if (!strcmp(
name,
"registration"))
189 registration = value;
190 else if (!strcmp(
name,
"airline"))
192 else if (!strcmp(
name,
"actype"))
194 else if (!strcmp(
name,
"required-aircraft"))
195 requiredAircraft = value;
196 else if (!strcmp(
name,
"flighttype"))
198 else if (!strcmp(
name,
"radius"))
199 radius =
atoi(value.c_str());
200 else if (!strcmp(
name,
"offset"))
201 offset =
atoi(value.c_str());
202 else if (!strcmp(
name,
"performance-class"))
204 else if (!strcmp(
name,
"heavy")) {
205 if (value ==
string(
"true"))
209 }
else if (!strcmp(
name,
"callsign"))
211 else if (!strcmp(
name,
"fltrules"))
213 else if (!strcmp(
name,
"port"))
215 else if (!strcmp(
name,
"time"))
217 else if (!strcmp(
name,
"departure")) {
218 departurePort = port;
219 departureTime = timeString;
220 }
else if (!strcmp(
name,
"cruise-alt"))
221 cruiseAlt =
atoi(value.c_str());
222 else if (!strcmp(
name,
"arrival")) {
224 arrivalTime = timeString;
225 }
else if (!strcmp(
name,
"repeat"))
227 else if (!strcmp(
name,
"flight")) {
247 if (requiredAircraft ==
"") {
249 snprintf(buffer, 16,
"%d", acCounter);
250 requiredAircraft = buffer;
252 SG_LOG(SG_AI, SG_BULK,
"Adding flight: " << callsign <<
" "
254 << departurePort <<
" "
255 << arrivalPort <<
" "
257 << departureTime <<
" "
258 << arrivalTime <<
" " << repeat <<
" " << requiredAircraft);
261 if (
fgGetBool(
"/sim/traffic-manager/dumpdata") ==
true) {
262 SG_LOG(SG_AI, SG_ALERT,
"Traffic Dump FLIGHT," << callsign <<
","
264 << departurePort <<
","
265 << arrivalPort <<
","
267 << departureTime <<
","
268 << arrivalTime <<
"," << repeat <<
"," << requiredAircraft);
271 _trafficManager->flights[requiredAircraft].push_back(
new FGScheduledFlight(callsign,
280 requiredAircraft =
"";
281 }
else if (!strcmp(
name,
"aircraft")) {
285 elementValueStack.pop_back();
289 void data(
const char *s,
int len)
291 string token = string(s, len);
293 elementValueStack.back() += token;
296 void pi(
const char *target,
const char *
data)
301 void warning(
const char *message,
int line,
int column)
303 SG_LOG(SG_IO, SG_WARN,
304 "Warning: " << message <<
" (" << line <<
',' << column <<
')');
307 void error(
const char *message,
int line,
int column)
309 SG_LOG(SG_IO, SG_ALERT,
310 "Error: " << message <<
" (" << line <<
',' << column <<
')');
316 string isHeavy = heavy ?
"true" :
"false";
318 if (missingModels.find(mdl) != missingModels.end()) {
320 requiredAircraft = homePort =
"";
325 missingModels.insert(mdl);
326 simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::AITrafficSchedule,
"Missing traffic model path:" + mdl, _currentFile);
327 requiredAircraft = homePort =
"";
332 (int) (
fgGetDouble(
"/sim/traffic-manager/proportion") * 100);
333 int randval = rand() & 100;
334 if (randval > proportion) {
335 requiredAircraft = homePort =
"";
339 if (
fgGetBool(
"/sim/traffic-manager/dumpdata") ==
true) {
340 SG_LOG(SG_AI, SG_ALERT,
"Traffic Dump AC," << homePort <<
"," << registration <<
"," << requiredAircraft
341 <<
"," << acType <<
"," << livery <<
","
342 << airline <<
"," << m_class <<
"," << offset <<
"," << radius <<
"," << flighttype <<
"," << isHeavy <<
"," << mdl);
345 if (requiredAircraft ==
"") {
347 snprintf(buffer, 16,
"%d", acCounter);
348 requiredAircraft = buffer;
350 if (homePort ==
"") {
351 homePort = departurePort;
357 _trafficManager->scheduledAircraft.push_back(
new FGAISchedule(mdl,
370 requiredAircraft =
"";
375 void parseTrafficDir(
const SGPath& path)
380 simgear::Dir trafficDir(path);
381 simgear::PathList d = trafficDir.children(simgear::Dir::TYPE_DIR | simgear::Dir::NO_DOT_OR_DOTDOT);
383 simgear::ErrorReportContext(
"ai-traffic-dir", path.utf8Str());
385 for (
const auto&
p : d) {
387 SG_LOG(SG_AI, SG_DEBUG,
"parsing traffic in:" <<
p);
388 simgear::PathList trafficFiles = d2.children(simgear::Dir::TYPE_FILE,
".xml");
389 for (
const auto& xml : trafficFiles) {
396 }
catch (sg_exception& e) {
397 simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::AITrafficSchedule,
398 "XML errors parsing traffic:" + e.getFormattedMessage(), xml);
403 SG_LOG(SG_AI, SG_INFO,
"parsing traffic schedules took:" << st.elapsedMSec() <<
"msec");
406 FGTrafficManager* _trafficManager;
407 mutable std::mutex _lock;
410 simgear::PathList _trafficDirPaths;
418 std::set<std::string> missingModels;
420 std::string mdl, livery, registration, callsign, fltrules,
421 port, timeString, departurePort, departureTime, arrivalPort, arrivalTime,
422 repeat, acType, airline, m_class, flighttype, requiredAircraft, homePort;
424 int score, acCounter;
425 double radius, offset;
436 trafficSyncRequested(false),
437 waitingMetarTime(0.0),
438 enabled(
"/sim/traffic-manager/enabled"),
439 aiEnabled(
"/sim/ai/enabled"),
440 realWxEnabled(
"/environment/realwx/enabled"),
441 metarValid(
"/environment/metar/valid"),
442 active(
"/sim/traffic-manager/active"),
443 aiDataUpdateNow(
"/sim/terrasync/ai-data-update-now")
452void FGTrafficManager::shutdown()
456 scheduleParser.reset();
465 bool saveData =
false;
466 sg_ofstream cachefile;
467 if (
fgGetBool(
"/sim/traffic-manager/heuristics")) {
469 cacheData.append(
"ai");
470 const string airport =
fgGetString(
"/sim/presets/airport-id");
472 if ((airport) !=
"") {
474 ::snprintf(buffer, 128,
"%c/%c/%c/",
475 airport[0], airport[1], airport[2]);
476 cacheData.append(buffer);
477 cacheData.append(airport +
"-cache.txt");
484 SG_LOG(SG_GENERAL, SG_DEBUG,
"Trying to create dir for : " << cacheData);
485 if (!cacheData.exists()) {
486 cacheData.create_dir(0755);
489 cachefile.open(cacheData);
490 cachefile <<
"[TrafficManagerCachedata:ref:2011:09:04]" << endl;
495 for (
auto acft : scheduledAircraft) {
497 cachefile << acft->getRegistration() <<
" "
498 << acft->getRunCount() <<
" "
499 << acft->getHits() <<
" "
500 << acft->getLastUsed() << endl;
507 scheduledAircraft.clear();
509 for (
auto flight : flights) {
510 for (
auto scheduled : flight.second)
515 currAircraft = scheduledAircraft.begin();
518 trafficSyncRequested =
false;
522bool FGTrafficManager::doDataSync()
524 auto terraSync =
globals->get_subsystem<simgear::SGTerraSync>();
525 bool doDataSync =
fgGetBool(
"/sim/terrasync/ai-data-enabled");
526 if (doDataSync && terraSync) {
527 if (!trafficSyncRequested) {
528 SG_LOG(SG_AI, SG_INFO,
"Sync of AI traffic via TerraSync enabled");
529 terraSync->scheduleDataDir(
"AI/Traffic");
530 trafficSyncRequested =
true;
533 if (terraSync->isDataDirPending(
"AI/Traffic")) {
537 trafficSyncRequested =
false;
552 if( !
fgGetBool(
"/sim/signals/fdm-initialized") )
561 if (
string(
fgGetString(
"/sim/traffic-manager/datafile")).empty()) {
562 simgear::PathList dirs =
globals->get_data_paths(
"AI/Traffic");
568 if (dirs.size() > 1) {
569 SGPath
p = dirs.back();
570 if (simgear::strutils::starts_with(
p.utf8Str(),
571 globals->get_fg_root().utf8Str()))
583 scheduleParser->setTrafficDirs(dirs);
584 scheduleParser->start();
586 fgSetBool(
"/sim/traffic-manager/heuristics",
false);
587 SGPath path = string(
fgGetString(
"/sim/traffic-manager/datafile"));
588 if (path.extension() ==
"xml") {
593 readXML(path, parser);
595 }
else if (path.extension() ==
"conf") {
597 readTimeTableFromFile(path);
600 SG_LOG(SG_AI, SG_ALERT,
601 "Unknown data format " << path
611void FGTrafficManager::finishInit()
614 SG_LOG(SG_AI, SG_INFO,
"finishing AI-Traffic init");
618 for (
auto schedule : scheduledAircraft) {
619 schedule->setScore();
620 if (!perfDB->havePerformanceDataForAircraftType(schedule->getAircraft())) {
621 SG_LOG(SG_AI, SG_DEV_WARN,
"AI-Traffic: schedule aircraft missing performance data:" << schedule->getAircraft());
626 currAircraft = scheduledAircraft.begin();
627 currAircraftClosest = scheduledAircraft.begin();
634void FGTrafficManager::loadHeuristics()
636 if (!
fgGetBool(
"/sim/traffic-manager/heuristics")) {
644 cacheData.append(
"ai");
645 string airport =
fgGetString(
"/sim/presets/airport-id");
646 if ((airport) !=
"") {
648 ::snprintf(buffer, 128,
"%c/%c/%c/",
649 airport[0], airport[1], airport[2]);
650 cacheData.append(buffer);
651 cacheData.append(airport +
"-cache.txt");
652 if (cacheData.exists()) {
654 sg_ifstream data(cacheData);
656 if (revisionStr !=
"[TrafficManagerCachedata:ref:2011:09:04]") {
657 SG_LOG(SG_AI, SG_ALERT,
"Traffic Manager Warning: discarding outdated cachefile " <<
658 cacheData <<
" for Airport " << airport);
666 if (itr != heurMap.end()) {
667 SG_LOG(SG_AI, SG_DEV_WARN,
"Traffic Manager Warning: found duplicate tailnumber " <<
677 for(currAircraft = scheduledAircraft.begin(); currAircraft != scheduledAircraft.end(); ++currAircraft) {
678 const string& registration = (*currAircraft)->getRegistration();
680 if (itr != heurMap.end()) {
681 (*currAircraft)->setrunCount(itr->second.runCount);
682 (*currAircraft)->setHits(itr->second.hits);
683 (*currAircraft)->setLastUsed(itr->second.lastRun);
688bool FGTrafficManager::metarReady(
double dt)
692 if (metarValid || !realWxEnabled)
694 waitingMetarTime = 0.0;
699 if (waitingMetarStation !=
fgGetString(
"/environment/metar/station-id"))
702 waitingMetarTime = 0.0;
703 waitingMetarStation =
fgGetString(
"/environment/metar/station-id");
708 if (waitingMetarTime > 20.0)
713 waitingMetarTime += dt;
721 if (inited || doingInit)
731 aiDataUpdateNow =
false;
745 if (!doingInit || !scheduleParser->isFinished()) {
753 if (scheduledAircraft.empty()) {
757 SGVec3d userCart =
globals->get_aircraft_position_cart();
759 if (currAircraft == scheduledAircraft.end()) {
760 currAircraft = scheduledAircraft.begin();
763 time_t now =
globals->get_time_params()->get_cur_time();
766 if ((*currAircraft)->update(now, userCart)) {
772void FGTrafficManager::readTimeTableFromFile(SGPath infileName)
788 vector <string> tokens, depTime,arrTime;
790 sg_ifstream infile(infileName);
792 infile.getline(buffer, 256);
797 string buffString = string(buffer);
799 Tokenize(buffString, tokens,
" \t");
804 if (!tokens.empty()) {
805 if (tokens[0] ==
string(
"AC")) {
806 if (tokens.size() != 13) {
807 throw sg_io_exception(
"Error parsing traffic file @ " + buffString, infileName);
812 homePort = tokens[1];
813 registration = tokens[2];
814 if (tokens[11] ==
string(
"false")) {
821 flightReq = tokens[3] + tokens[5];
822 m_class = tokens[10];
823 FlightType = tokens[9];
824 radius =
atof(tokens[8].c_str());
825 offset =
atof(tokens[7].c_str());;
828 simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::AITrafficSchedule,
"Missing traffic model path:" + model, infileName);
830 SG_LOG(SG_AI, SG_DEBUG,
"Adding Aircraft" << model <<
" " << livery <<
" " << homePort <<
" " << registration <<
" " << flightReq <<
" " << isHeavy <<
" " << acType <<
" " << airline <<
" " << m_class <<
" " << FlightType <<
" " << radius <<
" " << offset);
831 scheduledAircraft.push_back(
new FGAISchedule(model,
845 if (tokens[0] ==
string(
"FLIGHT")) {
847 if (tokens.size() != 10) {
848 SG_LOG(SG_AI, SG_ALERT,
"Error parsing traffic file " << infileName <<
" at " << buffString);
851 string callsign = tokens[1];
852 string fltrules = tokens[2];
853 string weekdays = tokens[3];
854 string departurePort = tokens[5];
855 string arrivalPort = tokens[7];
856 int cruiseAlt =
atoi(tokens[8].c_str());
857 string depTimeGen = tokens[4];
858 string arrTimeGen = tokens[6];
859 string repeat =
"WEEK";
860 string requiredAircraft = tokens[9];
862 if (weekdays.size() != 7) {
863 SG_LOG(SG_AI, SG_ALERT,
"Found misconfigured weekdays string" << weekdays);
868 Tokenize(depTimeGen, depTime,
":");
869 Tokenize(arrTimeGen, arrTime,
":");
870 double dep =
atof(depTime[0].c_str()) + (
atof(depTime[1].c_str()) / 60.0);
871 double arr =
atof(arrTime[0].c_str()) + (
atof(arrTime[1].c_str()) / 60.0);
873 bool arrivalWeekdayNeedsIncrement =
false;
875 arrivalWeekdayNeedsIncrement =
true;
877 for (
int i = 0;
i < 7;
i++) {
879 if (weekdays[
i] !=
'.') {
881 snprintf(l_buffer, 4,
"%d/", j);
882 string departureTime = string(l_buffer) + depTimeGen + string(
":00");
884 if (!arrivalWeekdayNeedsIncrement) {
885 arrivalTime = string(l_buffer) + arrTimeGen + string(
":00");
887 if (arrivalWeekdayNeedsIncrement &&
i != 6 ) {
888 snprintf(l_buffer, 4,
"%d/", j+1);
889 arrivalTime = string(l_buffer) + arrTimeGen + string(
":00");
891 if (arrivalWeekdayNeedsIncrement &&
i == 6 ) {
892 snprintf(l_buffer, 4,
"%d/", 0);
893 arrivalTime = string(l_buffer) + arrTimeGen + string(
":00");
895 SG_LOG(SG_AI, SG_ALERT,
"Adding flight " << callsign <<
" "
897 << departurePort <<
" "
898 << arrivalPort <<
" "
900 << departureTime <<
" "
901 << arrivalTime <<
" "
903 << requiredAircraft);
905 flights[requiredAircraft].push_back(
new FGScheduledFlight(callsign,
924void FGTrafficManager::Tokenize(
const string& str,
925 vector<string>& tokens,
926 const string& delimiters)
929 string::size_type lastPos = str.find_first_not_of(delimiters, 0);
931 string::size_type pos = str.find_first_of(delimiters, lastPos);
933 while (string::npos != pos || string::npos != lastPos)
936 tokens.push_back(str.substr(lastPos, pos - lastPos));
938 lastPos = str.find_first_not_of(delimiters, pos);
940 pos = str.find_first_of(delimiters, lastPos);
947 SGSubsystemMgr::POST_FDM,
948 {{
"terrasync", SGSubsystemMgr::Dependency::HARD},
949 {
"PerformanceDB", SGSubsystemMgr::Dependency::HARD}});
SGSubsystemMgr::Registrant< FGTrafficManager > registrantFGTrafficManager(SGSubsystemMgr::POST_FDM, {{"terrasync", SGSubsystemMgr::Dependency::HARD}, {"PerformanceDB", SGSubsystemMgr::Dependency::HARD}})
HeuristicMap::iterator HeuristicMapIterator
std::map< std::string, Heuristic > HeuristicMap
static bool validModelPath(const std::string &model)
static bool compareSchedules(const FGAISchedule *a, const FGAISchedule *b)
const SGPath & get_fg_home() const
void update(double time) override
friend class ScheduleParseThread
virtual ~FGTrafficManager()
void pi(const char *target, const char *data)
void endElement(const char *name)
void error(const char *message, int line, int column)
ScheduleParseThread(FGTrafficManager *traffic)
void warning(const char *message, int line, int column)
void startElement(const char *name, const XMLAttributes &atts)
void setTrafficDirs(const PathList &dirs)
void data(const char *s, int len)
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
std::vector< SGPath > PathList
std::vector< std::string > string_list
static double atof(const string &str)
static int atoi(const string &str)
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
bool fgSetBool(char const *name, bool val)
Set a bool value for a property.
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.