diff -Nru ignition-common-4.4.0+ds/Changelog.md ignition-common-4.5.0+ds/Changelog.md --- ignition-common-4.4.0+ds/Changelog.md 2021-10-15 18:24:48.000000000 +0000 +++ ignition-common-4.5.0+ds/Changelog.md 2022-01-12 22:11:17.000000000 +0000 @@ -1,8 +1,40 @@ ## Ignition Common 4.x +## Ignition Common 4.5.0 (2022-01-12) + +1. Fixed crash when a Collada file has an empty normal vector + * [Pull request #280](https://github.com/ignitionrobotics/ign-common/pull/280) + +1. Support 16 bit heightmaps + * [Pull request #266](https://github.com/ignitionrobotics/ign-common/pull/266) + +1. Fix bug in URIPath assignment operator + * [Pull request #275](https://github.com/ignitionrobotics/ign-common/pull/275) + +1. Use `libexec` to install binary `remotery_vis` + * [Pull request #272](https://github.com/ignitionrobotics/ign-common/pull/272) + +1. Normalize normal vectors from OBJ. + * [Pull request #269](https://github.com/ignitionrobotics/ign-common/pull/269) + +1. Synchronize console writes + * [Pull request #227](https://github.com/ignitionrobotics/ign-common/pull/227) + +1. Added method to remove meshes from the `MeshManager` + * [Pull request #222](https://github.com/ignitionrobotics/ign-common/pull/222) + +1. Fixed macOS symbol in `common::Profiler` + * [Pull request #262](https://github.com/ignitionrobotics/ign-common/pull/262) + +1. Fix skip logic for integration tests + * [Pull request #264](https://github.com/ignitionrobotics/ign-common/pull/264) + +1. Use direct evaluation for SKIP_av. + * [Pull request #250](https://github.com/ignitionrobotics/ign-common/pull/250) + ## Ignition Common 4.4.0 (2021-10-15) -1. Add support for animation tension +1. Add support for animation tension * [Pull request #256](https://github.com/ignitionrobotics/ign-common/pull/256) ## Ignition Common 4.3.0 (2021-09-27) @@ -26,7 +58,7 @@ * [Pull request #62](https://github.com/ignitionrobotics/ign-common/pull/62) * [Pull request #55](https://github.com/ignitionrobotics/ign-common/pull/55) * [Pull request #241](https://github.com/ignitionrobotics/ign-common/pull/241) - + 1. Documentation * [Pull request #252](https://github.com/ignitionrobotics/ign-common/pull/252) * [Pull request #253](https://github.com/ignitionrobotics/ign-common/pull/253) @@ -141,6 +173,32 @@ ## Ignition Common 3.X.X +## Ignition Common 3.14.0 (2021-10-12) + +1. Support loading PBR textures in OBJLoader + * [Pull request #216](https://github.com/ignitionrobotics/ign-common/pull/216) + +1. Remove CMAKE_CXX_FLAGS from test targetrs + * [Pull request #214](https://github.com/ignitionrobotics/ign-common/pull/214) + +1. Set project-wide standard to C++17 + * [Pull request #221](https://github.com/ignitionrobotics/ign-common/pull/221) + +1. Fix av_* API usage for deprecations + * [Pull request #220](https://github.com/ignitionrobotics/ign-common/pull/220) + +1. Make KeyEvent rule-of-five compliant + * [Pull request #224](https://github.com/ignitionrobotics/ign-common/pull/224) + +1. Fix segfault caused by destructionb order of Event and Connection + * [Pull request #234](https://github.com/ignitionrobotics/ign-common/pull/234) + +1. Fix a typo in VideoEncoder_TEST + * [Pull request #231](https://github.com/ignitionrobotics/ign-common/pull/231) + +1. Use direct evaluation for SKIP_av + * [Pull request #250](https://github.com/ignitionrobotics/ign-common/pull/250) + ## Ignition Common 3.13.2 (2021-05-11) 1. Backport collada fixes (Backport #204) diff -Nru ignition-common-4.4.0+ds/CMakeLists.txt ignition-common-4.5.0+ds/CMakeLists.txt --- ignition-common-4.4.0+ds/CMakeLists.txt 2021-10-15 18:24:48.000000000 +0000 +++ ignition-common-4.5.0+ds/CMakeLists.txt 2022-01-12 22:11:17.000000000 +0000 @@ -3,7 +3,7 @@ #============================================================================ # Initialize the project #============================================================================ -project(ignition-common4 VERSION 4.4.0) +project(ignition-common4 VERSION 4.5.0) #============================================================================ # Find ignition-cmake diff -Nru ignition-common-4.4.0+ds/debian/changelog ignition-common-4.5.0+ds/debian/changelog --- ignition-common-4.4.0+ds/debian/changelog 2022-01-29 08:22:23.000000000 +0000 +++ ignition-common-4.5.0+ds/debian/changelog 2022-02-14 23:38:49.000000000 +0000 @@ -1,3 +1,10 @@ +ignition-common (4.5.0+ds-1) unstable; urgency=medium + + * New upstream version 4.5.0+ds + * Delele patch merged upstream + + -- Jose Luis Rivero Mon, 14 Feb 2022 23:38:49 +0000 + ignition-common (4.4.0+ds-5) unstable; urgency=medium * Team upload. diff -Nru ignition-common-4.4.0+ds/debian/patches/0006-install-binary-in-libexec.patch ignition-common-4.5.0+ds/debian/patches/0006-install-binary-in-libexec.patch --- ignition-common-4.4.0+ds/debian/patches/0006-install-binary-in-libexec.patch 2022-01-28 08:36:05.000000000 +0000 +++ ignition-common-4.5.0+ds/debian/patches/0006-install-binary-in-libexec.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ -Description: Install remotery binary in libexec -Author: Jose Luis Rivero -Forwarded: https://github.com/ignitionrobotics/ign-common/pull/272 -Last-Update: 2021-11-26 ---- -This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ ---- a/profiler/src/CMakeLists.txt -+++ b/profiler/src/CMakeLists.txt -@@ -88,7 +88,7 @@ - endif() - - if(IGN_PROFILER_REMOTERY) -- set(IGN_PROFILER_SCRIPT_PATH ${CMAKE_INSTALL_LIBDIR}/ignition/ignition-common${PROJECT_VERSION_MAJOR}) -+ set(IGN_PROFILER_SCRIPT_PATH ${CMAKE_INSTALL_LIBEXECDIR}/ignition/ignition-common${PROJECT_VERSION_MAJOR}) - set(IGN_PROFILER_VIS_PATH ${IGN_DATA_INSTALL_DIR}/profiler_vis) - - configure_file(Remotery/ign_remotery_vis.in diff -Nru ignition-common-4.4.0+ds/debian/patches/series ignition-common-4.5.0+ds/debian/patches/series --- ignition-common-4.4.0+ds/debian/patches/series 2022-01-28 08:36:05.000000000 +0000 +++ ignition-common-4.5.0+ds/debian/patches/series 2022-02-14 23:38:41.000000000 +0000 @@ -1,4 +1,3 @@ -0006-install-binary-in-libexec.patch 0001_use_system_gtest.patch 0002_disable_gui_test.patch 0004-Disable-failing-tests-due-to-copyrighted-files.patch diff -Nru ignition-common-4.4.0+ds/graphics/include/ignition/common/ImageHeightmap.hh ignition-common-4.5.0+ds/graphics/include/ignition/common/ImageHeightmap.hh --- ignition-common-4.4.0+ds/graphics/include/ignition/common/ImageHeightmap.hh 2021-10-15 18:24:48.000000000 +0000 +++ ignition-common-4.5.0+ds/graphics/include/ignition/common/ImageHeightmap.hh 2022-01-12 22:11:17.000000000 +0000 @@ -17,6 +17,7 @@ #ifndef IGNITION_COMMON_IMAGEHEIGHTMAPDATA_HH_ #define IGNITION_COMMON_IMAGEHEIGHTMAPDATA_HH_ +#include #include #include #include @@ -63,6 +64,81 @@ /// \brief Image containing the heightmap data. private: ignition::common::Image img; + + /// \brief Get Heightmap heights given the image + /// \param[in] _data Image data + /// \param[in] _pitch Size of a row of image pixels in bytes + /// \param[in] _subSampling Subsampling factor + /// \param[in] _vertSize Number of points per row. + /// \param[in] _size Real dimmensions of the terrain. + /// \param[in] _scale Vector3 used to scale the height. + /// \param[in] _flipY If true, it inverts the order in which the vector + /// is filled. + /// \param[out] _heights Vector containing the terrain heights. + private: template + void FillHeights(T *_data, int _imgHeight, int _imgWidth, + unsigned int _pitch, int _subSampling, unsigned int _vertSize, + const ignition::math::Vector3d &_size, + const ignition::math::Vector3d &_scale, + bool _flipY, std::vector &_heights) + { + // bytes per pixel + const unsigned int bpp = _pitch / _imgWidth; + // number of channels in a pixel + const unsigned int channels = bpp / sizeof(T); + // number of pixels in a row of image + const unsigned int pitchInPixels = _pitch / bpp; + + const double maxPixelValue = + static_cast(std::numeric_limits::max()); + + // Iterate over all the vertices + for (unsigned int y = 0; y < _vertSize; ++y) + { + // yf ranges between 0 and 4 + const double yf = y / static_cast(_subSampling); + const int y1 = static_cast(std::floor(yf)); + int y2 = static_cast(std::ceil(yf)); + if (y2 >= _imgHeight) + y2 = _imgHeight - 1; + const double dy = yf - y1; + + for (unsigned int x = 0; x < _vertSize; ++x) + { + const double xf = x / static_cast(_subSampling); + const int x1 = static_cast(std::floor(xf)); + int x2 = static_cast(std::ceil(xf)); + if (x2 >= _imgWidth) + x2 = _imgWidth - 1; + const double dx = xf - x1; + + const double px1 = static_cast( + _data[(y1 * pitchInPixels + x1) * channels]) / maxPixelValue; + const double px2 = static_cast( + _data[(y1 * pitchInPixels + x2) * channels]) / maxPixelValue; + const float h1 = (px1 - ((px1 - px2) * dx)); + + const double px3 = static_cast( + _data[(y2 * pitchInPixels + x1) * channels]) / maxPixelValue; + const double px4 = static_cast( + _data[(y2 * pitchInPixels + x2) * channels]) / maxPixelValue; + const float h2 = (px3 - ((px3 - px4) * dx)); + float h = (h1 - ((h1 - h2) * dy)) * _scale.Z(); + + // invert pixel definition so 1=ground, 0=full height, + // if the terrain size has a negative z component + // this is mainly for backward compatibility + if (_size.Z() < 0) + h = 1.0 - h; + + // Store the height for future use + if (!_flipY) + _heights[y * _vertSize + x] = h; + else + _heights[(_vertSize - y - 1) * _vertSize + x] = h; + } + } + } }; } } diff -Nru ignition-common-4.4.0+ds/graphics/include/ignition/common/MeshManager.hh ignition-common-4.5.0+ds/graphics/include/ignition/common/MeshManager.hh --- ignition-common-4.4.0+ds/graphics/include/ignition/common/MeshManager.hh 2021-10-15 18:24:48.000000000 +0000 +++ ignition-common-4.5.0+ds/graphics/include/ignition/common/MeshManager.hh 2022-01-12 22:11:17.000000000 +0000 @@ -100,6 +100,15 @@ /// \param[in] the mesh to add. public: void AddMesh(Mesh *_mesh); + /// \brief Remove a mesh based on a name. + /// \param[in] _name Name of the mesh to remove. + /// \return True if the mesh was removed, false if the mesh with the + /// provided name could not be found. + public: bool RemoveMesh(const std::string &_name); + + /// \brief Remove all meshes. + public: void RemoveAll(); + /// \brief Get a mesh by name. /// \param[in] _name the name of the mesh to look for /// \return the mesh or nullptr if not found diff -Nru ignition-common-4.4.0+ds/graphics/src/ColladaLoader.cc ignition-common-4.5.0+ds/graphics/src/ColladaLoader.cc --- ignition-common-4.4.0+ds/graphics/src/ColladaLoader.cc 2021-10-15 18:24:48.000000000 +0000 +++ ignition-common-4.5.0+ds/graphics/src/ColladaLoader.cc 2022-01-12 22:11:17.000000000 +0000 @@ -2248,8 +2248,11 @@ inputRemappedNormalIndex = normalDupMap[inputRemappedNormalIndex]; } - subMesh->AddNormal(norms[inputRemappedNormalIndex]); - input.normalIndex = inputRemappedNormalIndex; + if (norms.size() > inputRemappedNormalIndex) + { + subMesh->AddNormal(norms[inputRemappedNormalIndex]); + input.normalIndex = inputRemappedNormalIndex; + } } if (!inputs[TEXCOORD].empty()) @@ -2370,7 +2373,8 @@ this->LoadNormals(source, _transform, norms, normalDupMap); combinedVertNorms = false; inputs[NORMAL].insert(ignition::math::parseInt(offset)); - hasNormals = true; + if (norms.size() > 0) + hasNormals = true; } else if (semantic == "TEXCOORD") { diff -Nru ignition-common-4.4.0+ds/graphics/src/ImageHeightmap.cc ignition-common-4.5.0+ds/graphics/src/ImageHeightmap.cc --- ignition-common-4.4.0+ds/graphics/src/ImageHeightmap.cc 2021-10-15 18:24:48.000000000 +0000 +++ ignition-common-4.5.0+ds/graphics/src/ImageHeightmap.cc 2022-01-12 22:11:17.000000000 +0000 @@ -54,57 +54,44 @@ // Bytes per row unsigned int pitch = this->img.Pitch(); - // Bytes per pixel - unsigned int bpp = pitch / imgWidth; + // Get the image format so we can arrange our heightmap + // Currently supported: 8-bit and 16-bit. + auto imgFormat = this->img.PixelFormat(); unsigned char *data = nullptr; unsigned int count; this->img.Data(&data, count); - // Iterate over all the vertices - for (unsigned int y = 0; y < _vertSize; ++y) + if (imgFormat == ignition::common::Image::PixelFormatType::L_INT8 || + imgFormat == ignition::common::Image::PixelFormatType::RGB_INT8 || + imgFormat == ignition::common::Image::PixelFormatType::RGBA_INT8 || + imgFormat == ignition::common::Image::PixelFormatType::BAYER_BGGR8 || + imgFormat == ignition::common::Image::PixelFormatType::BAYER_GBRG8 || + imgFormat == ignition::common::Image::PixelFormatType::BAYER_GRBG8 || + imgFormat == ignition::common::Image::PixelFormatType::BAYER_GRBG8 || + imgFormat == ignition::common::Image::PixelFormatType::BAYER_RGGB8 || + imgFormat == ignition::common::Image::PixelFormatType::BGR_INT8 || + imgFormat == ignition::common::Image::PixelFormatType::BGRA_INT8) { - // yf ranges between 0 and 4 - double yf = y / static_cast(_subSampling); - int y1 = static_cast(std::floor(yf)); - int y2 = static_cast(std::ceil(yf)); - if (y2 >= imgHeight) - y2 = imgHeight-1; - double dy = yf - y1; - - for (unsigned int x = 0; x < _vertSize; ++x) - { - double xf = x / static_cast(_subSampling); - int x1 = static_cast(std::floor(xf)); - int x2 = static_cast(std::ceil(xf)); - if (x2 >= imgWidth) - x2 = imgWidth-1; - double dx = xf - x1; - - double px1 = static_cast(data[y1 * pitch + x1 * bpp]) / 255.0; - double px2 = static_cast(data[y1 * pitch + x2 * bpp]) / 255.0; - float h1 = (px1 - ((px1 - px2) * dx)); - - double px3 = static_cast(data[y2 * pitch + x1 * bpp]) / 255.0; - double px4 = static_cast(data[y2 * pitch + x2 * bpp]) / 255.0; - float h2 = (px3 - ((px3 - px4) * dx)); - - float h = (h1 - ((h1 - h2) * dy)) * _scale.Z(); - - // invert pixel definition so 1=ground, 0=full height, - // if the terrain size has a negative z component - // this is mainly for backward compatibility - if (_size.Z() < 0) - h = 1.0 - h; - - // Store the height for future use - if (!_flipY) - _heights[y * _vertSize + x] = h; - else - _heights[(_vertSize - y - 1) * _vertSize + x] = h; - } + this->FillHeights(data, imgHeight, imgWidth, pitch, + _subSampling, _vertSize, _size, _scale, _flipY, _heights); + } + else if (imgFormat == ignition::common::Image::PixelFormatType::BGR_INT16 || + imgFormat == ignition::common::Image::PixelFormatType::L_INT16 || + imgFormat == ignition::common::Image::PixelFormatType::RGB_FLOAT16 || + imgFormat == ignition::common::Image::PixelFormatType::RGB_INT16 || + imgFormat == ignition::common::Image::PixelFormatType::R_FLOAT16) + { + uint16_t *dataShort = reinterpret_cast(data); + this->FillHeights(dataShort, imgHeight, imgWidth, pitch, + _subSampling, _vertSize, _size, _scale, _flipY, _heights); + } + else + { + ignerr << "Unsupported image format, " + "heightmap will not be loaded" << std::endl; + return; } - delete [] data; } diff -Nru ignition-common-4.4.0+ds/graphics/src/MeshManager.cc ignition-common-4.5.0+ds/graphics/src/MeshManager.cc --- ignition-common-4.4.0+ds/graphics/src/MeshManager.cc 2021-10-15 18:24:48.000000000 +0000 +++ ignition-common-4.5.0+ds/graphics/src/MeshManager.cc 2022-01-12 22:11:17.000000000 +0000 @@ -242,6 +242,33 @@ } ////////////////////////////////////////////////// +void MeshManager::RemoveAll() +{ + std::lock_guard lock(this->dataPtr->mutex); + for (auto m : this->dataPtr->meshes) + { + delete m.second; + } + this->dataPtr->meshes.clear(); +} + +////////////////////////////////////////////////// +bool MeshManager::RemoveMesh(const std::string &_name) +{ + std::lock_guard lock(this->dataPtr->mutex); + + auto iter = this->dataPtr->meshes.find(_name); + if (iter != this->dataPtr->meshes.end()) + { + delete iter->second; + this->dataPtr->meshes.erase(iter); + return true; + } + + return false; +} + +////////////////////////////////////////////////// bool MeshManager::HasMesh(const std::string &_name) const { if (_name.empty()) diff -Nru ignition-common-4.4.0+ds/graphics/src/MeshManager_TEST.cc ignition-common-4.5.0+ds/graphics/src/MeshManager_TEST.cc --- ignition-common-4.4.0+ds/graphics/src/MeshManager_TEST.cc 2021-10-15 18:24:48.000000000 +0000 +++ ignition-common-4.5.0+ds/graphics/src/MeshManager_TEST.cc 2022-01-12 22:11:17.000000000 +0000 @@ -268,6 +268,28 @@ } ///////////////////////////////////////////////// +TEST_F(MeshManager, Remove) +{ + auto mgr = common::MeshManager::Instance(); + + EXPECT_FALSE(mgr->HasMesh("box")); + mgr->CreateBox("box", + ignition::math::Vector3d(1, 1, 1), + ignition::math::Vector2d(0, 0)); + EXPECT_TRUE(mgr->HasMesh("box")); + + mgr->CreateSphere("sphere", 1.0, 1, 1); + EXPECT_TRUE(mgr->HasMesh("sphere")); + + EXPECT_TRUE(mgr->RemoveMesh("box")); + EXPECT_FALSE(mgr->HasMesh("box")); + EXPECT_TRUE(mgr->HasMesh("sphere")); + + mgr->RemoveAll(); + EXPECT_FALSE(mgr->HasMesh("sphere")); +} + +///////////////////////////////////////////////// int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); diff -Nru ignition-common-4.4.0+ds/graphics/src/OBJLoader.cc ignition-common-4.5.0+ds/graphics/src/OBJLoader.cc --- ignition-common-4.4.0+ds/graphics/src/OBJLoader.cc 2021-10-15 18:24:48.000000000 +0000 +++ ignition-common-4.5.0+ds/graphics/src/OBJLoader.cc 2022-01-12 22:11:17.000000000 +0000 @@ -234,6 +234,7 @@ ignition::math::Vector3d normal(attrib.normals[3 * nIdx], attrib.normals[3 * nIdx + 1], attrib.normals[3 * nIdx + 2]); + normal.Normalize(); subMesh->AddNormal(normal); } // texcoords diff -Nru ignition-common-4.4.0+ds/include/ignition/common/Console.hh ignition-common-4.5.0+ds/include/ignition/common/Console.hh --- ignition-common-4.4.0+ds/include/ignition/common/Console.hh 2021-10-15 18:24:48.000000000 +0000 +++ ignition-common-4.5.0+ds/include/ignition/common/Console.hh 2022-01-12 22:11:17.000000000 +0000 @@ -183,10 +183,17 @@ /// \brief Destructor. public: virtual ~Buffer(); + /// \brief Writes _count characters to the string buffer + /// \param[in] _char Input rharacter array. + /// \param[in] _count Number of characters in array. + /// \return The number of characters successfully written. + public: std::streamsize xsputn( + const char *_char, std::streamsize _count) override; + /// \brief Sync the stream (output the string buffer /// contents). /// \return Return 0 on success. - public: virtual int sync(); + public: int sync() override; /// \brief Destination type for the messages. public: LogType type; @@ -198,6 +205,12 @@ /// \brief Level of verbosity public: int verbosity; + + /// \brief Mutex to synchronize writes to the string buffer + /// and the output stream. + /// \todo(nkoenig) Put this back in for ign-common5, and + /// remove the corresponding static version. + // public: std::mutex syncMutex; }; IGN_COMMON_WARN_IGNORE__DLL_INTERFACE_MISSING diff -Nru ignition-common-4.4.0+ds/profiler/src/CMakeLists.txt ignition-common-4.5.0+ds/profiler/src/CMakeLists.txt --- ignition-common-4.4.0+ds/profiler/src/CMakeLists.txt 2021-10-15 18:24:48.000000000 +0000 +++ ignition-common-4.5.0+ds/profiler/src/CMakeLists.txt 2022-01-12 22:11:17.000000000 +0000 @@ -58,6 +58,7 @@ # Always enable profiler so that it's built, but make it # private so that downstream users can still disable profiling target_compile_definitions(${profiler_target} PRIVATE "IGN_PROFILER_ENABLE=1") +target_compile_definitions(${profiler_target} PRIVATE "RMT_USE_METAL=${RMT_USE_METAL}") if(IGN_PROFILER_REMOTERY) target_compile_definitions(${profiler_target} PRIVATE "IGN_PROFILER_REMOTERY=1") @@ -88,7 +89,7 @@ endif() if(IGN_PROFILER_REMOTERY) - set(IGN_PROFILER_SCRIPT_PATH ${CMAKE_INSTALL_LIBDIR}/ignition/ignition-common${PROJECT_VERSION_MAJOR}) + set(IGN_PROFILER_SCRIPT_PATH ${CMAKE_INSTALL_LIBEXECDIR}/ignition/ignition-common${PROJECT_VERSION_MAJOR}) set(IGN_PROFILER_VIS_PATH ${IGN_DATA_INSTALL_DIR}/profiler_vis) configure_file(Remotery/ign_remotery_vis.in diff -Nru ignition-common-4.4.0+ds/src/Console.cc ignition-common-4.5.0+ds/src/Console.cc --- ignition-common-4.4.0+ds/src/Console.cc 2021-10-15 18:24:48.000000000 +0000 +++ ignition-common-4.5.0+ds/src/Console.cc 2022-01-12 22:11:17.000000000 +0000 @@ -16,6 +16,7 @@ */ #include #include +#include #include #include @@ -27,6 +28,7 @@ using namespace ignition; using namespace common; + FileLogger ignition::common::Console::log(""); // On UNIX, these are ANSI-based color codes. On Windows, these are colors from @@ -107,6 +109,23 @@ return (*this); } + +///////////////////////////////////////////////// +/// \brief Provide a lock_guard for a given buffer +/// This is workaround to keep us from breaking ABI, and can be removed +/// in future versions +/// \param[in] _bufferId a unique id of the requesting buffer +/// Acquired via reinterpret_cast(this) +/// \returns A RAII lock guard +std::lock_guard BufferLock(std::uintptr_t _bufferId) +{ + static std::unordered_map *kSyncMutex{nullptr}; + if(nullptr == kSyncMutex) + kSyncMutex = new std::unordered_map(); + + return std::lock_guard(kSyncMutex->operator[](_bufferId)); +} + ///////////////////////////////////////////////// Logger::Buffer::Buffer(LogType _type, const int _color, const int _verbosity) : type(_type), color(_color), verbosity(_verbosity) @@ -120,13 +139,28 @@ } ///////////////////////////////////////////////// +std::streamsize Logger::Buffer::xsputn(const char *_char, + std::streamsize _count) +{ + auto lk = BufferLock(reinterpret_cast(this)); + return std::stringbuf::xsputn(_char, _count); +} + +///////////////////////////////////////////////// int Logger::Buffer::sync() { - std::string outstr = this->str(); + std::string outstr; + { + auto lk = BufferLock(reinterpret_cast(this)); + outstr = this->str(); + } // Log messages to disk - Console::log << outstr; - Console::log.flush(); + { + auto lk = BufferLock(reinterpret_cast(this)); + Console::log << outstr; + Console::log.flush(); + } // Output to terminal if (Console::Verbosity() >= this->verbosity && !outstr.empty()) @@ -143,7 +177,10 @@ if (lastNewLine) ss << std::endl; - fprintf(outstream, "%s", ss.str().c_str()); + { + auto lk = BufferLock(reinterpret_cast(this)); + fprintf(outstream, "%s", ss.str().c_str()); + } #else HANDLE hConsole = CreateFileW( L"CONOUT$", GENERIC_WRITE|GENERIC_READ, 0, nullptr, OPEN_EXISTING, @@ -168,14 +205,20 @@ std::ostream &outStream = this->type == Logger::STDOUT ? std::cout : std::cerr; - if (vtProcessing) - outStream << "\x1b[" << this->color << "m" << outstr << "\x1b[m"; - else - outStream << outstr; + { + auto lk = BufferLock(reinterpret_cast(this)); + if (vtProcessing) + outStream << "\x1b[" << this->color << "m" << outstr << "\x1b[m"; + else + outStream << outstr; + } #endif } - this->str(""); + { + auto lk = BufferLock(reinterpret_cast(this)); + this->str(""); + } return 0; } diff -Nru ignition-common-4.4.0+ds/src/URI.cc ignition-common-4.5.0+ds/src/URI.cc --- ignition-common-4.4.0+ds/src/URI.cc 2021-10-15 18:24:48.000000000 +0000 +++ ignition-common-4.5.0+ds/src/URI.cc 2022-01-12 22:11:17.000000000 +0000 @@ -587,6 +587,7 @@ { this->dataPtr->path = _path.dataPtr->path; this->dataPtr->isAbsolute = _path.dataPtr->isAbsolute; + this->dataPtr->trailingSlash = _path.dataPtr->trailingSlash; return *this; } diff -Nru ignition-common-4.4.0+ds/src/URI_TEST.cc ignition-common-4.5.0+ds/src/URI_TEST.cc --- ignition-common-4.4.0+ds/src/URI_TEST.cc 2021-10-15 18:24:48.000000000 +0000 +++ ignition-common-4.5.0+ds/src/URI_TEST.cc 2022-01-12 22:11:17.000000000 +0000 @@ -890,8 +890,8 @@ // Modifyng path updates string uri.Path() = URIPath("new_authority.com/another/path"); - EXPECT_EQ("new_authority.com/another/path/", uri.Path().Str()); - EXPECT_EQ("https://new_authority.com/another/path/", uri.Str()); + EXPECT_EQ("new_authority.com/another/path", uri.Path().Str()); + EXPECT_EQ("https://new_authority.com/another/path", uri.Str()); // Clearing keeps false authority uri.Clear(); diff -Nru ignition-common-4.4.0+ds/src/Util.cc ignition-common-4.5.0+ds/src/Util.cc --- ignition-common-4.4.0+ds/src/Util.cc 2021-10-15 18:24:48.000000000 +0000 +++ ignition-common-4.5.0+ds/src/Util.cc 2022-01-12 22:11:17.000000000 +0000 @@ -249,19 +249,36 @@ return timeToIso(IGN_SYSTEM_TIME()); } +// Taken from gtest.cc +static bool PortableLocaltime(time_t seconds, struct tm* out) { +#if defined(_MSC_VER) + return localtime_s(out, &seconds) == 0; +#elif defined(__MINGW32__) || defined(__MINGW64__) + // MINGW provides neither localtime_r nor localtime_s, but uses + // Windows' localtime(), which has a thread-local tm buffer. + struct tm* tm_ptr = localtime(&seconds); // NOLINT + if (tm_ptr == nullptr) return false; + *out = *tm_ptr; + return true; +#else + return localtime_r(&seconds, out) != nullptr; +#endif +} + ///////////////////////////////////////////////// std::string ignition::common::timeToIso( const std::chrono::time_point &_time) { char isoStr[25]; - auto epoch = _time.time_since_epoch(); auto sec = std::chrono::duration_cast(epoch).count(); auto nano = std::chrono::duration_cast( epoch).count() - sec * IGN_SEC_TO_NANO; time_t tmSec = static_cast(sec); - std::strftime(isoStr, sizeof(isoStr), "%FT%T", std::localtime(&tmSec)); + struct tm localTime; + PortableLocaltime(tmSec, &localTime); + std::strftime(isoStr, sizeof(isoStr), "%FT%T", &localTime); return std::string(isoStr) + "." + std::to_string(nano); } diff -Nru ignition-common-4.4.0+ds/test/integration/CMakeLists.txt ignition-common-4.5.0+ds/test/integration/CMakeLists.txt --- ignition-common-4.4.0+ds/test/integration/CMakeLists.txt 2021-10-15 18:24:48.000000000 +0000 +++ ignition-common-4.5.0+ds/test/integration/CMakeLists.txt 2022-01-12 22:11:17.000000000 +0000 @@ -6,7 +6,7 @@ # FIXME the mesh test does not work list(REMOVE_ITEM tests mesh.cc) -if (${SKIP_av}) +if (SKIP_av OR INTERNAL_SKIP_av) list(REMOVE_ITEM tests encoder_timing.cc) list(REMOVE_ITEM tests video_encoder.cc) endif() @@ -29,4 +29,4 @@ if(TARGET INTEGRATION_video_encoder) target_link_libraries(INTEGRATION_video_encoder ${PROJECT_LIBRARY_TARGET_NAME}-av) -endif() \ No newline at end of file +endif() diff -Nru ignition-common-4.4.0+ds/test/performance/logging.cc ignition-common-4.5.0+ds/test/performance/logging.cc --- ignition-common-4.4.0+ds/test/performance/logging.cc 1970-01-01 00:00:00.000000000 +0000 +++ ignition-common-4.5.0+ds/test/performance/logging.cc 2022-01-12 22:11:17.000000000 +0000 @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * 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. + * +*/ +#include + +#include +#include +#include + +#include + +namespace { +// Lower value than spdlog to keep CI from flaking +const uint64_t g_iterations{10000}; + +std::atomic g_counter = {0}; + +void WriteToFile(std::string result_filename, std::string content) +{ + std::ofstream out; + std::ios_base::openmode mode = std::ios_base::out | std::ios_base::app; + out.open(result_filename.c_str(), mode); + if (!out.is_open()) + { + std::cerr << "Error writing to " << result_filename << std::endl; + } + out << content << std::flush; + std::cout << content; +} + +void MeasurePeakDuringLogWrites(const size_t id, std::vector &result) +{ + while (true) + { + const size_t value_now = ++g_counter; + if (value_now > g_iterations) + { + return; + } + std::stringstream ss; + ss << "Some text to log for thread: " << id << "\n"; + auto start_time = std::chrono::high_resolution_clock::now(); + ignmsg << ss.str(); + auto stop_time = std::chrono::high_resolution_clock::now(); + uint64_t time_us = std::chrono::duration_cast( + stop_time - start_time) + .count(); + result.push_back(time_us); + } +} + +void PrintStats(const std::string &filename, + const std::map> &threads_result, + const uint64_t total_time_in_us) +{ + size_t idx = 0; + std::ostringstream oss; + for (auto t_result : threads_result) + { + uint64_t worstUs = + (*std::max_element(t_result.second.begin(), t_result.second.end())); + oss << idx++ << " the worst thread latency was:" << worstUs / uint64_t(1000) + << " ms (" << worstUs << " us)] " << std::endl; + } + + oss << "Total time :" << total_time_in_us / uint64_t(1000) << " ms (" + << total_time_in_us << " us)" << std::endl; + oss << "Average time: " << double(total_time_in_us) / double(g_iterations) + << " us" << std::endl; + WriteToFile(filename, oss.str()); +} + +void SaveResultToBucketFile( + std::string result_filename, + const std::map> &threads_result) +{ + // now split the result in buckets of 1ms each so that it's obvious how the + // peaks go + std::vector all_measurements; + all_measurements.reserve(g_iterations); + for (auto &t_result : threads_result) + { + all_measurements.insert(all_measurements.end(), t_result.second.begin(), + t_result.second.end()); + } + + std::map bucketsWithEmpty; + std::map buckets; + // force empty buckets to appear + auto maxValueIterator = + std::max_element(all_measurements.begin(), all_measurements.end()); + const auto kMaxValue = *maxValueIterator; + + for (uint64_t idx = 0; idx <= kMaxValue; ++idx) + { + bucketsWithEmpty[idx] = 0; + } + + for (auto value : all_measurements) + { + ++bucketsWithEmpty[value]; + ++buckets[value]; + } + + /* + std::cout << "\n\n Microsecond bucket measurement" << std::endl; + for (const auto ms_bucket : buckets) { + std::cout << ms_bucket.first << "\t, " << ms_bucket.second << std::endl; + } + std::cout << "\n\n"; + */ + + std::ostringstream oss; + // Save to xcel and google doc parsable format. with empty buckets + oss << "\n\n Microsecond bucket measurement with zero buckets till max" + << std::endl; + for (const auto ms_bucket : bucketsWithEmpty) + { + oss << ms_bucket.first << "\t, " << ms_bucket.second << std::endl; + } + WriteToFile(result_filename, oss.str()); + std::cout << "Worst Case Latency, max value: " << kMaxValue << std::endl; + std::cout << "microsecond bucket result is in file: " << result_filename + << std::endl; +} +} // namespace + +void run(size_t number_of_threads) +{ + g_counter = 0; + ignition::common::Console::SetVerbosity(4); + std::vector threads(number_of_threads); + std::map> threads_result; + + for (size_t idx = 0; idx < number_of_threads; ++idx) + { + // reserve to 1 million for all the result + // it's a test so let's not care about the wasted space + threads_result[idx].reserve(g_iterations); + } + + // int queue_size = 1048576; // 2 ^ 20 + // int queue_size = 524288; // 2 ^ 19 + // spdlog::set_async_mode(queue_size); // default size is 1048576 + auto filename_result = std::to_string(number_of_threads) + ".result.csv"; + std::ostringstream oss; + oss << "Using " << number_of_threads; + oss << " to log in total " << g_iterations << " log entries to " + << filename_result << std::endl; + WriteToFile(filename_result, oss.str()); + + auto start_time_application_total = + std::chrono::high_resolution_clock::now(); + for (uint64_t idx = 0; idx < number_of_threads; ++idx) + { + threads[idx] = std::thread(MeasurePeakDuringLogWrites, idx, + std::ref(threads_result[idx])); + } + for (size_t idx = 0; idx < number_of_threads; ++idx) + { + threads[idx].join(); + } + auto stop_time_application_total = std::chrono::high_resolution_clock::now(); + + uint64_t total_time_in_us = + std::chrono::duration_cast( + stop_time_application_total - start_time_application_total) + .count(); + + PrintStats(filename_result, threads_result, total_time_in_us); + SaveResultToBucketFile(filename_result, threads_result); +} + +class LoggingTest: + public ::testing::TestWithParam +{ +}; + +TEST_P(LoggingTest, RunThreads) +{ + run(GetParam()); + SUCCEED(); +} + +INSTANTIATE_TEST_SUITE_P(LoggingTest, LoggingTest, + ::testing::Values(1, 2, 4, 8, 16, 32)); + +///////////////////////////////////////////////// +// This test is valid (passes) if it runs without segfaults. +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}