ccSerializableObject.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. //##########################################################################
  2. //# #
  3. //# CLOUDCOMPARE #
  4. //# #
  5. //# This program is free software; you can redistribute it and/or modify #
  6. //# it under the terms of the GNU General Public License as published by #
  7. //# the Free Software Foundation; version 2 or later of the License. #
  8. //# #
  9. //# This program is distributed in the hope that it will be useful, #
  10. //# but WITHOUT ANY WARRANTY; without even the implied warranty of #
  11. //# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
  12. //# GNU General Public License for more details. #
  13. //# #
  14. //# COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI) #
  15. //# #
  16. //##########################################################################
  17. #ifndef CC_SERIALIZABLE_OBJECT_HEADER
  18. #define CC_SERIALIZABLE_OBJECT_HEADER
  19. //Local
  20. #include "ccLog.h"
  21. //CCCoreLib
  22. #include <CCPlatform.h>
  23. #include <CCTypes.h>
  24. //System
  25. #include <cassert>
  26. #include <cstdint>
  27. //Qt
  28. #include <QDataStream>
  29. #include <QMultiMap>
  30. #include <QFile>
  31. //! Serializable object interface
  32. class ccSerializableObject
  33. {
  34. public:
  35. //! Destructor
  36. virtual ~ccSerializableObject() = default;
  37. //! Returns whether object is serializable of not
  38. virtual bool isSerializable() const { return false; }
  39. //! Saves data to binary stream
  40. /** \param out output file (already opened)
  41. \param dataVersion target file version
  42. \return success
  43. **/
  44. virtual bool toFile(QFile& out, short dataVersion) const { return false; }
  45. //! Returns the minimum file version required to save this instance
  46. /** To be overridden
  47. \param out output file (already opened)
  48. \return success
  49. **/
  50. virtual short minimumFileVersion() const = 0;
  51. //! Deserialization flags (bit-field)
  52. enum DeserializationFlags
  53. {
  54. DF_POINT_COORDS_64_BITS = 1, /**< Point coordinates are stored as 64 bits double (otherwise 32 bits floats) **/
  55. //DGM: inversion is 'historical' ;)
  56. DF_SCALAR_VAL_32_BITS = 2, /**< Scalar values are stored as 32 bits floats (otherwise 64 bits double) **/
  57. };
  58. //! Map of loaded unique IDs (old ID --> new ID)
  59. typedef QMultiMap<unsigned, unsigned> LoadedIDMap;
  60. //! Loads data from binary stream
  61. /** \param in input file (already opened)
  62. \param dataVersion file version
  63. \param flags deserialization flags (see ccSerializableObject::DeserializationFlags)
  64. \param oldToNewIDMap map to link old IDs with new IDs
  65. \return success
  66. **/
  67. virtual bool fromFile(QFile& in, short dataVersion, int flags, LoadedIDMap& oldToNewIDMap) { return false; }
  68. //! Sends a custom error message (write error) and returns 'false'
  69. /** Shortcut for returning a standardized error message in the toFile method.
  70. \return always false
  71. **/
  72. static bool WriteError() { ccLog::Error("Write error (disk full or no access right?)"); return false; }
  73. //! Sends a custom error message (read error) and returns 'false'
  74. /** Shortcut for returning a standardized error message in the fromFile method.
  75. \return always false
  76. **/
  77. static bool ReadError() { ccLog::Error("Read error (corrupted file or no access right?)"); return false; }
  78. //! Sends a custom error message (not enough memory) and returns 'false'
  79. /** Shortcut for returning a standardized error message in the fromFile method.
  80. \return always false
  81. **/
  82. static bool MemoryError() { ccLog::Error("Not enough memory"); return false; }
  83. //! Sends a custom error message (corrupted file) and returns 'false'
  84. /** Shortcut for returning a standardized error message in the fromFile method.
  85. \return always false
  86. **/
  87. static bool CorruptError() { ccLog::Error("File seems to be corrupted"); return false; }
  88. };
  89. //! Serialization helpers
  90. class ccSerializationHelper
  91. {
  92. public:
  93. //! Reads one or several 'PointCoordinateType' values from a QDataStream either in float or double format depending on the 'flag' value
  94. static void CoordsFromDataStream(QDataStream& stream, int flags, PointCoordinateType* out, unsigned count = 1)
  95. {
  96. if (flags & ccSerializableObject::DF_POINT_COORDS_64_BITS)
  97. {
  98. for (unsigned i = 0; i < count; ++i, ++out)
  99. {
  100. double val;
  101. stream >> val;
  102. *out = static_cast<PointCoordinateType>(val);
  103. }
  104. }
  105. else
  106. {
  107. for (unsigned i = 0; i < count; ++i, ++out)
  108. {
  109. float val;
  110. stream >> val;
  111. *out = static_cast<PointCoordinateType>(val);
  112. }
  113. }
  114. }
  115. //! Reads one or several 'ScalarType' values from a QDataStream either in float or double format depending on the 'flag' value
  116. static void ScalarsFromDataStream(QDataStream& stream, int flags, ScalarType* out, unsigned count = 1)
  117. {
  118. if (flags & ccSerializableObject::DF_SCALAR_VAL_32_BITS)
  119. {
  120. for (unsigned i = 0; i < count; ++i, ++out)
  121. {
  122. float val;
  123. stream >> val;
  124. *out = static_cast<PointCoordinateType>(val);
  125. }
  126. }
  127. else
  128. {
  129. for (unsigned i = 0; i < count; ++i, ++out)
  130. {
  131. double val;
  132. stream >> val;
  133. *out = static_cast<PointCoordinateType>(val);
  134. }
  135. }
  136. }
  137. //! Returns the minimum file version to save/load a 'generic array'
  138. static short GenericArrayToFileMinVersion() { return 20; }
  139. //! Helper: saves a vector to file
  140. /** \param data vector to save (must be allocated)
  141. \param out output file (must be already opened)
  142. \return success
  143. **/
  144. template <class Type, int N, class ComponentType> static bool GenericArrayToFile(const std::vector<Type>& data, QFile& out)
  145. {
  146. assert(out.isOpen() && (out.openMode() & QIODevice::WriteOnly));
  147. //removed to allow saving empty clouds
  148. //if (data.empty())
  149. //{
  150. // return ccSerializableObject::MemoryError();
  151. //}
  152. //component count (dataVersion>=20)
  153. ::uint8_t componentCount = static_cast<::uint8_t>(N);
  154. if (out.write((const char*)&componentCount, 1) < 0)
  155. return ccSerializableObject::WriteError();
  156. //element count = array size (dataVersion>=20)
  157. ::uint32_t elementCount = static_cast<::uint32_t>(data.size());
  158. if (out.write((const char*)&elementCount, 4) < 0)
  159. return ccSerializableObject::WriteError();
  160. //array data (dataVersion>=20)
  161. {
  162. //DGM: do it by chunks, in case it's too big to be processed by the system
  163. const char* _data = (const char*)data.data();
  164. qint64 byteCount = static_cast<qint64>(elementCount);
  165. byteCount *= sizeof(Type);
  166. while (byteCount != 0)
  167. {
  168. static const qint64 s_maxByteSaveCount = (1 << 26); //64 Mb each time
  169. qint64 saveCount = std::min(byteCount, s_maxByteSaveCount);
  170. if (out.write(_data, saveCount) < 0)
  171. return ccSerializableObject::WriteError();
  172. _data += saveCount;
  173. byteCount -= saveCount;
  174. }
  175. }
  176. return true;
  177. }
  178. //! Helper: loads a vector structure from file
  179. /** \param data vector to load
  180. \param in input file (must be already opened)
  181. \param dataVersion version current data version
  182. \return success
  183. **/
  184. template <class Type, int N, class ComponentType>
  185. static bool GenericArrayFromFile( std::vector<Type>& data,
  186. QFile& in,
  187. short dataVersion,
  188. const QString& verboseDescription )
  189. {
  190. ::uint8_t componentCount = 0;
  191. ::uint32_t elementCount = 0;
  192. if (!ReadArrayHeader(in, dataVersion, componentCount, elementCount))
  193. {
  194. return false;
  195. }
  196. if (componentCount != N)
  197. {
  198. return ccSerializableObject::CorruptError();
  199. }
  200. ccLog::PrintVerbose(QString("Loading %0: %1 elements and %2 dimension(s)").arg(verboseDescription).arg(elementCount).arg(componentCount));
  201. if (elementCount)
  202. {
  203. //try to allocate memory
  204. try
  205. {
  206. data.resize(elementCount);
  207. }
  208. catch (const std::bad_alloc&)
  209. {
  210. return ccSerializableObject::MemoryError();
  211. }
  212. //array data (dataVersion>=20)
  213. {
  214. //Apparently Qt and/or Windows don't like to read too many bytes in a row...
  215. static const qint64 MaxElementPerChunk = (static_cast<qint64>(1) << 24);
  216. assert(sizeof(ComponentType) * N == sizeof(Type));
  217. qint64 byteCount = static_cast<qint64>(data.size()) * (sizeof(ComponentType) * N);
  218. char* dest = (char*)data.data();
  219. while (byteCount > 0)
  220. {
  221. qint64 chunkSize = std::min(MaxElementPerChunk, byteCount);
  222. if (in.read(dest, chunkSize) < 0)
  223. {
  224. return ccSerializableObject::ReadError();
  225. }
  226. byteCount -= chunkSize;
  227. dest += chunkSize;
  228. }
  229. }
  230. }
  231. return true;
  232. }
  233. //! Helper: loads a vector structure from a file stored with a different type
  234. /** \param data vector to load
  235. \param in input file (must be already opened)
  236. \param dataVersion version current data version
  237. \param _autoOffset optional: automatic offset to be applied at loading time (based on the first eleemnt)
  238. \return success
  239. **/
  240. template <class Type, int N, class ComponentType, class FileComponentType>
  241. static bool GenericArrayFromTypedFile( std::vector<Type>& data,
  242. QFile& in,
  243. short dataVersion,
  244. const QString& verboseDescription,
  245. FileComponentType* _autoOffset = nullptr)
  246. {
  247. ::uint8_t componentCount = 0;
  248. ::uint32_t elementCount = 0;
  249. if (!ReadArrayHeader(in, dataVersion, componentCount, elementCount))
  250. {
  251. return false;
  252. }
  253. if (componentCount != N)
  254. {
  255. return ccSerializableObject::CorruptError();
  256. }
  257. ccLog::PrintVerbose(QString("Loading %0: %1 elements and %2 dimension(s)").arg(verboseDescription).arg(elementCount).arg(componentCount));
  258. if (elementCount)
  259. {
  260. //try to allocate memory
  261. try
  262. {
  263. data.resize(elementCount);
  264. }
  265. catch (const std::bad_alloc&)
  266. {
  267. return ccSerializableObject::MemoryError();
  268. }
  269. //array data (dataVersion>=20)
  270. //--> sadly we can't read it as a block...
  271. //we must convert each element, value by value!
  272. FileComponentType dummyArray[N] { 0 };
  273. ComponentType* _data = (ComponentType*)data.data();
  274. size_t elementSize = sizeof(FileComponentType) * N;
  275. if (_autoOffset)
  276. {
  277. //read the first element
  278. if (in.read((char*)dummyArray, elementSize) >= 0)
  279. {
  280. for (unsigned k = 0; k < N; ++k)
  281. {
  282. *_autoOffset = dummyArray[k];
  283. *_data++ = 0;
  284. }
  285. }
  286. else
  287. {
  288. return ccSerializableObject::ReadError();
  289. }
  290. //read the next elements
  291. for (unsigned i = 1; i < elementCount; ++i)
  292. {
  293. if (in.read((char*)dummyArray, elementSize) >= 0)
  294. {
  295. for (unsigned k = 0; k < N; ++k)
  296. {
  297. *_data++ = static_cast<ComponentType>(dummyArray[k] - _autoOffset[k]);
  298. }
  299. }
  300. else
  301. {
  302. return ccSerializableObject::ReadError();
  303. }
  304. }
  305. }
  306. else
  307. {
  308. // no automatic offset
  309. for (unsigned i = 0; i < elementCount; ++i)
  310. {
  311. if (in.read((char*)dummyArray, sizeof(FileComponentType) * N) >= 0)
  312. {
  313. for (unsigned k = 0; k < N; ++k)
  314. {
  315. *_data++ = static_cast<ComponentType>(dummyArray[k]);
  316. }
  317. }
  318. else
  319. {
  320. return ccSerializableObject::ReadError();
  321. }
  322. }
  323. }
  324. }
  325. return true;
  326. }
  327. protected:
  328. static bool ReadArrayHeader(QFile& in,
  329. short dataVersion,
  330. ::uint8_t &componentCount,
  331. ::uint32_t &elementCount)
  332. {
  333. assert(in.isOpen() && (in.openMode() & QIODevice::ReadOnly));
  334. if (dataVersion < 20)
  335. return ccSerializableObject::CorruptError();
  336. //component count (dataVersion>=20)
  337. if (in.read((char*)&componentCount, 1) < 0)
  338. return ccSerializableObject::ReadError();
  339. //element count = array size (dataVersion>=20)
  340. if (in.read((char*)&elementCount, 4) < 0)
  341. return ccSerializableObject::ReadError();
  342. return true;
  343. }
  344. };
  345. #endif //CC_SERIALIZABLE_OBJECT_HEADER