Program Listing for File msg_storage.hpp

Return to documentation for file (src/msg_storage.hpp)

// Copyright 2022 Kenji Brameld
//
// 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.

#ifndef MSG_STORAGE_HPP_
#define MSG_STORAGE_HPP_

#include <map>
#include <deque>
#include <mutex>
#include <utility>
#include "rclcpp/time.hpp"
#include "exceptions.hpp"

namespace rqt_image_overlay
{

template<typename T>
class MsgStorage
{
public:
  explicit MsgStorage(unsigned capacity = 50)
  : capacity(capacity) {}

  // throws StorageEmptyException if storage is empty
  // throws std::runtime_error("can't compare times with different time sources") if ros time source
  //        doesn't match
  rclcpp::Time getClosestTime(const rclcpp::Time & targetTime) const
  {
    if (empty()) {
      throw StorageEmptyException();
    }

    std::lock_guard<std::mutex> guard(mutex);

    rclcpp::Time closestTimeReceived;
    rclcpp::Duration minDiff = rclcpp::Duration::max();
    for (const auto &[timeReceived, msg] : msgMap) {
      rclcpp::Duration diff =
        (timeReceived < targetTime) ? (targetTime - timeReceived) : (timeReceived - targetTime);
      if (diff < minDiff) {
        minDiff = diff;
        closestTimeReceived = timeReceived;
      } else {
        // A cpp map is sorted, so we won't find anything closer. Break out!
        break;
      }
    }
    return closestTimeReceived;
  }

  // throws std::out_of_range if doesn't exist
  T getMsg(const rclcpp::Time & time) const
  {
    std::lock_guard<std::mutex> guard(mutex);
    return msgMap.at(time);
  }

  bool empty() const
  {
    std::lock_guard<std::mutex> guard(mutex);
    return msgTimeDeque.empty();
  }

  void store(const rclcpp::Time & time, const T & msg)
  {
    std::lock_guard<std::mutex> guard(mutex);

    // Ensure size msgMap is less than capacity
    if (msgMap.size() == capacity) {
      msgMap.erase(msgTimeDeque.front());
      msgTimeDeque.pop_front();
    }

    msgMap.insert(make_pair(time, msg));
    msgTimeDeque.push_back(time);
  }

  void clear()
  {
    std::lock_guard<std::mutex> guard(mutex);
    msgMap.clear();
    msgTimeDeque.clear();
  }

private:
  mutable std::mutex mutex;

  // msgMap and msgTimeDeque two together, create a FIFO Map, as described in
  // https://stackoverflow.com/a/21315813
  std::map<const rclcpp::Time, const T> msgMap;
  std::deque<rclcpp::Time> msgTimeDeque;

  const unsigned capacity;
};

}  // namespace rqt_image_overlay

#endif  // MSG_STORAGE_HPP_