Emulate back/select gamepad button
This commit is contained in:
parent
0deb5433d1
commit
29edc88294
12 changed files with 524 additions and 41 deletions
|
|
@ -62,6 +62,7 @@ set(SUNSHINE_TARGET_FILES
|
|||
sunshine/config.h
|
||||
sunshine/config.cpp
|
||||
sunshine/main.cpp
|
||||
sunshine/main.h
|
||||
sunshine/crypto.cpp
|
||||
sunshine/crypto.h
|
||||
sunshine/nvhttp.cpp
|
||||
|
|
@ -78,6 +79,9 @@ set(SUNSHINE_TARGET_FILES
|
|||
sunshine/platform/common.h
|
||||
sunshine/process.cpp
|
||||
sunshine/process.h
|
||||
sunshine/move_by_copy.h
|
||||
sunshine/task_pool.h
|
||||
sunshine/thread_pool.h
|
||||
${PLATFORM_TARGET_FILES})
|
||||
|
||||
include_directories(
|
||||
|
|
|
|||
|
|
@ -25,6 +25,14 @@ ping_timeout = 2000
|
|||
# The higher fec_percentage, the lower space for the actual data to send per frame there is
|
||||
fec_percentage = 1
|
||||
|
||||
|
||||
# The back/select button on the controller
|
||||
# On the Shield, the home and powerbutton are not passed to Moonlight
|
||||
# If, after the timeout, the back button is still pressed down, Home/Guide button press is emulated.
|
||||
# If back_button_timeout < 0, then the Home/Guide button will not be emulated
|
||||
# back_button_timeout = 2000
|
||||
|
||||
|
||||
###############################################
|
||||
# FFmpeg software encoding parameters
|
||||
# Honestly, I have no idea what the optimal values would be.
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ nvhttp_t nvhttp {
|
|||
"devices.json" // file_devices
|
||||
};
|
||||
|
||||
input_t input {
|
||||
2s
|
||||
};
|
||||
|
||||
bool whitespace(char ch) {
|
||||
return ch == ' ' || ch == '\t';
|
||||
}
|
||||
|
|
@ -143,6 +147,13 @@ void parse_file(const char *file) {
|
|||
}
|
||||
string_f(vars, "file_apps", stream.file_apps);
|
||||
int_f(vars, "fec_percentage", stream.fec_percentage);
|
||||
|
||||
to = std::numeric_limits<int>::min();
|
||||
int_f(vars, "back_button_timeout", to);
|
||||
|
||||
if(to > std::numeric_limits<int>::min()) {
|
||||
input.back_button_timeout = std::chrono::milliseconds {to };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -37,9 +37,14 @@ struct nvhttp_t {
|
|||
std::string external_ip;
|
||||
};
|
||||
|
||||
struct input_t {
|
||||
std::chrono::milliseconds back_button_timeout;
|
||||
};
|
||||
|
||||
extern video_t video;
|
||||
extern stream_t stream;
|
||||
extern nvhttp_t nvhttp;
|
||||
extern input_t input;
|
||||
|
||||
void parse_file(const char *file);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ extern "C" {
|
|||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "main.h"
|
||||
#include "config.h"
|
||||
#include "input.h"
|
||||
#include "utility.h"
|
||||
|
||||
|
|
@ -132,7 +134,7 @@ void passthrough(platf::input_t &input, PNV_SCROLL_PACKET packet) {
|
|||
platf::scroll(input, util::endian::big(packet->scrollAmt1));
|
||||
}
|
||||
|
||||
void passthrough(input_t &input, PNV_MULTI_CONTROLLER_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET packet) {
|
||||
std::uint16_t bf;
|
||||
std::memcpy(&bf, &packet->buttonFlags, sizeof(std::uint16_t));
|
||||
|
||||
|
|
@ -146,7 +148,7 @@ void passthrough(input_t &input, PNV_MULTI_CONTROLLER_PACKET packet) {
|
|||
packet->rightStickY
|
||||
};
|
||||
|
||||
bf = gamepad_state.buttonFlags ^ input.gamepad_state.buttonFlags;
|
||||
bf = gamepad_state.buttonFlags ^ input->gamepad_state.buttonFlags;
|
||||
auto bf_new = gamepad_state.buttonFlags;
|
||||
|
||||
if(bf) {
|
||||
|
|
@ -154,73 +156,94 @@ void passthrough(input_t &input, PNV_MULTI_CONTROLLER_PACKET packet) {
|
|||
if((DPAD_UP | DPAD_DOWN) & bf) {
|
||||
int val = bf_new & DPAD_UP ? -1 : (bf_new & DPAD_DOWN ? 1 : 0);
|
||||
|
||||
platf::gp::dpad_y(input.input, val);
|
||||
platf::gp::dpad_y(input->input, val);
|
||||
}
|
||||
|
||||
if((DPAD_LEFT | DPAD_RIGHT) & bf) {
|
||||
int val = bf_new & DPAD_LEFT ? -1 : (bf_new & DPAD_RIGHT ? 1 : 0);
|
||||
platf::gp::dpad_x(input.input, val);
|
||||
platf::gp::dpad_x(input->input, val);
|
||||
}
|
||||
|
||||
if(START & bf) platf::gp::start(input.input, bf_new & START ? 1 : 0);
|
||||
if(BACK & bf) platf::gp::back(input.input, bf_new & BACK ? 1 : 0);
|
||||
if(LEFT_STICK & bf) platf::gp::left_stick(input.input, bf_new & LEFT_STICK ? 1 : 0);
|
||||
if(RIGHT_STICK & bf) platf::gp::right_stick(input.input, bf_new & RIGHT_STICK ? 1 : 0);
|
||||
if(LEFT_BUTTON & bf) platf::gp::left_button(input.input, bf_new & LEFT_BUTTON ? 1 : 0);
|
||||
if(RIGHT_BUTTON & bf) platf::gp::right_button(input.input, bf_new & RIGHT_BUTTON ? 1 : 0);
|
||||
if(HOME & bf) platf::gp::home(input.input, bf_new & HOME ? 1 : 0);
|
||||
if(A & bf) platf::gp::a(input.input, bf_new & A ? 1 : 0);
|
||||
if(B & bf) platf::gp::b(input.input, bf_new & B ? 1 : 0);
|
||||
if(X & bf) platf::gp::x(input.input, bf_new & X ? 1 : 0);
|
||||
if(Y & bf) platf::gp::y(input.input, bf_new & Y ? 1 : 0);
|
||||
if(START & bf) platf::gp::start(input->input, bf_new & START ? 1 : 0);
|
||||
if(LEFT_STICK & bf) platf::gp::left_stick(input->input, bf_new & LEFT_STICK ? 1 : 0);
|
||||
if(RIGHT_STICK & bf) platf::gp::right_stick(input->input, bf_new & RIGHT_STICK ? 1 : 0);
|
||||
if(LEFT_BUTTON & bf) platf::gp::left_button(input->input, bf_new & LEFT_BUTTON ? 1 : 0);
|
||||
if(RIGHT_BUTTON & bf) platf::gp::right_button(input->input, bf_new & RIGHT_BUTTON ? 1 : 0);
|
||||
if(HOME & bf) platf::gp::home(input->input, bf_new & HOME ? 1 : 0);
|
||||
if(A & bf) platf::gp::a(input->input, bf_new & A ? 1 : 0);
|
||||
if(B & bf) platf::gp::b(input->input, bf_new & B ? 1 : 0);
|
||||
if(X & bf) platf::gp::x(input->input, bf_new & X ? 1 : 0);
|
||||
if(Y & bf) platf::gp::y(input->input, bf_new & Y ? 1 : 0);
|
||||
|
||||
if(BACK & bf) {
|
||||
if(BACK & bf_new) {
|
||||
platf::gp::back(input->input,1);
|
||||
input->back_timeout_id = task_pool.pushDelayed([input]() {
|
||||
platf::gp::back(input->input, 0);
|
||||
|
||||
platf::gp::home(input->input,1);
|
||||
platf::gp::home(input->input,0);
|
||||
|
||||
input->back_timeout_id = nullptr;
|
||||
}, config::input.back_button_timeout).task_id;
|
||||
}
|
||||
else if(input->back_timeout_id) {
|
||||
platf::gp::back(input->input, 0);
|
||||
|
||||
task_pool.cancel(input->back_timeout_id);
|
||||
input->back_timeout_id = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(input.gamepad_state.lt != gamepad_state.lt) {
|
||||
platf::gp::left_trigger(input.input, gamepad_state.lt);
|
||||
if(input->gamepad_state.lt != gamepad_state.lt) {
|
||||
platf::gp::left_trigger(input->input, gamepad_state.lt);
|
||||
}
|
||||
|
||||
if(input.gamepad_state.rt != gamepad_state.rt) {
|
||||
platf::gp::right_trigger(input.input, gamepad_state.rt);
|
||||
if(input->gamepad_state.rt != gamepad_state.rt) {
|
||||
platf::gp::right_trigger(input->input, gamepad_state.rt);
|
||||
}
|
||||
|
||||
if(input.gamepad_state.lsX != gamepad_state.lsX) {
|
||||
platf::gp::left_stick_x(input.input, gamepad_state.lsX);
|
||||
if(input->gamepad_state.lsX != gamepad_state.lsX) {
|
||||
platf::gp::left_stick_x(input->input, gamepad_state.lsX);
|
||||
}
|
||||
|
||||
if(input.gamepad_state.lsY != gamepad_state.lsY) {
|
||||
platf::gp::left_stick_y(input.input, gamepad_state.lsY);
|
||||
if(input->gamepad_state.lsY != gamepad_state.lsY) {
|
||||
platf::gp::left_stick_y(input->input, gamepad_state.lsY);
|
||||
}
|
||||
|
||||
if(input.gamepad_state.rsX != gamepad_state.rsX) {
|
||||
platf::gp::right_stick_x(input.input, gamepad_state.rsX);
|
||||
if(input->gamepad_state.rsX != gamepad_state.rsX) {
|
||||
platf::gp::right_stick_x(input->input, gamepad_state.rsX);
|
||||
}
|
||||
|
||||
if(input.gamepad_state.rsY != gamepad_state.rsY) {
|
||||
platf::gp::right_stick_y(input.input, gamepad_state.rsY);
|
||||
if(input->gamepad_state.rsY != gamepad_state.rsY) {
|
||||
platf::gp::right_stick_y(input->input, gamepad_state.rsY);
|
||||
}
|
||||
|
||||
input.gamepad_state = gamepad_state;
|
||||
platf::gp::sync(input.input);
|
||||
input->gamepad_state = gamepad_state;
|
||||
platf::gp::sync(input->input);
|
||||
}
|
||||
|
||||
void passthrough(input_t &input, void *payload) {
|
||||
void passthrough_helper(std::shared_ptr<input_t> input, std::vector<std::uint8_t> &&input_data) {
|
||||
void *payload = input_data.data();
|
||||
|
||||
int input_type = util::endian::big(*(int*)payload);
|
||||
|
||||
switch(input_type) {
|
||||
case PACKET_TYPE_MOUSE_MOVE:
|
||||
passthrough(input.input, (PNV_MOUSE_MOVE_PACKET)payload);
|
||||
passthrough(input->input, (PNV_MOUSE_MOVE_PACKET)payload);
|
||||
break;
|
||||
case PACKET_TYPE_MOUSE_BUTTON:
|
||||
passthrough(input.input, (PNV_MOUSE_BUTTON_PACKET)payload);
|
||||
passthrough(input->input, (PNV_MOUSE_BUTTON_PACKET)payload);
|
||||
break;
|
||||
case PACKET_TYPE_SCROLL_OR_KEYBOARD:
|
||||
{
|
||||
char *tmp_input = (char*)payload + 4;
|
||||
if(tmp_input[0] == 0x0A) {
|
||||
passthrough(input.input, (PNV_SCROLL_PACKET)payload);
|
||||
passthrough(input->input, (PNV_SCROLL_PACKET)payload);
|
||||
}
|
||||
else {
|
||||
passthrough(input.input, (PNV_KEYBOARD_PACKET)payload);
|
||||
passthrough(input->input, (PNV_KEYBOARD_PACKET)payload);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -231,5 +254,9 @@ void passthrough(input_t &input, void *payload) {
|
|||
}
|
||||
}
|
||||
|
||||
input_t::input_t() : gamepad_state { 0 }, input { platf::input() } {}
|
||||
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data) {
|
||||
task_pool.push(passthrough_helper, input, util::cmove(input_data));
|
||||
}
|
||||
|
||||
input_t::input_t() : gamepad_state { 0 }, back_timeout_id { nullptr }, input { platf::input() } {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#define SUNSHINE_INPUT_H
|
||||
|
||||
#include "platform/common.h"
|
||||
#include "thread_pool.h"
|
||||
|
||||
namespace input {
|
||||
struct gamepad_state_t {
|
||||
|
|
@ -22,11 +23,13 @@ struct input_t {
|
|||
input_t();
|
||||
|
||||
gamepad_state_t gamepad_state;
|
||||
util::ThreadPool::task_id_t back_timeout_id;
|
||||
|
||||
platf::input_t input;
|
||||
};
|
||||
|
||||
void print(void *input);
|
||||
void passthrough(input_t &, void *input);
|
||||
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data);
|
||||
}
|
||||
|
||||
#endif //SUNSHINE_INPUT_H
|
||||
|
|
|
|||
|
|
@ -10,12 +10,15 @@
|
|||
#include "stream.h"
|
||||
#include "config.h"
|
||||
#include "process.h"
|
||||
#include "thread_pool.h"
|
||||
|
||||
extern "C" {
|
||||
#include <rs.h>
|
||||
}
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
util::ThreadPool task_pool;
|
||||
int main(int argc, char *argv[]) {
|
||||
if(argc > 1) {
|
||||
if(!std::filesystem::exists(argv[1])) {
|
||||
|
|
@ -34,11 +37,12 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
reed_solomon_init();
|
||||
|
||||
std::thread httpThread { nvhttp::start };
|
||||
std::thread rtpThread { stream::rtpThread };
|
||||
task_pool.start(1);
|
||||
|
||||
std::thread httpThread { nvhttp::start };
|
||||
|
||||
stream::rtpThread();
|
||||
httpThread.join();
|
||||
rtpThread.join();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
12
sunshine/main.h
Normal file
12
sunshine/main.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// Created by loki on 12/22/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_MAIN_H
|
||||
#define SUNSHINE_MAIN_H
|
||||
|
||||
#include "thread_pool.h"
|
||||
|
||||
extern util::ThreadPool task_pool;
|
||||
|
||||
#endif //SUNSHINE_MAIN_H
|
||||
50
sunshine/move_by_copy.h
Executable file
50
sunshine/move_by_copy.h
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
#ifndef DOSSIER_MOVE_BY_COPY_H
|
||||
#define DOSSIER_MOVE_BY_COPY_H
|
||||
|
||||
#include <utility>
|
||||
namespace util {
|
||||
/*
|
||||
* When a copy is made, it moves the object
|
||||
* This allows you to move an object when a move can't be done.
|
||||
*/
|
||||
template<class T>
|
||||
class MoveByCopy {
|
||||
public:
|
||||
typedef T move_type;
|
||||
private:
|
||||
move_type _to_move;
|
||||
public:
|
||||
|
||||
explicit MoveByCopy(move_type &&to_move) : _to_move(std::move(to_move)) { }
|
||||
|
||||
MoveByCopy(MoveByCopy &&other) = default;
|
||||
|
||||
MoveByCopy(const MoveByCopy &other) {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
MoveByCopy& operator=(MoveByCopy &&other) = default;
|
||||
|
||||
MoveByCopy& operator=(const MoveByCopy &other) {
|
||||
this->_to_move = std::move(const_cast<MoveByCopy&>(other)._to_move);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator move_type() {
|
||||
return std::move(_to_move);
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
MoveByCopy<T> cmove(T &movable) {
|
||||
return MoveByCopy<T>(std::move(movable));
|
||||
}
|
||||
|
||||
// Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller
|
||||
template<class T>
|
||||
MoveByCopy<T> const_cmove(const T &movable) {
|
||||
return MoveByCopy<T>(std::move(const_cast<T&>(movable)));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -468,7 +468,7 @@ void control_server_t::send(const std::string_view & payload) {
|
|||
void controlThread(video::idr_event_t idr_events) {
|
||||
control_server_t server { CONTROL_PORT };
|
||||
|
||||
input::input_t input;
|
||||
auto input = std::make_shared<input::input_t>();
|
||||
server.map(packetTypes[IDX_START_A], [](const std::string_view &payload) {
|
||||
session.pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout;
|
||||
|
||||
|
|
@ -539,7 +539,7 @@ void controlThread(video::idr_event_t idr_events) {
|
|||
}
|
||||
|
||||
input::print(plaintext.data());
|
||||
input::passthrough(input, plaintext.data());
|
||||
input::passthrough(input, std::move(plaintext));
|
||||
});
|
||||
|
||||
while(has_session) {
|
||||
|
|
|
|||
238
sunshine/task_pool.h
Executable file
238
sunshine/task_pool.h
Executable file
|
|
@ -0,0 +1,238 @@
|
|||
#ifndef KITTY_TASK_POOL_H
|
||||
#define KITTY_TASK_POOL_H
|
||||
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <future>
|
||||
#include <chrono>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
#include <optional>
|
||||
|
||||
#include "utility.h"
|
||||
#include "move_by_copy.h"
|
||||
namespace util {
|
||||
|
||||
class _ImplBase {
|
||||
public:
|
||||
//_unique_base_type _this_ptr;
|
||||
|
||||
inline virtual ~_ImplBase() = default;
|
||||
|
||||
virtual void run() = 0;
|
||||
};
|
||||
|
||||
template<class Function>
|
||||
class _Impl : public _ImplBase {
|
||||
Function _func;
|
||||
|
||||
public:
|
||||
|
||||
_Impl(Function&& f) : _func(std::forward<Function>(f)) { }
|
||||
|
||||
void run() override {
|
||||
_func();
|
||||
}
|
||||
};
|
||||
|
||||
class TaskPool {
|
||||
public:
|
||||
typedef std::unique_ptr<_ImplBase> __task;
|
||||
typedef _ImplBase* task_id_t;
|
||||
|
||||
|
||||
typedef std::chrono::steady_clock::time_point __time_point;
|
||||
|
||||
template<class R>
|
||||
class timer_task_t {
|
||||
public:
|
||||
task_id_t task_id;
|
||||
std::future<R> future;
|
||||
|
||||
timer_task_t(task_id_t task_id, std::future<R> &future) : task_id { task_id }, future { std::move(future) } {}
|
||||
};
|
||||
protected:
|
||||
std::deque<__task> _tasks;
|
||||
std::vector<std::pair<__time_point, __task>> _timer_tasks;
|
||||
std::mutex _task_mutex;
|
||||
|
||||
public:
|
||||
TaskPool() = default;
|
||||
TaskPool(TaskPool &&other) noexcept : _tasks { std::move(other._tasks) }, _timer_tasks { std::move(other._timer_tasks) } {}
|
||||
|
||||
TaskPool &operator=(TaskPool &&other) noexcept {
|
||||
std::swap(_tasks, other._tasks);
|
||||
std::swap(_timer_tasks, other._timer_tasks);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class Function, class... Args>
|
||||
auto push(Function && newTask, Args &&... args) {
|
||||
static_assert(std::is_invocable_v<Function, Args&&...>, "arguments don't match the function");
|
||||
|
||||
using __return = std::invoke_result_t<Function, Args &&...>;
|
||||
using task_t = std::packaged_task<__return()>;
|
||||
|
||||
auto bind = [task = std::forward<Function>(newTask), tuple_args = std::make_tuple(std::forward<Args>(args)...)]() mutable {
|
||||
return std::apply(task, std::move(tuple_args));
|
||||
};
|
||||
|
||||
task_t task(std::move(bind));
|
||||
|
||||
auto future = task.get_future();
|
||||
|
||||
std::lock_guard<std::mutex> lg(_task_mutex);
|
||||
_tasks.emplace_back(toRunnable(std::move(task)));
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
void pushDelayed(std::pair<__time_point, __task> &&task) {
|
||||
std::lock_guard lg(_task_mutex);
|
||||
|
||||
auto it = _timer_tasks.cbegin();
|
||||
for(; it < _timer_tasks.cend(); ++it) {
|
||||
if(std::get<0>(*it) < task.first) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_timer_tasks.emplace(it, task.first, std::move(task.second));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an id to potentially delay the task
|
||||
*/
|
||||
template<class Function, class X, class Y, class... Args>
|
||||
auto pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&... args) {
|
||||
static_assert(std::is_invocable_v<Function, Args&&...>, "arguments don't match the function");
|
||||
|
||||
using __return = std::invoke_result_t<Function, Args &&...>;
|
||||
using task_t = std::packaged_task<__return()>;
|
||||
|
||||
__time_point time_point = std::chrono::steady_clock::now() + duration;
|
||||
|
||||
auto bind = [task = std::forward<Function>(newTask), tuple_args = std::make_tuple(std::forward<Args>(args)...)]() mutable {
|
||||
return std::apply(task, std::move(tuple_args));
|
||||
};
|
||||
|
||||
task_t task(std::move(bind));
|
||||
|
||||
auto future = task.get_future();
|
||||
auto runnable = toRunnable(std::move(task));
|
||||
|
||||
task_id_t task_id = &*runnable;
|
||||
|
||||
pushDelayed(std::pair { time_point, std::move(runnable) });
|
||||
|
||||
return timer_task_t<__return> { task_id, future };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param duration The delay before executing the task
|
||||
*/
|
||||
template<class X, class Y>
|
||||
void delay(task_id_t task_id, std::chrono::duration<X, Y> duration) {
|
||||
std::lock_guard<std::mutex> lg(_task_mutex);
|
||||
|
||||
auto it = _timer_tasks.begin();
|
||||
for(; it < _timer_tasks.cend(); ++it) {
|
||||
const __task &task = std::get<1>(*it);
|
||||
|
||||
if(&*task == task_id) {
|
||||
std::get<0>(*it) = std::chrono::steady_clock::now() + duration;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(it == _timer_tasks.cend()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// smaller time goes to the back
|
||||
auto prev = it -1;
|
||||
while(it > _timer_tasks.cbegin()) {
|
||||
if(std::get<0>(*it) > std::get<0>(*prev)) {
|
||||
std::swap(*it, *prev);
|
||||
}
|
||||
|
||||
--prev; --it;
|
||||
}
|
||||
}
|
||||
|
||||
bool cancel(task_id_t task_id) {
|
||||
std::lock_guard lg(_task_mutex);
|
||||
|
||||
auto it = _timer_tasks.begin();
|
||||
for(; it < _timer_tasks.cend(); ++it) {
|
||||
const __task &task = std::get<1>(*it);
|
||||
|
||||
if(&*task == task_id) {
|
||||
_timer_tasks.erase(it);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<std::pair<__time_point, __task>> pop(task_id_t task_id) {
|
||||
std::lock_guard lg(_task_mutex);
|
||||
|
||||
auto pos = std::find_if(std::begin(_timer_tasks), std::end(_timer_tasks), [&task_id](const auto &t) { return t.second.get() == task_id; });
|
||||
|
||||
if(pos == std::end(_timer_tasks)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::move(*pos);
|
||||
}
|
||||
|
||||
std::optional<__task> pop() {
|
||||
std::lock_guard lg(_task_mutex);
|
||||
|
||||
if(!_tasks.empty()) {
|
||||
__task task = std::move(_tasks.front());
|
||||
_tasks.pop_front();
|
||||
return std::move(task);
|
||||
}
|
||||
|
||||
if(!_timer_tasks.empty() && std::get<0>(_timer_tasks.back()) <= std::chrono::steady_clock::now()) {
|
||||
__task task = std::move(std::get<1>(_timer_tasks.back()));
|
||||
_timer_tasks.pop_back();
|
||||
|
||||
return std::move(task);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool ready() {
|
||||
std::lock_guard<std::mutex> lg(_task_mutex);
|
||||
|
||||
return !_tasks.empty() || (!_timer_tasks.empty() && std::get<0>(_timer_tasks.back()) <= std::chrono::steady_clock::now());
|
||||
}
|
||||
|
||||
std::optional<__time_point> next() {
|
||||
std::lock_guard<std::mutex> lg(_task_mutex);
|
||||
|
||||
if(_timer_tasks.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::get<0>(_timer_tasks.back());
|
||||
}
|
||||
private:
|
||||
|
||||
template<class Function>
|
||||
std::unique_ptr<_ImplBase> toRunnable(Function &&f) {
|
||||
return std::make_unique<_Impl<Function>>(std::forward<Function&&>(f));
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
121
sunshine/thread_pool.h
Executable file
121
sunshine/thread_pool.h
Executable file
|
|
@ -0,0 +1,121 @@
|
|||
#ifndef KITTY_THREAD_POOL_H
|
||||
#define KITTY_THREAD_POOL_H
|
||||
|
||||
#include <thread>
|
||||
#include "task_pool.h"
|
||||
|
||||
namespace util {
|
||||
/*
|
||||
* Allow threads to execute unhindered
|
||||
* while keeping full controll over the threads.
|
||||
*/
|
||||
class ThreadPool : public TaskPool {
|
||||
public:
|
||||
typedef TaskPool::__task __task;
|
||||
|
||||
private:
|
||||
std::vector<std::thread> _thread;
|
||||
|
||||
std::condition_variable _cv;
|
||||
std::mutex _lock;
|
||||
|
||||
bool _continue;
|
||||
public:
|
||||
ThreadPool() : _continue { false } {}
|
||||
|
||||
explicit ThreadPool(int threads) : _thread(threads), _continue { true } {
|
||||
for (auto &t : _thread) {
|
||||
t = std::thread(&ThreadPool::_main, this);
|
||||
}
|
||||
}
|
||||
|
||||
~ThreadPool() noexcept {
|
||||
if (!_continue) return;
|
||||
|
||||
stop();
|
||||
join();
|
||||
}
|
||||
|
||||
template<class Function, class... Args>
|
||||
auto push(Function && newTask, Args &&... args) {
|
||||
std::lock_guard lg(_lock);
|
||||
auto future = TaskPool::push(std::forward<Function>(newTask), std::forward<Args>(args)...);
|
||||
|
||||
_cv.notify_one();
|
||||
return future;
|
||||
}
|
||||
|
||||
void pushDelayed(std::pair<__time_point, __task> &&task) {
|
||||
std::lock_guard lg(_lock);
|
||||
|
||||
TaskPool::pushDelayed(std::move(task));
|
||||
}
|
||||
|
||||
template<class Function, class X, class Y, class... Args>
|
||||
auto pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&... args) {
|
||||
std::lock_guard lg(_lock);
|
||||
auto future = TaskPool::pushDelayed(std::forward<Function>(newTask), duration, std::forward<Args>(args)...);
|
||||
|
||||
// Update all timers for wait_until
|
||||
_cv.notify_all();
|
||||
return future;
|
||||
}
|
||||
|
||||
void start(int threads) {
|
||||
_continue = true;
|
||||
|
||||
_thread.resize(threads);
|
||||
|
||||
for(auto &t : _thread) {
|
||||
t = std::thread(&ThreadPool::_main, this);
|
||||
}
|
||||
}
|
||||
|
||||
void stop() {
|
||||
std::lock_guard lg(_lock);
|
||||
|
||||
_continue = false;
|
||||
_cv.notify_all();
|
||||
}
|
||||
|
||||
void join() {
|
||||
for (auto & t : _thread) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
void _main() {
|
||||
while (_continue) {
|
||||
if(auto task = this->pop()) {
|
||||
(*task)->run();
|
||||
}
|
||||
else {
|
||||
std::unique_lock uniq_lock(_lock);
|
||||
|
||||
if(ready()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!_continue) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(auto tp = next()) {
|
||||
_cv.wait_until(uniq_lock, *tp);
|
||||
}
|
||||
else {
|
||||
_cv.wait(uniq_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute remaining tasks
|
||||
while(auto task = this->pop()) {
|
||||
(*task)->run();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue