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"] |
|||
path = googletest |
|||
path = src/test/googletest |
|||
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