diff --git a/include/cppcore/config.h b/include/cppcore/config.h new file mode 100644 index 0000000..b980a69 --- /dev/null +++ b/include/cppcore/config.h @@ -0,0 +1,11 @@ +#pragma once + +#define cppcore_os_linux 1 +#define cppcore_os_windows 2 +#define cppcore_os_ios 3 + +#if defined(__linux__) + #define cppcore_os cppcore_os_linux +#else + #error "Unknown or unsupported operation system!" +#endif diff --git a/include/cppcore/conversion/string.h b/include/cppcore/conversion/string.h index 2f5f16a..eda2bf4 100644 --- a/include/cppcore/conversion/string.h +++ b/include/cppcore/conversion/string.h @@ -115,14 +115,29 @@ namespace cppcore * * @tparam T_predicate Type of predicate to execute for each element. * - * @param s String to split. + * @param str String to split. * @param seperator Seperator to split string at. * @param predicate Predicate to execute for each element. * * @return Value returned from the predicate. */ template - inline bool string_split(const std::string& s, char seperator, const T_predicate& predicate); + inline bool string_split(const std::string& str, char seperator, const T_predicate& predicate); + + /** + * @brief Split string at the passed seperator and calls the passed predicate for each element. + * + * @tparam T_splitter Type of predicate to evaluate character to split string at. + * @tparam T_predicate Type of predicate to execute for each element. + * + * @param str String to split. + * @param splitter Predicate to evaluate character to split string at. + * @param predicate Predicate to execute for each element. + * + * @return Value returned from the predicate. + */ + template + inline bool string_split(const std::string& str, const T_splitter& splitter, const T_predicate& predicate); /** * @brief Class to handle string conversions. diff --git a/include/cppcore/conversion/string.inl b/include/cppcore/conversion/string.inl index be9cafb..f52de43 100644 --- a/include/cppcore/conversion/string.inl +++ b/include/cppcore/conversion/string.inl @@ -44,6 +44,10 @@ namespace cppcore template inline bool string_split(const std::string& str, char seperator, T_predicate&& predicate) + { return string_split(str, [&](auto c) { return c == seperator; }, predicate); } + + template + bool string_split(const std::string& str, const T_splitter& splitter, const T_predicate& predicate) { auto* i = str.c_str(); auto* e = str.c_str() + str.size(); @@ -51,8 +55,7 @@ namespace cppcore while (i <= e) { if ( s != e - && ( *i == seperator - || *i == '\0')) + && (*i == '\0' || splitter(*i))) { std::string tmp(s, convert_cast(i - s)); if (!predicate(std::move(tmp))) diff --git a/include/cppcore/conversion/time.inl b/include/cppcore/conversion/time.inl index da480fc..1b3408e 100644 --- a/include/cppcore/conversion/time.inl +++ b/include/cppcore/conversion/time.inl @@ -21,7 +21,6 @@ namespace cppcore tv.tv_usec = std::chrono::duration_cast(d - sec).count(); return tv; } - }; template @@ -45,7 +44,6 @@ namespace cppcore ts.tv_nsec = std::chrono::duration_cast(d - sec).count(); return ts; } - }; template diff --git a/include/cppcore/misc/flags.h b/include/cppcore/misc/flags.h index b81309d..8d69771 100644 --- a/include/cppcore/misc/flags.h +++ b/include/cppcore/misc/flags.h @@ -185,7 +185,7 @@ namespace cppcore inline void clear(enum_type e); /** - * @brief REset all flags. + * @brief Reset all flags. */ inline void reset(); diff --git a/include/cppcore/platform.h b/include/cppcore/platform.h index 026c331..1efb931 100644 --- a/include/cppcore/platform.h +++ b/include/cppcore/platform.h @@ -1,4 +1,3 @@ #pragma once -#include "platform/fdset.h" -#include "platform/select.h" +#include "platform/file_events.h" diff --git a/include/cppcore/platform/fdset.inl b/include/cppcore/platform/fdset.inl deleted file mode 100644 index 909094e..0000000 --- a/include/cppcore/platform/fdset.inl +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "fdset.h" - -namespace cppcore -{ - - /* fdset */ - - static_assert( - sizeof(fd_set) == sizeof(fdset), - "Size of fd set implementation does not match."); - - bool fdset::is_set(int fd) const - { return FD_ISSET(fd, this); } - - void fdset::set(int fd) - { FD_SET(fd, this); } - - void fdset::clear(int fd) - { FD_CLR(fd, this); } - - void fdset::reset() - { FD_ZERO(this); } - -} diff --git a/include/cppcore/platform/file_events.h b/include/cppcore/platform/file_events.h new file mode 100644 index 0000000..83f6877 --- /dev/null +++ b/include/cppcore/platform/file_events.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "file_events/file_events.h" + +#include "file_events/file_events.inl" + +#if cppcore_os == cppcore_os_linux +#include "file_events/driver/poll.linux.h" +#include "file_events/driver/epoll.linux.h" +#include "file_events/driver/fdset.linux.h" +#include "file_events/driver/select.linux.h" + +#include "file_events/driver/poll.linux.inl" +#include "file_events/driver/epoll.linux.inl" +#include "file_events/driver/fdset.linux.inl" +#include "file_events/driver/select.linux.inl" +#endif diff --git a/include/cppcore/platform/file_events/driver/epoll.linux.h b/include/cppcore/platform/file_events/driver/epoll.linux.h new file mode 100644 index 0000000..2eab339 --- /dev/null +++ b/include/cppcore/platform/file_events/driver/epoll.linux.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include + +#include "../types.h" + +namespace cppcore { +namespace driver { + + struct epoll + { + public: + static constexpr int DEFAULT_SIZE = 30; + + struct storage + : public std::vector + { + public: + inline bool is_set( + const event_types& p_events) const; + + template + inline bool is_set( + const X_handle& p_handle, + const event_types& p_events) const; + }; + + using result_type = storage; + + private: + size_t _size; + int _handle; + result_type& _result; + + public: + /** + * @brief Constructor. + */ + inline epoll(result_type& p_result, int p_size = DEFAULT_SIZE); + + /** + * @brief Move constructor. + */ + inline epoll(epoll&&) = default; + + /** + * @brief Copy constructor. + */ + inline epoll(const epoll&) = delete; + + /** + * @brief Destructor. + */ + inline ~epoll(); + + public: /* file_events implementation */ + /** + * @brief Add or change a event listener for the passed file. + */ + template + inline void add( + const X_handle& p_handle, + const event_types& p_events); + + /** + * @brief Remove the event listener for the passed handle. + */ + template + inline void erase( + const X_handle& p_handle); + + /** + * @brief Wait for file events and return the result. + */ + inline void wait( + const event_timeout * p_timeout) const; + + /** + * @brief Wait for file events and call the passed callback for each event. + */ + template + inline void wait( + T_lambda&& p_lambda, + const event_timeout * p_timeout) const; + + private: + /** + * @brief Convert event flags to epoll event mask. + */ + static inline uint32_t make_event_mask(const event_types& p_events); + + /** + * @brief Convert event mask to epoll event flags. + */ + static inline event_types make_event_types(uint32_t p_mask); + }; + +} } diff --git a/include/cppcore/platform/file_events/driver/epoll.linux.inl b/include/cppcore/platform/file_events/driver/epoll.linux.inl new file mode 100644 index 0000000..c5c6b6b --- /dev/null +++ b/include/cppcore/platform/file_events/driver/epoll.linux.inl @@ -0,0 +1,169 @@ +#pragma once + +#include + +#include +#include + +#include "epoll.linux.h" + +namespace cppcore { +namespace driver { + + /* epoll::storage */ + + bool epoll::storage::is_set( + const event_types& p_events) const + { + auto mask = epoll::make_event_mask(p_events); + + for (auto& ev : *this) + { + if (ev.events & mask) + return true; + } + + return false; + } + + template + bool epoll::storage::is_set( + const X_handle& p_handle, + const event_types& p_events) const + { + using handle_helper_type = handle_helper; + + auto fd = handle_helper_type::get_raw_handle(p_handle); + auto mask = epoll::make_event_mask(p_events); + + for (auto& ev : *this) + { + if ( ev.data.fd == fd + && (ev.events & mask)) + return true; + } + + return false; + } + + /* epoll */ + + epoll::epoll(result_type& p_result, int p_size) + : _size (static_cast(p_size > 0 ? p_size : DEFAULT_SIZE)) + , _handle (-1) + , _result (p_result) + { + _handle = epoll_create(static_cast(_size)); + if (_handle < 0) + throw error_exception("Unable to create 'epoll' instance", errno); + } + + epoll::~epoll() + { + ::close(_handle); + } + + template + void epoll::add( + const X_handle& p_handle, + const event_types& p_events) + { + using handle_helper_type = handle_helper; + + auto fd = handle_helper_type::get_raw_handle(p_handle); + + struct epoll_event ev; + ev.events = make_event_mask(p_events); + ev.data.fd = fd; + + if (epoll_ctl(_handle, EPOLL_CTL_ADD, fd, &ev) == 0) + return; + + auto err = errno; + if (err != EEXIST) + throw error_exception("Error while adding file listener to 'epoll' instance", err); + + if (epoll_ctl(_handle, EPOLL_CTL_MOD, fd, &ev) != 0) + throw error_exception("Error while modifying file listener of 'epoll' instance", errno); + } + + template + void epoll::erase(const X_handle& p_handle) + { + using handle_helper_type = handle_helper; + + auto fd = handle_helper_type::get_raw_handle(p_handle); + + struct epoll_event ev; + ev.events = 0; + ev.data.ptr = nullptr; + + if (epoll_ctl(_handle, EPOLL_CTL_DEL, fd, &ev) != 0) + throw error_exception("Error while removing file listener from 'epoll' instance", errno); + } + + void epoll::wait(const event_timeout * p_timeout) const + { + int timeout = 0; + if (p_timeout) + { + auto now = std::chrono::steady_clock::now(); + if (now < *p_timeout) + { + auto diff = *p_timeout - now; + timeout = std::chrono::duration_cast>(diff).count(); + } + } + + _result.resize(_size); + int cnt = epoll_pwait( + _handle, + _result.data(), + static_cast(_result.size()), + p_timeout + ? timeout + : -1, + nullptr); + + if (cnt < 0) + { + auto err = errno; + if (err != EINTR) + throw cppcore::error_exception("Error while fetching file events with 'epoll'", err); + } + + _result.resize(static_cast(cnt)); + } + + template + void epoll::wait(T_lambda&& p_lambda, const event_timeout * p_timeout) const + { + wait(p_timeout); + + for (auto& ev : _result) + p_lambda(ev.data.fd, make_event_types(ev.events)); + } + + uint32_t epoll::make_event_mask(const event_types& p_events) + { + uint32_t ret = 0; + + if (p_events.is_set(event_type::read)) ret |= EPOLLRDNORM | EPOLLRDBAND | EPOLLIN | EPOLLHUP | EPOLLERR; + if (p_events.is_set(event_type::write)) ret |= EPOLLWRBAND | EPOLLWRNORM | EPOLLOUT | EPOLLERR; + if (p_events.is_set(event_type::except)) ret |= EPOLLPRI; + + return ret; + } + + event_types epoll::make_event_types(uint32_t p_mask) + { + event_types ret; + + if (p_mask & (EPOLLRDNORM | EPOLLRDBAND | EPOLLIN | EPOLLHUP | EPOLLERR)) ret.set(event_type::read); + if (p_mask & (EPOLLWRBAND | EPOLLWRNORM | EPOLLOUT | EPOLLERR)) ret.set(event_type::write); + if (p_mask & (EPOLLPRI)) ret.set(event_type::except); + + return ret; + } + +} } diff --git a/include/cppcore/platform/fdset.h b/include/cppcore/platform/file_events/driver/fdset.linux.h similarity index 65% rename from include/cppcore/platform/fdset.h rename to include/cppcore/platform/file_events/driver/fdset.linux.h index 1e82ed7..1338251 100644 --- a/include/cppcore/platform/fdset.h +++ b/include/cppcore/platform/file_events/driver/fdset.linux.h @@ -2,13 +2,28 @@ #include -namespace cppcore -{ +namespace cppcore { +namespace driver { - struct fdset + struct fdset final : public fd_set { public: + /** + * @brief Constructor. + */ + inline fdset(); + + /** + * @brief Check if any file descriptor is set in this set. + * + * @param[in] max_fd Largest file descriptor stored. + * + * @retval true If any file descriptor is set. + * @retval false If no file descriptor is set. + */ + inline bool is_any(int max_fd) const; + /** * @brief Check if the passed file descriptor is set in this set. * @@ -39,6 +54,4 @@ namespace cppcore inline void reset(); }; -} - -#include "fdset.inl" +} } diff --git a/include/cppcore/platform/file_events/driver/fdset.linux.inl b/include/cppcore/platform/file_events/driver/fdset.linux.inl new file mode 100644 index 0000000..3325802 --- /dev/null +++ b/include/cppcore/platform/file_events/driver/fdset.linux.inl @@ -0,0 +1,65 @@ +#pragma once + +#include + +#include "fdset.linux.h" + +namespace cppcore { +namespace driver { + + /* fdset */ + + static_assert( + sizeof(fd_set) == sizeof(fdset), + "Size of fd set implementation does not match."); + + fdset::fdset() + { + FD_ZERO(this); + } + + bool fdset::is_any(int max_fd) const + { + static constexpr int field_count = sizeof(fds_bits) / sizeof(fds_bits[0]); + + if (max_fd >= FD_SETSIZE) + throw exception("Size of fd set exceeded!"); + + if (max_fd < 0) + return false; + + auto cnt = (max_fd / field_count) + 1; + for (int i = 0; i < cnt; ++i) + { + if (fds_bits[i]) + return true; + } + + return false; + } + + bool fdset::is_set(int fd) const + { + if (fd >= FD_SETSIZE) + throw exception("Size of fd set exceeded!"); + return FD_ISSET(fd, this); + } + + void fdset::set(int fd) + { + if (fd >= FD_SETSIZE) + throw exception("Size of fd set exceeded!"); + FD_SET(fd, this); + } + + void fdset::clear(int fd) + { + if (fd >= FD_SETSIZE) + throw exception("Size of fd set exceeded!"); + FD_CLR(fd, this); + } + + void fdset::reset() + { FD_ZERO(this); } + +} } diff --git a/include/cppcore/platform/file_events/driver/poll.linux.h b/include/cppcore/platform/file_events/driver/poll.linux.h new file mode 100644 index 0000000..7fdd7ae --- /dev/null +++ b/include/cppcore/platform/file_events/driver/poll.linux.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include + +#include "../types.h" + +namespace cppcore { +namespace driver { + + struct poll + { + public: + struct storage + : public std::vector + { + public: + friend struct poll; + + private: + bool _cleared { true }; + + public: + inline bool is_set( + const event_types& p_events) const; + + template + inline bool is_set( + const X_handle& p_handle, + const event_types& p_events) const; + + inline void clear(); + + public: + template + static inline auto find( + X_storage& p_storage, + int p_handle); + }; + + using result_type = storage; + + private: + result_type& _result; + + public: + /** + * @brief Constructor. + */ + inline poll(result_type& p_result); + + public: /* file_events implementation */ + /** + * @brief Add or change a event listener for the passed file. + */ + template + inline void add( + const X_handle& p_handle, + const event_types& p_events); + + /** + * @brief Remove the event listener for the passed handle. + */ + template + inline void erase( + const X_handle& p_handle); + + /** + * @brief Wait for file events and return the result. + */ + inline int wait( + const event_timeout * p_timeout) const; + + /** + * @brief Wait for file events and call the passed callback for each event. + */ + template + inline void wait( + T_lambda&& p_lambda, + const event_timeout * p_timeout) const; + + private: + /** + * @brief Convert event flags to poll event mask. + */ + static inline short int make_event_mask(const event_types& p_events); + + /** + * @brief Convert event mask to poll event flags. + */ + static inline event_types make_event_types(short int p_mask); + }; + +} } diff --git a/include/cppcore/platform/file_events/driver/poll.linux.inl b/include/cppcore/platform/file_events/driver/poll.linux.inl new file mode 100644 index 0000000..9b565e9 --- /dev/null +++ b/include/cppcore/platform/file_events/driver/poll.linux.inl @@ -0,0 +1,192 @@ +#pragma once + +#include + +#include +#include + +#include "poll.linux.h" + +namespace cppcore { +namespace driver { + + /* poll::storage */ + + bool poll::storage::is_set( + const event_types& p_events) const + { + auto mask = poll::make_event_mask(p_events); + + for (auto& v : *this) + { + if (v.revents & mask) + return true; + } + + return false; + } + + template + bool poll::storage::is_set( + const X_handle& p_handle, + const event_types& p_events) const + { + using handle_helper_type = handle_helper; + + auto fd = handle_helper_type::get_raw_handle(p_handle); + auto mask = poll::make_event_mask(p_events); + + auto it = find(*this, fd); + return (it != end()) + && (it->fd == fd) + && (it->revents & mask); + } + + void poll::storage::clear() + { + if (!_cleared) + { + _cleared = true; + for (auto& v : *this) + v.revents = 0; + } + } + + template + auto poll::storage::find( + X_storage& p_storage, + int p_handle) + { + struct less_comparer + { + constexpr bool operator()(const pollfd& lhv, const pollfd& rhv) const + { return lhv.fd < rhv.fd; } + }; + + pollfd value; + value.fd = p_handle; + + return std::lower_bound(p_storage.begin(), p_storage.end(), value, less_comparer { }); + } + + /* poll */ + + poll::poll(result_type& p_result) + : _result(p_result) + { } + + template + void poll::add( + const X_handle& p_handle, + const event_types& p_events) + { + using handle_helper_type = handle_helper; + + pollfd value; + value.fd = handle_helper_type::get_raw_handle(p_handle); + value.events = make_event_mask(p_events); + value.revents = 0; + + auto it = storage::find(_result, value.fd); + auto found = (it != _result.end()) && (it->fd == value.fd); + + if (found) *it = value; + else _result.insert(it, value); + } + + template + void poll::erase(const X_handle& p_handle) + { + using handle_helper_type = handle_helper; + + pollfd value; + value.fd = handle_helper_type::get_raw_handle(p_handle); + value.events = 0; + value.revents = 0; + + auto it = storage::find(_result, value.fd); + auto found = (it != _result.end()) && (it->fd == value.fd); + + if (found) + _result.erase(it); + } + + int poll::wait(const event_timeout * p_timeout) const + { + struct timespec timeout { 0, 0 }; + if (p_timeout) + { + auto now = std::chrono::steady_clock::now(); + if (now < *p_timeout) + { + auto diff = *p_timeout - now; + timeout = cppcore::duration_cast(diff); + } + } + + _result.clear(); + + int cnt = ::ppoll( + _result.data(), + _result.size(), + p_timeout + ? &timeout + : nullptr, + nullptr); + + if (cnt < 0) + { + auto err = errno; + if (err != EINTR) + throw cppcore::error_exception("Error while fetching file events with 'poll'", err); + } + + return cnt; + } + + template + void poll::wait(T_lambda&& p_lambda, const event_timeout * p_timeout) const + { + int cnt = wait(p_timeout); + + for (auto& v : _result) + { + if (v.revents) + { + auto ev = make_event_types(v.revents); + v.revents = 0; + + if (ev) + p_lambda(v.fd, ev); + + if (--cnt <= 0) + return; + } + } + + _result._cleared = true; + } + + short int poll::make_event_mask(const event_types& p_events) + { + short int ret = 0; + + if (p_events.is_set(event_type::read)) ret |= POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR; + if (p_events.is_set(event_type::write)) ret |= POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR; + if (p_events.is_set(event_type::except)) ret |= POLLPRI; + + return ret; + } + + event_types poll::make_event_types(short int p_mask) + { + event_types ret; + + if (p_mask & (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR)) ret.set(event_type::read); + if (p_mask & (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR)) ret.set(event_type::write); + if (p_mask & (POLLPRI)) ret.set(event_type::except); + + return ret; + } + +} } diff --git a/include/cppcore/platform/file_events/driver/select.linux.h b/include/cppcore/platform/file_events/driver/select.linux.h new file mode 100644 index 0000000..65cfdd1 --- /dev/null +++ b/include/cppcore/platform/file_events/driver/select.linux.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +#include "../types.h" +#include "fdset.linux.h" + +namespace cppcore { +namespace driver { + + struct select + { + public: + struct storage + { + int max_fd { -1 }; + fdset read; + fdset write; + fdset except; + + inline bool is_set( + const event_types& p_events) const; + + template + inline bool is_set( + const X_handle& p_handle, + const event_types& p_events) const; + }; + + using result_type = storage; + + private: + storage _storage; + result_type& _result; + + public: + /** + * @brief Constructor. + */ + inline select(result_type& p_result); + + public: /* file_events implementation */ + /** + * @brief Add or change a event listener for the passed file. + */ + template + inline void add( + const X_handle& p_handle, + const event_types& p_events); + + /** + * @brief Remove the event listener for the passed handle. + */ + template + inline void erase( + const X_handle& p_handle); + + /** + * @brief Wait for file events and return the result. + */ + inline void wait( + const event_timeout * p_timeout) const; + + /** + * @brief Wait for file events and call the passed callback for each event. + */ + template + inline void wait( + T_lambda&& p_lambda, + const event_timeout * p_timeout) const; + }; + +} } diff --git a/include/cppcore/platform/file_events/driver/select.linux.inl b/include/cppcore/platform/file_events/driver/select.linux.inl new file mode 100644 index 0000000..4ebafeb --- /dev/null +++ b/include/cppcore/platform/file_events/driver/select.linux.inl @@ -0,0 +1,135 @@ +#pragma once + +#include +#include + +#include "select.linux.h" + +namespace cppcore { +namespace driver { + + /* select::storage */ + + bool select::storage::is_set( + const event_types& p_events) const + { + return (p_events.is_set(event_type::read) && read.is_any(max_fd)) + || (p_events.is_set(event_type::write) && write.is_any(max_fd)) + || (p_events.is_set(event_type::except) && except.is_any(max_fd)); + } + + template + bool select::storage::is_set( + const X_handle& p_handle, + const event_types& p_events) const + { + using handle_helper_type = handle_helper; + + auto fd = handle_helper_type::get_raw_handle(p_handle); + + return (p_events.is_set(event_type::read) && read.is_set(fd)) + || (p_events.is_set(event_type::write) && write.is_set(fd)) + || (p_events.is_set(event_type::except) && except.is_set(fd)); + } + + /* select */ + + select::select(result_type& p_result) + : _storage () + , _result (p_result) + { } + + template + void select::add( + const X_handle& p_handle, + const event_types& p_events) + { + int fd = p_handle.handle(); + if (fd < 0) + throw exception("The passed file is not opened!"); + + if (p_events.is_set(event_type::read)) _storage.read.set(fd); + if (p_events.is_set(event_type::write)) _storage.write.set(fd); + if (p_events.is_set(event_type::except)) _storage.except.set(fd); + + if ( !_storage.read.is_set(fd) + && !_storage.write.is_set(fd) + && !_storage.except.is_set(fd)) + throw exception("No or unkown events passed!"); + + _storage.max_fd = std::max(fd, _storage.max_fd); + } + + template + void select::erase(const X_handle& p_handle) + { + int fd = p_handle.handle(); + if (fd < 0) + throw exception("The passed file is not opened!"); + + _storage.read.clear(fd); + _storage.write.clear(fd); + _storage.except.clear(fd); + + if (fd == _storage.max_fd) + { + while (_storage.max_fd >= 0 + && !_storage.read.is_set(_storage.max_fd) + && !_storage.write.is_set(_storage.max_fd) + && !_storage.except.is_set(_storage.max_fd)) + --_storage.max_fd; + } + } + + void select::wait(const event_timeout * p_timeout) const + { + struct timespec timeout { 0, 0 }; + if (p_timeout) + { + auto now = std::chrono::steady_clock::now(); + if (now < *p_timeout) + { + auto diff = *p_timeout - now; + timeout = cppcore::duration_cast(diff); + } + } + + _result = _storage; + + int cnt = ::pselect( + _result.max_fd + 1, + &_result.read, + &_result.write, + &_result.except, + p_timeout + ? &timeout + : nullptr, + nullptr); + + if (cnt < 0) + { + auto err = errno; + if (err != EINTR) + throw cppcore::error_exception("Error while fetching file events with 'select'", err); + } + } + + template + void select::wait(T_lambda&& p_lambda, const event_timeout * p_timeout) const + { + wait(p_timeout); + + for (auto fd = 0; fd <= _result.max_fd; ++fd) + { + event_types ev; + + if (_result.read.is_set(fd)) ev.set(event_type::read); + if (_result.write.is_set(fd)) ev.set(event_type::write); + if (_result.except.is_set(fd)) ev.set(event_type::except); + + if (ev) + p_lambda(fd, ev); + } + } + +} } diff --git a/include/cppcore/platform/file_events/file_events.h b/include/cppcore/platform/file_events/file_events.h new file mode 100644 index 0000000..97db7f3 --- /dev/null +++ b/include/cppcore/platform/file_events/file_events.h @@ -0,0 +1,91 @@ +#pragma once + +#include "types.h" + +namespace cppcore +{ + + template + struct file_events + { + public: + using driver_type = T_driver; + using result_driver_type = typename driver_type::result_type; + + struct result + { + public: + template + friend struct file_events; + + private: + result_driver_type _driver; + + public: + /** + * @brief Constructor. + */ + inline result() = default; + + /** + * @brief Check if any event has occured in any handle. + */ + bool is_set( + const event_types& p_events = event_types::all()) const; + + /** + * @brief Check if any event has occured in the passed handle. + */ + template + bool is_set( + const X_handle& p_handle, + const event_types& p_events) const; + }; + + private: + result _result; + driver_type _driver; + + public: + /** + * @brief Constructor. + */ + template + inline file_events(X_args&&... p_args); + + /** + * @brief Add or change a event listener for the passed file. + */ + template + inline void add( + const X_handle& p_handle, + const event_types& p_events); + + /** + * @brief Remove the event listener for the passed file. + */ + template + inline void erase( + const X_handle& p_handle); + + /** + * @brief Reset all event listeners. + */ + inline void reset(); + + /** + * @brief Wait for file events and return the result. + */ + inline const result& wait( + const event_timeout * p_timeout) const; + + /** + * @brief Wait for file events and call the passed callback for each event. + */ + template + inline void wait( + T_lambda&& p_lambda, + const event_timeout * p_timeout) const; + }; + +} diff --git a/include/cppcore/platform/file_events/file_events.inl b/include/cppcore/platform/file_events/file_events.inl new file mode 100644 index 0000000..bf96115 --- /dev/null +++ b/include/cppcore/platform/file_events/file_events.inl @@ -0,0 +1,69 @@ +#pragma once + +#include "file_events.h" + +namespace cppcore +{ + + /* file_events::result */ + + template + bool file_events::result::is_set( + const event_types& p_events) const + { + return _driver.is_set(p_events); + } + + template + template + bool file_events::result::is_set( + const X_handle& p_handle, + const event_types& p_events) const + { + return _driver.is_set(p_handle, p_events); + } + + /* file_events */ + + template + template + file_events::file_events(X_args&&... p_args) + : _driver(_result._driver, std::forward(p_args)...) + { } + + template + template + void file_events::add( + const X_handle& p_handle, + const event_types& p_events) + { _driver.add(p_handle, p_events); } + + template + template + void file_events::erase( + const X_handle& p_handle) + { _driver.erase(p_handle); } + + template + void file_events::reset() + { _driver = driver_type(); } + + template + typename file_events::result const & + file_events::wait( + const event_timeout * p_timeout) const + { + _driver.wait(p_timeout); + return _result; + } + + template + template + void file_events::wait( + T_lambda&& p_lambda, + const event_timeout * p_timeout) const + { + _driver.wait(std::forward(p_lambda), p_timeout); + } + +} diff --git a/include/cppcore/platform/file_events/raw_hande.h b/include/cppcore/platform/file_events/raw_hande.h new file mode 100644 index 0000000..e69de29 diff --git a/include/cppcore/platform/file_events/types.h b/include/cppcore/platform/file_events/types.h new file mode 100644 index 0000000..e5b3186 --- /dev/null +++ b/include/cppcore/platform/file_events/types.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include +#include + +namespace cppcore +{ + + enum class event_type + { + unknown = 0, + read, + write, + except, + }; + + using event_types = cppcore::shifted_flags; + + using event_clock = std::chrono::steady_clock; + using event_timeout = event_clock::time_point; + + /** + * @brief Helper class to implement different methods on generic handle types. + */ + template + struct handle_helper + { + public: + /** + * @brief Extract the raw handle from the passed handle. + */ + static inline auto get_raw_handle(const X_handle& p_handle) = delete; + }; + +} + +#if cppcore_os == cppcore_os_linux +#include "types.linux.inl" +#endif diff --git a/include/cppcore/platform/file_events/types.linux.inl b/include/cppcore/platform/file_events/types.linux.inl new file mode 100644 index 0000000..1af6686 --- /dev/null +++ b/include/cppcore/platform/file_events/types.linux.inl @@ -0,0 +1,32 @@ +#pragma once + +#include "types.h" + +namespace cppcore +{ + + template<> + struct handle_helper + { + public: + static inline int get_raw_handle(const int& p_handle) + { return p_handle; } + }; + + template + struct handle_helper< + X_handle, + std::enable_if_t< + std::is_same_v< + int, + decltype(std::declval().handle()) + > + > + > + { + public: + static inline int get_raw_handle(const X_handle& p_handle) + { return p_handle.handle(); } + }; + +} diff --git a/include/cppcore/platform/select.h b/include/cppcore/platform/select.h deleted file mode 100644 index 39f5719..0000000 --- a/include/cppcore/platform/select.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#include - -#include "fdset.h" - -namespace cppcore -{ - - /** - * @brief Wait for the file descriptors passed in the file descriptor sets until - * any file descriptor is read. - * - * @param[in] fd_count Number of file descriptor stored in the file descriptor sets. - * @param[in] read File descriptor set to wait for read operations. - * @param[in] write File descriptor set to wait for write operations. - * @param[in] except File descriptor set to wait for a exceptional state. - * - * @return Number of active file descriptors. - */ - inline uint select( - int fd_count, - fdset& read, - fdset& write, - fdset& except); - - /** - * @brief Wait for the file descriptors passed in the file descriptor sets until - * any file descriptor is read, or the timeout has occured. - * - * @param[in] fd_count Number of file descriptor stored in the file descriptor sets. - * @param[in] read File descriptor set to wait for read operations. - * @param[in] write File descriptor set to wait for write operations. - * @param[in] except File descriptor set to wait for a exceptional state. - * @param[in] timeout Relative timeout to wait for te file descriptors. - * May be updated to the time left after select returned. - * - * @return Number of active file descriptors. - */ - template - inline uint select( - int fd_count, - fdset& read, - fdset& write, - fdset& except, - std::chrono::duration& timeout); - - /** - * @brief Wait for the file descriptors passed in the file descriptor sets until - * any file descriptor is read, or the timeout has occured. - * - * @param[in] fd_count Number of file descriptor stored in the file descriptor sets. - * @param[in] read File descriptor set to wait for read operations. - * @param[in] write File descriptor set to wait for write operations. - * @param[in] except File descriptor set to wait for a exceptional state. - * @param[in] timeout Relative timeout to wait for te file descriptors. - * - * @return Number of active file descriptors. - */ - template - inline uint select( - int fd_count, - fdset& read, - fdset& write, - fdset& except, - const std::chrono::duration& timeout); - -} - -#include "select.inl" diff --git a/include/cppcore/platform/select.inl b/include/cppcore/platform/select.inl deleted file mode 100644 index c6086a9..0000000 --- a/include/cppcore/platform/select.inl +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include "select.h" -#include "../misc/exception.h" -#include "../conversion/time.h" - -namespace cppcore -{ - - namespace __impl - { - inline uint throw_select_error(int err) - { - if (err == EINTR) - return 0; - throw error_exception("Error while waiting for select operation", err); - } - } - - uint select( - int fd_count, - fdset& read, - fdset& write, - fdset& except) - { - auto ret = ::select(fd_count, &read, &write, &except, nullptr); - if (ret < 0) - return __impl::throw_select_error(errno); - return static_cast(ret); - } - - template - uint select( - int fd_count, - fdset& read, - fdset& write, - fdset& except, - std::chrono::duration& timeout) - { - using duration_type = std::chrono::duration; - - auto tmp = duration_cast(timeout); - auto ret = ::select(fd_count, &read, &write, &except, &tmp); - if (ret < 0) - return __impl::throw_select_error(errno); - timeout = duration_cast(tmp); - return static_cast(ret); - } - - template - uint select( - int fd_count, - fdset& read, - fdset& write, - fdset& except, - const std::chrono::duration& timeout) - { - auto tmp = duration_cast(timeout); - auto ret = ::select(fd_count, &read, &write, &except, &tmp); - if (ret < 0) - return __impl::throw_select_error(errno); - return static_cast(ret); - } - -} diff --git a/test/cppcore/platform/file_events_tests.cpp b/test/cppcore/platform/file_events_tests.cpp new file mode 100644 index 0000000..7c1eff3 --- /dev/null +++ b/test/cppcore/platform/file_events_tests.cpp @@ -0,0 +1,140 @@ +#include + +#include + +#include + +using namespace ::testing; +using namespace ::cppcore; + +struct pipe_handle +{ +private: + int _handle; + +public: + pipe_handle(int p_handle) + : _handle(p_handle) + { } + + inline ~pipe_handle() + { close(_handle); } + + int handle() const + { return _handle; } +}; + +template +void test_with_callback(X_args&&... p_args) +{ + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + + pipe_handle read_handle(fds[0]); + pipe_handle write_handle(fds[1]); + + file_events fe(std::forward(p_args)...); + + fe.add(read_handle, event_types::all()); + fe.add(write_handle, event_types::all()); + + auto timeout = event_clock::now(); + fe.wait( + [&](auto fd, auto events) { + EXPECT_EQ(fd, write_handle.handle()); + }, + &timeout); + + write(write_handle.handle(), "fuuu", 4); + + uint i = 3; + fe.wait( + [&](auto fd, auto events) { + if ( (i & 1) + && fd == read_handle.handle()) + { + i &= ~static_cast(1); + EXPECT_EQ(events, event_types(event_type::read)); + } + else if ( (i & 2) + && fd == write_handle.handle()) + { + i &= ~static_cast(2); + EXPECT_EQ(events, event_types(event_type::write)); + } + }, + &timeout); + + EXPECT_EQ(0, i); +} + +template +void test_with_result(X_args&&... p_args) +{ + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + + pipe_handle read_handle(fds[0]); + pipe_handle write_handle(fds[1]); + + file_events fe(std::forward(p_args)...); + + fe.add(read_handle, event_types::all()); + fe.add(write_handle, event_types::all()); + + auto timeout = event_clock::now(); + + auto ret = fe.wait(&timeout); + + EXPECT_TRUE (ret.is_set()); + EXPECT_FALSE(ret.is_set(event_type::read)); + EXPECT_TRUE (ret.is_set(event_type::write)); + EXPECT_FALSE(ret.is_set(event_type::except)); + EXPECT_FALSE(ret.is_set(read_handle, event_type::read)); + EXPECT_FALSE(ret.is_set(read_handle, event_type::write)); + EXPECT_FALSE(ret.is_set(read_handle, event_type::except)); + EXPECT_FALSE(ret.is_set(write_handle, event_type::read)); + EXPECT_TRUE (ret.is_set(write_handle, event_type::write)); + EXPECT_FALSE(ret.is_set(write_handle, event_type::except)); + + write(write_handle.handle(), "fuuu", 4); + + ret = fe.wait(&timeout); + + EXPECT_TRUE (ret.is_set()); + EXPECT_TRUE (ret.is_set(event_type::read)); + EXPECT_TRUE (ret.is_set(event_type::write)); + EXPECT_FALSE(ret.is_set(event_type::except)); + EXPECT_TRUE (ret.is_set(read_handle, event_type::read)); + EXPECT_FALSE(ret.is_set(read_handle, event_type::write)); + EXPECT_FALSE(ret.is_set(read_handle, event_type::except)); + EXPECT_FALSE(ret.is_set(write_handle, event_type::read)); + EXPECT_TRUE (ret.is_set(write_handle, event_type::write)); + EXPECT_FALSE(ret.is_set(write_handle, event_type::except)); +} + +TEST(file_events_tests, select_with_callback) + { test_with_callback(); } + +TEST(file_events_tests, select_with_result) + { test_with_result(); } + +TEST(file_events_tests, poll_with_callback) + { test_with_callback(); } + +TEST(file_events_tests, poll_with_result) + { test_with_result(); } + +TEST(file_events_tests, epoll_with_callback) + { test_with_callback(15); } + +TEST(file_events_tests, epoll_with_result) + { test_with_result(20 ); } + +TEST(file_events_tests, vector_resize_does_not_change_capacity) +{ + std::vector v; + v.resize(1000); + v.resize(1); + EXPECT_EQ(1000, v.capacity()); +}