#include "continuous.hxx" #include #include
#include #include #include #include #include #include #include #include #include #include Continuous::Continuous(std::shared_ptr flight_recorder) : m_flight_recorder(flight_recorder) { SGPropertyNode* record_continuous = fgGetNode("/sim/replay/record-continuous", true); SGPropertyNode* fdm_initialized = fgGetNode("/sim/signals/fdm-initialized", true); record_continuous->addChangeListener(this, true /*initial*/); fdm_initialized->addChangeListener(this, true /*initial*/); } // Reads binary data from a stream into an instance of a type. template static void readRaw(std::istream& in, T& data) { in.read(reinterpret_cast(&data), sizeof(data)); } // Writes instance of a type as binary data to a stream. template static void writeRaw(std::ostream& out, const T& data) { out.write(reinterpret_cast(&data), sizeof(data)); } // Reads uncompressed vector from file. Throws if length field is longer // than . template static SizeType VectorRead(std::istream& in, std::vector& out, uint32_t max_length=(1u << 31)) { SizeType length; readRaw(in, length); if (sizeof(length) + length > max_length) { SG_LOG(SG_SYSTEMS, SG_ALERT, "recording data vector too long." << " max_length=" << max_length << " sizeof(length)=" << sizeof(length) << " length=" << length ); throw std::runtime_error("Failed to read vector in recording"); } out.resize(length); in.read(&out.front(), length); return sizeof(length) + length; } static int16_t read_int16(std::istream& in, size_t& pos) { int16_t a; readRaw(in, a); pos += sizeof(a); return a; } static std::string read_string(std::istream& in, size_t& pos) { int16_t length = read_int16(in, pos); std::vector path(length); in.read(&path[0], length); pos += length; std::string ret(&path[0], length); return ret; } static int PropertiesWrite(SGPropertyNode* root, std::ostream& out) { stringstream buffer; writeProperties(buffer, root, true /*write_all*/); uint32_t buffer_len = buffer.str().size() + 1; writeRaw(out, buffer_len); out.write(buffer.str().c_str(), buffer_len); return 0; } // Reads extra-property change items in next bytes. Throws if we don't // exactly read bytes. static void ReadFGReplayDataExtraProperties(std::istream& in, FGReplayData* replay_data, uint32_t length) { SG_LOG(SG_SYSTEMS, SG_BULK, "reading extra-properties. length=" << length); size_t pos=0; for(;;) { if (pos == length) { break; } if (pos > length) { SG_LOG(SG_SYSTEMS, SG_ALERT, "Overrun while reading extra-properties:" " length=" << length << ": pos=" << pos); in.setstate(std::ios_base::failbit); break; } SG_LOG(SG_SYSTEMS, SG_BULK, "length=" << length<< " pos=" << pos); std::string path = read_string(in, pos); if (path == "") { path = read_string(in, pos); SG_LOG(SG_SYSTEMS, SG_DEBUG, "property deleted: " << path); replay_data->replay_extra_property_removals.push_back(path); } else { std::string value = read_string(in, pos); SG_LOG(SG_SYSTEMS, SG_DEBUG, "property changed: " << path << "=" << value); replay_data->replay_extra_property_changes[path] = value; } } } static bool ReadFGReplayData2( std::istream& in, SGPropertyNode* config, bool load_signals, bool load_multiplayer, bool load_extra_properties, FGReplayData* ret ) { ret->raw_data.resize(0); for (auto data: config->getChildren("data")) { std::string data_type = data->getStringValue(); SG_LOG(SG_SYSTEMS, SG_BULK, "in.tellg()=" << in.tellg() << " data_type=" << data_type); uint32_t length; readRaw(in, length); SG_LOG(SG_SYSTEMS, SG_DEBUG, "length=" << length); if (!in) break; if (load_signals && data_type == "signals") { ret->raw_data.resize(length); in.read(&ret->raw_data.front(), ret->raw_data.size()); } else if (load_multiplayer && data_type == "multiplayer") { /* Multiplayer information is a vector of vectors. */ ret->multiplayer_messages.clear(); uint32_t pos = 0; for(;;) { assert(pos <= length); if (pos == length) break; std::shared_ptr> v(new std::vector); ret->multiplayer_messages.push_back(v); pos += VectorRead(in, *ret->multiplayer_messages.back(), length - pos); SG_LOG(SG_SYSTEMS, SG_BULK, "replaying multiplayer data" << " ret->sim_time=" << ret->sim_time << " length=" << length << " pos=" << pos << " callsign=" << ((T_MsgHdr*) &v->front())->Callsign ); } } else if (load_extra_properties && data_type == "extra-properties") { ReadFGReplayDataExtraProperties(in, ret, length); } else { SG_LOG(SG_GENERAL, SG_BULK, "Skipping unrecognised/unwanted data: " << data_type); in.seekg(length, std::ios_base::cur); } if (!in) break; } if (!in) { SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read fgtape data"); return false; } return true; } /* Removes items more than away from . can be -ve. */ template static void remove_far_away(Container& container, Iterator it, int n) { SG_LOG(SG_GENERAL, SG_DEBUG, "container.size()=" << container.size()); if (n > 0) { for (int i=0; ipbase() == &buffer_uncompressed[0]); zstream.next_in = (unsigned char*) &buffer_uncompressed[0]; zstream.avail_in = n; for(;;) { if (!flush && !zstream.avail_in) break; if (!zstream.avail_out) _flush(); int e = deflate(&zstream, (!zstream.avail_in && flush) ? Z_FINISH : Z_NO_FLUSH); if (e != Z_OK && e != Z_STREAM_END) { throw std::runtime_error("zip_deflate() failed"); } if (e == Z_STREAM_END) break; } if (flush) _flush(); // We leave space for one character to simplify overflow(). setp(&buffer_uncompressed[0], &buffer_uncompressed[0] + buffer_uncompressed_size - 1); if (!out) return true; // EOF. return false; } int overflow(int c) override { // We've deliberately left space for one character, into which we write . assert(this->pptr() == &buffer_uncompressed[0] + buffer_uncompressed_size - 1); *this->pptr() = (char) c; if (_deflate(buffer_uncompressed_size, false /*flush*/)) return EOF; return c; } int sync() override { _deflate(pptr() - &buffer_uncompressed[0], true /*flush*/); return 0; } ~compression_streambuf() { deflateEnd(&zstream); } std::ostream& out; z_stream zstream; std::unique_ptr buffer_uncompressed; size_t buffer_uncompressed_size; std::unique_ptr buffer_compressed; size_t buffer_compressed_size; }; // Accepts uncompressed data via .write(), operator<< etc, and writes // compressed data to the supplied std::ostream. struct compression_ostream : std::ostream { compression_ostream( std::ostream& out, size_t buffer_uncompressed_size, size_t buffer_compressed_size ) : std::ostream(&streambuf), streambuf(out, buffer_uncompressed_size, buffer_compressed_size) { } compression_streambuf streambuf; }; static void writeFrame2(FGReplayData* r, std::ostream& out, SGPropertyNode_ptr config) { for (auto data: config->getChildren("data")) { std::string data_type = data->getStringValue(); if (data_type == "signals") { uint32_t signals_size = r->raw_data.size(); writeRaw(out, signals_size); out.write(&r->raw_data.front(), r->raw_data.size()); } else if (data_type == "multiplayer") { uint32_t length = 0; for (auto message: r->multiplayer_messages) { length += sizeof(uint16_t) + message->size(); } SG_LOG(SG_SYSTEMS, SG_DEBUG, "data_type=" << data_type << " out.tellp()=" << out.tellp() << " length=" << length); writeRaw(out, length); for (auto message: r->multiplayer_messages) { uint16_t message_size = message->size(); writeRaw(out, message_size); out.write(&message->front(), message_size); } } else if (data_type == "extra-properties") { uint32_t length = r->extra_properties.size(); SG_LOG(SG_SYSTEMS, SG_DEBUG, "data_type=" << data_type << " out.tellp()=" << out.tellp() << " length=" << length); writeRaw(out, length); out.write(&r->extra_properties[0], length); } else { SG_LOG(SG_SYSTEMS, SG_ALERT, "unrecognised data_type=" << data_type); assert(0); } } } bool continuousWriteFrame( Continuous& continuous, FGReplayData* r, std::ostream& out, SGPropertyNode_ptr config, FGTapeType tape_type ) { SG_LOG(SG_SYSTEMS, SG_BULK, "writing frame." << " out.tellp()=" << out.tellp() << " r->sim_time=" << r->sim_time ); // Don't write frame if no data to write. //bool r_has_data = false; bool has_signals = false; bool has_multiplayer = false; bool has_extra_properties = false; for (auto data: config->getChildren("data")) { std::string data_type = data->getStringValue(); if (data_type == "signals") { has_signals = true; } else if (data_type == "multiplayer") { if (!r->multiplayer_messages.empty()) { has_multiplayer = true; } } else if (data_type == "extra-properties") { if (!r->extra_properties.empty()) { has_extra_properties = true; } } else { SG_LOG(SG_SYSTEMS, SG_ALERT, "unrecognised data_type=" << data_type); assert(0); } } if (!has_signals && !has_multiplayer && !has_extra_properties) { SG_LOG(SG_SYSTEMS, SG_DEBUG, "Not writing frame because no data to write"); return true; } writeRaw(out, r->sim_time); if (tape_type == FGTapeType_CONTINUOUS && continuous.m_out_compression) { uint8_t flags = 0; if (has_signals) flags |= 1; if (has_multiplayer) flags |= 2; if (has_extra_properties) flags |= 4; out.write((char*) &flags, sizeof(flags)); /* We need to first write the size of the compressed data so compress to a temporary ostringstream first. */ std::ostringstream compressed; compression_ostream out_compressing(compressed, 1024, 1024); writeFrame2(r, out_compressing, config); out_compressing.flush(); uint32_t compressed_size = compressed.str().size(); out.write((char*) &compressed_size, sizeof(compressed_size)); out.write((char*) compressed.str().c_str(), compressed.str().size()); } else { writeFrame2(r, out, config); } bool ok = true; if (!out) ok = false; return ok; } SGPropertyNode_ptr continuousWriteHeader( Continuous& continuous, FGFlightRecorder* flight_recorder, std::ofstream& out, const SGPath& path, FGTapeType tape_type ) { continuous.m_out_compression = fgGetInt("/sim/replay/record-continuous-compression"); SGPropertyNode_ptr config = saveSetup(NULL /*Extra*/, path, 0 /*Duration*/, tape_type, continuous.m_out_compression); SGPropertyNode* signals = config->getNode("signals", true /*create*/); flight_recorder->getConfig(signals); out.open(path.c_str(), std::ofstream::binary | std::ofstream::trunc); out.write(FlightRecorderFileMagic, strlen(FlightRecorderFileMagic)+1); PropertiesWrite(config, out); if (tape_type == FGTapeType_CONTINUOUS) { // Ensure that all recorded properties are written in first frame. // flight_recorder->resetExtraProperties(); } if (!out) { out.close(); config = nullptr; } return config; } /* Replays one frame from Continuous recording. and are offsets in file of frames that are >= and <