remove invoke module. use jobber and scheduler from promise.hpp.

This commit is contained in:
2018-12-13 01:13:14 +07:00
parent 258f03cf58
commit ac885a51ef
21 changed files with 24 additions and 1125 deletions

3
.gitmodules vendored
View File

@@ -25,9 +25,6 @@
[submodule "toolset/model_converter/modules/assimp"]
path = toolset/model_converter/modules/assimp
url = https://github.com/assimp/assimp
[submodule "modules/invoke.hpp"]
path = modules/invoke.hpp
url = https://github.com/BlackMATov/invoke.hpp
[submodule "modules/promise.hpp"]
path = modules/promise.hpp
url = https://github.com/BlackMATov/promise.hpp

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* This file is part of the "invoke.hpp"
* This file is part of the "https://github.com/blackmatov/invoke.hpp"
* For conditions of distribution and use, see copyright notice in LICENSE.md
* Copyright (C) 2018 Matvey Cherevko
******************************************************************************/

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* This file is part of the "promise.hpp"
* This file is part of the "https://github.com/blackmatov/promise.hpp"
* For conditions of distribution and use, see copyright notice in LICENSE.md
* Copyright (C) 2018 Matvey Cherevko
******************************************************************************/

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* This file is part of the "promise.hpp"
* This file is part of the "https://github.com/blackmatov/promise.hpp"
* For conditions of distribution and use, see copyright notice in LICENSE.md
* Copyright (C) 2018 Matvey Cherevko
******************************************************************************/
@@ -23,254 +23,7 @@
#include <type_traits>
#include <condition_variable>
//
// invoke.hpp
// https://github.com/BlackMATov/invoke.hpp
//
namespace promise_hpp
{
#define INVOKE_HPP_NOEXCEPT_RETURN(...) \
noexcept(noexcept(__VA_ARGS__)) { return __VA_ARGS__; }
#define INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(...) \
noexcept(noexcept(__VA_ARGS__)) -> decltype (__VA_ARGS__) { return __VA_ARGS__; }
//
// void_t
//
namespace invoke_hpp
{
namespace impl
{
template < typename... Args >
struct make_void {
using type = void;
};
}
template < typename... Args >
using void_t = typename impl::make_void<Args...>::type;
}
//
// is_reference_wrapper
//
namespace invoke_hpp
{
namespace impl
{
template < typename T >
struct is_reference_wrapper_impl
: std::false_type {};
template < typename U >
struct is_reference_wrapper_impl<std::reference_wrapper<U>>
: std::true_type {};
}
template < typename T >
struct is_reference_wrapper
: impl::is_reference_wrapper_impl<std::remove_cv_t<T>> {};
}
//
// invoke
//
namespace invoke_hpp
{
namespace impl
{
//
// invoke_member_object_impl
//
template
<
typename Base, typename F, typename Derived,
typename std::enable_if_t<std::is_base_of<Base, std::decay_t<Derived>>::value, int> = 0
>
constexpr auto invoke_member_object_impl(F Base::* f, Derived&& ref)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
std::forward<Derived>(ref).*f)
template
<
typename Base, typename F, typename RefWrap,
typename std::enable_if_t<is_reference_wrapper<std::decay_t<RefWrap>>::value, int> = 0
>
constexpr auto invoke_member_object_impl(F Base::* f, RefWrap&& ref)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
ref.get().*f)
template
<
typename Base, typename F, typename Pointer,
typename std::enable_if_t<
!std::is_base_of<Base, std::decay_t<Pointer>>::value &&
!is_reference_wrapper<std::decay_t<Pointer>>::value
, int> = 0
>
constexpr auto invoke_member_object_impl(F Base::* f, Pointer&& ptr)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
(*std::forward<Pointer>(ptr)).*f)
//
// invoke_member_function_impl
//
template
<
typename Base, typename F, typename Derived, typename... Args,
typename std::enable_if_t<std::is_base_of<Base, std::decay_t<Derived>>::value, int> = 0
>
constexpr auto invoke_member_function_impl(F Base::* f, Derived&& ref, Args&&... args)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
(std::forward<Derived>(ref).*f)(std::forward<Args>(args)...))
template
<
typename Base, typename F, typename RefWrap, typename... Args,
typename std::enable_if_t<is_reference_wrapper<std::decay_t<RefWrap>>::value, int> = 0
>
constexpr auto invoke_member_function_impl(F Base::* f, RefWrap&& ref, Args&&... args)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
(ref.get().*f)(std::forward<Args>(args)...))
template
<
typename Base, typename F, typename Pointer, typename... Args,
typename std::enable_if_t<
!std::is_base_of<Base, std::decay_t<Pointer>>::value &&
!is_reference_wrapper<std::decay_t<Pointer>>::value
, int> = 0
>
constexpr auto invoke_member_function_impl(F Base::* f, Pointer&& ptr, Args&&... args)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
((*std::forward<Pointer>(ptr)).*f)(std::forward<Args>(args)...))
}
template
<
typename F, typename... Args,
typename std::enable_if_t<!std::is_member_pointer<std::decay_t<F>>::value, int> = 0
>
constexpr auto invoke(F&& f, Args&&... args)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
std::forward<F>(f)(std::forward<Args>(args)...))
template
<
typename F, typename T,
typename std::enable_if_t<std::is_member_object_pointer<std::decay_t<F>>::value, int> = 0
>
constexpr auto invoke(F&& f, T&& t)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
impl::invoke_member_object_impl(std::forward<F>(f), std::forward<T>(t)))
template
<
typename F, typename... Args,
typename std::enable_if_t<std::is_member_function_pointer<std::decay_t<F>>::value, int> = 0
>
constexpr auto invoke(F&& f, Args&&... args)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
impl::invoke_member_function_impl(std::forward<F>(f), std::forward<Args>(args)...))
}
//
// invoke_result
//
namespace invoke_hpp
{
namespace impl
{
struct invoke_result_impl_tag {};
template < typename Void, typename F, typename... Args >
struct invoke_result_impl {};
template < typename F, typename... Args >
struct invoke_result_impl<void_t<invoke_result_impl_tag, decltype(invoke_hpp::invoke(std::declval<F>(), std::declval<Args>()...))>, F, Args...> {
using type = decltype(invoke_hpp::invoke(std::declval<F>(), std::declval<Args>()...));
};
}
template < typename F, typename... Args >
struct invoke_result
: impl::invoke_result_impl<void, F, Args...> {};
template < typename F, typename... Args >
using invoke_result_t = typename invoke_result<F, Args...>::type;
}
//
// is_invocable
//
namespace invoke_hpp
{
namespace impl
{
struct is_invocable_r_impl_tag {};
template < typename Void, typename R, typename F, typename... Args >
struct is_invocable_r_impl
: std::false_type {};
template < typename R, typename F, typename... Args >
struct is_invocable_r_impl<void_t<is_invocable_r_impl_tag, invoke_result_t<F, Args...>>, R, F, Args...>
: std::conditional_t<
std::is_void<R>::value,
std::true_type,
std::is_convertible<invoke_result_t<F, Args...>, R>> {};
}
template < typename R, typename F, typename... Args >
struct is_invocable_r
: impl::is_invocable_r_impl<void, R, F, Args...> {};
template < typename F, typename... Args >
using is_invocable = is_invocable_r<void, F, Args...>;
}
//
// apply
//
namespace invoke_hpp
{
namespace impl
{
template < typename F, typename Tuple, std::size_t... I >
constexpr decltype(auto) apply_impl(F&& f, Tuple&& args, std::index_sequence<I...>)
INVOKE_HPP_NOEXCEPT_RETURN(
invoke_hpp::invoke(
std::forward<F>(f),
std::get<I>(std::forward<Tuple>(args))...))
}
template < typename F, typename Tuple >
constexpr decltype(auto) apply(F&& f, Tuple&& args)
INVOKE_HPP_NOEXCEPT_RETURN(
impl::apply_impl(
std::forward<F>(f),
std::forward<Tuple>(args),
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>()))
}
#undef INVOKE_HPP_NOEXCEPT_RETURN
#undef INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN
}
//
// promise.hpp
// https://github.com/BlackMATov/promise.hpp
//
#include "invoke.hpp"
namespace promise_hpp
{

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* This file is part of the "promise.hpp"
* This file is part of the "https://github.com/blackmatov/promise.hpp"
* For conditions of distribution and use, see copyright notice in LICENSE.md
* Copyright (C) 2018 Matvey Cherevko
******************************************************************************/

View File

@@ -43,6 +43,8 @@
#include <functional>
#include <type_traits>
#include <3rdparty/invoke.hpp/invoke.hpp>
#include <3rdparty/promise.hpp/promise.hpp>
#include <3rdparty/variant/variant.hpp>
#include <3rdparty/promise.hpp/invoke.hpp>
#include <3rdparty/promise.hpp/jobber.hpp>
#include <3rdparty/promise.hpp/promise.hpp>
#include <3rdparty/promise.hpp/scheduler.hpp>

View File

@@ -9,7 +9,7 @@
#include "macros.hpp"
//
// variant
// https://github.com/mpark/variant
//
namespace e2d { namespace stdex
@@ -18,21 +18,15 @@ namespace e2d { namespace stdex
}}
//
// invoke
// https://github.com/blackmatov/promise.hpp
//
namespace e2d { namespace stdex
{
using namespace invoke_hpp;
}}
//
// promise
//
namespace e2d { namespace stdex
{
using namespace jobber_hpp;
using namespace promise_hpp;
using namespace scheduler_hpp;
}}
//

View File

@@ -126,8 +126,8 @@ namespace e2d
u32 frame_count() const noexcept;
f32 realtime_time() const noexcept;
scheduler& main_thread_scheduler() noexcept;
const scheduler& main_thread_scheduler() const noexcept;
stdex::scheduler& main_thread_scheduler() noexcept;
const stdex::scheduler& main_thread_scheduler() const noexcept;
private:
class internal_state;
std::unique_ptr<internal_state> state_;

View File

@@ -46,7 +46,7 @@ namespace e2d
input_stream_uptr open(const url& url) const;
std::pair<buffer,bool> load(const url& url) const;
output_stream_uptr write(const url& url, bool append) const;
std::future<std::pair<buffer,bool>> load_async(const url& url) const;
stdex::promise<std::pair<buffer,bool>> load_async(const url& url) const;
template < typename Iter >
bool extract(const url& url, Iter result_iter) const;

View File

@@ -14,11 +14,9 @@
#include "filesystem.hpp"
#include "filesystem.inl"
#include "image.hpp"
#include "jobber.hpp"
#include "mesh.hpp"
#include "module.hpp"
#include "path.hpp"
#include "scheduler.hpp"
#include "streams.hpp"
#include "strfmts.hpp"
#include "strings.hpp"

View File

@@ -15,9 +15,7 @@ namespace e2d
class color;
class color32;
class image;
class jobber;
class mesh;
class scheduler;
class input_stream;
class output_stream;
class input_sequence;

View File

@@ -1,237 +0,0 @@
/*******************************************************************************
* This file is part of the "Enduro2D"
* For conditions of distribution and use, see copyright notice in LICENSE.md
* Copyright (C) 2018 Matvey Cherevko
******************************************************************************/
#pragma once
#include "_utils.hpp"
#include "time.hpp"
namespace e2d
{
class jobber final : private noncopyable {
public:
enum class priority {
lowest,
below_normal,
normal,
above_normal,
highest
};
public:
explicit jobber(u32 threads);
~jobber() noexcept;
template < typename F, typename... Args >
using async_invoke_result_t = stdex::invoke_result_t<
std::decay_t<F>,
std::decay_t<Args>...>;
template < typename F, typename... Args
, typename R = async_invoke_result_t<F, Args...> >
std::future<R> async(F&& f, Args&&... args);
template < typename F, typename... Args
, typename R = async_invoke_result_t<F, Args...> >
std::future<R> async(priority priority, F&& f, Args&&... args);
void pause() noexcept;
void resume() noexcept;
bool is_paused() const noexcept;
void wait_all() const noexcept;
void active_wait_all() noexcept;
template < typename T, typename TimeTag >
void wait_for(const unit<T, TimeTag>& time_for) const noexcept;
template < typename T, typename TimeTag >
void wait_until(const unit<T, TimeTag>& time_until) const noexcept;
template < typename T, typename TimeTag >
void active_wait_for(const unit<T, TimeTag>& time_for) noexcept;
template < typename T, typename TimeTag >
void active_wait_until(const unit<T, TimeTag>& time_until) noexcept;
private:
class task;
using task_ptr = std::unique_ptr<task>;
template < typename R, typename F, typename... Args >
class concrete_task;
private:
void push_task_(priority priority, task_ptr task);
task_ptr pop_task_() noexcept;
void shutdown_() noexcept;
void worker_main_() noexcept;
void process_task_(std::unique_lock<std::mutex> lock) noexcept;
private:
vector<std::thread> threads_;
vector<std::pair<priority, task_ptr>> tasks_;
std::mutex tasks_mutex_;
std::condition_variable cond_var_;
std::atomic<bool> paused_{false};
std::atomic<bool> cancelled_{false};
std::atomic<size_t> active_task_count_{0};
};
class jobber::task : private noncopyable {
public:
virtual ~task() noexcept = default;
virtual void run() noexcept = 0;
};
template < typename R, typename F, typename... Args >
class jobber::concrete_task : public task {
F f_;
std::tuple<Args...> args_;
std::promise<R> promise_;
public:
template < typename U >
concrete_task(U&& u, std::tuple<Args...>&& args);
void run() noexcept final;
std::future<R> future() noexcept;
};
template < typename F, typename... Args >
class jobber::concrete_task<void, F, Args...> : public task {
F f_;
std::tuple<Args...> args_;
std::promise<void> promise_;
public:
template < typename U >
concrete_task(U&& u, std::tuple<Args...>&& args);
void run() noexcept final;
std::future<void> future() noexcept;
};
}
namespace e2d
{
//
// async
//
template < typename F, typename... Args, typename R >
std::future<R> jobber::async(F&& f, Args&&... args) {
return async(
priority::normal,
std::forward<F>(f),
std::forward<Args>(args)...);
}
template < typename F, typename... Args, typename R >
std::future<R> jobber::async(priority priority, F&& f, Args&&... args) {
using task_t = concrete_task<
R,
std::decay_t<F>,
std::decay_t<Args>...>;
std::unique_ptr<task_t> task = std::make_unique<task_t>(
std::forward<F>(f),
std::make_tuple(std::forward<Args>(args)...));
std::future<R> future = task->future();
std::lock_guard<std::mutex> guard(tasks_mutex_);
push_task_(priority, std::move(task));
return future;
}
//
// wait
//
template < typename T, typename TimeTag >
void jobber::wait_for(const unit<T, TimeTag>& time_for) const noexcept {
if ( time_for.value > T(0) ) {
wait_until(
time::now<TimeTag, u64>() +
time_for.template cast_to<u64>());
}
}
template < typename T, typename TimeTag >
void jobber::wait_until(const unit<T, TimeTag>& time_until) const noexcept {
while ( active_task_count_ ) {
const auto time_now = time::now<TimeTag, u64>();
if ( time_now >= time_until.template cast_to<u64>() ) {
break;
}
std::this_thread::yield();
}
}
//
// active_wait
//
template < typename T, typename TimeTag >
void jobber::active_wait_for(const unit<T, TimeTag>& time_for) noexcept {
if ( time_for.value > T(0) ) {
active_wait_until(
time::now<TimeTag, u64>() +
time_for.template cast_to<u64>());
}
}
template < typename T, typename TimeTag >
void jobber::active_wait_until(const unit<T, TimeTag>& time_until) noexcept {
while ( active_task_count_ ) {
const auto time_now = time::now<TimeTag, u64>();
if ( time_now >= time_until.template cast_to<u64>() ) {
break;
}
std::unique_lock<std::mutex> lock(tasks_mutex_);
process_task_(std::move(lock));
}
}
//
// concrete_task<R, F, Args...>
//
template < typename R, typename F, typename... Args >
template < typename U >
jobber::concrete_task<R, F, Args...>::concrete_task(U&& u, std::tuple<Args...>&& args)
: f_(std::forward<U>(u))
, args_(std::move(args)) {}
template < typename R, typename F, typename... Args >
void jobber::concrete_task<R, F, Args...>::run() noexcept {
try {
R value = stdex::apply(std::move(f_), std::move(args_));
promise_.set_value(std::move(value));
} catch (...) {
promise_.set_exception(std::current_exception());
}
}
template < typename R, typename F, typename... Args >
std::future<R> jobber::concrete_task<R, F, Args...>::future() noexcept {
return promise_.get_future();
}
//
// concrete_task<void, F, Args...>
//
template < typename F, typename... Args >
template < typename U >
jobber::concrete_task<void, F, Args...>::concrete_task(U&& u, std::tuple<Args...>&& args)
: f_(std::forward<U>(u))
, args_(std::move(args)) {}
template < typename F, typename... Args >
void jobber::concrete_task<void, F, Args...>::run() noexcept {
try {
stdex::apply(std::move(f_), std::move(args_));
promise_.set_value();
} catch (...) {
promise_.set_exception(std::current_exception());
}
}
template < typename F, typename... Args >
std::future<void> jobber::concrete_task<void, F, Args...>::future() noexcept {
return promise_.get_future();
}
}

View File

@@ -1,196 +0,0 @@
/*******************************************************************************
* This file is part of the "Enduro2D"
* For conditions of distribution and use, see copyright notice in LICENSE.md
* Copyright (C) 2018 Matvey Cherevko
******************************************************************************/
#pragma once
#include "_utils.hpp"
#include "time.hpp"
namespace e2d
{
class scheduler final : private noncopyable {
public:
enum class priority {
lowest,
below_normal,
normal,
above_normal,
highest
};
public:
scheduler();
~scheduler() noexcept;
template < typename F, typename... Args >
using schedule_invoke_result_t = stdex::invoke_result_t<
std::decay_t<F>,
std::decay_t<Args>...>;
template < typename F, typename... Args
, typename R = schedule_invoke_result_t<F, Args...> >
std::future<R> schedule(F&& f, Args&&... args);
template < typename F, typename... Args
, typename R = schedule_invoke_result_t<F, Args...> >
std::future<R> schedule(priority priority, F&& f, Args&&... args);
void process_all_tasks() noexcept;
template < typename T, typename TimeTag >
void process_tasks_for(const unit<T, TimeTag>& time_for) noexcept;
template < typename T, typename TimeTag >
void process_tasks_until(const unit<T, TimeTag>& time_until) noexcept;
private:
class task;
using task_ptr = std::unique_ptr<task>;
template < typename R, typename F, typename... Args >
class concrete_task;
private:
void push_task_(priority priority, task_ptr task);
task_ptr pop_task_() noexcept;
void process_task_(std::unique_lock<std::mutex> lock) noexcept;
private:
std::mutex tasks_mutex_;
std::atomic<size_t> active_task_count_{0};
vector<std::pair<priority, task_ptr>> tasks_;
};
class scheduler::task : private noncopyable {
public:
virtual ~task() noexcept = default;
virtual void run() noexcept = 0;
};
template < typename R, typename F, typename... Args >
class scheduler::concrete_task : public task {
F f_;
std::tuple<Args...> args_;
std::promise<R> promise_;
public:
template < typename U >
concrete_task(U&& u, std::tuple<Args...>&& args);
void run() noexcept final;
std::future<R> future() noexcept;
};
template < typename F, typename... Args >
class scheduler::concrete_task<void, F, Args...> : public task {
F f_;
std::tuple<Args...> args_;
std::promise<void> promise_;
public:
template < typename U >
concrete_task(U&& u, std::tuple<Args...>&& args);
void run() noexcept final;
std::future<void> future() noexcept;
};
}
namespace e2d
{
//
// schedule
//
template < typename F, typename... Args, typename R >
std::future<R> scheduler::schedule(F&& f, Args&&... args) {
return schedule(
priority::normal,
std::forward<F>(f),
std::forward<Args>(args)...);
}
template < typename F, typename... Args, typename R >
std::future<R> scheduler::schedule(priority priority, F&& f, Args&&... args) {
using task_t = concrete_task<
R,
std::decay_t<F>,
std::decay_t<Args>...>;
std::unique_ptr<task_t> task = std::make_unique<task_t>(
std::forward<F>(f),
std::make_tuple(std::forward<Args>(args)...));
std::future<R> future = task->future();
std::lock_guard<std::mutex> guard(tasks_mutex_);
push_task_(priority, std::move(task));
return future;
}
//
// process_all_tasks
//
template < typename T, typename TimeTag >
void scheduler::process_tasks_for(const unit<T, TimeTag>& time_for) noexcept {
if ( time_for.value > T(0) ) {
process_tasks_until(
time::now<TimeTag, u64>() +
time_for.template cast_to<u64>());
}
}
template < typename T, typename TimeTag >
void scheduler::process_tasks_until(const unit<T, TimeTag>& time_until) noexcept {
while ( active_task_count_ ) {
const auto time_now = time::now<TimeTag, u64>();
if ( time_now >= time_until.template cast_to<u64>() ) {
break;
}
std::unique_lock<std::mutex> lock(tasks_mutex_);
process_task_(std::move(lock));
}
}
//
// concrete_task<R, F, Args...>
//
template < typename R, typename F, typename... Args >
template < typename U >
scheduler::concrete_task<R, F, Args...>::concrete_task(U&& u, std::tuple<Args...>&& args)
: f_(std::forward<U>(u))
, args_(std::move(args)) {}
template < typename R, typename F, typename... Args >
void scheduler::concrete_task<R, F, Args...>::run() noexcept {
try {
R value = stdex::apply(std::move(f_), std::move(args_));
promise_.set_value(std::move(value));
} catch (...) {
promise_.set_exception(std::current_exception());
}
}
template < typename R, typename F, typename... Args >
std::future<R> scheduler::concrete_task<R, F, Args...>::future() noexcept {
return promise_.get_future();
}
//
// concrete_task<void, F, Args...>
//
template < typename F, typename... Args >
template < typename U >
scheduler::concrete_task<void, F, Args...>::concrete_task(U&& u, std::tuple<Args...>&& args)
: f_(std::forward<U>(u))
, args_(std::move(args)) {}
template < typename F, typename... Args >
void scheduler::concrete_task<void, F, Args...>::run() noexcept {
try {
stdex::apply(std::move(f_), std::move(args_));
promise_.set_value();
} catch (...) {
promise_.set_exception(std::current_exception());
}
}
template < typename F, typename... Args >
std::future<void> scheduler::concrete_task<void, F, Args...>::future() noexcept {
return promise_.get_future();
}
}

View File

@@ -22,10 +22,8 @@ git submodule update --remote --recursive
mkdir -p $UNTESTS_DIR/catch
cp -fv $MODULES_DIR/catch2/single_include/catch2/catch.hpp $UNTESTS_DIR/catch/catch.hpp
mkdir -p $HEADERS_RDPARTY_DIR/invoke.hpp
cp -rfv $MODULES_DIR/invoke.hpp/invoke.hpp $HEADERS_RDPARTY_DIR/invoke.hpp/invoke.hpp
mkdir -p $HEADERS_RDPARTY_DIR/promise.hpp
cp -rfv $MODULES_DIR/promise.hpp/invoke.hpp $HEADERS_RDPARTY_DIR/promise.hpp/invoke.hpp
cp -rfv $MODULES_DIR/promise.hpp/jobber.hpp $HEADERS_RDPARTY_DIR/promise.hpp/jobber.hpp
cp -rfv $MODULES_DIR/promise.hpp/promise.hpp $HEADERS_RDPARTY_DIR/promise.hpp/promise.hpp
cp -rfv $MODULES_DIR/promise.hpp/scheduler.hpp $HEADERS_RDPARTY_DIR/promise.hpp/scheduler.hpp

View File

@@ -251,11 +251,11 @@ namespace e2d
return time::to_seconds(delta_us.cast_to<f32>()).value;
}
scheduler& main_thread_scheduler() noexcept {
stdex::scheduler& main_thread_scheduler() noexcept {
return main_thread_scheduler_;
}
const scheduler& main_thread_scheduler() const noexcept {
const stdex::scheduler& main_thread_scheduler() const noexcept {
return main_thread_scheduler_;
}
public:
@@ -298,7 +298,7 @@ namespace e2d
}
private:
timer_parameters timer_params_;
scheduler main_thread_scheduler_;
stdex::scheduler main_thread_scheduler_;
microseconds<u64> init_time_{time::now_us<u64>()};
microseconds<u64> prev_frame_time_{time::now_us<u64>()};
microseconds<u64> prev_frame_rate_time_{time::now_us<u64>()};
@@ -422,11 +422,11 @@ namespace e2d
return state_->realtime_time();
}
scheduler& engine::main_thread_scheduler() noexcept {
stdex::scheduler& engine::main_thread_scheduler() noexcept {
return state_->main_thread_scheduler();
}
const scheduler& engine::main_thread_scheduler() const noexcept {
const stdex::scheduler& engine::main_thread_scheduler() const noexcept {
return state_->main_thread_scheduler();
}
}

View File

@@ -79,7 +79,7 @@ namespace e2d
class vfs::state final : private e2d::noncopyable {
public:
std::mutex mutex;
jobber worker{1};
stdex::jobber worker{1};
hash_map<str, url> aliases;
hash_map<str, file_source_uptr> schemes;
public:
@@ -164,7 +164,7 @@ namespace e2d
}, output_stream_uptr());
}
std::future<std::pair<buffer,bool>> vfs::load_async(const url& url) const {
stdex::promise<std::pair<buffer,bool>> vfs::load_async(const url& url) const {
return state_->worker.async([](input_stream_uptr stream){
buffer buf;
return streams::try_read_tail(buf, stream)
@@ -196,7 +196,6 @@ namespace e2d
using stream_ptr = std::shared_ptr<input_stream>;
archive_ptr archive;
stream_ptr stream;
jobber worker{1};
public:
state(input_stream_uptr nstream)
: archive(open_archive_(nstream))

View File

@@ -1,108 +0,0 @@
/*******************************************************************************
* This file is part of the "Enduro2D"
* For conditions of distribution and use, see copyright notice in LICENSE.md
* Copyright (C) 2018 Matvey Cherevko
******************************************************************************/
#include <enduro2d/utils/jobber.hpp>
namespace e2d
{
jobber::jobber(u32 threads) {
try {
threads_.resize(math::max(1u, threads));
for ( std::thread& thread : threads_ ) {
thread = std::thread(&jobber::worker_main_, this);
}
} catch (...) {
shutdown_();
throw;
}
}
jobber::~jobber() noexcept {
shutdown_();
}
void jobber::pause() noexcept {
paused_.store(true);
}
void jobber::resume() noexcept {
paused_.store(false);
{
std::lock_guard<std::mutex> guard(tasks_mutex_);
cond_var_.notify_all();
}
}
bool jobber::is_paused() const noexcept {
return paused_;
}
void jobber::wait_all() const noexcept {
while ( active_task_count_ ) {
std::this_thread::yield();
}
}
void jobber::active_wait_all() noexcept {
while ( active_task_count_ ) {
std::unique_lock<std::mutex> lock(tasks_mutex_);
process_task_(std::move(lock));
}
}
void jobber::push_task_(priority priority, task_ptr task) {
tasks_.emplace_back(priority, std::move(task));
std::push_heap(tasks_.begin(), tasks_.end());
++active_task_count_;
cond_var_.notify_one();
}
jobber::task_ptr jobber::pop_task_() noexcept {
if ( !tasks_.empty() ) {
std::pop_heap(tasks_.begin(), tasks_.end());
task_ptr task = std::move(tasks_.back().second);
tasks_.pop_back();
return task;
}
return nullptr;
}
void jobber::shutdown_() noexcept {
cancelled_.store(true);
{
std::lock_guard<std::mutex> guard(tasks_mutex_);
cond_var_.notify_all();
}
for ( std::thread& thread : threads_ ) {
if ( thread.joinable() ) {
thread.join();
}
}
}
void jobber::worker_main_() noexcept {
for (;;) {
std::unique_lock<std::mutex> lock(tasks_mutex_);
cond_var_.wait(lock, [this](){
return cancelled_ || (!paused_ && !tasks_.empty());
});
if ( cancelled_ ) {
break;
}
process_task_(std::move(lock));
}
}
void jobber::process_task_(std::unique_lock<std::mutex> lock) noexcept {
E2D_ASSERT(lock.owns_lock());
task_ptr task = pop_task_();
if ( task ) {
lock.unlock();
task->run();
--active_task_count_;
}
}
}

View File

@@ -1,51 +0,0 @@
/*******************************************************************************
* This file is part of the "Enduro2D"
* For conditions of distribution and use, see copyright notice in LICENSE.md
* Copyright (C) 2018 Matvey Cherevko
******************************************************************************/
#include <enduro2d/utils/scheduler.hpp>
namespace
{
using namespace e2d;
}
namespace e2d
{
scheduler::scheduler() = default;
scheduler::~scheduler() noexcept = default;
void scheduler::process_all_tasks() noexcept {
while ( active_task_count_ ) {
std::unique_lock<std::mutex> lock(tasks_mutex_);
process_task_(std::move(lock));
}
}
void scheduler::push_task_(priority priority, task_ptr task) {
tasks_.emplace_back(priority, std::move(task));
std::push_heap(tasks_.begin(), tasks_.end());
++active_task_count_;
}
scheduler::task_ptr scheduler::pop_task_() noexcept {
if ( !tasks_.empty() ) {
std::pop_heap(tasks_.begin(), tasks_.end());
task_ptr task = std::move(tasks_.back().second);
tasks_.pop_back();
return task;
}
return nullptr;
}
void scheduler::process_task_(std::unique_lock<std::mutex> lock) noexcept {
E2D_ASSERT(lock.owns_lock());
task_ptr task = pop_task_();
if ( task ) {
lock.unlock();
task->run();
--active_task_count_;
}
}
}

View File

@@ -1,167 +0,0 @@
/*******************************************************************************
* This file is part of the "Enduro2D"
* For conditions of distribution and use, see copyright notice in LICENSE.md
* Copyright (C) 2018 Matvey Cherevko
******************************************************************************/
#include "_utils.hpp"
using namespace e2d;
TEST_CASE("jobber") {
{
jobber j(1);
auto pv0 = j.async([](){
throw exception();
});
REQUIRE_THROWS_AS(pv0.get(), exception);
}
{
i32 v5 = 5;
jobber j(1);
auto pv0 = j.async([](i32 v){
REQUIRE(v == 5);
throw exception();
}, v5);
REQUIRE_THROWS_AS(pv0.get(), exception);
auto pv1 = j.async([](int& v){
REQUIRE(v == 5);
return v != 5
? 0
: throw exception();
}, std::ref(v5));
REQUIRE_THROWS_AS(pv1.get(), exception);
auto pv3 = j.async([](int& v){
v = 4;
return v;
}, std::ref(v5));
REQUIRE(pv3.get() == v5);
REQUIRE(v5 == 4);
}
{
jobber j(1);
auto p0 = j.async([](rad<f32> angle){
return math::sin(angle);
}, math::pi<f32>());
auto p1 = j.async([](rad<f32> angle){
return math::cos(angle);
}, math::two_pi<f32>());
REQUIRE(math::approximately(p0.get(), 0.f));
REQUIRE(math::approximately(p1.get(), 1.f));
}
{
jobber j(1);
j.pause();
jobber::priority max_priority = jobber::priority::highest;
j.async([](){
std::this_thread::sleep_for(std::chrono::milliseconds(2));
});
for ( std::size_t i = 0; i < 10; ++i ) {
jobber::priority p = static_cast<jobber::priority>(
i % static_cast<std::size_t>(jobber::priority::highest));
j.async(p, [&max_priority](jobber::priority priority) {
REQUIRE(priority <= max_priority);
max_priority = priority;
}, p);
}
j.resume();
j.wait_all();
}
{
jobber j(1);
i32 counter = 0;
j.pause();
for ( std::size_t i = 0; i < 50; ++i ) {
j.async([&counter](){
++counter;
std::this_thread::sleep_for(std::chrono::milliseconds(5));
});
}
const auto b = time::now_ms();
j.wait_for(make_milliseconds(15));
REQUIRE(time::now_ms() - b > make_milliseconds<i64>(10));
REQUIRE(counter == 0);
j.wait_until(time::now_ms() + make_milliseconds<i64>(15));
REQUIRE(time::now_ms() - b > make_milliseconds<i64>(20));
REQUIRE(counter == 0);
j.active_wait_for(make_milliseconds(60));
REQUIRE(time::now_ms() - b > make_milliseconds<i64>(70));
REQUIRE(counter > 2);
REQUIRE(counter < 50);
j.active_wait_until(time::now_s() + make_seconds<i64>(3));
REQUIRE(counter == 50);
}
{
jobber j(2);
jobber g(2);
std::vector<std::future<f32>> jp(50);
for ( std::size_t i = 0; i < jp.size(); ++i ) {
jp[i] = j.async([&g](){
std::vector<std::future<f32>> gp(50);
for ( std::size_t ii = 0; ii < gp.size(); ++ii ) {
gp[ii] = g.async([](rad<f32> angle){
return math::sin(angle);
}, make_rad(ii).cast_to<f32>());
}
return std::accumulate(gp.begin(), gp.end(), 0.f,
[](f32 r, std::future<f32>& f){
return r + f.get();
});
});
}
f32 r0 = std::accumulate(jp.begin(), jp.end(), 0.f,
[](f32 r, std::future<f32>& f){
return r + f.get();
});
f32 r1 = 0.f;
for ( std::size_t i = 0; i < 50; ++i ) {
r1 += math::sin(make_rad(i).cast_to<f32>());
}
REQUIRE(math::approximately(r0, r1 * 50.f));
}
SECTION("performance") {
std::printf("-= jobber::performance tests =-\n");
#if defined(E2D_BUILD_MODE) && E2D_BUILD_MODE == E2D_BUILD_MODE_DEBUG
const std::size_t task_n = 100'000;
#else
const std::size_t task_n = 1'000'000;
#endif
const auto big_task = [](std::size_t b, std::size_t n){
f32 result = 0.f;
for ( std::size_t i = 0; i < n; ++i ) {
result += math::sin(make_rad(b + i).cast_to<f32>());
result += math::cos(make_rad(b + i).cast_to<f32>());
}
return result;
};
const auto multi_task = [&](u32 threads, std::size_t tasks){
e2d_untests::verbose_profiler_ms p(
str("cores: ") + std::to_string(threads) +
str(", tasks: ") + std::to_string(tasks));
jobber j(threads);
std::vector<std::future<f32>> rs(tasks);
for ( std::size_t i = 0; i < tasks; ++i ) {
const std::size_t n = task_n / tasks;
rs[i] = j.async(big_task, i * n, n);
}
f32 result = std::accumulate(rs.begin(), rs.end(), 0.f,
[](f32 r, std::future<f32>& f){
return r + f.get();
});
p.done(result);
};
for ( u32 i = 1; i <= 4; ++i ) {
multi_task(i, 10);
multi_task(i, 100);
multi_task(i, 1000);
}
}
}

View File

@@ -1,80 +0,0 @@
/*******************************************************************************
* This file is part of the "Enduro2D"
* For conditions of distribution and use, see copyright notice in LICENSE.md
* Copyright (C) 2018 Matvey Cherevko
******************************************************************************/
#include "_utils.hpp"
using namespace e2d;
TEST_CASE("scheduler") {
{
scheduler s;
i32 counter = 0;
s.schedule([&counter](){ ++counter; });
REQUIRE(counter == 0);
s.process_all_tasks();
REQUIRE(counter == 1);
s.schedule([&counter](){ ++counter; });
REQUIRE(counter == 1);
s.process_all_tasks();
REQUIRE(counter == 2);
}
{
scheduler s;
i32 counter = 0;
for ( std::size_t i = 0; i < 50; ++i ) {
s.schedule([&counter](){
++counter;
std::this_thread::sleep_for(std::chrono::milliseconds(5));
});
}
s.process_tasks_for(make_milliseconds(-1));
s.process_tasks_for(make_milliseconds(0));
REQUIRE(counter == 0);
s.process_tasks_for(make_milliseconds(60));
REQUIRE(counter > 2);
REQUIRE(counter < 50);
s.process_tasks_for(make_seconds(3));
REQUIRE(counter == 50);
}
{
scheduler s;
i32 counter = 0;
for ( std::size_t i = 0; i < 50; ++i ) {
s.schedule([&counter](){
++counter;
std::this_thread::sleep_for(std::chrono::milliseconds(5));
});
}
s.process_tasks_until(time::now_ms() - make_milliseconds<i64>(1));
s.process_tasks_until(time::now_ms());
REQUIRE(counter == 0);
s.process_tasks_until(time::now_ms() + make_milliseconds<i64>(60));
REQUIRE(counter > 2);
REQUIRE(counter < 50);
s.process_tasks_until(time::now_s() + make_seconds<i64>(3));
REQUIRE(counter == 50);
}
{
scheduler s;
str accumulator;
s.schedule(scheduler::priority::lowest, [](str& acc){
acc.append("o");
}, std::ref(accumulator));
s.schedule(scheduler::priority::below_normal, [](str& acc){
acc.append("l");
}, std::ref(accumulator));
s.schedule(scheduler::priority::highest, [](str& acc){
acc.append("h");
}, std::ref(accumulator));
s.schedule(scheduler::priority::above_normal, [](str& acc){
acc.append("e");
}, std::ref(accumulator));
s.schedule(scheduler::priority::normal, [](str& acc){
acc.append("l");
}, std::ref(accumulator));
s.process_all_tasks();
REQUIRE(accumulator == "hello");
}
}