/* * Copyright 2018 The Cartographer Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using CartographerSharp.Models.Mapping; namespace CartographerSharp.IO; /// /// Helper for deserializing a previously serialized mapping state from a proto stream, /// abstracting away the format parsing logic. /// public class ProtoStreamDeserializer { private const uint FormatVersion = 2; private const uint FormatVersionWithoutSubmapHistograms = 1; private readonly IProtoStreamReader _reader; private readonly SerializationHeader _header; private readonly SerializedData _poseGraph; private readonly SerializedData _allTrajectoryBuilderOptions; public ProtoStreamDeserializer(IProtoStreamReader reader) { ArgumentNullException.ThrowIfNull(reader); _reader = reader; // Read header - use same pattern as ReadNextSerializedData // ReadProto returns T? which for structs becomes Nullable // Using out var should work, but compiler may not infer nullable correctly // So we use a helper method pattern if (!ReadNextSerializedData(out var headerDataNullable) || !headerDataNullable.HasValue) { throw new InvalidOperationException("Failed to read SerializationHeader."); } var headerData = headerDataNullable.Value; if (!headerData.SerializationHeader.HasValue) { throw new InvalidOperationException("SerializedData does not contain SerializationHeader."); } _header = headerData.SerializationHeader.Value; // Validate format version if (!IsVersionSupported(_header)) { throw new NotSupportedException( $"Unsupported serialization format version: {_header.FormatVersion}. " + $"Supported versions: {FormatVersionWithoutSubmapHistograms}, {FormatVersion}"); } // Read pose graph if (!ReadNextSerializedData(out SerializedData? poseGraphDataNullable) || !poseGraphDataNullable.HasValue) { throw new InvalidOperationException( "Serialized stream misses PoseGraph. Expecting `PoseGraph` after `SerializationHeader`."); } var poseGraphData = poseGraphDataNullable.Value; if (!poseGraphData.PoseGraph.HasValue) { throw new InvalidOperationException( "SerializedData does not contain PoseGraph. Expecting `PoseGraph` after `SerializationHeader`."); } _poseGraph = poseGraphData; // Read trajectory builder options if (!ReadNextSerializedData(out var optionsDataNullable) || !optionsDataNullable.HasValue) { throw new InvalidOperationException( "Serialized stream misses `AllTrajectoryBuilderOptions`. " + "Expecting `AllTrajectoryBuilderOptions` after PoseGraph."); } var optionsData = optionsDataNullable.Value; if (!optionsData.AllTrajectoryBuilderOptions.HasValue) { throw new InvalidOperationException( "SerializedData does not contain AllTrajectoryBuilderOptions. " + "Expecting `AllTrajectoryBuilderOptions` after PoseGraph."); } _allTrajectoryBuilderOptions = optionsData; // Validate that trajectory count matches if (_poseGraph.PoseGraph.HasValue && _allTrajectoryBuilderOptions.AllTrajectoryBuilderOptions.HasValue) { var poseGraphProto = _poseGraph.PoseGraph.Value; var optionsProto = _allTrajectoryBuilderOptions.AllTrajectoryBuilderOptions.Value; if (poseGraphProto.Trajectories.Count != optionsProto.OptionsWithSensorIds.Count) { throw new InvalidOperationException( $"Trajectory count mismatch: PoseGraph has {poseGraphProto.Trajectories.Count} trajectories, " + $"but AllTrajectoryBuilderOptions has {optionsProto.OptionsWithSensorIds.Count} options."); } } } /// /// Gets the serialization header. /// public SerializationHeader Header => _header; /// /// Gets the pose graph proto. /// public PoseGraph PoseGraph { get { if (!_poseGraph.PoseGraph.HasValue) throw new InvalidOperationException("PoseGraph is not available."); return _poseGraph.PoseGraph.Value; } } /// /// Gets all trajectory builder options. /// public AllTrajectoryBuilderOptions AllTrajectoryBuilderOptions { get { if (!_allTrajectoryBuilderOptions.AllTrajectoryBuilderOptions.HasValue) throw new InvalidOperationException("AllTrajectoryBuilderOptions is not available."); return _allTrajectoryBuilderOptions.AllTrajectoryBuilderOptions.Value; } } /// /// Reads the next SerializedData message from the ProtoStream. /// Returns true if the message was successfully read, or false if there are no more messages or an error occurred. /// public bool ReadNextSerializedData(out SerializedData? data) { if (_reader.Eof) { data = null; return false; } if (!_reader.ReadProto(out var proto)) { data = null; return false; } data = proto; return data.HasValue; } private static bool IsVersionSupported(SerializationHeader header) { return header.FormatVersion == FormatVersion || header.FormatVersion == FormatVersionWithoutSubmapHistograms; } }