13#include <simgear/misc/sg_dir.hxx>
14#include <simgear/props/props_io.hxx>
15#include <simgear/structure/commands.hxx>
16#include <simgear/structure/exception.hxx>
17#include <simgear/structure/subsystem_mgr.hxx>
145 in.read(
reinterpret_cast<char*
>(&data),
sizeof(data));
150static void writeRaw(std::ostream& out,
const T& data)
152 out.write(
reinterpret_cast<const char*
>(&data),
sizeof(data));
160 std::vector<char> buffer(buffer_len);
161 in.read(&buffer.front(), buffer.size());
162 readProperties(&buffer.front(), buffer.size() - 1, node);
166static void popupTip(
const char* message,
int delay)
168 SGPropertyNode_ptr args(
new SGPropertyNode);
169 args->setStringValue(
"label", message);
170 args->setIntValue(
"delay", delay);
171 globals->get_commands()->execute(
"show-message", args);
177 std::string tape_directory =
fgGetString(
"/sim/replay/tape-directory",
"");
178 std::string aircraftType =
fgGetString(
"/sim/aircraft",
"unknown");
179 if (path_timeless) *path_timeless =
"";
180 SGPath path = SGPath(tape_directory);
181 path.append(aircraftType);
183 path.concat(
"-recovery");
185 if (path_timeless) *path_timeless = path;
186 time_t calendar_time = time(NULL);
188 local_tm = localtime(&calendar_time);
190 strftime(time_str, 256,
"-%Y%m%d-%H%M%S", local_tm);
191 path.concat(time_str);
194 path.concat(
"-continuous");
195 if (path_timeless) path_timeless->concat(
"-continuous");
197 path.concat(
".fgtape");
198 if (path_timeless) path_timeless->concat(
".fgtape");
223 fgGetNode(
"/sim/replay/messages", 0,
true)->removeChildren(
"msg");
261 simgear::PropertyList msgs =
fgGetNode(
"/sim/replay/messages",
true)->getChildren(
"msg");
263 for (
auto it = msgs.begin(); it != msgs.end(); ++it) {
264 std::string msgText = (*it)->getStringValue(
"text",
"");
265 const double msgTime = (*it)->getDoubleValue(
"time", -1.0);
266 std::string msgSpeaker = (*it)->getStringValue(
"speaker",
"pilot");
267 if (!msgText.empty() && msgTime >= 0) {
283 for (
int i = 0;
i < estNrObjects;
i++) {
288 SG_LOG(SG_SYSTEMS, SG_ALERT,
"ReplaySystem: Out of memory!");
334printTimeStr(
char* pStrBuffer,
int bufferSize,
double _Time,
bool ShowDecimal =
true)
338 unsigned int Time = _Time * 10;
339 unsigned int h = Time / 36000;
340 unsigned int m = (Time % 36000) / 600;
341 unsigned int s = (Time % 600) / 10;
342 unsigned int d = Time % 10;
346 len = snprintf(pStrBuffer, bufferSize,
"%u:%02u:%02u", h, m, s);
348 len = snprintf(pStrBuffer, bufferSize,
"%u:%02u", m, s);
356 snprintf(&pStrBuffer[len], bufferSize - len,
".%u", d);
360static void setTimeStr(
const char* property_path,
double t,
bool show_decimal =
false)
376 std::lock_guard<std::mutex> lock(self.
m_continuous->m_in_time_to_frameinfo_lock);
377 if (!self.
m_continuous->m_in_time_to_frameinfo.empty()) {
378 ret = self.
m_continuous->m_in_time_to_frameinfo.begin()->first;
379 SG_LOG(SG_SYSTEMS, SG_DEBUG,
381 <<
" m_in_time_to_frameinfo is "
382 << self.
m_continuous->m_in_time_to_frameinfo.begin()->first
384 << self.
m_continuous->m_in_time_to_frameinfo.rbegin()->first);
399 setTimeStr(
"/sim/replay/start-time-str", ret);
405 std::lock_guard<std::mutex> lock(self.
m_continuous->m_in_time_to_frameinfo_lock);
406 if (!self.
m_continuous->m_in_time_to_frameinfo.empty()) {
407 double ret = self.
m_continuous->m_in_time_to_frameinfo.rbegin()->first;
408 SG_LOG(SG_SYSTEMS, SG_DEBUG,
410 <<
" m_in_time_to_frameinfo is "
411 << self.
m_continuous->m_in_time_to_frameinfo.begin()->first
413 << self.
m_continuous->m_in_time_to_frameinfo.rbegin()->first);
438 fgSetString(
"/sim/replay/start-time-str", StrBuffer);
440 fgSetString(
"/sim/replay/end-time-str", StrBuffer);
471 const T_MsgHdr* header =
reinterpret_cast<const T_MsgHdr*
>(&raw_message.front());
475static bool CallsignsEqual(
const std::vector<char>& a_message,
const std::vector<char>& b_message)
491 auto it = list.begin();
494 if (it == list.end()) {
519 simgear::gzContainerWriter& output,
520 const std::deque<FGReplayData*>& replay_data,
522 SGPropertyNode* meta)
525 size_t count = replay_data.size();
529 SG_LOG(SG_SYSTEMS, SG_ALERT,
"Failed to save replay data."
530 <<
" Cannot write data container. Disk full?");
535 auto it = replay_data.begin();
536 size_t check_count = 0;
537 while (it != replay_data.end() && !output.fail()) {
539 assert(record_size == frame->
raw_data.size());
543 for (
auto data : meta->getNode(
"meta")->getChildren(
"data")) {
544 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"data->getStringValue()=" << data->getStringValue());
545 if (data->getStringValue() ==
"multiplayer") {
548 length +=
sizeof(uint16_t) + message->size();
552 uint16_t message_size = message->size();
554 output.write(&message->front(), message_size);
563 if (check_count != count) {
564 SG_LOG(SG_SYSTEMS, SG_ALERT,
"Failed to save replay data."
565 " Expected to write "
566 << count <<
" records"
567 <<
", but wrote " << check_count);
571 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"Saved " << check_count <<
" records"
572 <<
" of size " << record_size);
573 return !output.fail();
578 const SGPropertyNode* extra,
582 int continuous_compression)
584 SGPropertyNode_ptr config;
587 SG_LOG(SG_SYSTEMS, SG_ALERT,
"Error, flight recorder tape file with same name already exists: " << path);
591 config =
new SGPropertyNode;
592 SGPropertyNode* meta = config->getNode(
"meta", 0 ,
true );
595 meta->setStringValue(
"aircraft-type",
fgGetString(
"/sim/aircraft",
"unknown"));
596 meta->setStringValue(
"aircraft-description",
fgGetString(
"/sim/description",
""));
597 meta->setStringValue(
"aircraft-fdm",
fgGetString(
"/sim/flight-model",
""));
598 meta->setStringValue(
"closest-airport-id",
fgGetString(
"/sim/airport/closest-airport-id",
""));
599 meta->setStringValue(
"aircraft-version",
fgGetString(
"/sim/aircraft-version",
"(undefined)"));
601 meta->setIntValue(
"continuous-compression", continuous_compression);
604 meta->setDoubleValue(
"tape-duration", duration);
607 meta->setStringValue(
"tape-duration-str", buffer);
610 copyProperties(
fgGetNode(
"/sim/version", 0,
true), meta->getNode(
"version", 0,
true));
611 if (extra && extra->getNode(
"user-data")) {
612 copyProperties(extra->getNode(
"user-data"), meta->getNode(
"user-data", 0,
true));
616 config->addChild(
"data")->setStringValue(
"signals");
620 if (
fgGetBool(
"/sim/replay/record-multiplayer",
false)) {
621 config->addChild(
"data")->setStringValue(
"multiplayer");
623 if (0 ||
fgGetBool(
"/sim/replay/record-extra-properties",
false) ||
fgGetBool(
"/sim/replay/record-main-window",
false) ||
fgGetBool(
"/sim/replay/record-main-view",
false)) {
624 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"Adding data[]=extra-properties."
625 <<
" record-extra-properties=" <<
fgGetBool(
"/sim/replay/record-extra-properties",
false) <<
" record-main-window=" <<
fgGetBool(
"/sim/replay/record-main-window",
false) <<
" record-main-view=" <<
fgGetBool(
"/sim/replay/record-main-view",
false));
626 config->addChild(
"data")->setStringValue(
"extra-properties");
631 copyProperties(
fgGetNode(
"/sim/replay/messages", 0,
true), meta->getNode(
"messages", 0,
true));
639 <<
" offset=" << frame_info.
offset
674 int* xsize =
nullptr,
675 int* ysize =
nullptr)
677 self.
m_flight_recorder->replay(time, current_frame, old_frame, xpos, ypos, xsize, ysize);
688 }
else if (list.size() == 1) {
693 unsigned int last = list.size() - 1;
694 unsigned int first = 0;
695 unsigned int mid = (last + first) / 2;
702 }
else if (list[mid]->sim_time < time && list[mid + 1]->sim_time < time) {
705 mid = (last + first) / 2;
706 }
else if (list[mid]->sim_time > time && list[mid + 1]->sim_time > time) {
709 mid = (last + first) / 2;
734 }
else if (time <= t1 && time >= t2) {
739 if (time <= t1 && time >= t2) {
744 if (time <= t1 && time >= t2) {
749 if (time <= t1 && time >= t2) {
754 if (time <= t1 && time >= t2) {
788 std::lock_guard<std::mutex> lock(self.
m_continuous->m_in_time_to_frameinfo_lock);
790 if (!self.
m_continuous->m_in_time_to_frameinfo.empty()) {
824 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"End replay");
843 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"Unloading continuous recording");
856 if (current_replay_state == 3) {
873 if (replay_state == 1)
880 switch (replay_state) {
889 bool reset_time = (current_time <= 0.0);
897 if (duration && duration < (endTime - startTime)) {
898 current_time = endTime - duration;
900 current_time = startTime;
909 SG_LOG(SG_GENERAL, SG_BULK,
"current_time=" << std::fixed << std::setprecision(6) << current_time);
911 bool is_finished =
replay(*
this, current_time);
952 throw sg_range_exception(
"unknown FGReplayInternal state");
958 if (!
fgGetBool(
"/sim/fdm-initialized",
false) || dt == 0.0)
967 SG_LOG(SG_SYSTEMS, SG_ALERT,
"ReplaySystem: Time warp detected!");
978 SG_LOG(SG_SYSTEMS, SG_ALERT,
"ReplaySystem: Out of memory!");
993 SG_LOG(SG_SYSTEMS, SG_ALERT,
"ReplaySystem: Inconsistent data!");
1000 if (replay_state == 0) {
1004 if (recovery_period_s > 0) {
1005 static time_t s_last_recovery = 0;
1006 time_t t = time(NULL);
1007 if (s_last_recovery == 0) {
1009 s_last_recovery = t;
1014 if (t - s_last_recovery >= recovery_period_s) {
1015 s_last_recovery = t;
1021 static SGPath path_temp = SGPath(path.str() +
"-");
1022 static SGPropertyNode_ptr Config;
1024 SG_LOG(SG_SYSTEMS, SG_BULK,
"Creating recovery file: " << path);
1030 (void)remove(path_temp.c_str());
1039 if (!config) ok =
false;
1043 rename(path_temp.c_str(), path.c_str());
1045 std::string message =
"Failed to update recovery snapshot file '" + path.str() +
"';" +
" See File / Flight Recorder Control / 'Maintain recovery snapshot'.";
1121 simgear::gzContainerReader& input,
1123 std::deque<FGReplayData*>& replay_data,
1126 bool multiplayer_legacy)
1132 if (!input.readContainerHeader(&Type, &size)) {
1133 SG_LOG(SG_SYSTEMS, SG_ALERT,
"Failed to load replay data. Missing data container.");
1136 SG_LOG(SG_SYSTEMS, SG_ALERT,
"Failed to load replay data. Expected data container, got " << Type);
1140 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"multiplayer=" << multiplayer <<
" record_size=" << record_size <<
" Type=" << Type <<
" size=" << size);
1143 size_t count = size / record_size;
1144 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"Loading replay data. Container size is " << size <<
", record size " << record_size <<
", expected record count " << count <<
".");
1146 size_t check_count = 0;
1147 for (check_count = 0; (check_count < count) && (!input.eof()); ++check_count) {
1150 buffer->
raw_data.resize(record_size);
1151 input.read(&buffer->
raw_data.front(), record_size);
1152 replay_data.push_back(buffer);
1155 if (multiplayer_legacy) {
1156 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"reading Normal multiplayer recording with legacy format");
1157 size_t num_messages = 0;
1158 input.read(
reinterpret_cast<char*
>(&num_messages),
sizeof(num_messages));
1159 for (
size_t i = 0;
i < num_messages; ++
i) {
1160 size_t message_size;
1161 input.read(
reinterpret_cast<char*
>(&message_size),
sizeof(message_size));
1162 std::shared_ptr<std::vector<char>> message(
new std::vector<char>(message_size));
1163 input.read(&message->front(), message_size);
1167 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"reading Normal multiplayer recording");
1172 if (pos == length)
break;
1173 assert(pos < length);
1174 uint16_t message_size;
1176 pos +=
sizeof(message_size) + message_size;
1178 SG_LOG(SG_SYSTEMS, SG_ALERT,
"Tape appears to have corrupted multiplayer data"
1179 <<
" length=" << length <<
" message_size=" << message_size <<
" pos=" << pos);
1182 auto message = std::make_shared<std::vector<char>>(message_size);
1183 input.read(&message->front(), message_size);
1191 if (check_count != count) {
1193 SG_LOG(SG_SYSTEMS, SG_ALERT,
"Unexpected end of file.");
1195 SG_LOG(SG_SYSTEMS, SG_ALERT,
"Failed to load replay data. Expected " << count <<
" records, but got " << check_count);
1199 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"Loaded " << check_count <<
" records of size " << record_size);
1211 if (!output.good()) {
1212 SG_LOG(SG_SYSTEMS, SG_ALERT,
"Cannot open file" << filename);
1216 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"writing MetaData:");
1221 SGPropertyNode_ptr config;
1223 config =
new SGPropertyNode();
1230 size_t record_size = config->getIntValue(
"recorder/record-size", 0);
1231 SG_LOG(SG_SYSTEMS, SG_DEBUG,
""
1232 <<
"Config:recorder/signal-count=" << config->getIntValue(
"recorder/signal-count", 0) <<
" RecordSize: " << record_size);
1245 if (!ok) SG_LOG(SG_SYSTEMS, SG_ALERT,
"Failed to write to file " << filename);
1252 SGPath path_timeless;
1254 SGPropertyNode_ptr metadata =
saveSetup(
1270 path_timeless.remove();
1271 bool ok_link = path_timeless.makeLink(path.file());
1273 SG_LOG(SG_SYSTEMS, SG_ALERT,
"Failed to create link " << path_timeless.c_str() <<
" => " << path.file());
1278 guiMessage(
"Flight recorder tape saved successfully!");
1280 guiMessage(
"Failed to save tape! See log output.");
1294 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"Failed to open path=" << path);
1298 in->read(&buffer.front(), buffer.size());
1299 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"in->gcount()=" << in->gcount() <<
" buffer.size()=" << buffer.size());
1300 if ((
size_t)in->gcount() != buffer.size()) {
1305 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"fgtape prefix doesn't match FlightRecorderFileMagic in path: " << path);
1312 }
catch (std::exception& e) {
1313 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"Failed to read Config properties in: " << path <<
": " << e.what());
1319 SG_LOG(SG_SYSTEMS, SG_BULK,
"properties is:\n"
1320 << writePropertiesInline(properties,
true ) <<
"\n");
1333 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"Indexing Continuous recording "
1334 <<
" data=" << data <<
" numbytes=" << numbytes <<
" m_indexing_pos=" << self.
m_continuous->m_indexing_pos <<
" m_in_compression=" << self.
m_continuous->m_in_compression <<
" m_in_time_to_frameinfo.size()=" << self.
m_continuous->m_in_time_to_frameinfo.size());
1335 time_t t0 = time(NULL);
1336 std::streampos original_pos = self.
m_continuous->m_indexing_pos;
1337 size_t original_num_frames = self.
m_continuous->m_in_time_to_frameinfo.size();
1342 struct data_stats_t {
1343 size_t num_frames = 0;
1346 std::map<std::string, data_stats_t> stats;
1349 SG_LOG(SG_SYSTEMS, SG_BULK,
"reading frame."
1350 <<
" m_in.tellg()=" << self.
m_continuous->m_in.tellg());
1355 SG_LOG(SG_SYSTEMS, SG_BULK,
""
1356 <<
" m_indexing_pos=" << self.
m_continuous->m_indexing_pos <<
" m_indexing_in.tellg()=" << self.
m_continuous->m_indexing_in.tellg() <<
" sim_time=" << sim_time);
1363 self.
m_continuous->m_indexing_in.read((
char*)&flags,
sizeof(flags));
1369 stats[
"signals"].num_frames += 1;
1372 stats[
"multiplayer"].num_frames += 1;
1377 stats[
"extra-properties"].num_frames += 1;
1382 uint32_t compressed_size;
1384 SG_LOG(SG_SYSTEMS, SG_BULK,
"compressed_size=" << compressed_size);
1386 self.
m_continuous->m_indexing_in.seekg(compressed_size, std::ios_base::cur);
1389 auto datas = self.
m_continuous->m_in_config->getChildren(
"data");
1390 SG_LOG(SG_SYSTEMS, SG_BULK,
"datas.size()=" << datas.size());
1391 for (
auto l_data : datas) {
1394 SG_LOG(SG_SYSTEMS, SG_BULK,
1395 "m_in.tellg()=" << self.
m_continuous->m_indexing_in.tellg()
1396 <<
" Skipping data_type=" << l_data->getStringValue()
1397 <<
" length=" << length);
1399 self.
m_continuous->m_indexing_in.seekg(length, std::ios_base::cur);
1405 std::string data_type = l_data->getStringValue();
1406 stats[data_type].num_frames += 1;
1407 stats[data_type].bytes += length;
1408 if (data_type ==
"signals") {
1410 }
else if (data_type ==
"multiplayer") {
1414 }
else if (data_type ==
"extra-properties") {
1423 SG_LOG(SG_SYSTEMS, SG_BULK,
""
1424 <<
" pos=" << self.
m_continuous->m_indexing_pos <<
" sim_time=" << sim_time <<
" m_num_frames_multiplayer=" << self.
m_continuous->m_num_frames_multiplayer <<
" m_num_frames_extra_properties=" << self.
m_continuous->m_num_frames_extra_properties);
1431 SG_LOG(SG_SYSTEMS, SG_BULK,
"m_indexing_in failed, giving up");
1439 std::lock_guard<std::mutex> lock(self.
m_continuous->m_in_time_to_frameinfo_lock);
1440 self.
m_continuous->m_in_time_to_frameinfo[sim_time] = frameinfo;
1442 time_t t = time(NULL) - t0;
1443 auto new_bytes = self.
m_continuous->m_indexing_pos - original_pos;
1444 auto num_frames = self.
m_continuous->m_in_time_to_frameinfo.size();
1445 auto num_new_frames = num_frames - original_num_frames;
1446 if (num_new_frames) {
1447 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"Continuous recording: index updated:"
1448 <<
" num_frames=" << num_frames <<
" num_new_frames=" << num_new_frames <<
" new_bytes=" << new_bytes);
1450 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"Continuous recording indexing complete."
1451 <<
" time taken=" << t <<
"s."
1452 <<
" num_new_frames=" << num_new_frames <<
" m_indexing_pos=" << self.
m_continuous->m_indexing_pos <<
" m_in_time_to_frameinfo.size()=" << self.
m_continuous->m_in_time_to_frameinfo.size() <<
" m_num_frames_multiplayer=" << self.
m_continuous->m_num_frames_multiplayer <<
" m_num_frames_extra_properties=" << self.
m_continuous->m_num_frames_extra_properties);
1453 for (
auto stat : stats) {
1454 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"data type " << stat.first <<
":"
1455 <<
" num_frames=" << stat.second.num_frames <<
" bytes=" << stat.second.bytes);
1458 std::lock_guard<std::mutex> lock(self.
m_continuous->m_in_time_to_frameinfo_lock);
1459 fgSetInt(
"/sim/replay/continuous-stats-num-frames", self.
m_continuous->m_in_time_to_frameinfo.size());
1460 fgSetInt(
"/sim/replay/continuous-stats-num-frames-extra-properties", self.
m_continuous->m_num_frames_extra_properties);
1461 fgSetInt(
"/sim/replay/continuous-stats-num-frames-multiplayer", self.
m_continuous->m_num_frames_multiplayer);
1462 if (!self.
m_continuous->m_in_time_to_frameinfo.empty()) {
1463 double t_begin = self.
m_continuous->m_in_time_to_frameinfo.begin()->first;
1464 double t_end = self.
m_continuous->m_in_time_to_frameinfo.rbegin()->first;
1467 setTimeStr(
"/sim/replay/start-time-str", t_begin);
1468 setTimeStr(
"/sim/replay/end-time-str", t_end);
1469 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"Have set /sim/replay/end-time to " <<
fgGetDouble(
"/sim/replay/end-time"));
1472 SG_LOG(SG_SYSTEMS, SG_ALERT,
"Continuous recording: indexing finished"
1473 <<
" m_in_time_to_frameinfo.size()=" << self.
m_continuous->m_in_time_to_frameinfo.size());
1482 const SGPath& filename,
1486 SGPropertyNode& meta_meta,
1487 simgear::HTTP::FileRequestRef file_request)
1490 SG_LOG(SG_SYSTEMS, SG_BULK,
"m_in_config is:\n"
1491 << writePropertiesInline(continuous->m_in_config,
true ) <<
"\n");
1492 copyProperties(continuous->m_in_config->getNode(
"meta", 0,
true), &meta_meta);
1498 clear(replay_internal);
1500 continuous->m_in_time_last = -1;
1501 continuous->m_in_frame_time_last = -1;
1502 continuous->m_in_time_to_frameinfo.clear();
1503 continuous->m_num_frames_extra_properties = 0;
1504 continuous->m_num_frames_multiplayer = 0;
1505 continuous->m_indexing_in.open(filename.str());
1506 continuous->m_indexing_pos = in.tellg();
1507 continuous->m_in_compression = continuous
1508 ->m_in_config->getNode(
"meta/continuous-compression",
true )
1510 continuous->m_replay_create_video = create_video;
1511 continuous->m_replay_fixed_dt = fixed_dt;
1512 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"m_in_compression=" << continuous->m_in_compression);
1513 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"filerequest=" << file_request.get());
1517 auto p_replay_internal = &replay_internal;
1518 file_request->setCallback(
1519 [p_replay_internal](
const void* data,
size_t numbytes) {
1526 if (continuous->m_replay_fixed_dt) {
1527 SG_LOG(SG_GENERAL, SG_ALERT,
"Replaying with fixed_dt=" << continuous->m_replay_fixed_dt);
1528 continuous->m_replay_fixed_dt_prev =
fgGetDouble(
"/sim/time/fixed-dt");
1529 fgSetDouble(
"/sim/time/fixed-dt", continuous->m_replay_fixed_dt);
1531 continuous->m_replay_fixed_dt_prev = -1;
1535 if (continuous->m_replay_create_video) {
1536 SG_LOG(SG_GENERAL, SG_ALERT,
"Replaying with create-video");
1539 ok = view_mgr->video_start(
1547 SG_LOG(SG_GENERAL, SG_ALERT,
"Cannot handle create_video=true because FGViewMgr not available");
1552 if (ok) ok = replay_internal.
start(
true );
1575 std::stringstream buffer;
1578 return buffer.str();
1590 const SGPath& filename,
1594 SGPropertyNode& meta_meta,
1595 simgear::HTTP::FileRequestRef file_request)
1597 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"loading Preview=" << preview <<
" Filename=" << filename);
1601 std::ifstream in_preview;
1602 std::ifstream& in(preview ? in_preview :
m_continuous->m_in);
1603 in.open(filename.str());
1605 SG_LOG(SG_SYSTEMS, SG_ALERT,
"Failed to open"
1606 <<
" Filename=" << filename.str() <<
" in.is_open()=" << in.is_open());
1609 size_t file_size = filename.sizeInBytes();
1610 meta_meta.setLongValue(
"tape-size", file_size);
1611 meta_meta.setStringValue(
"tape-size-str",
numberCommas(file_size));
1615 return loadTapeContinuous(*
this, in, filename, preview, create_video, fixed_dt, meta_meta, file_request);
1621 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"Cannot load filename=" << filename <<
" because it is download but not Continuous recording");
1628 if (input.eof() || !input.good()) {
1629 SG_LOG(SG_SYSTEMS, SG_ALERT,
"Cannot open file " << filename);
1633 SGPropertyNode_ptr meta_data_props =
new SGPropertyNode();
1637 char* meta_data = NULL;
1640 if (!input.readContainer(&type, &meta_data, &size) || size < 1) {
1641 SG_LOG(SG_SYSTEMS, SG_ALERT,
"File not recognized. This is not a valid FlightGear flight recorder tape: " << filename <<
". Invalid meta data.");
1644 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"Invalid header. Container type " << type);
1645 SG_LOG(SG_SYSTEMS, SG_ALERT,
"File not recognized. This is not a valid FlightGear flight recorder tape: " << filename);
1648 SG_LOG(SG_SYSTEMS, SG_BULK,
"meta_data is:\n"
1651 readProperties(meta_data, size - 1, meta_data_props);
1652 copyProperties(meta_data_props->getNode(
"meta", 0,
true), &meta_meta);
1653 }
catch (
const sg_exception& exc) {
1654 SG_LOG(SG_SYSTEMS, SG_ALERT,
"Error reading flight recorder tape: " << filename <<
", XML parser message:" << exc.getFormattedMessage());
1667 if (ok && !preview) {
1668 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"Loading flight recorder data...");
1669 char* config_xml =
nullptr;
1672 if (!input.readContainer(&type, &config_xml, &size) || size < 1) {
1673 SG_LOG(SG_SYSTEMS, SG_ALERT,
"File not recognized."
1674 <<
" This is not a valid FlightGear flight recorder tape: " << filename <<
". Invalid configuration container.");
1677 SG_LOG(SG_SYSTEMS, SG_ALERT,
"File not recognized."
1678 " This is not a valid FlightGear flight recorder tape: "
1679 << filename <<
". Unexpected container type, expected \"properties\".");
1683 SGPropertyNode_ptr config =
new SGPropertyNode();
1685 SG_LOG(SG_SYSTEMS, SG_BULK,
"config_xml is:\n"
1688 readProperties(config_xml, size - 1, config);
1689 }
catch (
const sg_exception& exc) {
1690 SG_LOG(SG_SYSTEMS, SG_ALERT,
"Error reading flight recorder tape: " << filename <<
", XML parser message:" << exc.getFormattedMessage());
1709 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"RecordSize=" << record_size);
1710 size_t original_size = config->getIntValue(
"recorder/record-size", 0);
1711 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"OriginalSize=" << original_size);
1714 if (original_size != record_size && original_size != 0) {
1716 SG_LOG(SG_SYSTEMS, SG_ALERT,
"Error: Data inconsistency."
1717 <<
" Flight recorder tape has record size " << record_size <<
", expected size was " << original_size <<
".");
1720 bool multiplayer =
false;
1721 bool multiplayer_legacy =
false;
1722 for (
auto data : meta_meta.getChildren(
"data")) {
1723 if (data->getStringValue() ==
"multiplayer") {
1728 multiplayer_legacy = meta_meta.getBoolValue(
"multiplayer", 0);
1729 if (multiplayer_legacy) {
1733 SG_LOG(SG_SYSTEMS, SG_ALERT,
"multiplayer=" << multiplayer);
1741 meta_data_props->getNode(
"messages", 0,
true),
1742 fgGetNode(
"/sim/replay/messages", 0,
true));
1755 guiMessage(
"Flight recorder tape loaded successfully!");
1758 guiMessage(
"Failed to load tape. See log output.");
1767bool listTapes(
bool same_aircraft_filter,
const SGPath& tape_directory)
1769 const std::string aircraft_type = simgear::strutils::uppercase(
fgGetString(
"/sim/aircraft",
"unknown"));
1772 simgear::Dir dir(tape_directory);
1773 simgear::PathList list = dir.children(simgear::Dir::TYPE_FILE,
".fgtape");
1775 SGPropertyNode* tape_list =
fgGetNode(
"/sim/replay/tape-list",
true);
1776 tape_list->removeChildren(
"tape");
1778 size_t l = aircraft_type.size();
1779 for (
auto it = list.begin(); it != list.end(); ++it) {
1780 SGPath file(it->file());
1781 std::string
name(file.base());
1782 if (!same_aircraft_filter || !simgear::strutils::uppercase(
name).compare(0, l, aircraft_type)) {
1783 tape_list->getNode(
"tape", index++,
true)->setStringValue(
name);
1791 std::string path = tape_name;
1792 if (simgear::strutils::ends_with(path,
".fgtape")) {
1795 SGPath path2(
fgGetString(
"/sim/replay/tape-directory",
""));
1796 path2.append(path +
".fgtape");
1802 return ::loadContinuousHeader(path, in, properties);
1808 SGPath tape_directory(
fgGetString(
"/sim/replay/tape-directory",
""));
1811 bool preview = config_data->getBoolValue(
"preview", 0);
1814 std::string tape = config_data->getStringValue(
"tape",
"");
1817 if (preview)
return true;
1818 return listTapes(config_data->getBoolValue(
"same-aircraft", 0), tape_directory);
1820 SGPropertyNode* meta_meta =
fgGetNode(
"/sim/gui/dialogs/flightrecorder/preview",
true);
1822 SG_LOG(SG_SYSTEMS, SG_DEBUG,
"Checking flight recorder file " << path <<
", preview: " << preview);
1826 config_data->getBoolValue(
"create-video"),
1827 config_data->getDoubleValue(
"fixed-dt", -1),
bool continuousWriteFrame(Continuous &continuous, FGReplayData *r, std::ostream &out, SGPropertyNode_ptr config, FGTapeType tape_type)
bool replayContinuous(FGReplayInternal &self, double time)
void continuous_replay_video_end(Continuous &continuous)
SGPropertyNode_ptr continuousWriteHeader(Continuous &continuous, FGFlightRecorder *flight_recorder, std::ofstream &out, const SGPath &path, FGTapeType tape_type)
static void popupTip(const char *message, int delay)
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
bool fgSetInt(const char *name, int val)
Set an int value for a property.
@ Properties
XML data describing the recorded flight recorder properties.
@ MetaData
XML data / properties with arbitrary data, such as description, aircraft type, ...
@ RawData
Actual binary data blobs (the recorder's tape).
@ Header
Used for initial file header (fixed identification string).
bool fgSetDouble(const char *name, double defaultValue)
Set a double value for a property.
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.
bool fgSetString(char const *name, char const *str)
Set a string value for a property.
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
double get_end_time(FGReplayInternal &self)
double get_start_time(FGReplayInternal &self)
static FGReplayData * record(FGReplayInternal &self, double sim_time)
int loadContinuousHeader(const std::string &path, std::istream *in, SGPropertyNode *properties)
static void loadMessages(FGReplayInternal &self)
std::ostream & operator<<(std::ostream &out, const FGFrameInfo &frame_info)
bool loadTapeContinuous(FGReplayInternal &replay_internal, std::ifstream &in, const SGPath &filename, bool preview, bool create_video, double fixed_dt, SGPropertyNode &meta_meta, simgear::HTTP::FileRequestRef file_request)
static bool saveTape2(FGReplayInternal &self, const SGPath &filename, SGPropertyNode_ptr metadata)
Write flight recorder tape with given filename and meta properties to disk.
static void MoveFrontMultiplayerPackets(std::deque< FGReplayData * > &list)
static bool saveRawReplayData(simgear::gzContainerWriter &output, const std::deque< FGReplayData * > &replay_data, size_t record_size, SGPropertyNode *meta)
Save raw replay data in a separate container.
static const char * MultiplayerMessageCallsign(const std::vector< char > &raw_message)
const char *const FlightRecorderFileMagic
Magic string to verify valid FG flight recorder tapes.
static std::string numberCommas(size_t n)
static void guiMessage(const char *message)
static void replayMessage(FGReplayInternal &self, double time)
static std::locale s_comma_locale(std::locale(), new NumberCommasHelper())
static bool loadRawReplayData(simgear::gzContainerReader &input, std::deque< FGReplayData * > &replay_data, size_t record_size, bool multiplayer, bool multiplayer_legacy)
Load raw replay data from a separate container.
void fillRecycler(FGReplayInternal &self)
static void writeRaw(std::ostream &out, const T &data)
static void interpolate(FGReplayInternal &self, double time, const std::deque< FGReplayData * > &list)
interpolate a specific time from a specific list
bool listTapes(bool same_aircraft_filter, const SGPath &tape_directory)
List available tapes in current directory.
bool replay(FGReplayInternal &self, double time)
Replay a saved frame based on time, interpolate from the two nearest saved frames.
static void readRaw(std::istream &in, T &data)
bool replayNormal(FGReplayInternal &self, double time)
static void printTimeStr(char *pStrBuffer, int bufferSize, double _Time, bool ShowDecimal=true)
SGPath makeSavePath(FGTapeType type, SGPath *path_timeless)
static void clear(FGReplayInternal &self)
SGPropertyNode_ptr saveSetup(const SGPropertyNode *extra, const SGPath &path, double duration, FGTapeType tape_type, int continuous_compression)
static int PropertiesRead(std::istream &in, SGPropertyNode *node)
static void setTimeStr(const char *property_path, double t, bool show_decimal=false)
static void indexContinuousRecording(FGReplayInternal &self, const void *data, size_t numbytes)
static bool CallsignsEqual(const std::vector< char > &a_message, const std::vector< char > &b_message)
static void popupTip(const char *message, int delay)
static void replayNormal2(FGReplayInternal &self, double time, const FGReplayData *current_frame, const FGReplayData *old_frame=nullptr, int *xpos=nullptr, int *ypos=nullptr, int *xsize=nullptr, int *ysize=nullptr)
given two FGReplayData elements and a time, interpolate between them
SGPropertyNode_ptr saveSetup(const SGPropertyNode *extra, const SGPath &path, double duration, FGTapeType tape_type, int continuous_compression=0)
SGPath makeSavePath(FGTapeType type, SGPath *path_timeless=nullptr)
const char *const FlightRecorderFileMagic
Magic string to verify valid FG flight recorder tapes.
bool has_extra_properties
size_t m_num_multiplayer_messages
std::vector< std::shared_ptr< std::vector< char > > > multiplayer_messages
static SGPropertyNode_ptr s_prop_num
static size_t s_bytes_multiplayer_messages
static void resetStatisticsProperties()
static size_t s_bytes_raw_data
static SGPropertyNode_ptr s_prop_bytes_raw_data
static SGPropertyNode_ptr s_prop_bytes_multiplayer_messages
std::vector< char > raw_data
size_t m_bytes_multiplayer_messages
static size_t s_num_multiplayer_messages
static SGPropertyNode_ptr s_prop_num_multiplayer_messages
SGPropertyNode_ptr m_sim_startup_xpos
FGMultiplayMgr * m_MultiplayMgr
SGPropertyNode_ptr m_recovery_period
SGPropertyNode_ptr m_replay_multiplayer
std::unique_ptr< struct Continuous > m_continuous
SGPropertyNode_ptr m_replay_time
SGPropertyNode_ptr m_record_normal_begin
double m_replay_time_prev
SGPropertyNode_ptr m_replay_time_str
SGPropertyNode_ptr m_replay_duration_act
SGPropertyNode_ptr m_log_frame_times
virtual ~FGReplayInternal()
SGPropertyNode_ptr m_replay_master
SGPropertyNode_ptr m_replay_master_eof
bool start(bool NewTape=false)
Start replay session.
SGPropertyNode_ptr m_record_normal_end
static int loadContinuousHeader(const std::string &path, std::istream *in, SGPropertyNode *properties)
SGPropertyNode_ptr m_speed_up
SGPropertyNode_ptr m_sim_startup_ypos
std::vector< FGReplayMessages >::iterator m_current_msg
std::deque< FGReplayData * > m_medium_term
std::deque< FGReplayData * > m_long_term
SGPropertyNode_ptr m_sim_startup_ysize
SGPropertyNode_ptr m_sim_startup_xsize
static std::string makeTapePath(const std::string &tape_name)
std::deque< FGReplayData * > m_short_term
std::shared_ptr< FGFlightRecorder > m_flight_recorder
bool m_was_finished_already
bool loadTape(const SGPropertyNode *ConfigData)
Load a flight recorder tape from disk.
SGPropertyNode_ptr m_simple_time_enabled
double m_medium_sample_rate
SGPropertyNode_ptr m_replay_error
SGPropertyNode_ptr m_disable_replay
std::vector< FGReplayMessages > m_replay_messages
SGPropertyNode_ptr m_replay_looped
std::deque< FGReplayData * > m_recycler
double m_long_sample_rate
bool saveTape(const SGPropertyNode *ConfigData)
Write flight recorder tape to disk.
virtual std::string do_grouping() const
virtual char do_thousands_sep() const