Merge pull request #5 from cynnyx/master

More and more requests, handles, and so on...
This commit is contained in:
Michele Caini 2016-07-14 23:39:18 +02:00 committed by GitHub
commit 1bb837830a
19 changed files with 238 additions and 128 deletions

View File

@ -3,8 +3,8 @@
**Please, note that `uvw` is still a work in progress**.
`uvw` is a header-only, event based, tiny and easy to use C++ wrapper for *libuv*.
The basic idea is to hide completely the *C-ish* interace of *libuv* behind a graceful C++ API. Currently, no `uv_*_t` data structure is actually exposed by the library.
Note that `uvw` stays true to the API of *libuv* and it doesn't add anything to its interface. For the same reason, users of the library must follow the same rules who are used to follow with *libuv*.
The basic idea is to hide completely the *C-ish* interface of *libuv* behind a graceful C++ API. Currently, no `uv_*_t` data structure is actually exposed by the library.
Note that `uvw` stays true to the API of *libuv* and it doesn't add anything to its interface. For the same reasons, users of the library must follow the same rules who are used to follow with *libuv*.
As an example, a *handle* should be initialized before any other operation and closed once it is no longer in use.
## Code Example
@ -14,10 +14,10 @@ As an example, a *handle* should be initialized before any other operation and c
#include <memory>
void listen(uvw::Loop &loop) {
std::shared_ptr<uvw::Tcp> tcp = loop.handle<uvw::Tcp>();
std::shared_ptr<uvw::Tcp> tcp = loop.resource<uvw::Tcp>();
tcp->once<uvw::ListenEvent>([](const uvw::ListenEvent &event, uvw::Tcp &srv) mutable {
std::shared_ptr<uvw::Tcp> client = srv.loop().handle<uvw::Tcp>();
std::shared_ptr<uvw::Tcp> client = srv.loop().resource<uvw::Tcp>();
client->on<uvw::CloseEvent>([ptr = srv.shared_from_this()](const uvw::CloseEvent &, uvw::Tcp &) mutable { ptr->close(); });
client->on<uvw::EndEvent>([](const uvw::EndEvent &, uvw::Tcp &client) { client.close(); });
@ -31,7 +31,7 @@ void listen(uvw::Loop &loop) {
}
void conn(uvw::Loop &loop) {
auto tcp = loop.handle<uvw::Tcp>();
auto tcp = loop.resource<uvw::Tcp>();
tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::Tcp &) { /* handle errors */ });
@ -90,7 +90,7 @@ To navigate it with your favorite browser:
There is only one rule when using `uvw`: always initialize the handles and close them.
Handles keep themselves alive until one closes them. Because of that, leaks are possible if users simply forget about a handle.
To be honest, initialization is performed under the hood and can be even passed over, as far as resources are created using the `Loop::handle` member method.
To be honest, initialization is performed under the hood and can be even passed over, as far as resources are created using the `Loop::resource` member method.
Thus the rule quickly becomes *always close your handles*. It's simple as calling the `close` member method on them.
The first thing to do to use `uvw` is to create a loop. In case the default one is enough, it's easy as doing this:
@ -102,9 +102,9 @@ Loops can be run using the `run`, `runOnce` and `runWait` member methods. Please
In order to create a handle and to bind it to the given loop, just do the following:
auto tcp = loop.handle<uvw::Tcp>();
auto tcp = loop.resource<uvw::Tcp>();
A tcp handle will be created and initialized, thus a shared pointer to that handle will be returned.
A tcp handle will be created and initialized, thus a shared pointer to the handle will be returned.
Users should check if pointers have been correctly initialized: in case of errors, they won't be.
Another way to create a handle is:
@ -113,7 +113,7 @@ Another way to create a handle is:
Pretty annoying indeed. Using a loop is the recommended approach.
Once a handle has been created, it will keep itself alive until one invoke the `close` member method on it.
Once a handle has been created, it will keep itself alive until one invokes the `close` member method on it.
To know what are the handles that are still alive and bound to a given loop, just do the following:
loop.walk([](uvw::BaseHandle &){ /* application code here */ });
@ -151,12 +151,12 @@ The code below shows how to create a simple tcp server using `uvw`:
```
auto loop = uvw::Loop::getDefault();
auto tcp = loop.handle<uvw::Tcp>();
auto tcp = loop.resource<uvw::Tcp>();
tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::Tcp &srv) { /* something went wrong */ });
tcp->on<uvw::ListenEvent>([](const uvw::ListenEvent &event, uvw::Tcp &srv) mutable {
std::shared_ptr<uvw::Tcp> client = srv.loop().handle<uvw::Tcp>();
std::shared_ptr<uvw::Tcp> client = srv.loop().resource<uvw::Tcp>();
client->once<uvw::EndEvent>([](const uvw::EndEvent &, uvw::Tcp &client) { client.close(); });
client->on<uvw::DataEvent>([](const uvw::DataEvent &, uvw::Tcp &) { /* data received */ });
srv.accept(*client);

View File

@ -1,15 +1,12 @@
#include "uvw/async.hpp"
#include "uvw/check.hpp"
#include "uvw/emitter.hpp"
#include "uvw/event.hpp"
#include "uvw/handle.hpp"
#include "uvw/idle.hpp"
#include "uvw/loop.hpp"
#include "uvw/prepare.hpp"
#include "uvw/request.hpp"
#include "uvw/signal.hpp"
#include "uvw/stream.hpp"
#include "uvw/tcp.hpp"
#include "uvw/timer.hpp"
#include "uvw/tty.hpp"
#include "uvw/util.hpp"
#include "uvw/work.hpp"

View File

@ -19,7 +19,7 @@ class Async final: public Handle<Async> {
}
explicit Async(std::shared_ptr<Loop> ref)
: Handle{HandleType<uv_async_t>{}, std::move(ref)}
: Handle{ResourceType<uv_async_t>{}, std::move(ref)}
{ }
public:

View File

@ -19,7 +19,7 @@ class Check final: public Handle<Check> {
}
explicit Check(std::shared_ptr<Loop> ref)
: Handle{HandleType<uv_check_t>{}, std::move(ref)}
: Handle{ResourceType<uv_check_t>{}, std::move(ref)}
{ }
public:

View File

@ -30,6 +30,7 @@ struct Event: BaseEvent {
};
struct AfterWorkEvent: Event<AfterWorkEvent> { };
struct AsyncEvent: Event<AsyncEvent> { };
struct CheckEvent: Event<CheckEvent> { };
struct CloseEvent: Event<CloseEvent> { };
@ -82,6 +83,7 @@ private:
struct TimerEvent: Event<TimerEvent> { };
struct UninitializedEvent: Event<UninitializedEvent> { };
struct WorkEvent: Event<WorkEvent> { };
struct WriteEvent: Event<WriteEvent> { };

View File

@ -1,124 +1,73 @@
#pragma once
#include <cstddef>
#include <utility>
#include <memory>
#include <uv.h>
#include "emitter.hpp"
#include "loop.hpp"
#include "resource.hpp"
namespace uvw {
template<typename>
struct HandleType;
template<> struct HandleType<uv_async_t> { };
template<> struct HandleType<uv_check_t> { };
template<> struct HandleType<uv_idle_t> { };
template<> struct HandleType<uv_prepare_t> { };
template<> struct HandleType<uv_signal_t> { };
template<> struct HandleType<uv_tcp_t> { };
template<> struct HandleType<uv_timer_t> { };
template<> struct HandleType<uv_tty_t> { };
template<typename T>
class Handle
: public BaseHandle,
public Emitter<T>,
public std::enable_shared_from_this<T>
{
struct BaseWrapper {
virtual ~BaseWrapper() = default;
virtual void * get() const noexcept = 0;
};
template<typename U>
struct Wrapper: BaseWrapper {
Wrapper(): handle{std::make_unique<U>()} { }
void * get() const noexcept override { return handle.get(); }
private:
std::unique_ptr<U> handle;
};
class Handle: public BaseHandle, public Resource<T> {
static void closeCallback(uv_handle_t *handle) {
Handle<T> &ref = *(static_cast<T*>(handle->data));
ref.initialized = false;
auto ptr = ref.shared_from_this();
ptr->reset();
ref.publish(CloseEvent{});
ref.leak.reset();
}
protected:
template<typename U>
explicit Handle(HandleType<U>, std::shared_ptr<Loop> ref)
: Emitter<T>{},
std::enable_shared_from_this<T>{},
wrapper{std::make_unique<Wrapper<U>>()},
pLoop{std::move(ref)},
leak{nullptr},
initialized{false}
{
this->template get<uv_handle_t>()->data = static_cast<T*>(this);
}
uv_loop_t* parent() const noexcept { return pLoop->loop.get(); }
template<typename U>
U* get() const noexcept { return reinterpret_cast<U*>(wrapper->get()); }
using Resource<T>::Resource;
template<typename U, typename F, typename... Args>
bool initialize(F &&f, Args&&... args) {
bool ret = true;
if(!initialized) {
auto err = std::forward<F>(f)(parent(), get<U>(), std::forward<Args>(args)...);
if(!this->self()) {
auto err = std::forward<F>(f)(this->parent(), this->template get<U>(), std::forward<Args>(args)...);
if(err) {
this->publish(ErrorEvent{err});
ret = false;
} else {
leak = this->shared_from_this();
initialized = true;
this->leak();
}
}
return ret;
}
template<typename F, typename... Args>
auto invoke(F &&f, Args&&... args) {
auto err = std::forward<F>(f)(std::forward<Args>(args)...);
if(err) { Emitter<T>::publish(ErrorEvent{err}); }
return err;
return this->self();
}
public:
virtual ~Handle() {
static_assert(std::is_base_of<Handle<T>, T>::value, "!");
bool active() const noexcept override {
return !(uv_is_active(this->template get<uv_handle_t>()) == 0);
}
Loop& loop() const noexcept { return *pLoop; }
bool active() const noexcept override { return !(uv_is_active(get<uv_handle_t>()) == 0); }
bool closing() const noexcept override { return !(uv_is_closing(get<uv_handle_t>()) == 0); }
void reference() noexcept override { uv_ref(get<uv_handle_t>()); }
void unreference() noexcept override { uv_unref(get<uv_handle_t>()); }
bool referenced() const noexcept override { return !(uv_has_ref(get<uv_handle_t>()) == 0); }
bool closing() const noexcept override {
return !(uv_is_closing(this->template get<uv_handle_t>()) == 0);
}
void close() noexcept override {
if(!closing()) {
uv_close(get<uv_handle_t>(), &Handle<T>::closeCallback);
uv_close(this->template get<uv_handle_t>(), &Handle<T>::closeCallback);
}
}
private:
std::unique_ptr<BaseWrapper> wrapper;
std::shared_ptr<Loop> pLoop;
std::shared_ptr<void> leak;
bool initialized;
void reference() noexcept override {
uv_ref(this->template get<uv_handle_t>());
}
void unreference() noexcept override {
uv_unref(this->template get<uv_handle_t>());
}
bool referenced() const noexcept override {
return !(uv_has_ref(this->template get<uv_handle_t>()) == 0);
}
std::size_t size() const noexcept {
return uv_handle_size(this->template get<uv_handle_t>()->type);
}
};

View File

@ -19,7 +19,7 @@ class Idle final: public Handle<Idle> {
}
explicit Idle(std::shared_ptr<Loop> ref)
: Handle{HandleType<uv_idle_t>{}, std::move(ref)}
: Handle{ResourceType<uv_idle_t>{}, std::move(ref)}
{ }
public:

View File

@ -26,7 +26,7 @@ public:
class Loop final: public Emitter<Loop>, public std::enable_shared_from_this<Loop> {
template<typename>
friend class Handle;
friend class Resource;
using Deleter = std::function<void(uv_loop_t *)>;
@ -76,12 +76,19 @@ public:
}
template<typename R, typename... Args>
std::shared_ptr<R> handle(Args&&... args) {
std::enable_if_t<std::is_base_of<BaseHandle, R>::value, std::shared_ptr<R>>
resource(Args&&... args) {
auto ptr = R::create(shared_from_this(), std::forward<Args>(args)...);
ptr = ptr->init() ? ptr : nullptr;
return ptr;
}
template<typename R, typename... Args>
std::enable_if_t<not std::is_base_of<BaseHandle, R>::value, std::shared_ptr<R>>
resource(Args&&... args) {
return R::create(shared_from_this(), std::forward<Args>(args)...);
}
void close() noexcept {
auto err = uv_loop_close(loop.get());
if(err) { publish(ErrorEvent{err}); }

View File

@ -19,7 +19,7 @@ class Prepare final: public Handle<Prepare> {
}
explicit Prepare(std::shared_ptr<Loop> ref)
: Handle{HandleType<uv_prepare_t>{}, std::move(ref)}
: Handle{ResourceType<uv_prepare_t>{}, std::move(ref)}
{ }
public:

View File

@ -1,28 +1,28 @@
#pragma once
#include <utility>
#include <memory>
#include <uv.h>
#include "resource.hpp"
namespace uvw {
template<uv_req_type>
struct RequestType;
template<> struct RequestType<UV_CONNECT> { };
template<> struct RequestType<UV_WRITE> { };
template<> struct RequestType<UV_SHUTDOWN> { };
template<> struct RequestType<UV_UDP_SEND> { };
template<> struct RequestType<UV_FS> { };
template<> struct RequestType<UV_WORK> { };
template<> struct RequestType<UV_GETADDRINFO> { };
template<> struct RequestType<UV_GETNAMEINFO> { };
template<typename T>
struct Request: T {
// TODO room for a pointer to a memory pool and a better memory management
class Request: public Resource<T> {
protected:
using Resource<T>::Resource;
public:
void cancel() noexcept {
invoke(&uv_cancel, this->template get<uv_req_t>());
}
std::size_t size() const noexcept {
return uv_req_size(this->template get<uv_req_t>()->type);
}
};

80
src/uvw/resource.hpp Normal file
View File

@ -0,0 +1,80 @@
#pragma once
#include <utility>
#include <memory>
#include <uv.h>
#include "emitter.hpp"
#include "self.hpp"
#include "loop.hpp"
namespace uvw {
template<typename>
struct ResourceType;
template<> struct ResourceType<uv_async_t> { };
template<> struct ResourceType<uv_check_t> { };
template<> struct ResourceType<uv_idle_t> { };
template<> struct ResourceType<uv_prepare_t> { };
template<> struct ResourceType<uv_signal_t> { };
template<> struct ResourceType<uv_tcp_t> { };
template<> struct ResourceType<uv_timer_t> { };
template<> struct ResourceType<uv_tty_t> { };
template<> struct ResourceType<uv_work_t> { };
template<typename T>
class Resource: public Emitter<T>, public Self<T> {
struct BaseWrapper {
virtual ~BaseWrapper() = default;
virtual void * get() const noexcept = 0;
};
template<typename U>
struct Wrapper: BaseWrapper {
Wrapper(): resource{std::make_unique<U>()} { }
void * get() const noexcept override { return resource.get(); }
private:
std::unique_ptr<U> resource;
};
protected:
template<typename U>
explicit Resource(ResourceType<U>, std::shared_ptr<Loop> ref)
: Emitter<T>{},
Self<T>{},
wrapper{std::make_unique<Wrapper<U>>()},
pLoop{std::move(ref)}
{
this->template get<U>()->data = static_cast<T*>(this);
}
uv_loop_t* parent() const noexcept { return pLoop->loop.get(); }
template<typename U>
U* get() const noexcept { return reinterpret_cast<U*>(wrapper->get()); }
template<typename F, typename... Args>
auto invoke(F &&f, Args&&... args) {
auto err = std::forward<F>(f)(std::forward<Args>(args)...);
if(err) { Emitter<T>::publish(ErrorEvent{err}); }
return err;
}
public:
virtual ~Resource() {
static_assert(std::is_base_of<Resource<T>, T>::value, "!");
}
Loop& loop() const noexcept { return *pLoop; }
private:
std::unique_ptr<BaseWrapper> wrapper;
std::shared_ptr<Loop> pLoop;
};
}

21
src/uvw/self.hpp Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include <memory>
namespace uvw {
template<typename T>
struct Self: std::enable_shared_from_this<T> {
void leak() noexcept { ptr = this->shared_from_this(); }
void reset() noexcept { ptr.reset(); }
bool self() const noexcept { return static_cast<bool>(ptr); }
private:
std::shared_ptr<void> ptr{nullptr};
};
}

View File

@ -19,7 +19,7 @@ class Signal final: public Handle<Signal> {
}
explicit Signal(std::shared_ptr<Loop> ref)
: Handle{HandleType<uv_signal_t>{}, std::move(ref)}
: Handle{ResourceType<uv_signal_t>{}, std::move(ref)}
{ }
public:

View File

@ -37,6 +37,7 @@ class Stream: public Handle<T> {
}
static void writeCallback(uv_write_t *req, int status) {
// TODO migrate to Request (see request.hpp for further details)
T &ref = *(static_cast<T*>(req->handle->data));
if(status) { ref.publish(ErrorEvent{status}); }
else { ref.publish(WriteEvent{}); }
@ -44,6 +45,7 @@ class Stream: public Handle<T> {
}
static void shutdownCallback(uv_shutdown_t *req, int status) {
// TODO migrate to Request (see request.hpp for further details)
T &ref = *(static_cast<T*>(req->handle->data));
if(status) { ref.publish(ErrorEvent{status}); }
else { ref.publish(ShutdownEvent{}); }
@ -57,9 +59,9 @@ class Stream: public Handle<T> {
protected:
template<typename U>
Stream(HandleType<U> rt, std::shared_ptr<Loop> ref)
: Handle<T>{std::move(rt),
std::move(ref)},
Stream(ResourceType<U> rt, std::shared_ptr<Loop> ref)
: Handle<T>{std::move(rt), std::move(ref)},
// TODO migrate to Request (see request.hpp for further details)
sdown{std::make_unique<uv_shutdown_t>()}
{ }
@ -86,6 +88,7 @@ public:
void write(char *data, ssize_t length) {
uv_buf_t bufs[] = { uv_buf_init(data, length) };
// TODO migrate to Request (see request.hpp for further details)
uv_write_t *req = new uv_write_t;
auto err = uv_write(req, this->template get<uv_stream_t>(), bufs, 1, &Stream<T>::writeCallback);
@ -125,6 +128,7 @@ public:
}
private:
// TODO migrate to Request (see request.hpp for further details)
std::unique_ptr<uv_shutdown_t> sdown;
};

View File

@ -16,13 +16,15 @@ namespace uvw {
class Tcp final: public Stream<Tcp> {
static void connectCallback(uv_connect_t *req, int status) {
// TODO migrate to Request (see request.hpp for further details)
Tcp &tcp = *(static_cast<Tcp*>(req->handle->data));
if(status) { tcp.publish(ErrorEvent{status}); }
else { tcp.publish(ConnectEvent{}); }
}
explicit Tcp(std::shared_ptr<Loop> ref)
: Stream{HandleType<uv_tcp_t>{}, std::move(ref)},
// TODO migrate to Request (see request.hpp for further details)
: Stream{ResourceType<uv_tcp_t>{}, std::move(ref)},
conn{std::make_unique<uv_connect_t>()}
{ }
@ -112,6 +114,7 @@ public:
}
private:
// TODO migrate to Request (see request.hpp for further details)
std::unique_ptr<uv_connect_t> conn;
};

View File

@ -20,7 +20,7 @@ class Timer final: public Handle<Timer> {
}
explicit Timer(std::shared_ptr<Loop> ref)
: Handle{HandleType<uv_timer_t>{}, std::move(ref)}
: Handle{ResourceType<uv_timer_t>{}, std::move(ref)}
{ }
public:

View File

@ -31,7 +31,7 @@ class TTY final: public Stream<TTY> {
explicit TTY(std::shared_ptr<Loop> ref,
details::FileDescriptor<FD>,
bool readable)
: Stream{HandleType<uv_tty_t>{}, std::move(ref)},
: Stream{ResourceType<uv_tty_t>{}, std::move(ref)},
fd{FD},
rw{readable ? 1 : 0}
{ }

47
src/uvw/work.hpp Normal file
View File

@ -0,0 +1,47 @@
#pragma once
#include <utility>
#include <memory>
#include <uv.h>
#include "event.hpp"
#include "request.hpp"
#include "util.hpp"
namespace uvw {
class Work final: public Request<Work> {
static void workCallback(uv_work_t *req) {
Work &work = *(static_cast<Work*>(req->data));
work.publish(WorkEvent{});
}
static void afterWorkCallback(uv_work_t *req, int status) {
Work &work = *(static_cast<Work*>(req->data));
auto ptr = work.shared_from_this();
ptr->reset();
if(status) { work.publish(ErrorEvent{status}); }
else { work.publish(AfterWorkEvent{}); }
}
explicit Work(std::shared_ptr<Loop> ref)
: Request{ResourceType<uv_work_t>{}, std::move(ref)}
{ }
public:
template<typename... Args>
static std::shared_ptr<Work> create(Args&&... args) {
return std::shared_ptr<Work>{new Work{std::forward<Args>(args)...}};
}
void queue() noexcept {
if(0 == invoke(&uv_queue_work, parent(), get<uv_work_t>(), &workCallback, &afterWorkCallback)) {
leak();
}
}
};
}

View File

@ -6,7 +6,7 @@
void listen(uvw::Loop &loop) {
std::shared_ptr<uvw::Tcp> tcp = loop.handle<uvw::Tcp>();
std::shared_ptr<uvw::Tcp> tcp = loop.resource<uvw::Tcp>();
tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::Tcp &) {
std::cout << "error " << std::endl;
@ -15,7 +15,7 @@ void listen(uvw::Loop &loop) {
tcp->once<uvw::ListenEvent>([](const uvw::ListenEvent &event, uvw::Tcp &srv) mutable {
std::cout << "listen" << std::endl;
std::shared_ptr<uvw::Tcp> client = srv.loop().handle<uvw::Tcp>();
std::shared_ptr<uvw::Tcp> client = srv.loop().resource<uvw::Tcp>();
client->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::Tcp &) {
std::cout << "error " << std::endl;
@ -62,7 +62,7 @@ void listen(uvw::Loop &loop) {
void conn(uvw::Loop &loop) {
auto tcp = loop.handle<uvw::Tcp>();
auto tcp = loop.resource<uvw::Tcp>();
tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::Tcp &) {
std::cout << "error " << std::endl;