mirror of https://github.com/JAJames/jessilib.git
Jessica James
7 years ago
12 changed files with 510 additions and 2 deletions
@ -0,0 +1,7 @@ |
|||||
|
root = true |
||||
|
|
||||
|
[**.{cpp, c, hpp, h}] |
||||
|
indent_style = tab |
||||
|
trim_trailing_whitespace = true |
||||
|
end_of_line = lf |
||||
|
insert_final_newline = true |
@ -1,3 +1,3 @@ |
|||||
[submodule "googletest"] |
[submodule "googletest"] |
||||
path = googletest |
path = src/test/googletest |
||||
url = https://github.com/google/googletest.git |
url = https://github.com/google/googletest.git |
||||
|
@ -0,0 +1,7 @@ |
|||||
|
cmake_minimum_required(VERSION 3.8) |
||||
|
project(jessilib) |
||||
|
|
||||
|
#set(CMAKE_CXX_STANDARD 14) |
||||
|
|
||||
|
# Setup source files |
||||
|
add_subdirectory(src) |
@ -0,0 +1,6 @@ |
|||||
|
cmake_minimum_required(VERSION 3.8) |
||||
|
|
||||
|
# Setup source files |
||||
|
include_directories(include) |
||||
|
add_subdirectory(common) |
||||
|
add_subdirectory(test) |
@ -0,0 +1,8 @@ |
|||||
|
cmake_minimum_required(VERSION 3.8) |
||||
|
|
||||
|
# Setup source files |
||||
|
include_directories(../include) |
||||
|
set(SOURCE_FILES |
||||
|
timer.cpp) |
||||
|
|
||||
|
add_library(jessilib ${SOURCE_FILES}) |
@ -0,0 +1,205 @@ |
|||||
|
/**
|
||||
|
* Copyright (C) 2017 Jessica James. |
||||
|
* |
||||
|
* Permission to use, copy, modify, and/or distribute this software for any |
||||
|
* purpose with or without fee is hereby granted, provided that the above |
||||
|
* copyright notice and this permission notice appear in all copies. |
||||
|
* |
||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
||||
|
* |
||||
|
* Written by Jessica James <jessica.aj@outlook.com> |
||||
|
*/ |
||||
|
|
||||
|
#include "timer.hpp" |
||||
|
#include <iostream> |
||||
|
|
||||
|
namespace jessilib { |
||||
|
|
||||
|
/** timer::manager */ |
||||
|
|
||||
|
timer::manager timer::s_manager; |
||||
|
|
||||
|
timer::manager::~manager() { |
||||
|
m_thread_active = false; |
||||
|
m_cvar.notify_one(); |
||||
|
m_thread.join(); |
||||
|
} |
||||
|
|
||||
|
void timer::manager::loop() { |
||||
|
// loop
|
||||
|
while (m_thread_active) { |
||||
|
std::unique_lock<std::mutex> lock(m_mutex); |
||||
|
auto itr{ m_timers.begin() }; |
||||
|
if (itr != m_timers.end()) { |
||||
|
// Wait until the next timer is ready to fire
|
||||
|
if (m_cvar.wait_until(lock, (*itr)->next()) == std::cv_status::timeout && itr != m_timers.end()) { |
||||
|
// Set active
|
||||
|
std::shared_ptr<timer::node> target = *itr; |
||||
|
m_active = target.get(); |
||||
|
|
||||
|
// Execute timer
|
||||
|
target->m_callback(*target); |
||||
|
itr = m_timers.begin(); |
||||
|
if (itr != m_timers.end() && *itr == target) { |
||||
|
// Timer still exists; pop
|
||||
|
m_timers.erase(itr); |
||||
|
|
||||
|
// reset timings
|
||||
|
target->m_next = target->calc_next(); |
||||
|
|
||||
|
// push
|
||||
|
m_timers.insert(std::move(target)); |
||||
|
} |
||||
|
|
||||
|
// Reset active
|
||||
|
m_active = nullptr; |
||||
|
} |
||||
|
} |
||||
|
else { |
||||
|
// Wait until a timer is added
|
||||
|
m_cvar.wait(lock); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** timer::node */ |
||||
|
timer::node::node(duration_t period, callback_t callback) |
||||
|
: m_period{ period }, |
||||
|
m_next{ calc_next() }, |
||||
|
m_callback{ callback } { |
||||
|
// Empty ctor body
|
||||
|
} |
||||
|
|
||||
|
timer::time_point_t timer::node::next() const { |
||||
|
return m_next; |
||||
|
} |
||||
|
|
||||
|
timer::duration_t timer::node::period() const { |
||||
|
return m_period; |
||||
|
} |
||||
|
|
||||
|
timer::callback_t timer::node::function() const { |
||||
|
return m_callback; |
||||
|
} |
||||
|
|
||||
|
void timer::node::cancel() { |
||||
|
// Race condition: cancel() being called externally while active
|
||||
|
if (s_manager.m_active == this && s_manager.m_thread.get_id() == std::this_thread::get_id()) { |
||||
|
s_manager.m_timers.erase(shared_from_this()); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
{ |
||||
|
std::lock_guard<std::mutex> lock(s_manager.m_mutex); |
||||
|
s_manager.m_timers.erase(shared_from_this()); |
||||
|
} |
||||
|
s_manager.m_cvar.notify_one(); |
||||
|
} |
||||
|
|
||||
|
// Internals
|
||||
|
|
||||
|
timer::time_point_t timer::node::calc_next() { |
||||
|
return std::chrono::steady_clock::now() + m_period; |
||||
|
} |
||||
|
|
||||
|
/** timer */ |
||||
|
|
||||
|
// Static functions
|
||||
|
void timer::set(duration_t period, callback_t callback) { |
||||
|
// Emplace timer
|
||||
|
{ |
||||
|
std::lock_guard<std::mutex> lock(s_manager.m_mutex); |
||||
|
s_manager.m_timers.emplace(new node(period, callback)); |
||||
|
} |
||||
|
|
||||
|
// Notify
|
||||
|
s_manager.m_cvar.notify_one(); |
||||
|
} |
||||
|
|
||||
|
void timer::set(duration_t period, size_t iterations, callback_t callback) { |
||||
|
// Expand callback and forward to set
|
||||
|
set(period, callback_with_iterations(iterations, callback)); |
||||
|
} |
||||
|
|
||||
|
// Constructors
|
||||
|
timer::timer(duration_t period, callback_t callback) { |
||||
|
// Sanity check
|
||||
|
assert(callback != nullptr); |
||||
|
|
||||
|
// Initialize timer
|
||||
|
{ |
||||
|
std::lock_guard<std::mutex> lock(s_manager.m_mutex); |
||||
|
m_timer = *s_manager.m_timers.emplace(new node(period, callback)); |
||||
|
} |
||||
|
|
||||
|
// Notify
|
||||
|
s_manager.m_cvar.notify_one(); |
||||
|
} |
||||
|
|
||||
|
timer::timer(duration_t period, size_t iterations, callback_t callback) |
||||
|
: timer{ period, callback_with_iterations(iterations, callback) } { |
||||
|
// Empty ctor body
|
||||
|
} |
||||
|
|
||||
|
// Destructor
|
||||
|
timer::~timer() { |
||||
|
if (auto my_timer = m_timer.lock()) { |
||||
|
my_timer->cancel(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Functions
|
||||
|
timer::time_point_t timer::next() const { |
||||
|
if (auto my_timer = m_timer.lock()) { |
||||
|
my_timer->next(); |
||||
|
} |
||||
|
|
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
timer::duration_t timer::period() const { |
||||
|
if (auto my_timer = m_timer.lock()) { |
||||
|
return my_timer->period(); |
||||
|
} |
||||
|
|
||||
|
return duration_t{ 0 }; |
||||
|
} |
||||
|
|
||||
|
timer::callback_t timer::function() const { |
||||
|
if (auto my_timer = m_timer.lock()) { |
||||
|
return my_timer->function(); |
||||
|
} |
||||
|
|
||||
|
return nullptr; |
||||
|
} |
||||
|
|
||||
|
void timer::cancel() { |
||||
|
if (auto my_timer = m_timer.lock()) { |
||||
|
my_timer->cancel(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool timer::active() const { |
||||
|
return m_timer.lock() != nullptr; |
||||
|
} |
||||
|
|
||||
|
timer::callback_t timer::callback_with_iterations(size_t iterations, callback_t callback) { |
||||
|
assert(iterations > 0); |
||||
|
|
||||
|
return [iterations, callback](node& timer) mutable { |
||||
|
callback(timer); |
||||
|
|
||||
|
if (--iterations == 0) { |
||||
|
timer.cancel(); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
} // namespace jessilib
|
@ -0,0 +1,139 @@ |
|||||
|
/**
|
||||
|
* Copyright (C) 2017 Jessica James. |
||||
|
* |
||||
|
* Permission to use, copy, modify, and/or distribute this software for any |
||||
|
* purpose with or without fee is hereby granted, provided that the above |
||||
|
* copyright notice and this permission notice appear in all copies. |
||||
|
* |
||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
||||
|
* |
||||
|
* Written by Jessica James <jessica.aj@outlook.com> |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <cassert> |
||||
|
#include <mutex> |
||||
|
#include <chrono> |
||||
|
#include <set> |
||||
|
#include <condition_variable> |
||||
|
#include <atomic> |
||||
|
#include <memory> |
||||
|
|
||||
|
// TODO: add 'timer* m_parent' to timer::node; rename timer::node to timer::internals?
|
||||
|
|
||||
|
namespace jessilib { |
||||
|
|
||||
|
/** timer */ |
||||
|
|
||||
|
class timer { |
||||
|
public: |
||||
|
class node; |
||||
|
|
||||
|
// Types
|
||||
|
using callback_t = std::function<void(node&)>; /** Function type called by the timer */ |
||||
|
using time_point_t = std::chrono::steady_clock::time_point; /** Type representing the point in time at which a timer will be called */ |
||||
|
using duration_t = std::chrono::steady_clock::duration; |
||||
|
|
||||
|
// Static functions
|
||||
|
static void set(duration_t period, callback_t callback); |
||||
|
static void set(duration_t period, size_t iterations, callback_t callback); |
||||
|
|
||||
|
// Constructors
|
||||
|
timer(duration_t period, callback_t callback); |
||||
|
timer(duration_t period, size_t iterations, callback_t callback); |
||||
|
|
||||
|
// Explicitly deleted constructors
|
||||
|
timer() = delete; |
||||
|
timer(const timer&) = delete; |
||||
|
timer(timer&& in_timer) = delete; |
||||
|
|
||||
|
// Destructor
|
||||
|
~timer(); |
||||
|
|
||||
|
// Functions
|
||||
|
time_point_t next() const; |
||||
|
duration_t period() const; |
||||
|
callback_t function() const; |
||||
|
void cancel(); |
||||
|
bool active() const; |
||||
|
|
||||
|
private: |
||||
|
// Weak pointer to referenced timer
|
||||
|
std::weak_ptr<node> m_timer; |
||||
|
|
||||
|
// Timer manager
|
||||
|
class manager; |
||||
|
static manager s_manager; |
||||
|
|
||||
|
// Helper
|
||||
|
static callback_t callback_with_iterations(size_t iterations, callback_t callback); |
||||
|
}; // timer
|
||||
|
|
||||
|
/** timer::node */ |
||||
|
|
||||
|
class timer::node : public std::enable_shared_from_this<node> { |
||||
|
public: |
||||
|
time_point_t next() const; |
||||
|
duration_t period() const; |
||||
|
callback_t function() const; |
||||
|
void cancel(); |
||||
|
|
||||
|
private: |
||||
|
// Internal constructor
|
||||
|
node(duration_t period, callback_t callback); |
||||
|
|
||||
|
// Explicitly deleted constructors
|
||||
|
node() = delete; |
||||
|
node(const node&) = delete; |
||||
|
node(node&& in_timer) = delete; |
||||
|
|
||||
|
// Internal helpers
|
||||
|
time_point_t calc_next(); |
||||
|
|
||||
|
// Members
|
||||
|
duration_t m_period; |
||||
|
callback_t m_callback; |
||||
|
time_point_t m_next; |
||||
|
|
||||
|
// Friends
|
||||
|
friend class timer; |
||||
|
}; // class timer::node
|
||||
|
|
||||
|
|
||||
|
/** timer::manager */ |
||||
|
|
||||
|
class timer::manager { |
||||
|
// Destructor
|
||||
|
~manager(); |
||||
|
|
||||
|
struct timer_sort { |
||||
|
bool operator()(const std::shared_ptr<node>& lhs, const std::shared_ptr<node>& rhs) const { |
||||
|
return lhs->next() < rhs->next(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// Loop
|
||||
|
void loop(); |
||||
|
|
||||
|
// Members
|
||||
|
std::multiset<std::shared_ptr<node>, timer_sort> m_timers; |
||||
|
std::mutex m_mutex; |
||||
|
std::condition_variable m_cvar; |
||||
|
std::atomic<node*> m_active{ nullptr }; |
||||
|
std::atomic<bool> m_thread_active{ true }; |
||||
|
std::thread m_thread{ [this]() { |
||||
|
loop(); |
||||
|
}}; |
||||
|
|
||||
|
// Friends
|
||||
|
friend class timer; |
||||
|
}; // class timer::manager
|
||||
|
|
||||
|
}; // namespace jessilib
|
@ -0,0 +1,16 @@ |
|||||
|
cmake_minimum_required(VERSION 3.8) |
||||
|
|
||||
|
# Setup source files |
||||
|
include_directories(../include) |
||||
|
set(SOURCE_FILES |
||||
|
timer.cpp) |
||||
|
|
||||
|
# Setup gtest |
||||
|
add_subdirectory(googletest/googletest) |
||||
|
#include_directories(googletest/googletest) |
||||
|
#include_directories(googletest/googlemock) |
||||
|
|
||||
|
add_executable(jessilib_tests ${SOURCE_FILES}) |
||||
|
|
||||
|
# Link with gtest |
||||
|
target_link_libraries(jessilib_tests gtest gtest_main jessilib) |
@ -0,0 +1,110 @@ |
|||||
|
/**
|
||||
|
* Copyright (C) 2017 Jessica James. |
||||
|
* |
||||
|
* Permission to use, copy, modify, and/or distribute this software for any |
||||
|
* purpose with or without fee is hereby granted, provided that the above |
||||
|
* copyright notice and this permission notice appear in all copies. |
||||
|
* |
||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
||||
|
* |
||||
|
* Written by Jessica James <jessica.aj@outlook.com> |
||||
|
*/ |
||||
|
|
||||
|
#include <future> |
||||
|
#include "gtest/gtest.h" |
||||
|
#include "timer.hpp" |
||||
|
|
||||
|
using namespace jessilib; |
||||
|
using namespace std::literals; |
||||
|
|
||||
|
constexpr size_t total_iterations{ 4 }; |
||||
|
constexpr std::chrono::steady_clock::duration period = 1ms; |
||||
|
constexpr std::chrono::steady_clock::duration timeout = 1s; |
||||
|
|
||||
|
TEST(TimerTest, set) { |
||||
|
size_t iterations{ 0 }; |
||||
|
std::promise<void> promise; |
||||
|
|
||||
|
timer::set(period, [&iterations, &promise](timer::node& in_timer) { |
||||
|
if (++iterations == total_iterations) { |
||||
|
in_timer.cancel(); |
||||
|
promise.set_value(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
EXPECT_EQ(promise.get_future().wait_for(timeout), std::future_status::ready); |
||||
|
} |
||||
|
|
||||
|
TEST(TimerTest, scoped) { |
||||
|
size_t iterations{ 0 }; |
||||
|
std::promise<void> promise; |
||||
|
|
||||
|
timer timer{ period, [&iterations, &promise](timer::node& in_timer) { |
||||
|
if (++iterations == total_iterations) { |
||||
|
in_timer.cancel(); |
||||
|
promise.set_value(); |
||||
|
} |
||||
|
} }; |
||||
|
|
||||
|
EXPECT_EQ(promise.get_future().wait_for(timeout), std::future_status::ready); |
||||
|
} |
||||
|
|
||||
|
TEST(TimerTest, setWithIterations) { |
||||
|
size_t iterations{ 0 }; |
||||
|
std::promise<void> promise; |
||||
|
|
||||
|
timer::set(period, total_iterations, [&iterations, &promise](timer::node& in_timer) { |
||||
|
if (++iterations == total_iterations) { |
||||
|
promise.set_value(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
EXPECT_EQ(promise.get_future().wait_for(timeout), std::future_status::ready); |
||||
|
} |
||||
|
|
||||
|
TEST(TimerTest, scopedWithIterations) { |
||||
|
size_t iterations{ 0 }; |
||||
|
std::promise<void> promise; |
||||
|
|
||||
|
timer timer{ period, total_iterations, [&iterations, &promise](timer::node& in_timer) { |
||||
|
if (++iterations == total_iterations) { |
||||
|
promise.set_value(); |
||||
|
} |
||||
|
} }; |
||||
|
|
||||
|
EXPECT_EQ(promise.get_future().wait_for(timeout), std::future_status::ready); |
||||
|
} |
||||
|
|
||||
|
TEST(TimerTest, setWithIterationsCancel) { |
||||
|
size_t iterations{ 0 }; |
||||
|
std::promise<void> promise; |
||||
|
|
||||
|
timer::set(period, total_iterations, [&iterations, &promise](timer::node& in_timer) { |
||||
|
if (++iterations == total_iterations) { |
||||
|
in_timer.cancel(); |
||||
|
promise.set_value(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
EXPECT_EQ(promise.get_future().wait_for(timeout), std::future_status::ready); |
||||
|
} |
||||
|
|
||||
|
TEST(TimerTest, scopedWithIterationsCancel) { |
||||
|
size_t iterations{ 0 }; |
||||
|
std::promise<void> promise; |
||||
|
|
||||
|
timer timer{ period, total_iterations, [&iterations, &promise](timer::node& in_timer) { |
||||
|
if (++iterations == total_iterations) { |
||||
|
in_timer.cancel(); |
||||
|
promise.set_value(); |
||||
|
} |
||||
|
} }; |
||||
|
|
||||
|
EXPECT_EQ(promise.get_future().wait_for(timeout), std::future_status::ready); |
||||
|
} |
Loading…
Reference in new issue