// Copyright 2015, Tobias Hermann and the FunctionalPlus contributors. // https://github.com/Dobiasd/FunctionalPlus // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace fplus { // Executes a function f in a fixed interval, // i.e. an average timespan between two consecutive calls of f, // given in microseconds. // f is a unary function, taking the time delta (in microseconds) // between the last and the current call as its argument. // In case of a delay outdated calls are be executed immediately. // So the average executation time of f should be way shorter // than the requested interval. // Call ticker::start() to run. // The ticker stops when ticker::stop() is called // or the instance runs out of scope. // // Example usage: // // void say_hi(std::int64_t) // { // std::cout << "hi " << std::endl; // } // int main() // { // ticker hi_ticker(say_hi, 2 * 1000 * 1000); // hi_ticker.start(); // std::this_thread::sleep_for(std::chrono::milliseconds(4500)); // } class ticker { public: typedef std::function function; ticker(const function& f, std::int64_t interval_us) : f_(f), interval_us_(interval_us), control_mutex_(), is_running_(false), thread_(), stop_mutex_() { } bool is_running() { std::lock_guard lock(control_mutex_); return is_running_; } bool start() { std::lock_guard lock(control_mutex_); if (is_running_) return false; stop_mutex_.lock(); thread_ = std::thread([this]() { thread_function(); }); is_running_ = true; return true; } bool stop() { std::lock_guard lock(control_mutex_); if (!is_running_) return false; stop_mutex_.unlock(); if (thread_.joinable()) { thread_.join(); thread_ = std::thread(); } is_running_ = false; return true; } ~ticker() { stop(); } private: void thread_function() { auto last_wake_up_time = std::chrono::steady_clock::now(); auto last_time = last_wake_up_time; bool quit = false; while (!quit) { const auto wake_up_time = last_wake_up_time + std::chrono::microseconds{ interval_us_ }; const auto sleep_time = wake_up_time - std::chrono::steady_clock::now(); if (stop_mutex_.try_lock_for(sleep_time)) { stop_mutex_.unlock(); quit = true; } const auto current_time = std::chrono::steady_clock::now(); const auto elapsed = current_time - last_time; last_wake_up_time = wake_up_time; last_time = current_time; const auto elapsed_us = std::chrono::duration_cast( elapsed).count(); try { f_(elapsed_us); } catch (...) { } } } const function f_; const std::int64_t interval_us_; std::mutex control_mutex_; bool is_running_; std::thread thread_; std::timed_mutex stop_mutex_; }; // API search type: sleep_for_n_seconds : Int -> Io () // Returns a function that suspends // the calling thread for n seconds when executed. inline std::function sleep_for_n_seconds(std::size_t seconds) { return [seconds]() { std::this_thread::sleep_for(std::chrono::seconds(seconds)); }; } // API search type: sleep_for_n_milliseconds : Int -> Io () // Returns a function that suspends // the calling thread for n milliseconds when executed. inline std::function sleep_for_n_milliseconds(std::size_t milliseconds) { return [milliseconds]() { std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); }; } // API search type: sleep_for_n_microseconds : Int -> Io () // Returns a function that suspends // the calling thread for n microseconds when executed. inline std::function sleep_for_n_microseconds(std::size_t microseconds) { return [microseconds]() { std::this_thread::sleep_for(std::chrono::microseconds(microseconds)); }; } // API search type: execute_serially : [Io ()] -> Io () // Returns a function that executes // the given side effects one after another when called. template auto execute_serially(const Container& effs) { using Effect = typename Container::value_type; using Result = internal::invoke_result_t; return [effs] { std::vector> results; for (const Effect& e : effs) { results.push_back(internal::invoke(e)); } return results; }; } // API search type: execute_serially_until_success : [Io Bool] -> Io Bool // Returns a function that (when called) executes // the given side effects one after another until one of it returns true. template auto execute_serially_until_success(const Container& effs) { using Effect = typename Container::value_type; using Result = internal::invoke_result_t; static_assert(std::is_convertible::value, "Effects must return a boolish type."); return [effs]() -> bool { for (const Effect& e : effs) { if (internal::invoke(e)) { return true; } } return false; }; } // API search type: execute_and_return_fixed_value : (a, [Io b]) -> Io a // Returns a function that executes the given side effect // and returns a fixed value when called. template std::function execute_and_return_fixed_value( Result result, Effect eff) { return [eff, result]() -> Result { eff(); return result; }; } // Converts an arbitrary callable effect to an std::function. template std::function ()> effect_to_std_function(Effect eff) { return [eff] { return internal::invoke(eff); }; } // API search type: execute_max_n_times_until_success : (Int, Io (), Int) -> Io Bool // Returns a function that (when called) executes a side effect // until it succeds once or the maximum number // of attempts with an optional pause in between. template auto execute_max_n_times_until_success(std::size_t n, const Effect& eff, std::size_t pause_in_milliseconds = 0) { if (pause_in_milliseconds > 0) { auto sleep_and_return_false = execute_and_return_fixed_value( false, sleep_for_n_milliseconds(pause_in_milliseconds)); return execute_serially_until_success( intersperse( sleep_and_return_false, replicate(n, effect_to_std_function(eff)))); } return execute_serially_until_success( replicate(n, effect_to_std_function(eff))); } // API search type: execute_n_times : (Int, Io a) -> Io () // Returns a function that (when called) executes n times // the provided side effect function. // The return values (if present) are dropped. template auto execute_n_times(std::size_t n, const Effect& eff) { for (auto _ : fplus::numbers(static_cast(0), n)) { (void) _; // suppress warning / unused variable eff(); } } // API search type: execute_serially_until_failure : [Io Bool] -> Io Bool // Returns a function that (when called) executes the given side effects // one after another until one of them returns false. template std::function execute_serially_until_failure(const Container& effs) { using Effect = typename Container::value_type; using Result = internal::invoke_result_t; static_assert(std::is_convertible::value, "Effects must return a boolish type."); return [effs]() -> bool { for (const Effect& e : effs) { if (!internal::invoke(e)) { return false; } } return true; }; } // API search type: execute_parallelly : [Io a] -> Io [a] // Returns a function that (when called) executes the given side effects // in parallel and returns the collected results. template auto execute_parallelly(const Container& effs) { using Effect = typename Container::value_type; using Result = internal::invoke_result_t; return [effs] { auto handles = transform( [](Effect e) { return std::async(std::launch::async, e); }, effs); std::vector> results; results.reserve(size_of_cont(handles)); for (auto& handle : handles) { results.push_back(handle.get()); } return results; }; } // API search type: execute_fire_and_forget : Io a -> Io a // Returns a function that (when called) executes the given side effect // in a new thread and returns immediately. template std::function execute_fire_and_forget(Effect eff) { return [eff]() { std::thread t(eff); t.detach(); }; } // API search type: read_text_file_maybe : String -> Io (Maybe String) // Returns a function that reads the content of a text file when called. inline std::function()> read_text_file_maybe( const std::string& filename) { return [filename]() -> maybe { std::ifstream input(filename); if (!input.good()) return {}; return just(std::string( std::istreambuf_iterator(input), std::istreambuf_iterator())); }; } // API search type: read_text_file : String -> Io String // Returns a function that reads the content of a text file when called. // This function then returns an empty string if the file could not be read. inline std::function read_text_file(const std::string& filename) { return [filename]() -> std::string { return just_with_default( std::string(), read_text_file_maybe(filename)()); }; } // API search type: read_binary_file_maybe : String -> Io (Maybe [Int]) // Returns a function that reads the content of a binary file when executed. inline std::function>()> read_binary_file_maybe( const std::string& filename) { return [filename]() -> maybe> { std::ifstream file(filename, std::ios::binary); if (!file.good()) return {}; file.unsetf(std::ios::skipws); std::streampos fileSize; file.seekg(0, std::ios::end); fileSize = file.tellg(); if (fileSize == static_cast(0)) return {}; file.seekg(0, std::ios::beg); std::vector vec(static_cast(fileSize), 0); file.read(reinterpret_cast(&vec[0]), fileSize); return vec; }; } // API search type: read_binary_file : String -> Io [Int] // Returns a function that reads the content of a binary file when executed. // This function then returns an empty vector if the file could not be read. inline std::function()> read_binary_file( const std::string& filename) { return [filename]() -> std::vector { return just_with_default( std::vector(), read_binary_file_maybe(filename)()); }; } // API search type: read_text_file_lines_maybe : (String, Bool) -> Io (Maybe [String]) // Returns a function that (when called) reads the content of a text file // and returns it line by line. inline std::function>()> read_text_file_lines_maybe( bool allow_empty, const std::string& filename) { return [filename, allow_empty]() -> maybe> { const auto maybe_content = read_text_file_maybe(filename)(); if (maybe_content.is_nothing()) return {}; else return split_lines(allow_empty, maybe_content.unsafe_get_just()); }; } // API search type: read_text_file_lines : (String, Bool) -> Io [String] // Returns a function that (when called) reads the content of a text file // and returns it line by line. // This function then returns an empty vector if the file could not be read. inline std::function()> read_text_file_lines( bool allow_empty, const std::string& filename) { return [filename, allow_empty]() -> std::vector { return just_with_default( std::vector(), read_text_file_lines_maybe(allow_empty, filename)()); }; } // API search type: write_text_file : (String, String) -> Io Bool // Returns a function that (when called) writes content into a text file, // replacing it if it already exists. inline std::function write_text_file(const std::string& filename, const std::string& content) { return [filename, content]() -> bool { std::ofstream output(filename); output << content; return output.good(); }; } // API search type: write_binary_file : (String, [Int]) -> Io Bool // Returns a function that (when called) writes content into a binary file, // replacing it if it already exists. inline std::function write_binary_file(const std::string& filename, const std::vector& content) { return [filename, content]() -> bool { std::ofstream file(filename, std::ios::binary); file.write(reinterpret_cast(&content[0]), static_cast(content.size())); return file.good(); }; } // API search type: write_text_file_lines : (String, [String], Bool) -> Io Bool // Returns a function that (when called) writes lines into a text file, // replacing it if it already exists. inline std::function write_text_file_lines(bool trailing_newline, const std::string& filename, const std::vector& lines) { std::string content = join(std::string("\n"), lines); if (trailing_newline) { content += "\n"; } return write_text_file(filename, content); } // API search type: execute_effect : Io a -> a // Simply run a side effect (call a function without parameters) // and returns the result. // Can be useful for chaining. template auto execute_effect(const F f) { return internal::invoke(f); } // API search type: interact : (String -> String) -> Io () // Takes a function F of type (String -> String) // and returns a function that // reads the entire input from standard input, // passes it through the given function, // and writes the result to standard output. template std::function interact(F f) { return [f]() -> void { std::cout << f(std::string( std::istreambuf_iterator(std::cin.rdbuf()), std::istreambuf_iterator())); }; } // API search type: execute_with_maybe : ((a -> void), Maybe a) -> Io Bool // Returns a function that // akes a unary side-effect function with // a maybe holding a matching type // and runs the sideeffect if the Maybe holds a just. // The returned function returns false if the maybe was a nothing. template std::function execute_with_maybe(Effect eff, const maybe& m) { return [eff, m]() -> bool { if (m.is_nothing()) { return false; } eff(m.unsafe_get_just()); return true; }; } } // namespace fplus