Browse Source

Initial working commit

master
Jessica James 7 years ago
parent
commit
f71c170cd9
  1. 7
      .editorconfig
  2. 10
      .gitignore
  3. 2
      .gitmodules
  4. 7
      CMakeLists.txt
  5. 1
      googletest
  6. 6
      src/CMakeLists.txt
  7. 8
      src/common/CMakeLists.txt
  8. 205
      src/common/timer.cpp
  9. 139
      src/include/timer.hpp
  10. 16
      src/test/CMakeLists.txt
  11. 1
      src/test/googletest
  12. 110
      src/test/timer.cpp

7
.editorconfig

@ -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

10
.gitignore

@ -30,3 +30,13 @@
*.exe *.exe
*.out *.out
*.app *.app
# Build directories
.idea
.vs/
build/
cmake-build-*
[Dd]ebug/
[Rr]elease/
[Bb]in/

2
.gitmodules

@ -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

7
CMakeLists.txt

@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.8)
project(jessilib)
#set(CMAKE_CXX_STANDARD 14)
# Setup source files
add_subdirectory(src)

1
googletest

@ -1 +0,0 @@
Subproject commit e5b88b227e6adfa7196575b1264384e718d16cab

6
src/CMakeLists.txt

@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.8)
# Setup source files
include_directories(include)
add_subdirectory(common)
add_subdirectory(test)

8
src/common/CMakeLists.txt

@ -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})

205
src/common/timer.cpp

@ -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

139
src/include/timer.hpp

@ -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

16
src/test/CMakeLists.txt

@ -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)

1
src/test/googletest

@ -0,0 +1 @@
Subproject commit d175c8bf823e709d570772b038757fadf63bc632

110
src/test/timer.cpp

@ -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…
Cancel
Save