Skip to content
Merged
8 changes: 8 additions & 0 deletions rosbag2_cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ add_library(${PROJECT_NAME} SHARED
src/rosbag2_cpp/cache/cache_consumer.cpp
src/rosbag2_cpp/cache/message_cache_buffer.cpp
src/rosbag2_cpp/cache/message_cache.cpp
src/rosbag2_cpp/clocks/time_controller_clock.cpp
src/rosbag2_cpp/converter.cpp
src/rosbag2_cpp/info.cpp
src/rosbag2_cpp/reader.cpp
Expand Down Expand Up @@ -222,6 +223,13 @@ if(BUILD_TESTING)
ament_target_dependencies(test_multifile_reader rosbag2_storage)
target_link_libraries(test_multifile_reader ${PROJECT_NAME})
endif()

ament_add_gmock(test_time_controller_clock
test/rosbag2_cpp/test_time_controller_clock.cpp)
if(TARGET test_time_controller_clock)
# ament_target_dependencies(test_player_clock rosbag2_cpp)
Comment thread
emersonknapp marked this conversation as resolved.
Outdated
target_link_libraries(test_time_controller_clock ${PROJECT_NAME})
endif()
endif()

ament_package()
74 changes: 74 additions & 0 deletions rosbag2_cpp/include/rosbag2_cpp/clocks/player_clock.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// 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 ROSBAG2_CPP__CLOCKS__PLAYER_CLOCK_HPP_
#define ROSBAG2_CPP__CLOCKS__PLAYER_CLOCK_HPP_

#include <chrono>
#include <functional>
#include <memory>

#include "rcutils/time.h"
#include "rosbag2_cpp/visibility_control.hpp"

namespace rosbag2_cpp
{

/**
* Virtual interface used to drive the timing of bag playback.
* This clock should be used to query times and sleep between message playing,
* so that the complexity involved around time control and time sources
* is encapsulated in this one place.
*/
class PlayerClock
{
public:
// Type representing the current time as according to the playback
typedef rcutils_time_point_value_t ROSTimePoint;
Comment thread
Karsten1987 marked this conversation as resolved.
Outdated
/**
* Type representing an arbitrary steady time, used to measure real-time durations
* This type is never exposed by the PlayerClock - it is only used as input to the PlayerClock.
*/
typedef std::chrono::time_point<std::chrono::steady_clock> SteadyTimePoint;
typedef std::function<SteadyTimePoint()> NowFunction;

ROSBAG2_CPP_PUBLIC
virtual ~PlayerClock() = default;

/**
* Calculate and return current ROSTimePoint based on starting time, playback rate, pause state.
*/
ROSBAG2_CPP_PUBLIC
virtual ROSTimePoint now() const = 0;

/**
* Try to sleep (non-busy) the current thread until the provided time is reached - according to this Clock
*
* Return true if the time has been reached, false if it was not successfully reached after sleeping
* for the appropriate duration.
* The user should not take action based on this sleep until it returns true.
*/
ROSBAG2_CPP_PUBLIC
virtual bool sleep_until(ROSTimePoint until) = 0;

/**
* Return the current playback rate.
*/
ROSBAG2_CPP_PUBLIC
virtual double get_rate() const = 0;
};

} // namespace rosbag2_cpp

#endif // ROSBAG2_CPP__CLOCKS__PLAYER_CLOCK_HPP_
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// 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 ROSBAG2_CPP__CLOCKS__TIME_CONTROLLER_CLOCK_HPP_
#define ROSBAG2_CPP__CLOCKS__TIME_CONTROLLER_CLOCK_HPP_

#include <chrono>
#include <memory>

#include "rosbag2_cpp/clocks/player_clock.hpp"

namespace rosbag2_cpp
{

/**
* Version of the PlayerClock interface that has control over time.
* It does not listen to any external ROS Time Source and can optionally publish /clock
*/
class TimeControllerClockImpl;
class TimeControllerClock : public PlayerClock
{
public:
/**
* Constructor.
*
* \param starting_time: provides an initial offset for managing time
* This will likely be the timestamp of the first message in the bag
* \param rate: Rate of playback, a unit-free ratio. 1.0 is real-time
* \param now_fn: Function used to get the current steady time
* defaults to std::chrono::steady_clock::now
* Used to control for unit testing, or for specialized needs
* \throws std::runtime_error if rate is <= 0
*/
ROSBAG2_CPP_PUBLIC
TimeControllerClock(
ROSTimePoint starting_time,
double rate = 1.0,
NowFunction now_fn = std::chrono::steady_clock::now);

ROSBAG2_CPP_PUBLIC
virtual ~TimeControllerClock();

/**
* Calculate and return current ROSTimePoint based on starting time, playback rate, pause state.
*/
ROSBAG2_CPP_PUBLIC
ROSTimePoint now() const override;

/**
* Try to sleep (non-busy) the current thread until the provided time is reached - according to this Clock
*
* Return true if the time has been reached, false if it was not successfully reached after sleeping
* for the appropriate duration.
* The user should not take action based on this sleep until it returns true.
*/
ROSBAG2_CPP_PUBLIC
bool sleep_until(ROSTimePoint until) override;

/**
* Return the current playback rate.
*/
ROSBAG2_CPP_PUBLIC
double get_rate() const override;

private:
std::unique_ptr<TimeControllerClockImpl> impl_;
};


} // namespace rosbag2_cpp

#endif // ROSBAG2_CPP__CLOCKS__TIME_CONTROLLER_CLOCK_HPP_
107 changes: 107 additions & 0 deletions rosbag2_cpp/src/rosbag2_cpp/clocks/time_controller_clock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// 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 <memory>
#include <mutex>
#include <thread>

#include "rcpputils/thread_safety_annotations.hpp"
#include "rosbag2_cpp/clocks/time_controller_clock.hpp"
#include "rosbag2_cpp/types.hpp"

namespace rosbag2_cpp
{

class TimeControllerClockImpl
{
public:
/**
* Stores an exact time match between a system steady clock and the playback ROS clock.
* This snapshot is taken whenever a factor changes such that a new reference is needed,
* such as pause, resume, rate change, or jump
*/
struct TimeReference
{
PlayerClock::ROSTimePoint ros;
PlayerClock::SteadyTimePoint steady;
};

TimeControllerClockImpl() = default;
virtual ~TimeControllerClockImpl() = default;

template<typename T>
rcutils_duration_value_t duration_nanos(const T & duration)
{
return std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count();
}

PlayerClock::ROSTimePoint steady_to_ros(PlayerClock::SteadyTimePoint steady_time)
{
return reference.ros + (rate * duration_nanos(steady_time - reference.steady));
}

PlayerClock::SteadyTimePoint ros_to_steady(PlayerClock::ROSTimePoint ros_time)
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar. It would be nice to add REQUIRES(mutex) attribute to ros_to_steady function.

const rcutils_duration_value_t diff_nanos = (ros_time - reference.ros) / rate;
return reference.steady + std::chrono::nanoseconds(diff_nanos);
}

double rate = 1.0;
PlayerClock::NowFunction now_fn;
std::mutex mutex;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is bad practice to use names reserved in C++. It would be more safe if rename variable mutex .
Maybe ref_rate_mutex or time_control_mutex ?

TimeReference reference RCPPUTILS_TSA_GUARDED_BY(mutex);
};

TimeControllerClock::TimeControllerClock(
ROSTimePoint starting_time,
double rate,
NowFunction now_fn)
: impl_(std::make_unique<TimeControllerClockImpl>())
{
std::lock_guard<std::mutex> lock(impl_->mutex);
impl_->now_fn = now_fn;
impl_->reference.ros = starting_time;
impl_->reference.steady = impl_->now_fn();
impl_->rate = rate;
}

TimeControllerClock::~TimeControllerClock()
{}

PlayerClock::ROSTimePoint TimeControllerClock::now() const
{
std::lock_guard<std::mutex> lock(impl_->mutex);
return impl_->steady_to_ros(impl_->now_fn());
}

bool TimeControllerClock::sleep_until(ROSTimePoint until)
{
SteadyTimePoint steady_until;
{
std::lock_guard<std::mutex> lock(impl_->mutex);
steady_until = impl_->ros_to_steady(until);
}
// TODO(emersonknapp) - when we have methods that can change timeflow during a sleep,
// it will probably be better to use a condition_variable::wait_until
Comment thread
Karsten1987 marked this conversation as resolved.
Outdated
std::this_thread::sleep_until(steady_until);
return now() >= until;
Comment thread
Karsten1987 marked this conversation as resolved.
}

double TimeControllerClock::get_rate() const
{
std::lock_guard<std::mutex> lock(impl_->mutex);
return impl_->rate;
}

} // namespace rosbag2_cpp
Loading