Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PlayerClock initial implementation - Player functionally unchanged #689

Merged
merged 11 commits into from
Apr 3, 2021
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)
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;
/**
* 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
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
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
std::this_thread::sleep_until(steady_until);
return now() >= until;
}

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

} // namespace rosbag2_cpp
Loading