FlightGear next
continuous.cxx
Go to the documentation of this file.
1#include "continuous.hxx"
2
4#include <Main/fg_props.hxx>
7#include <Viewer/renderer.hxx>
8#include <Viewer/viewmgr.hxx>
9
10#include <simgear/io/iostreams/zlibstream.hxx>
11#include <simgear/props/props_io.hxx>
12#include <simgear/structure/commands.hxx>
13
14#include <osgViewer/ViewerBase>
15
16#include <assert.h>
17#include <string.h>
18
19
20Continuous::Continuous(std::shared_ptr<FGFlightRecorder> flight_recorder)
21:
22m_flight_recorder(flight_recorder)
23{
24 SGPropertyNode* record_continuous = fgGetNode("/sim/replay/record-continuous", true);
25 SGPropertyNode* fdm_initialized = fgGetNode("/sim/signals/fdm-initialized", true);
26 record_continuous->addChangeListener(this, true /*initial*/);
27 fdm_initialized->addChangeListener(this, true /*initial*/);
28}
29
30// Reads binary data from a stream into an instance of a type.
31template<typename T>
32static void readRaw(std::istream& in, T& data)
33{
34 in.read(reinterpret_cast<char*>(&data), sizeof(data));
35}
36
37// Writes instance of a type as binary data to a stream.
38template<typename T>
39static void writeRaw(std::ostream& out, const T& data)
40{
41 out.write(reinterpret_cast<const char*>(&data), sizeof(data));
42}
43
44// Reads uncompressed vector<char> from file. Throws if length field is longer
45// than <max_length>.
46template<typename SizeType>
47static SizeType VectorRead(std::istream& in, std::vector<char>& out, uint32_t max_length=(1u << 31))
48{
49 SizeType length;
50 readRaw(in, length);
51 if (sizeof(length) + length > max_length)
52 {
53 SG_LOG(SG_SYSTEMS, SG_ALERT, "recording data vector too long."
54 << " max_length=" << max_length
55 << " sizeof(length)=" << sizeof(length)
56 << " length=" << length
57 );
58 throw std::runtime_error("Failed to read vector in recording");
59 }
60 out.resize(length);
61 in.read(&out.front(), length);
62 return sizeof(length) + length;
63}
64
65static int16_t read_int16(std::istream& in, size_t& pos)
66{
67 int16_t a;
68 readRaw(in, a);
69 pos += sizeof(a);
70 return a;
71}
72static std::string read_string(std::istream& in, size_t& pos)
73{
74 int16_t length = read_int16(in, pos);
75 std::vector<char> path(length);
76 in.read(&path[0], length);
77 pos += length;
78 std::string ret(&path[0], length);
79 return ret;
80}
81
82static int PropertiesWrite(SGPropertyNode* root, std::ostream& out)
83{
84 std::stringstream buffer;
85 writeProperties(buffer, root, true /*write_all*/);
86 uint32_t buffer_len = buffer.str().size() + 1;
87 writeRaw(out, buffer_len);
88 out.write(buffer.str().c_str(), buffer_len);
89 return 0;
90}
91
92// Reads extra-property change items in next <length> bytes. Throws if we don't
93// exactly read <length> bytes.
94static void ReadFGReplayDataExtraProperties(std::istream& in, FGReplayData* replay_data, uint32_t length)
95{
96 SG_LOG(SG_SYSTEMS, SG_BULK, "reading extra-properties. length=" << length);
97 size_t pos=0;
98 for(;;)
99 {
100 if (pos == length)
101 {
102 break;
103 }
104 if (pos > length)
105 {
106 SG_LOG(SG_SYSTEMS, SG_ALERT, "Overrun while reading extra-properties:"
107 " length=" << length << ": pos=" << pos);
108 in.setstate(std::ios_base::failbit);
109 break;
110 }
111 SG_LOG(SG_SYSTEMS, SG_BULK, "length=" << length<< " pos=" << pos);
112 std::string path = read_string(in, pos);
113 if (path == "")
114 {
115 path = read_string(in, pos);
116 SG_LOG(SG_SYSTEMS, SG_DEBUG, "property deleted: " << path);
117 replay_data->replay_extra_property_removals.push_back(path);
118 }
119 else
120 {
121 std::string value = read_string(in, pos);
122 SG_LOG(SG_SYSTEMS, SG_DEBUG, "property changed: " << path << "=" << value);
123 replay_data->replay_extra_property_changes[path] = value;
124 }
125 }
126}
127
129 std::istream& in,
130 SGPropertyNode* config,
131 bool load_signals,
132 bool load_multiplayer,
133 bool load_extra_properties,
134 FGReplayData* ret
135 )
136{
137 ret->raw_data.resize(0);
138 for (auto data: config->getChildren("data"))
139 {
140 std::string data_type = data->getStringValue();
141 SG_LOG(SG_SYSTEMS, SG_BULK, "in.tellg()=" << in.tellg() << " data_type=" << data_type);
142 uint32_t length;
143 readRaw(in, length);
144 SG_LOG(SG_SYSTEMS, SG_DEBUG, "length=" << length);
145 if (!in) break;
146 if (load_signals && data_type == "signals")
147 {
148 ret->raw_data.resize(length);
149 in.read(&ret->raw_data.front(), ret->raw_data.size());
150 }
151 else if (load_multiplayer && data_type == "multiplayer")
152 {
153 /* Multiplayer information is a vector of vectors. */
154 ret->multiplayer_messages.clear();
155 uint32_t pos = 0;
156 for(;;)
157 {
158 assert(pos <= length);
159 if (pos == length) break;
160 std::shared_ptr<std::vector<char>> v(new std::vector<char>);
161 ret->multiplayer_messages.push_back(v);
162 pos += VectorRead<uint16_t>(in, *ret->multiplayer_messages.back(), length - pos);
163 SG_LOG(SG_SYSTEMS, SG_BULK, "replaying multiplayer data"
164 << " ret->sim_time=" << ret->sim_time
165 << " length=" << length
166 << " pos=" << pos
167 << " callsign=" << ((T_MsgHdr*) &v->front())->Callsign
168 );
169 }
170 }
171 else if (load_extra_properties && data_type == "extra-properties")
172 {
173 ReadFGReplayDataExtraProperties(in, ret, length);
174 }
175 else
176 {
177 SG_LOG(SG_GENERAL, SG_BULK, "Skipping unrecognised/unwanted data: " << data_type);
178 in.seekg(length, std::ios_base::cur);
179 }
180 if (!in) break;
181 }
182 if (!in)
183 {
184 SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape data");
185 return false;
186 }
187 return true;
188}
189
190/* Removes items more than <n> away from <it>. <n> can be -ve. */
191template<typename Container, typename Iterator>
192static void remove_far_away(Container& container, Iterator it, int n)
193{
194 SG_LOG(SG_GENERAL, SG_DEBUG, "container.size()=" << container.size());
195 if (n > 0)
196 {
197 for (int i=0; i<n; ++i)
198 {
199 if (it == container.end()) return;
200 ++it;
201 }
202 container.erase(it, container.end());
203 }
204 else
205 {
206 for (int i=0; i<-n-1; ++i)
207 {
208 if (it == container.begin()) return;
209 --it;
210 }
211 container.erase(container.begin(), it);
212 }
213 SG_LOG(SG_GENERAL, SG_DEBUG, "container.size()=" << container.size());
214}
215
216/* Returns FGReplayData for frame at specified position in file. Uses
217continuous.m_in_pos_to_frame as a cache, and trims this cache using
218remove_far_away(). */
219static std::shared_ptr<FGReplayData> ReadFGReplayData(
220 Continuous& continuous,
221 std::ifstream& in,
222 size_t pos,
223 SGPropertyNode* config,
224 bool load_signals,
225 bool load_multiplayer,
226 bool load_extra_properties,
227 int in_compression
228 )
229{
230 std::shared_ptr<FGReplayData> ret;
231 auto it = continuous.m_in_pos_to_frame.find(pos);
232
233 if (it != continuous.m_in_pos_to_frame.end())
234 {
235 if (0
236 || (load_signals && !it->second->load_signals)
237 || (load_multiplayer && !it->second->load_multiplayer)
238 || (load_extra_properties && !it->second->load_extra_properties)
239 )
240 {
241 /* This frame is in the continuous.m_in_pos_to_frame cache, but
242 doesn't contain all of the required items, so we need to reload. */
243 continuous.m_in_pos_to_frame.erase(it);
244 it = continuous.m_in_pos_to_frame.end();
245 }
246 }
247 if (it == continuous.m_in_pos_to_frame.end())
248 {
249 /* Load FGReplayData at offset <pos>.
250
251 We need to clear any eof bit, otherwise seekg() will not work (which is
252 pretty unhelpful). E.g. see:
253 https://stackoverflow.com/questions/16364301/whats-wrong-with-the-ifstream-seekg
254 */
255 SG_LOG(SG_SYSTEMS, SG_BULK, "reading frame. pos=" << pos);
256 in.clear();
257 in.seekg(pos);
258
259 ret.reset(new FGReplayData);
260
261 readRaw(in, ret->sim_time);
262 if (!in)
263 {
264 SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
265 return nullptr;
266 }
267 bool ok;
268 if (in_compression)
269 {
270 uint8_t flags;
271 uint32_t compressed_size;
272 in.read((char*) &flags, sizeof(flags));
273 in.read((char*) &compressed_size, sizeof(compressed_size));
274 simgear::ZlibDecompressorIStream in_decompress(in, SGPath(), simgear::ZLibCompressionFormat::ZLIB_RAW);
275 ok = ReadFGReplayData2(in_decompress, config, load_signals, load_multiplayer, load_extra_properties, ret.get());
276 }
277 else
278 {
279 ok = ReadFGReplayData2(in, config, load_signals, load_multiplayer, load_extra_properties, ret.get());
280 }
281 if (!ok)
282 {
283 SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset " << pos);
284 return nullptr;
285 }
286 it = continuous.m_in_pos_to_frame.lower_bound(pos);
287 it = continuous.m_in_pos_to_frame.insert(it, std::make_pair(pos, ret));
288
289 /* Delete faraway items. */
290 size_t size_old = continuous.m_in_pos_to_frame.size();
291 int n = 2;
292 size_t size_max = 2*n - 1;
293 remove_far_away(continuous.m_in_pos_to_frame, it, n);
294 remove_far_away(continuous.m_in_pos_to_frame, it, -n);
295 size_t size_new = continuous.m_in_pos_to_frame.size();
296 SG_LOG(SG_GENERAL, SG_DEBUG, ""
297 << " n=" << size_old
298 << " size_max=" << size_max
299 << " size_old=" << size_old
300 << " size_new=" << size_new
301 );
302 assert(size_new <= size_max);
303 }
304 else
305 {
306 ret = it->second;
307 }
308 return ret;
309}
310
311
312// streambuf that compresses using deflate().
313struct compression_streambuf : std::streambuf
314{
316 std::ostream& out,
319 )
320 :
321 std::streambuf(),
322 out(out),
327 {
328 zstream.zalloc = nullptr;
329 zstream.zfree = nullptr;
330 zstream.opaque = nullptr;
331
332 zstream.next_in = nullptr;
333 zstream.avail_in = 0;
334
335 zstream.next_out = (unsigned char*) &buffer_compressed[0];
337
338 int e = deflateInit2(
339 &zstream,
340 Z_DEFAULT_COMPRESSION,
341 Z_DEFLATED,
342 -15 /*windowBits*/,
343 8 /*memLevel*/,
344 Z_DEFAULT_STRATEGY
345 );
346 if (e != Z_OK)
347 {
348 throw std::runtime_error("deflateInit2() failed");
349 }
350 // We leave space for one character to simplify overflow().
352 }
353
354 // Flush compressed data to .out and reset zstream.next_out.
355 void _flush()
356 {
357 // Send all data in .buffer_compressed to .out.
358 size_t n = (char*) zstream.next_out - &buffer_compressed[0];
359 out.write(&buffer_compressed[0], n);
360 zstream.next_out = (unsigned char*) &buffer_compressed[0];
362 }
363
364 // Compresses specified bytes from buffer_uncompressed into
365 // buffer_compressed, flushing to .out as necessary. Returns true if we get
366 // EOF writing to .out.
367 bool _deflate(size_t n, bool flush)
368 {
369 assert(this->pbase() == &buffer_uncompressed[0]);
370 zstream.next_in = (unsigned char*) &buffer_uncompressed[0];
371 zstream.avail_in = n;
372 for(;;)
373 {
374 if (!flush && !zstream.avail_in) break;
375 if (!zstream.avail_out) _flush();
376 int e = deflate(&zstream, (!zstream.avail_in && flush) ? Z_FINISH : Z_NO_FLUSH);
377 if (e != Z_OK && e != Z_STREAM_END)
378 {
379 throw std::runtime_error("zip_deflate() failed");
380 }
381 if (e == Z_STREAM_END) break;
382 }
383 if (flush) _flush();
384 // We leave space for one character to simplify overflow().
386 if (!out) return true; // EOF.
387 return false;
388 }
389
390 int overflow(int c) override
391 {
392 // We've deliberately left space for one character, into which we write <c>.
393 assert(this->pptr() == &buffer_uncompressed[0] + buffer_uncompressed_size - 1);
394 *this->pptr() = (char) c;
395 if (_deflate(buffer_uncompressed_size, false /*flush*/)) return EOF;
396 return c;
397 }
398
399 int sync() override
400 {
401 _deflate(pptr() - &buffer_uncompressed[0], true /*flush*/);
402 return 0;
403 }
404
406 {
407 deflateEnd(&zstream);
408 }
409
410 std::ostream& out;
411 z_stream zstream;
412 std::unique_ptr<char[]> buffer_uncompressed;
414 std::unique_ptr<char[]> buffer_compressed;
416};
417
418
419// Accepts uncompressed data via .write(), operator<< etc, and writes
420// compressed data to the supplied std::ostream.
421struct compression_ostream : std::ostream
422{
424 std::ostream& out,
425 size_t buffer_uncompressed_size,
426 size_t buffer_compressed_size
427 )
428 :
429 std::ostream(&streambuf),
430 streambuf(out, buffer_uncompressed_size, buffer_compressed_size)
431 {
432 }
433
435};
436
437
438static void writeFrame2(FGReplayData* r, std::ostream& out, SGPropertyNode_ptr config)
439{
440 for (auto data: config->getChildren("data"))
441 {
442 std::string data_type = data->getStringValue();
443 if (data_type == "signals")
444 {
445 uint32_t signals_size = r->raw_data.size();
446 writeRaw(out, signals_size);
447 out.write(&r->raw_data.front(), r->raw_data.size());
448 }
449 else if (data_type == "multiplayer")
450 {
451 uint32_t length = 0;
452 for (auto message: r->multiplayer_messages)
453 {
454 length += sizeof(uint16_t) + message->size();
455 }
456 SG_LOG(SG_SYSTEMS, SG_DEBUG, "data_type=" << data_type << " out.tellp()=" << out.tellp()
457 << " length=" << length);
458 writeRaw(out, length);
459 for (auto message: r->multiplayer_messages)
460 {
461 uint16_t message_size = message->size();
462 writeRaw(out, message_size);
463 out.write(&message->front(), message_size);
464 }
465 }
466 else if (data_type == "extra-properties")
467 {
468 uint32_t length = r->extra_properties.size();
469 SG_LOG(SG_SYSTEMS, SG_DEBUG, "data_type=" << data_type << " out.tellp()=" << out.tellp()
470 << " length=" << length);
471 writeRaw(out, length);
472 out.write(&r->extra_properties[0], length);
473 }
474 else
475 {
476 SG_LOG(SG_SYSTEMS, SG_ALERT, "unrecognised data_type=" << data_type);
477 assert(0);
478 }
479 }
480
481}
482
484 Continuous& continuous,
485 FGReplayData* r,
486 std::ostream& out,
487 SGPropertyNode_ptr config,
488 FGTapeType tape_type
489 )
490{
491 SG_LOG(SG_SYSTEMS, SG_BULK, "writing frame."
492 << " out.tellp()=" << out.tellp()
493 << " r->sim_time=" << r->sim_time
494 );
495 // Don't write frame if no data to write.
496 //bool r_has_data = false;
497 bool has_signals = false;
498 bool has_multiplayer = false;
499 bool has_extra_properties = false;
500 for (auto data: config->getChildren("data"))
501 {
502 std::string data_type = data->getStringValue();
503 if (data_type == "signals")
504 {
505 has_signals = true;
506 }
507 else if (data_type == "multiplayer")
508 {
509 if (!r->multiplayer_messages.empty())
510 {
511 has_multiplayer = true;
512 }
513 }
514 else if (data_type == "extra-properties")
515 {
516 if (!r->extra_properties.empty())
517 {
518 has_extra_properties = true;
519 }
520 }
521 else
522 {
523 SG_LOG(SG_SYSTEMS, SG_ALERT, "unrecognised data_type=" << data_type);
524 assert(0);
525 }
526 }
527 if (!has_signals && !has_multiplayer && !has_extra_properties)
528 {
529 SG_LOG(SG_SYSTEMS, SG_DEBUG, "Not writing frame because no data to write");
530 return true;
531 }
532
533 writeRaw(out, r->sim_time);
534
535 if (tape_type == FGTapeType_CONTINUOUS && continuous.m_out_compression)
536 {
537 uint8_t flags = 0;
538 if (has_signals) flags |= 1;
539 if (has_multiplayer) flags |= 2;
540 if (has_extra_properties) flags |= 4;
541 out.write((char*) &flags, sizeof(flags));
542
543 /* We need to first write the size of the compressed data so compress
544 to a temporary ostringstream first. */
545 std::ostringstream compressed;
546 compression_ostream out_compressing(compressed, 1024, 1024);
547 writeFrame2(r, out_compressing, config);
548 out_compressing.flush();
549
550 uint32_t compressed_size = compressed.str().size();
551 out.write((char*) &compressed_size, sizeof(compressed_size));
552 out.write((char*) compressed.str().c_str(), compressed.str().size());
553 }
554 else
555 {
556 writeFrame2(r, out, config);
557 }
558 bool ok = true;
559 if (!out) ok = false;
560 return ok;
561}
562
563SGPropertyNode_ptr continuousWriteHeader(
564 Continuous& continuous,
565 FGFlightRecorder* flight_recorder,
566 std::ofstream& out,
567 const SGPath& path,
568 FGTapeType tape_type
569 )
570{
571 continuous.m_out_compression = fgGetInt("/sim/replay/record-continuous-compression");
572 SGPropertyNode_ptr config = saveSetup(NULL /*Extra*/, path, 0 /*Duration*/,
573 tape_type, continuous.m_out_compression);
574 SGPropertyNode* signals = config->getNode("signals", true /*create*/);
575 flight_recorder->getConfig(signals);
576
577 out.open(path.c_str(), std::ofstream::binary | std::ofstream::trunc);
579 PropertiesWrite(config, out);
580
581 if (tape_type == FGTapeType_CONTINUOUS)
582 {
583 // Ensure that all recorded properties are written in first frame.
584 //
585 flight_recorder->resetExtraProperties();
586 }
587
588 if (!out)
589 {
590 out.close();
591 config = nullptr;
592 }
593 return config;
594}
595
596/* Replays one frame from Continuous recording. <offset> and <offset_old> are
597offsets in file of frames that are >= and < <time> respectively. <offset_old>
598may be 0, in which case it is ignored.
599
600We load the frame(s) from disc, omitting some data depending on
601replay_signals, replay_multiplayer and replay_extra_properties. Then call
602m_pRecorder->replay(), which updates the global state.
603
604Returns true on success, otherwise we failed to read from Continuous recording.
605*/
607 Continuous& continuous,
608 FGFlightRecorder* recorder,
609 double time,
610 size_t offset,
611 size_t offset_old,
612 bool replay_signals,
613 bool replay_multiplayer,
614 bool replay_extra_properties,
615 int* xpos,
616 int* ypos,
617 int* xsize,
618 int* ysize
619 )
620{
621 std::shared_ptr<FGReplayData> replay_data = ReadFGReplayData(
622 continuous,
623 continuous.m_in,
624 offset,
625 continuous.m_in_config,
626 replay_signals,
627 replay_multiplayer,
628 replay_extra_properties,
629 continuous.m_in_compression
630 );
631 if (!replay_data)
632 {
633 SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape frame at offset=" << offset << " time=" << time);
634 return false;
635 }
636 assert(replay_data.get());
637 std::shared_ptr<FGReplayData> replay_data_old;
638 if (offset_old)
639 {
640 replay_data_old = ReadFGReplayData(
641 continuous,
642 continuous.m_in,
643 offset_old,
644 continuous.m_in_config,
645 replay_signals,
646 replay_multiplayer,
647 replay_extra_properties,
648 continuous.m_in_compression
649 );
650 }
651 if (replay_extra_properties) SG_LOG(SG_SYSTEMS, SG_DEBUG,
652 "replay():"
653 << " time=" << time
654 << " offset=" << offset
655 << " offset_old=" << offset_old
656 << " replay_data_old=" << replay_data_old
657 << " replay_data->raw_data.size()=" << replay_data->raw_data.size()
658 << " replay_data->multiplayer_messages.size()=" << replay_data->multiplayer_messages.size()
659 << " replay_data->extra_properties.size()=" << replay_data->extra_properties.size()
660 << " replay_data->replay_extra_property_changes.size()=" << replay_data->replay_extra_property_changes.size()
661 );
662 recorder->replay(time, replay_data.get(), replay_data_old.get(), xpos, ypos, xsize, ysize);
663 return true;
664}
665
666// fixme: this is duplicated in replay.cxx.
667static void popupTip(const char* message, int delay)
668{
669 SGPropertyNode_ptr args(new SGPropertyNode);
670 args->setStringValue("label", message);
671 args->setIntValue("delay", delay);
672 globals->get_commands()->execute("show-message", args);
673}
674
676{
677 if (continuous.m_replay_create_video)
678 {
679 SG_LOG(SG_GENERAL, SG_ALERT, "Stopping replay create-video");
680 auto view_mgr = globals->get_subsystem<FGViewMgr>();
681 if (view_mgr)
682 {
683 view_mgr->video_stop();
684 }
685 continuous.m_replay_create_video = false;
686 }
687 if (continuous.m_replay_fixed_dt_prev != -1)
688 {
689 SG_LOG(SG_GENERAL, SG_ALERT, "Resetting fixed-dt to" << continuous.m_replay_fixed_dt_prev);
690 fgSetDouble("/sim/time/fixed-dt", continuous.m_replay_fixed_dt_prev);
691 continuous.m_replay_fixed_dt_prev = -1;
692 }
693}
694
695bool replayContinuous(FGReplayInternal& self, double time)
696{
697 // We need to detect whether replay() updates the values for the main
698 // window's position and size.
699 int xpos0 = self.m_sim_startup_xpos->getIntValue();
700 int ypos0 = self.m_sim_startup_xpos->getIntValue();
701 int xsize0 = self.m_sim_startup_xpos->getIntValue();
702 int ysize0 = self.m_sim_startup_xpos->getIntValue();
703
704 int xpos = xpos0;
705 int ypos = ypos0;
706 int xsize = xsize0;
707 int ysize = ysize0;
708
709 double multiplayer_recent = 3;
710
711 // We replay all frames from just after the previously-replayed frame,
712 // in order to replay extra properties and multiplayer aircraft
713 // correctly.
714 //
715 double t_begin = self.m_continuous->m_in_frame_time_last;
716
717 if (time < self.m_continuous->m_in_time_last)
718 {
719 // We have gone backwards, e.g. user has clicked on the back
720 // buttons in the Replay dialogue.
721 //
722
723 if (self.m_continuous->m_in_multiplayer)
724 {
725 // Continuous recording has multiplayer data, so replay recent
726 // ones.
727 //
728 t_begin = time - multiplayer_recent;
729 }
730
731 if (self.m_continuous->m_in_extra_properties)
732 {
733 // Continuous recording has property changes. we need to replay
734 // all property changes from the beginning.
735 //
736 t_begin = -1;
737 }
738
739 SG_LOG(SG_SYSTEMS, SG_DEBUG, "Have gone backwards."
740 << " m_in_time_last=" << self.m_continuous->m_in_time_last
741 << " time=" << time
742 << " t_begin=" << t_begin
743 << " m_in_extra_properties=" << self.m_continuous->m_in_extra_properties
744 );
745 }
746
747 // Prepare to replay signals from Continuoue recording file. We want
748 // to find a pair of frames that straddle the requested <time> so that
749 // we can interpolate.
750 //
751 auto p = self.m_continuous->m_in_time_to_frameinfo.lower_bound(time);
752 bool ret = false;
753
754 size_t offset;
755 size_t offset_prev = 0;
756
757 if (p == self.m_continuous->m_in_time_to_frameinfo.end())
758 {
759 // We are at end of recording; replay last frame.
761 --p;
762 offset = p->second.offset;
763 ret = true;
764 }
765 else if (p->first > time)
766 {
767 // Look for preceding item.
768 if (p == self.m_continuous->m_in_time_to_frameinfo.begin())
769 {
770 // <time> is before beginning of recording.
771 offset = p->second.offset;
772 }
773 else
774 {
775 // Interpolate between pair of items that straddle <time>.
776 auto prev = p;
777 --prev;
778 offset_prev = prev->second.offset;
779 offset = p->second.offset;
780 }
781 }
782 else
783 {
784 // Exact match.
785 offset = p->second.offset;
786 }
787
788 // Before interpolating signals, we replay all property changes from
789 // all frame times t satisfying t_prop_begin < t < time. We also replay
790 // all recent multiplayer packets in this range, i.e. for which t >
791 // time - multiplayer_recent.
792 //
793 // todo: figure out how to interpolate view position/direction, to
794 // smooth things out if replay fps is different from record fps e.g.
795 // with new fixed dt support.
796 //
797 for (auto p_before = self.m_continuous->m_in_time_to_frameinfo.upper_bound(t_begin);
798 p_before != self.m_continuous->m_in_time_to_frameinfo.end();
799 ++p_before)
800 {
801 if (p_before->first >= p->first)
802 {
803 break;
804 }
805 // Replaying a frame is expensive because we read frame data
806 // from disc each time. So we only replay this frame if it has
807 // extra_properties, or if it has multiplayer packets and we are
808 // within <multiplayer_recent> seconds of current time.
809 //
810 bool replay_this_frame = p_before->second.has_extra_properties;
811 if (p_before->second.has_multiplayer && p_before->first > time - multiplayer_recent)
812 {
813 replay_this_frame = true;
814 }
815
816 SG_LOG(SG_SYSTEMS, SG_DEBUG, "Looking at extra property changes."
817 << " replay_this_frame=" << replay_this_frame
818 << " m_continuous->m_in_time_last=" << self.m_continuous->m_in_time_last
819 << " m_continuous->m_in_frame_time_last=" << self.m_continuous->m_in_frame_time_last
820 << " time=" << time
821 << " t_begin=" << t_begin
822 << " p_before->first=" << p_before->first
823 << " p_before->second=" << p_before->second
824 );
825
826 if (replay_this_frame)
827 {
828 size_t pos_prev = 0;
829 if (p_before != self.m_continuous->m_in_time_to_frameinfo.begin())
830 {
831 auto p_before_prev = p_before;
832 --p_before_prev;
833 pos_prev = p_before_prev->second.offset;
834 }
835 bool ok = replayContinuousInternal(
836 *self.m_continuous,
837 self.m_flight_recorder.get(),
838 p_before->first,
839 p_before->second.offset,
840 pos_prev /*offset_old*/,
841 false /*replay_signals*/,
842 p_before->first > time - multiplayer_recent /*replay_multiplayer*/,
843 true /*replay_extra_properties*/,
844 &xpos,
845 &ypos,
846 &xsize,
847 &ysize
848 );
849 if (!ok)
850 {
851 if (!self.m_replay_error->getBoolValue())
852 {
853 SG_LOG(SG_SYSTEMS, SG_ALERT, "Replay failed: cannot read fgtape data");
854 popupTip("Replay failed: cannot read fgtape data", 10);
855 self.m_replay_error->setBoolValue(true);
856 }
857 return true;
858 }
859 }
860 }
861
862 /* Now replay signals, interpolating between frames atoffset_prev and
863 offset. */
864 bool ok = replayContinuousInternal(
865 *self.m_continuous,
866 self.m_flight_recorder.get(),
867 time,
868 offset,
869 offset_prev /*offset_old*/,
870 true /*replay_signals*/,
871 true /*replay_multiplayer*/,
872 true /*replay_extra_properties*/,
873 &xpos,
874 &ypos,
875 &xsize,
876 &ysize
877 );
878 if (!ok)
879 {
880 if (!self.m_replay_error->getBoolValue())
881 {
882 SG_LOG(SG_SYSTEMS, SG_ALERT, "Replay failed: cannot read fgtape data");
883 popupTip("Replay failed: cannot read fgtape data", 10);
884 self.m_replay_error->setBoolValue(true);
885 }
886 return true;
887 }
888 if (0
889 || xpos != xpos0
890 || ypos != ypos0
891 || xsize != xsize0
892 || ysize != ysize0
893 )
894 {
895 // Move/resize the main window to reflect the updated values.
896 globals->get_props()->setIntValue("/sim/startup/xpos", xpos);
897 globals->get_props()->setIntValue("/sim/startup/ypos", ypos);
898 globals->get_props()->setIntValue("/sim/startup/xsize", xsize);
899 globals->get_props()->setIntValue("/sim/startup/ysize", ysize);
900
901 osgViewer::ViewerBase* viewer_base = globals->get_renderer()->getViewerBase();
902 if (viewer_base)
903 {
904 std::vector<osgViewer::GraphicsWindow*> windows;
905 viewer_base->getWindows(windows);
906 osgViewer::GraphicsWindow* window = windows[0];
907
908 // We use FGEventHandler::setWindowRectangle() to move the
909 // window, because it knows how to convert from window work-area
910 // coordinates to window-including-furniture coordinates.
911 //
912 flightgear::FGEventHandler* event_handler = globals->get_renderer()->getEventHandler();
913 event_handler->setWindowRectangleInteriorWithCorrection(window, xpos, ypos, xsize, ysize);
914 }
915 }
916
917 self.m_continuous->m_in_time_last = time;
918 self.m_continuous->m_in_frame_time_last = p->first;
919
920 return ret;
921}
922
923/* SGPropertyChangeListener callback for detecing when FDM is initialised and
924for when continuous recording is started or stopped. */
925void Continuous::valueChanged(SGPropertyNode * node)
926{
927 bool prop_continuous = fgGetBool("/sim/replay/record-continuous");
928 bool prop_fdm = fgGetBool("/sim/signals/fdm-initialized");
929
930 bool continuous = prop_continuous && prop_fdm;
931 if (continuous == (m_out.is_open() ? true : false))
932 {
933 // No change.
934 return;
935 }
936
937 if (m_out.is_open())
938 {
939 // Stop existing continuous recording.
940 SG_LOG(SG_SYSTEMS, SG_ALERT, "Stopping continuous recording");
941 m_out.close();
942 popupTip("Continuous record to file stopped", 5 /*delay*/);
943 }
944
945 if (continuous)
946 {
947 // Start continuous recording.
948 SGPath path_timeless;
949 SGPath path = makeSavePath(FGTapeType_CONTINUOUS, &path_timeless);
951 *this,
952 m_flight_recorder.get(),
953 m_out,
954 path,
956 );
957 if (!m_out_config)
958 {
959 SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to start continuous recording");
960 popupTip("Continuous record to file failed to start", 5 /*delay*/);
961 return;
962 }
963
964 SG_LOG(SG_SYSTEMS, SG_ALERT, "Starting continuous recording");
965
966 /* Make a convenience link to the recording. E.g.
967 harrier-gr3-continuous.fgtape -> harrier-gr3-20201224-005034-continuous.fgtape.
968
969 Link destination is in same directory as link so we use leafname
970 path.file(). */
971 path_timeless.remove();
972 bool ok = path_timeless.makeLink(path.file());
973 if (!ok)
974 {
975 SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to create link " << path_timeless.c_str() << " => " << path.file());
976 }
977 SG_LOG(SG_SYSTEMS, SG_DEBUG, "Starting continuous recording to " << path);
979 {
980 popupTip("Continuous+compressed record to file started", 5 /*delay*/);
981 }
982 else
983 {
984 popupTip("Continuous record to file started", 5 /*delay*/);
985 }
986 }
987}
#define p(x)
#define i(x)
void getConfig(SGPropertyNode *root)
void replay(double SimTime, const FGReplayData *pNextBuffer, const FGReplayData *pLastBuffer, int *main_window_xpos, int *main_window_ypos, int *main_window_xsize, int *main_window_ysize)
Replay.
void setWindowRectangleInteriorWithCorrection(osgViewer::GraphicsWindow *window, int x, int y, int width, int height)
static int16_t read_int16(std::istream &in, size_t &pos)
bool continuousWriteFrame(Continuous &continuous, FGReplayData *r, std::ostream &out, SGPropertyNode_ptr config, FGTapeType tape_type)
static void writeFrame2(FGReplayData *r, std::ostream &out, SGPropertyNode_ptr config)
static std::string read_string(std::istream &in, size_t &pos)
static bool replayContinuousInternal(Continuous &continuous, FGFlightRecorder *recorder, double time, size_t offset, size_t offset_old, bool replay_signals, bool replay_multiplayer, bool replay_extra_properties, int *xpos, int *ypos, int *xsize, int *ysize)
static void ReadFGReplayDataExtraProperties(std::istream &in, FGReplayData *replay_data, uint32_t length)
static SizeType VectorRead(std::istream &in, std::vector< char > &out, uint32_t max_length=(1u<< 31))
static void remove_far_away(Container &container, Iterator it, int n)
bool replayContinuous(FGReplayInternal &self, double time)
static bool ReadFGReplayData2(std::istream &in, SGPropertyNode *config, bool load_signals, bool load_multiplayer, bool load_extra_properties, FGReplayData *ret)
static std::shared_ptr< FGReplayData > ReadFGReplayData(Continuous &continuous, std::ifstream &in, size_t pos, SGPropertyNode *config, bool load_signals, bool load_multiplayer, bool load_extra_properties, int in_compression)
void continuous_replay_video_end(Continuous &continuous)
static void writeRaw(std::ostream &out, const T &data)
SGPropertyNode_ptr continuousWriteHeader(Continuous &continuous, FGFlightRecorder *flight_recorder, std::ofstream &out, const SGPath &path, FGTapeType tape_type)
static void readRaw(std::istream &in, T &data)
static int PropertiesWrite(SGPropertyNode *root, std::ostream &out)
static void popupTip(const char *message, int delay)
SGPropertyNode_ptr continuousWriteHeader(Continuous &continuous, FGFlightRecorder *m_pRecorder, std::ofstream &out, const SGPath &path, FGTapeType tape_type)
int fgGetInt(const char *name, int defaultValue)
Get an int value for a property.
Definition fg_props.cxx:532
FGGlobals * globals
Definition globals.cxx:142
bool fgSetDouble(const char *name, double defaultValue)
Set a double value for a property.
Definition proptest.cpp:31
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
Definition proptest.cpp:25
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27
const char *const FlightRecorderFileMagic
Magic string to verify valid FG flight recorder tapes.
SGPath makeSavePath(FGTapeType type, SGPath *path_timeless)
SGPropertyNode_ptr saveSetup(const SGPropertyNode *extra, const SGPath &path, double duration, FGTapeType tape_type, int continuous_compression)
@ FGTapeType_CONTINUOUS
double m_replay_fixed_dt_prev
Continuous(std::shared_ptr< FGFlightRecorder > flight_recorder)
std::ifstream m_in
SGPropertyNode_ptr m_out_config
std::map< size_t, std::shared_ptr< FGReplayData > > m_in_pos_to_frame
std::shared_ptr< FGFlightRecorder > m_flight_recorder
SGPropertyNode_ptr m_in_config
int m_out_compression
bool m_replay_create_video
void valueChanged(SGPropertyNode *node) override
int m_in_compression
std::ofstream m_out
std::vector< std::shared_ptr< std::vector< char > > > multiplayer_messages
std::vector< char > extra_properties
std::map< std::string, std::string > replay_extra_property_changes
std::vector< std::string > replay_extra_property_removals
std::vector< char > raw_data
SGPropertyNode_ptr m_sim_startup_xpos
std::unique_ptr< struct Continuous > m_continuous
std::shared_ptr< FGFlightRecorder > m_flight_recorder
SGPropertyNode_ptr m_replay_error
compression_streambuf streambuf
compression_ostream(std::ostream &out, size_t buffer_uncompressed_size, size_t buffer_compressed_size)
compression_streambuf(std::ostream &out, size_t buffer_uncompressed_size, size_t buffer_compressed_size)
int sync() override
std::unique_ptr< char[]> buffer_uncompressed
std::unique_ptr< char[]> buffer_compressed
bool _deflate(size_t n, bool flush)
int overflow(int c) override