From 6f1924a16894e8ce729e40033c2646e0ad495681 Mon Sep 17 00:00:00 2001 From: Sharon Fox Date: Sat, 9 Jan 2021 00:15:41 -0500 Subject: [PATCH 1/6] 3.0.0 beta changes Replace aegis::future with async++ Increased C++ requirement to 17 minimum Removed c++14 optional due to minimum change --- .travis.yml | 17 +- README.md | 9 +- include/aegis/async++.hpp | 4524 +++++++++++++++++ include/aegis/channel.hpp | 200 +- include/aegis/config.hpp | 91 +- include/aegis/core.hpp | 132 +- include/aegis/futures.hpp | 6 +- .../aegis/gateway/events/message_create.hpp | 7 +- .../aegis/gateway/events/message_update.hpp | 2 +- .../aegis/gateway/objects/impl/message.cpp | 28 +- include/aegis/gateway/objects/member.hpp | 6 +- include/aegis/gateway/objects/message.hpp | 29 +- include/aegis/guild.hpp | 253 +- include/aegis/impl/channel.cpp | 191 +- include/aegis/impl/core.cpp | 120 +- include/aegis/impl/guild.cpp | 236 +- include/aegis/impl/user.cpp | 26 +- include/aegis/optional.hpp | 1088 ---- include/aegis/permission.hpp | 3 +- include/aegis/ratelimit/ratelimit.hpp | 8 +- include/aegis/rest/rest_controller.hpp | 3 +- include/aegis/shards/impl/shard.cpp | 21 +- include/aegis/shards/impl/shard_mgr.cpp | 16 +- include/aegis/shards/shard.hpp | 3 +- include/aegis/snowflake.hpp | 2 - include/aegis/user.hpp | 21 +- 26 files changed, 5172 insertions(+), 1870 deletions(-) create mode 100644 include/aegis/async++.hpp delete mode 100644 include/aegis/optional.hpp diff --git a/.travis.yml b/.travis.yml index a061cf4c..dcb45cff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,9 @@ jobs: - MATRIX_EVAL="COMPILER=g++-10 && CXX=g++-10 && CXX_STANDARD=17" addons: apt: - packages: libcrypto++-dev g++-10 + packages: + - libcrypto++-dev + - g++-10 sources: &sources - ubuntu-toolchain-r-test # GCC 7 C++17 @@ -19,16 +21,9 @@ jobs: - MATRIX_EVAL="COMPILER=g++-7 && CXX=g++-7 && CXX_STANDARD=17" addons: apt: - packages: libcrypto++-dev g++-7 - sources: &sources - - ubuntu-toolchain-r-test - # GCC 5 C++14 - - compiler: gcc - env: - - MATRIX_EVAL="COMPILER=g++-5 && CXX=g++-5 && CXX_STANDARD=14" - addons: - apt: - packages: libcrypto++-dev g++-5 + packages: + - libcrypto++-dev + - g++-10 sources: &sources - ubuntu-toolchain-r-test diff --git a/README.md b/README.md index 09fcbcda..6c4fb267 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Aegis Library ======= -C++14/17 library for interfacing with the [Discord API](https://discordapp.com/developers/docs/intro) +C++17 library for interfacing with the [Discord API](https://discordapp.com/developers/docs/intro) # License # @@ -72,8 +72,7 @@ You can also add `-DBUILD_EXAMPLES=1` and it will build 3 examples within the ./ ## Compiler Options ## You can pass these flags to CMake to change what it builds
`-DBUILD_EXAMPLES=1` will build the examples
-`-DCMAKE_CXX_COMPILER=g++-7` will let you select the compiler used
-`-DCMAKE_CXX_STANDARD=17` will let you select C++14 (default) or C++17 +`-DCMAKE_CXX_COMPILER=g++-7` will let you select the compiler used ##### Library ##### You can pass these flags to your compiler (and/or CMake) to alter how the library is built
@@ -93,10 +92,10 @@ Options above, as well as: ## CMake misc ## If configured with CMake, it will create a pkg-config file that may help with compiling your own project.
It can be used as such:
-`g++ -std=c++14 myfile.cpp $(pkg-config --cflags --libs aegis)`
+`g++ -std=c++17 myfile.cpp $(pkg-config --cflags --libs aegis)`
to link to the shared object -`g++ -std=c++14 myfile.cpp $(pkg-config --cflags --libs aegis_static)`
+`g++ -std=c++17 myfile.cpp $(pkg-config --cflags --libs aegis_static)`
to link to the static object
You can also use this library within your own CMake project by adding `find_package(Aegis REQUIRED)` to your `CMakeLists.txt`. diff --git a/include/aegis/async++.hpp b/include/aegis/async++.hpp new file mode 100644 index 00000000..e225d259 --- /dev/null +++ b/include/aegis/async++.hpp @@ -0,0 +1,4524 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +#define ASYNCXX_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "config.hpp" + +#define LIBASYNC_EXPORT inline +#define LIBASYNC_EXPORT_EXCEPTION + +// Support compiling without exceptions +#ifndef LIBASYNC_NO_EXCEPTIONS +# ifdef __clang__ +# if !defined(__EXCEPTIONS) || !__has_feature(cxx_exceptions) +# define LIBASYNC_NO_EXCEPTIONS +# endif +# elif defined(__GNUC__) && !defined(__EXCEPTIONS) +# define LIBASYNC_NO_EXCEPTIONS +# elif defined(_MSC_VER) && defined(_HAS_EXCEPTIONS) && !_HAS_EXCEPTIONS +# define LIBASYNC_NO_EXCEPTIONS +# endif +#endif +#ifdef LIBASYNC_NO_EXCEPTIONS +# define LIBASYNC_THROW(...) std::abort() +# define LIBASYNC_RETHROW() do {} while (false) +# define LIBASYNC_RETHROW_EXCEPTION(except) std::terminate() +# define LIBASYNC_TRY if (true) +# define LIBASYNC_CATCH(...) else if (false) +#else +# define LIBASYNC_THROW(...) throw __VA_ARGS__ +# define LIBASYNC_RETHROW() throw +# define LIBASYNC_RETHROW_EXCEPTION(except) std::rethrow_exception(except) +# define LIBASYNC_TRY try +# define LIBASYNC_CATCH(...) catch (__VA_ARGS__) +#endif + +// Optional debug assertions. If exceptions are enabled then use those, but +// otherwise fall back to an assert message. +#ifndef NDEBUG +# ifndef LIBASYNC_NO_EXCEPTIONS +# define LIBASYNC_ASSERT(pred, except, message) ((pred) ? ((void)0) : throw except(message)) +# else +# define LIBASYNC_ASSERT(pred, except, message) ((pred) ? ((void)0) : assert(message)) +# endif +#else +# define LIBASYNC_ASSERT(pred, except, message) ((void)0) +#endif + +// Annotate move constructors and move assignment with noexcept to allow objects +// to be moved if they are in containers. Compilers which don't support noexcept +// will usually move regardless. +#if defined(__GNUC__) || _MSC_VER >= 1900 +# define LIBASYNC_NOEXCEPT noexcept +#else +# define LIBASYNC_NOEXCEPT throw() +#endif + +// Cacheline alignment to avoid false sharing between different threads +#define LIBASYNC_CACHELINE_SIZE 64 +#ifdef __GNUC__ +# define LIBASYNC_CACHELINE_ALIGN __attribute__((aligned(LIBASYNC_CACHELINE_SIZE))) +#elif defined(_MSC_VER) +# define LIBASYNC_CACHELINE_ALIGN __declspec(align(LIBASYNC_CACHELINE_SIZE)) +#else +# define LIBASYNC_CACHELINE_ALIGN alignas(LIBASYNC_CACHELINE_SIZE) +#endif + +// Force symbol visibility to hidden unless explicity exported +#ifndef LIBASYNC_STATIC +#if defined(__GNUC__) && !defined(_WIN32) +# pragma GCC visibility push(hidden) +#endif +#endif + +// Some forward declarations +namespace async { + +template +class task; +template +class shared_task; +template +class event_task; + +} // namespace async + +// Include sub-headers +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Pseudo-void type: it takes up no space but can be moved and copied +struct fake_void {}; +template +struct void_to_fake_void { + typedef T type; +}; +template<> +struct void_to_fake_void { + typedef fake_void type; +}; +template +T fake_void_to_void(T && x) +{ + return std::forward(x); +} +inline void fake_void_to_void(fake_void) {} + +// Check if type is a task type, used to detect task unwraping +template +struct is_task : public std::false_type {}; +template +struct is_task> : public std::true_type {}; +template +struct is_task> : public std::true_type {}; +template +struct is_task> : public std::true_type {}; +template +struct is_task> : public std::true_type {}; + +// Extract the result type of a task if T is a task, otherwise just return T +template +struct remove_task { + typedef T type; +}; +template +struct remove_task> { + typedef T type; +}; +template +struct remove_task> { + typedef T type; +}; +template +struct remove_task> { + typedef T type; +}; +template +struct remove_task> { + typedef T type; +}; + +// Check if a type is callable with the given arguments +typedef char one[1]; +typedef char two[2]; +template()(std::declval()...))> +two & is_callable_helper(int); +template +one & is_callable_helper(...); +template +struct is_callable; +template +struct is_callable : public std::integral_constant(0)) - 1> {}; + +// Wrapper to run a function object with an optional parameter: +// - void returns are turned into fake_void +// - fake_void parameter will invoke the function with no arguments +template()())>::value>::type> +decltype(std::declval()()) invoke_fake_void(Func && f) +{ + return std::forward(f)(); +} +template()())>::value>::type> +fake_void invoke_fake_void(Func && f) +{ + std::forward(f)(); + return fake_void(); +} +template +typename void_to_fake_void()(std::declval()))>::type invoke_fake_void(Func && f, Param && p) +{ + return detail::invoke_fake_void([&f, &p] {return std::forward(f)(std::forward(p)); }); +} +template +typename void_to_fake_void()())>::type invoke_fake_void(Func && f, fake_void) +{ + return detail::invoke_fake_void(std::forward(f)); +} + +// Various properties of a continuation function +template()())> +fake_void is_value_cont_helper(const Parent &, int, int); +template()(std::declval().get()))> +std::true_type is_value_cont_helper(const Parent &, int, int); +template()())> +std::true_type is_value_cont_helper(const task &, int, int); +template()())> +std::true_type is_value_cont_helper(const shared_task &, int, int); +template()(std::declval()))> +std::false_type is_value_cont_helper(const Parent &, int, ...); +template +void is_value_cont_helper(const Parent &, ...); +template +struct continuation_traits { + typedef typename std::decay::type decay_func; + typedef decltype(detail::is_value_cont_helper(std::declval(), 0, 0)) is_value_cont; + static_assert(!std::is_void::value, "Parameter type for continuation function is invalid for parent task type"); + typedef typename std::conditional::value, fake_void, typename std::conditional::value, typename void_to_fake_void().get())>::type, Parent>::type>::type param_type; + typedef decltype(detail::fake_void_to_void(detail::invoke_fake_void(std::declval(), std::declval()))) result_type; + typedef task::type> task_type; +}; + +} // namespace detail +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +namespace async { +namespace detail { + +// Allocate an aligned block of memory +LIBASYNC_EXPORT void * aligned_alloc(std::size_t size, std::size_t align); + +// Free an aligned block of memory +LIBASYNC_EXPORT void aligned_free(void * addr) LIBASYNC_NOEXCEPT; + +// Class representing an aligned array and its length +template::value> +class aligned_array { + std::size_t length; + T * ptr; + +public: + aligned_array() + : length(0), ptr(nullptr) {} + aligned_array(std::nullptr_t) + : length(0), ptr(nullptr) {} + explicit aligned_array(std::size_t length) + : length(length) + { + ptr = static_cast(aligned_alloc(length * sizeof(T), Align)); + std::size_t i; + LIBASYNC_TRY{ + for (i = 0; i < length; i++) + new(ptr + i) T; + } LIBASYNC_CATCH(...) { + for (std::size_t j = 0; j < i; j++) + ptr[i].~T(); + aligned_free(ptr); + LIBASYNC_RETHROW(); + } + } + aligned_array(aligned_array && other) LIBASYNC_NOEXCEPT + : length(other.length), ptr(other.ptr) + { + other.ptr = nullptr; + other.length = 0; + } + aligned_array & operator=(aligned_array && other) LIBASYNC_NOEXCEPT + { + aligned_array(std::move(*this)); + std::swap(ptr, other.ptr); + std::swap(length, other.length); + return *this; + } + aligned_array & operator=(std::nullptr_t) + { + return *this = aligned_array(); + } + ~aligned_array() + { + for (std::size_t i = 0; i < length; i++) + ptr[i].~T(); + aligned_free(ptr); + } + + T & operator[](std::size_t i) const + { + return ptr[i]; + } + std::size_t size() const + { + return length; + } + T * get() const + { + return ptr; + } + explicit operator bool() const + { + return ptr != nullptr; + } +}; + +} // namespace detail +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Default deleter which just uses the delete keyword +template +struct default_deleter { + static void do_delete(T * p) + { + delete p; + } +}; + +// Reference-counted object base class +template> +struct ref_count_base { + std::atomic ref_count; + + // By default the reference count is initialized to 1 + explicit ref_count_base(std::size_t count = 1) + : ref_count(count) {} + + void add_ref(std::size_t count = 1) + { + ref_count.fetch_add(count, std::memory_order_relaxed); + } + void remove_ref(std::size_t count = 1) + { + if (ref_count.fetch_sub(count, std::memory_order_release) == count) { + std::atomic_thread_fence(std::memory_order_acquire); + Deleter::do_delete(static_cast(this)); + } + } + void add_ref_unlocked() + { + ref_count.store(ref_count.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed); + } + bool is_unique_ref(std::memory_order order) + { + return ref_count.load(order) == 1; + } +}; + +// Pointer to reference counted object, based on boost::intrusive_ptr +template +class ref_count_ptr { + T * p; + +public: + // Note that this doesn't increment the reference count, instead it takes + // ownership of a pointer which you already own a reference to. + explicit ref_count_ptr(T * t) + : p(t) {} + + ref_count_ptr() + : p(nullptr) {} + ref_count_ptr(std::nullptr_t) + : p(nullptr) {} + ref_count_ptr(const ref_count_ptr & other) LIBASYNC_NOEXCEPT + : p(other.p) + { + if (p) + p->add_ref(); + } + ref_count_ptr(ref_count_ptr && other) LIBASYNC_NOEXCEPT + : p(other.p) + { + other.p = nullptr; + } + ref_count_ptr & operator=(std::nullptr_t) + { + if (p) + p->remove_ref(); + p = nullptr; + return *this; + } + ref_count_ptr & operator=(const ref_count_ptr & other) LIBASYNC_NOEXCEPT + { + if (p) { + p->remove_ref(); + p = nullptr; + } + p = other.p; + if (p) + p->add_ref(); + return *this; + } + ref_count_ptr & operator=(ref_count_ptr && other) LIBASYNC_NOEXCEPT + { + if (p) { + p->remove_ref(); + p = nullptr; + } + p = other.p; + other.p = nullptr; + return *this; + } + ~ref_count_ptr() + { + if (p) + p->remove_ref(); + } + + T & operator*() const + { + return *p; + } + T * operator->() const + { + return p; + } + T * get() const + { + return p; + } + T * release() + { + T * out = p; + p = nullptr; + return out; + } + + explicit operator bool() const + { + return p != nullptr; + } + friend bool operator==(const ref_count_ptr & a, const ref_count_ptr & b) + { + return a.p == b.p; + } + friend bool operator!=(const ref_count_ptr & a, const ref_count_ptr & b) + { + return a.p != b.p; + } + friend bool operator==(const ref_count_ptr & a, std::nullptr_t) + { + return a.p == nullptr; + } + friend bool operator!=(const ref_count_ptr & a, std::nullptr_t) + { + return a.p != nullptr; + } + friend bool operator==(std::nullptr_t, const ref_count_ptr & a) + { + return a.p == nullptr; + } + friend bool operator!=(std::nullptr_t, const ref_count_ptr & a) + { + return a.p != nullptr; + } +}; + +} // namespace detail +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { + +// Forward declarations +class task_run_handle; +class threadpool_scheduler; + +// Scheduler interface: +// A scheduler is any type that implements this function: +// void schedule(async::task_run_handle t); +// This function should result in t.run() being called at some future point. + +namespace detail { + +// Detect whether an object is a scheduler +template().schedule(std::declval()))> +two & is_scheduler_helper(int); +template +one & is_scheduler_helper(...); +template +struct is_scheduler : public std::integral_constant(0)) - 1> {}; + +// Singleton scheduler classes +class thread_scheduler_impl { +public: + LIBASYNC_EXPORT static void schedule(task_run_handle t); +}; +class inline_scheduler_impl { +public: + static void schedule(task_run_handle t); +}; + +// Reference counted pointer to task data +struct task_base; +typedef ref_count_ptr task_ptr; + +// Helper function to schedule a task using a scheduler +template +void schedule_task(Sched & sched, task_ptr t); + +// Wait for the given task to finish. This will call the wait handler currently +// active for this thread, which causes the thread to sleep by default. +LIBASYNC_EXPORT void wait_for_task(task_base * wait_task); + +// Forward-declaration for data used by threadpool_scheduler +struct threadpool_data; + +} // namespace detail + +// Run a task in the current thread as soon as it is scheduled +inline detail::inline_scheduler_impl & inline_scheduler() +{ + static detail::inline_scheduler_impl instance; + return instance; +} + +// Run a task in a separate thread. Note that this scheduler does not wait for +// threads to finish at process exit. You must ensure that all threads finish +// before ending the process. +inline detail::thread_scheduler_impl & thread_scheduler() +{ + static detail::thread_scheduler_impl instance; + return instance; +} + +// Built-in thread pool scheduler with a size that is configurable from the +// LIBASYNC_NUM_THREADS environment variable. If that variable does not exist +// then the number of CPUs in the system is used instead. +LIBASYNC_EXPORT threadpool_scheduler & default_threadpool_scheduler(); + +// Default scheduler that is used when one isn't specified. This defaults to +// default_threadpool_scheduler(), but can be overriden by defining +// LIBASYNC_CUSTOM_DEFAULT_SCHEDULER before including async++.h. Keep in mind +// that in that case async::default_scheduler should be declared before +// including async++.h. +#ifndef LIBASYNC_CUSTOM_DEFAULT_SCHEDULER +inline threadpool_scheduler & default_scheduler() +{ + return default_threadpool_scheduler(); +} +#endif + +// Scheduler that holds a list of tasks which can then be explicitly executed +// by a thread. Both adding and running tasks are thread-safe operations. +class fifo_scheduler { + struct internal_data; + std::unique_ptr impl; + +public: + LIBASYNC_EXPORT fifo_scheduler(); + LIBASYNC_EXPORT ~fifo_scheduler(); + + // Add a task to the queue + LIBASYNC_EXPORT void schedule(task_run_handle t); + + // Try running one task from the queue. Returns false if the queue was empty. + LIBASYNC_EXPORT bool try_run_one_task(); + + // Run all tasks in the queue + LIBASYNC_EXPORT void run_all_tasks(); +}; + +// Scheduler that runs tasks in a work-stealing thread pool of the given size. +// Note that destroying the thread pool before all tasks have completed may +// result in some tasks not being executed. +class threadpool_scheduler { + std::unique_ptr impl; + +public: + LIBASYNC_EXPORT threadpool_scheduler(threadpool_scheduler && other); + + // Create a thread pool with the given number of threads + LIBASYNC_EXPORT threadpool_scheduler(std::size_t num_threads); + + // Create a thread pool with the given number of threads. Call `prerun` + // function before execution loop and `postrun` after. + LIBASYNC_EXPORT threadpool_scheduler(std::size_t num_threads, + std::function && prerun_, + std::function && postrun_); + + // Destroy the thread pool, tasks that haven't been started are dropped + LIBASYNC_EXPORT ~threadpool_scheduler(); + + // Schedule a task to be run in the thread pool + LIBASYNC_EXPORT void schedule(task_run_handle t); +}; + +namespace detail { + +// Work-around for Intel compiler handling decltype poorly in function returns +typedef std::remove_reference::type default_scheduler_type; + +} // namespace detail +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Compress the flags in the low bits of the pointer if the structures are +// suitably aligned. Fall back to a separate flags variable otherwise. +template +class compressed_ptr { + void * ptr; + std::uintptr_t flags; + +public: + compressed_ptr() = default; + compressed_ptr(void * ptr, std::uintptr_t flags) + : ptr(ptr), flags(flags) {} + + template + T * get_ptr() const + { + return static_cast(ptr); + } + std::uintptr_t get_flags() const + { + return flags; + } + + void set_ptr(void * p) + { + ptr = p; + } + void set_flags(std::uintptr_t f) + { + flags = f; + } +}; +template +class compressed_ptr { + std::uintptr_t data; + +public: + compressed_ptr() = default; + compressed_ptr(void * ptr, std::uintptr_t flags) + : data(reinterpret_cast(ptr) | flags) {} + + template + T * get_ptr() const + { + return reinterpret_cast(data & ~Mask); + } + std::uintptr_t get_flags() const + { + return data & Mask; + } + + void set_ptr(void * p) + { + data = reinterpret_cast(p) | (data & Mask); + } + void set_flags(std::uintptr_t f) + { + data = (data & ~Mask) | f; + } +}; + +// Thread-safe vector of task_ptr which is optimized for the common case of +// only having a single continuation. +class continuation_vector { + // Heap-allocated data for the slow path + struct vector_data { + std::vector vector; + std::mutex lock; + }; + + // Flags to describe the state of the vector + enum flags { + // If set, no more changes are allowed to internal_data + is_locked = 1, + + // If set, the pointer is a vector_data* instead of a task_base*. If + // there are 0 or 1 elements in the vector, the task_base* form is used. + is_vector = 2 + }; + static const std::uintptr_t flags_mask = 3; + + // Embed the two bits in the data if they are suitably aligned. We only + // check the alignment of vector_data here because task_base isn't defined + // yet. Since we align task_base to LIBASYNC_CACHELINE_SIZE just use that. + typedef compressed_ptr::value & flags_mask) == 0> internal_data; + + // All changes to the internal data are atomic + std::atomic atomic_data; + +public: + // Start unlocked with zero elements in the fast path + continuation_vector() + { + // Workaround for a bug in certain versions of clang with libc++ + // error: no viable conversion from 'async::detail::compressed_ptr<3, true>' to '_Atomic(async::detail::compressed_ptr<3, true>)' + atomic_data.store(internal_data(nullptr, 0), std::memory_order_relaxed); + } + + // Free any left over data + ~continuation_vector() + { + // Converting to task_ptr instead of using remove_ref because task_base + // isn't defined yet at this point. + internal_data data = atomic_data.load(std::memory_order_relaxed); + if (data.get_flags() & flags::is_vector) { + // No need to lock the mutex, we are the only thread at this point + for (task_base * i : data.get_ptr()->vector) + (task_ptr(i)); + delete data.get_ptr(); + } + else { + // If the data is locked then the inline pointer is already gone + if (!(data.get_flags() & flags::is_locked)) + task_ptr tmp(data.get_ptr()); + } + } + + // Try adding an element to the vector. This fails and returns false if + // the vector has been locked. In that case t is not modified. + bool try_add(task_ptr && t) + { + // Cache to avoid re-allocating vector_data multiple times. This is + // automatically freed if it is not successfully saved to atomic_data. + std::unique_ptr vector; + + // Compare-exchange loop on atomic_data + internal_data data = atomic_data.load(std::memory_order_relaxed); + internal_data new_data; + do { + // Return immediately if the vector is locked + if (data.get_flags() & flags::is_locked) + return false; + + if (data.get_flags() & flags::is_vector) { + // Larger vectors use a mutex, so grab the lock + std::atomic_thread_fence(std::memory_order_acquire); + std::lock_guard locked(data.get_ptr()->lock); + + // We need to check again if the vector has been locked here + // to avoid a race condition with flush_and_lock + if (atomic_data.load(std::memory_order_relaxed).get_flags() & flags::is_locked) + return false; + + // Add the element to the vector and return + data.get_ptr()->vector.push_back(t.release()); + return true; + } + else { + if (data.get_ptr()) { + // Going from 1 to 2 elements, allocate a vector_data + if (!vector) + vector.reset(new vector_data{ {data.get_ptr(), t.get()}, {} }); + new_data = { vector.get(), flags::is_vector }; + } + else { + // Going from 0 to 1 elements + new_data = { t.get(), 0 }; + } + } + } while (!atomic_data.compare_exchange_weak(data, new_data, std::memory_order_release, std::memory_order_relaxed)); + + // If we reach this point then atomic_data was successfully changed. + // Since the pointers are now saved in the vector, release them from + // the smart pointers. + t.release(); + vector.release(); + return true; + } + + // Lock the vector and flush all elements through the given function + template void flush_and_lock(Func && func) + { + // Try to lock the vector using a compare-exchange loop + internal_data data = atomic_data.load(std::memory_order_relaxed); + internal_data new_data; + do { + new_data = data; + new_data.set_flags(data.get_flags() | flags::is_locked); + } while (!atomic_data.compare_exchange_weak(data, new_data, std::memory_order_acquire, std::memory_order_relaxed)); + + if (data.get_flags() & flags::is_vector) { + // If we are using vector_data, lock it and flush all elements + std::lock_guard locked(data.get_ptr()->lock); + for (auto i : data.get_ptr()->vector) + func(task_ptr(i)); + + // Clear the vector to save memory. Note that we don't actually free + // the vector_data here because other threads may still be using it. + // This isn't a very significant cost since multiple continuations + // are relatively rare. + data.get_ptr()->vector.clear(); + } + else { + // If there is an inline element, just pass it on + if (data.get_ptr()) + func(task_ptr(data.get_ptr())); + } + } +}; + +} // namespace detail +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Task states +enum class task_state : unsigned char { + pending, // Task has not completed yet + locked, // Task is locked (used by event_task to prevent double set) + unwrapped, // Task is waiting for an unwrapped task to finish + completed, // Task has finished execution and a result is available + canceled // Task has been canceled and an exception is available +}; + +// Determine whether a task is in a final state +inline bool is_finished(task_state s) +{ + return s == task_state::completed || s == task_state::canceled; +} + +// Virtual function table used to allow dynamic dispatch for task objects. +// While this is very similar to what a compiler would generate with virtual +// functions, this scheme was found to result in significantly smaller +// generated code size. +struct task_base_vtable { + // Destroy the function and result + void (*destroy)(task_base *) LIBASYNC_NOEXCEPT; + + // Run the associated function + void (*run)(task_base *) LIBASYNC_NOEXCEPT; + + // Cancel the task with an exception + void (*cancel)(task_base *, std::exception_ptr &&) LIBASYNC_NOEXCEPT; + + // Schedule the task using its scheduler + void (*schedule)(task_base * parent, task_ptr t); +}; + +// Type-generic base task object +struct task_base_deleter; +struct LIBASYNC_CACHELINE_ALIGN task_base : public ref_count_base { + // Task state + std::atomic state; + + // Whether get_task() was already called on an event_task + bool event_task_got_task; + + // Vector of continuations + continuation_vector continuations; + + // Virtual function table used for dynamic dispatch + const task_base_vtable * vtable; + + // Use aligned memory allocation + static void * operator new(std::size_t size) + { + return aligned_alloc(size, LIBASYNC_CACHELINE_SIZE); + } + static void operator delete(void * ptr) + { + aligned_free(ptr); + } + + // Initialize task state + task_base() + : state(task_state::pending) {} + + // Check whether the task is ready and include an acquire barrier if it is + bool ready() const + { + return is_finished(state.load(std::memory_order_acquire)); + } + + // Run a single continuation + template + void run_continuation(Sched & sched, task_ptr && cont) + { + LIBASYNC_TRY{ + detail::schedule_task(sched, std::move(cont)); + } LIBASYNC_CATCH(...) { + // This is suboptimal, but better than letting the exception leak + cont->vtable->cancel(cont.get(), std::current_exception()); + } + } + + // Run all of the task's continuations after it has completed or canceled. + // The list of continuations is emptied and locked to prevent any further + // continuations from being added. + void run_continuations() + { + continuations.flush_and_lock([this](task_ptr t) { + const task_base_vtable * vtable = t->vtable; + vtable->schedule(this, std::move(t)); + }); + } + + // Add a continuation to this task + template + void add_continuation(Sched & sched, task_ptr cont) + { + // Check for task completion + task_state current_state = state.load(std::memory_order_relaxed); + if (!is_finished(current_state)) { + // Try to add the task to the continuation list. This can fail only + // if the task has just finished, in which case we run it directly. + if (continuations.try_add(std::move(cont))) + return; + } + + // Otherwise run the continuation directly + std::atomic_thread_fence(std::memory_order_acquire); + run_continuation(sched, std::move(cont)); + } + + // Finish the task after it has been executed and the result set + void finish() + { + state.store(task_state::completed, std::memory_order_release); + run_continuations(); + } + + // Wait for the task to finish executing + task_state wait() + { + task_state s = state.load(std::memory_order_acquire); + if (!is_finished(s)) { + wait_for_task(this); + s = state.load(std::memory_order_relaxed); + } + return s; + } +}; + +// Deleter for task_ptr +struct task_base_deleter { + static void do_delete(task_base * p) + { + // Go through the vtable to delete p with its proper type + p->vtable->destroy(p); + } +}; + +// Result type-specific task object +template +struct task_result_holder : public task_base { + union { + typename std::aligned_storage::value>::type result; + std::aligned_storage::value>::type except; + + // Scheduler that should be used to schedule this task. The scheduler + // type has been erased and is held by vtable->schedule. + void * sched; + }; + + template + void set_result(T && t) + { + new(&result) Result(std::forward(t)); + } + + // Return a result using an lvalue or rvalue reference depending on the task + // type. The task parameter is not used, it is just there for overload resolution. + template + Result && get_result(const task &) + { + return std::move(*reinterpret_cast(&result)); + } + template + const Result & get_result(const shared_task &) + { + return *reinterpret_cast(&result); + } + + // Destroy the result + ~task_result_holder() + { + // Result is only present if the task completed successfully + if (state.load(std::memory_order_relaxed) == task_state::completed) + reinterpret_cast(&result)->~Result(); + } +}; + +// Specialization for references +template +struct task_result_holder : public task_base { + union { + // Store as pointer internally + Result * result; + std::aligned_storage::value>::type except; + void * sched; + }; + + void set_result(Result & obj) + { + result = std::addressof(obj); + } + + template + Result & get_result(const task &) + { + return *result; + } + template + Result & get_result(const shared_task &) + { + return *result; + } +}; + +// Specialization for void +template<> +struct task_result_holder : public task_base { + union { + std::aligned_storage::value>::type except; + void * sched; + }; + + void set_result(fake_void) {} + + // Get the result as fake_void so that it can be passed to set_result and + // continuations + template + fake_void get_result(const task &) + { + return fake_void(); + } + template + fake_void get_result(const shared_task &) + { + return fake_void(); + } +}; + +template +struct task_result : public task_result_holder { + // Virtual function table for task_result + static const task_base_vtable vtable_impl; + task_result() + { + this->vtable = &vtable_impl; + } + + // Destroy the exception + ~task_result() + { + // Exception is only present if the task was canceled + if (this->state.load(std::memory_order_relaxed) == task_state::canceled) + reinterpret_cast(&this->except)->~exception_ptr(); + } + + // Cancel a task with the given exception + void cancel_base(std::exception_ptr && except) + { + set_exception(std::move(except)); + this->state.store(task_state::canceled, std::memory_order_release); + this->run_continuations(); + } + + // Set the exception value of the task + void set_exception(std::exception_ptr && except) + { + new(&this->except) std::exception_ptr(std::move(except)); + } + + // Get the exception a task was canceled with + std::exception_ptr & get_exception() + { + return *reinterpret_cast(&this->except); + } + + // Wait and throw the exception if the task was canceled + void wait_and_throw() + { + if (this->wait() == task_state::canceled) + LIBASYNC_RETHROW_EXCEPTION(get_exception()); + } + + // Delete the task using its proper type + static void destroy(task_base * t) LIBASYNC_NOEXCEPT + { + delete static_cast *>(t); + } +}; +template +const task_base_vtable task_result::vtable_impl = { + task_result::destroy, // destroy + nullptr, // run + nullptr, // cancel + nullptr // schedule +}; + +// Class to hold a function object, with empty base class optimization +template +struct func_base { + Func func; + + template + explicit func_base(F && f) + : func(std::forward(f)) {} + Func & get_func() + { + return func; + } +}; +template +struct func_base::value>::type> { + template + explicit func_base(F && f) + { + new(this) Func(std::forward(f)); + } + ~func_base() + { + get_func().~Func(); + } + Func & get_func() + { + return *reinterpret_cast(this); + } +}; + +// Class to hold a function object and initialize/destroy it at any time +template +struct func_holder { + typename std::aligned_storage::value>::type func; + + Func & get_func() + { + return *reinterpret_cast(&func); + } + template + void init_func(Args&&... args) + { + new(&func) Func(std::forward(args)...); + } + void destroy_func() + { + get_func().~Func(); + } +}; +template +struct func_holder::value>::type> { + Func & get_func() + { + return *reinterpret_cast(this); + } + template + void init_func(Args&&... args) + { + new(this) Func(std::forward(args)...); + } + void destroy_func() + { + get_func().~Func(); + } +}; + +// Task object with an associated function object +// Using private inheritance so empty Func doesn't take up space +template +struct task_func : public task_result, func_holder { + // Virtual function table for task_func + static const task_base_vtable vtable_impl; + template + explicit task_func(Args&&... args) + { + this->vtable = &vtable_impl; + this->init_func(std::forward(args)...); + } + + // Run the stored function + static void run(task_base * t) LIBASYNC_NOEXCEPT + { + LIBASYNC_TRY{ + // Dispatch to execution function + static_cast*>(t)->get_func()(t); + } LIBASYNC_CATCH(...) { + cancel(t, std::current_exception()); + } + } + + // Cancel the task + static void cancel(task_base * t, std::exception_ptr && except) LIBASYNC_NOEXCEPT + { + // Destroy the function object when canceling since it won't be + // used anymore. + static_cast *>(t)->destroy_func(); + static_cast *>(t)->cancel_base(std::move(except)); + } + + // Schedule a continuation task using its scheduler + static void schedule(task_base * parent, task_ptr t) + { + void * sched = static_cast*>(t.get())->sched; + parent->run_continuation(*static_cast(sched), std::move(t)); + } + + // Free the function + ~task_func() + { + // If the task hasn't completed yet, destroy the function object. Note + // that an unwrapped task has already destroyed its function object. + if (this->state.load(std::memory_order_relaxed) == task_state::pending) + this->destroy_func(); + } + + // Delete the task using its proper type + static void destroy(task_base * t) LIBASYNC_NOEXCEPT + { + delete static_cast *>(t); + } +}; +template +const task_base_vtable task_func::vtable_impl = { + task_func::destroy, // destroy + task_func::run, // run + task_func::cancel, // cancel + task_func::schedule // schedule +}; + +// Helper functions to access the internal_task member of a task object, which +// avoids us having to specify half of the functions in the detail namespace +// as friend. Also, internal_task is downcast to the appropriate task_result<>. +template +typename Task::internal_task_type * get_internal_task(const Task & t) +{ + return static_cast(t.internal_task.get()); +} +template +void set_internal_task(Task & t, task_ptr p) +{ + t.internal_task = std::move(p); +} + +// Common code for task unwrapping +template +struct unwrapped_func { + explicit unwrapped_func(task_ptr t) + : parent_task(std::move(t)) {} + void operator()(Child child_task) const + { + // Forward completion state and result to parent task + task_result * parent = static_cast*>(parent_task.get()); + LIBASYNC_TRY{ + if (get_internal_task(child_task)->state.load(std::memory_order_relaxed) == task_state::completed) { + parent->set_result(get_internal_task(child_task)->get_result(child_task)); + parent->finish(); + } + else { + // We don't call the generic cancel function here because + // the function of the parent task has already been destroyed. + parent->cancel_base(std::exception_ptr(get_internal_task(child_task)->get_exception())); + } + } LIBASYNC_CATCH(...) { + // If the copy/move constructor of the result threw, propagate the exception + parent->cancel_base(std::current_exception()); + } + } + task_ptr parent_task; +}; +template +void unwrapped_finish(task_base * parent_base, Child child_task) +{ + // Destroy the parent task's function since it has been executed + parent_base->state.store(task_state::unwrapped, std::memory_order_relaxed); + static_cast *>(parent_base)->destroy_func(); + + // Set up a continuation on the child to set the result of the parent + LIBASYNC_TRY{ + parent_base->add_ref(); + child_task.then(inline_scheduler(), unwrapped_func(task_ptr(parent_base))); + } LIBASYNC_CATCH(...) { + // Use cancel_base here because the function object is already destroyed. + static_cast *>(parent_base)->cancel_base(std::current_exception()); + } +} + +// Execution functions for root tasks: +// - With and without task unwraping +template +struct root_exec_func : private func_base { + template + explicit root_exec_func(F && f) + : func_base(std::forward(f)) {} + void operator()(task_base * t) + { + static_cast *>(t)->set_result(detail::invoke_fake_void(std::move(this->get_func()))); + static_cast *>(t)->destroy_func(); + t->finish(); + } +}; +template +struct root_exec_func : private func_base { + template + explicit root_exec_func(F && f) + : func_base(std::forward(f)) {} + void operator()(task_base * t) + { + unwrapped_finish(t, std::move(this->get_func())()); + } +}; + +// Execution functions for continuation tasks: +// - With and without task unwraping +// - For void, value-based and task-based continuations +template +struct continuation_exec_func : private func_base { + template + continuation_exec_func(F && f, P && p) + : func_base(std::forward(f)), parent(std::forward

(p)) {} + void operator()(task_base * t) + { + static_cast *>(t)->set_result(detail::invoke_fake_void(std::move(this->get_func()), std::move(parent))); + static_cast *>(t)->destroy_func(); + t->finish(); + } + Parent parent; +}; +template +struct continuation_exec_func : private func_base { + template + continuation_exec_func(F && f, P && p) + : func_base(std::forward(f)), parent(std::forward

(p)) {} + void operator()(task_base * t) + { + if (get_internal_task(parent)->state.load(std::memory_order_relaxed) == task_state::canceled) + task_func::cancel(t, std::exception_ptr(get_internal_task(parent)->get_exception())); + else { + static_cast *>(t)->set_result(detail::invoke_fake_void(std::move(this->get_func()), get_internal_task(parent)->get_result(parent))); + static_cast *>(t)->destroy_func(); + t->finish(); + } + } + Parent parent; +}; +template +struct continuation_exec_func : private func_base { + template + continuation_exec_func(F && f, P && p) + : func_base(std::forward(f)), parent(std::forward

(p)) {} + void operator()(task_base * t) + { + if (get_internal_task(parent)->state.load(std::memory_order_relaxed) == task_state::canceled) + task_func::cancel(t, std::exception_ptr(get_internal_task(parent)->get_exception())); + else { + static_cast *>(t)->set_result(detail::invoke_fake_void(std::move(this->get_func()), fake_void())); + static_cast *>(t)->destroy_func(); + t->finish(); + } + } + Parent parent; +}; +template +struct continuation_exec_func : private func_base { + template + continuation_exec_func(F && f, P && p) + : func_base(std::forward(f)), parent(std::forward

(p)) {} + void operator()(task_base * t) + { + unwrapped_finish(t, detail::invoke_fake_void(std::move(this->get_func()), std::move(parent))); + } + Parent parent; +}; +template +struct continuation_exec_func : private func_base { + template + continuation_exec_func(F && f, P && p) + : func_base(std::forward(f)), parent(std::forward

(p)) {} + void operator()(task_base * t) + { + if (get_internal_task(parent)->state.load(std::memory_order_relaxed) == task_state::canceled) + task_func::cancel(t, std::exception_ptr(get_internal_task(parent)->get_exception())); + else + unwrapped_finish(t, detail::invoke_fake_void(std::move(this->get_func()), get_internal_task(parent)->get_result(parent))); + } + Parent parent; +}; +template +struct continuation_exec_func : private func_base { + template + continuation_exec_func(F && f, P && p) + : func_base(std::forward(f)), parent(std::forward

(p)) {} + void operator()(task_base * t) + { + if (get_internal_task(parent)->state.load(std::memory_order_relaxed) == task_state::canceled) + task_func::cancel(t, std::exception_ptr(get_internal_task(parent)->get_exception())); + else + unwrapped_finish(t, detail::invoke_fake_void(std::move(this->get_func()), fake_void())); + } + Parent parent; +}; + +} // namespace detail +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { + +// Improved version of std::hardware_concurrency: +// - It never returns 0, 1 is returned instead. +// - It is guaranteed to remain constant for the duration of the program. +LIBASYNC_EXPORT std::size_t hardware_concurrency() LIBASYNC_NOEXCEPT; + +// Task handle used by a wait handler +class task_wait_handle { + detail::task_base * handle; + + // Allow construction in wait_for_task() + friend LIBASYNC_EXPORT void detail::wait_for_task(detail::task_base * t); + task_wait_handle(detail::task_base * t) + : handle(t) {} + + // Execution function for use by wait handlers + template + struct wait_exec_func : private detail::func_base { + template + explicit wait_exec_func(F && f) + : detail::func_base(std::forward(f)) {} + void operator()(detail::task_base *) + { + // Just call the function directly, all this wrapper does is remove + // the task_base* parameter. + this->get_func()(); + } + }; + +public: + task_wait_handle() + : handle(nullptr) {} + + // Check if the handle is valid + explicit operator bool() const + { + return handle != nullptr; + } + + // Check if the task has finished executing + bool ready() const + { + return detail::is_finished(handle->state.load(std::memory_order_acquire)); + } + + // Queue a function to be executed when the task has finished executing. + template + void on_finish(Func && func) + { + // Make sure the function type is callable + static_assert(detail::is_callable::value, "Invalid function type passed to on_finish()"); + + auto cont = new detail::task_func::type, wait_exec_func::type>, detail::fake_void>(std::forward(func)); + cont->sched = std::addressof(inline_scheduler()); + handle->add_continuation(inline_scheduler(), detail::task_ptr(cont)); + } +}; + +// Wait handler function prototype +typedef void (*wait_handler)(task_wait_handle t); + +// Set a wait handler to control what a task does when it has "free time", which +// is when it is waiting for another task to complete. The wait handler can do +// other work, but should return when it detects that the task has completed. +// The previously installed handler is returned. +LIBASYNC_EXPORT wait_handler set_thread_wait_handler(wait_handler w) LIBASYNC_NOEXCEPT; + +// Exception thrown if a task_run_handle is destroyed without being run +struct LIBASYNC_EXPORT_EXCEPTION task_not_executed {}; + +// Task handle used in scheduler, acts as a unique_ptr to a task object +class task_run_handle { + detail::task_ptr handle; + + // Allow construction in schedule_task() + template + friend void detail::schedule_task(Sched & sched, detail::task_ptr t); + explicit task_run_handle(detail::task_ptr t) + : handle(std::move(t)) {} + +public: + // Movable but not copyable + task_run_handle() = default; + task_run_handle(task_run_handle && other) LIBASYNC_NOEXCEPT + : handle(std::move(other.handle)) {} + task_run_handle & operator=(task_run_handle && other) LIBASYNC_NOEXCEPT + { + handle = std::move(other.handle); + return *this; + } + + // If the task is not executed, cancel it with an exception + ~task_run_handle() + { + if (handle) + handle->vtable->cancel(handle.get(), std::make_exception_ptr(task_not_executed())); + } + + // Check if the handle is valid + explicit operator bool() const + { + return handle != nullptr; + } + + // Run the task and release the handle + void run() + { + handle->vtable->run(handle.get()); + handle = nullptr; + } + + // Run the task but run the given wait handler when waiting for a task, + // instead of just sleeping. + void run_with_wait_handler(wait_handler handler) + { + wait_handler old = set_thread_wait_handler(handler); + run(); + set_thread_wait_handler(old); + } + + // Conversion to and from void pointer. This allows the task handle to be + // sent through C APIs which don't preserve types. + void * to_void_ptr() + { + return handle.release(); + } + static task_run_handle from_void_ptr(void * ptr) + { + return task_run_handle(detail::task_ptr(static_cast(ptr))); + } +}; + +namespace detail { + +// Schedule a task for execution using its scheduler +template +void schedule_task(Sched & sched, task_ptr t) +{ + static_assert(is_scheduler::value, "Type is not a valid scheduler"); + sched.schedule(task_run_handle(std::move(t))); +} + +// Inline scheduler implementation +inline void inline_scheduler_impl::schedule(task_run_handle t) +{ + t.run(); +} + +} // namespace detail +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { + +// Exception thrown when an event_task is destroyed without setting a value +struct LIBASYNC_EXPORT_EXCEPTION abandoned_event_task {}; + +namespace detail { + +// Common code for task and shared_task +template +class basic_task { + // Reference counted internal task object + detail::task_ptr internal_task; + + // Real result type, with void turned into fake_void + typedef typename void_to_fake_void::type internal_result; + + // Type-specific task object + typedef task_result internal_task_type; + + // Friend access + friend async::task; + friend async::shared_task; + template + friend typename T::internal_task_type * get_internal_task(const T & t); + template + friend void set_internal_task(T & t, task_ptr p); + + // Common code for get() + void get_internal() const + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); + + // If the task was canceled, throw the associated exception + get_internal_task(*this)->wait_and_throw(); + } + + // Common code for then() + template + typename continuation_traits::task_type then_internal(Sched & sched, Func && f, Parent && parent) const + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); + + // Save a copy of internal_task because it might get moved into exec_func + task_base * my_internal = internal_task.get(); + + // Create continuation + typedef continuation_traits traits; + typedef typename void_to_fake_void::type cont_internal_result; + typedef continuation_exec_func::type, cont_internal_result, typename traits::decay_func, typename traits::is_value_cont, is_task::value> exec_func; + typename traits::task_type cont; + set_internal_task(cont, task_ptr(new task_func(std::forward(f), std::forward(parent)))); + + // Add the continuation to this task + // Avoid an expensive ref-count modification since the task isn't shared yet + get_internal_task(cont)->add_ref_unlocked(); + get_internal_task(cont)->sched = std::addressof(sched); + my_internal->add_continuation(sched, task_ptr(get_internal_task(cont))); + + return cont; + } + +public: + // Task result type + typedef Result result_type; + + // Check if this task is not empty + bool valid() const + { + return internal_task != nullptr; + } + + // Query whether the task has finished executing + bool ready() const + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); + return internal_task->ready(); + } + + // Query whether the task has been canceled with an exception + bool canceled() const + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); + return internal_task->state.load(std::memory_order_acquire) == task_state::canceled; + } + + // Wait for the task to complete + void wait() const + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); + internal_task->wait(); + } + + // Get the exception associated with a canceled task + std::exception_ptr get_exception() const + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); + if (internal_task->wait() == task_state::canceled) + return get_internal_task(*this)->get_exception(); + else + return std::exception_ptr(); + } +}; + +// Common code for event_task specializations +template +class basic_event { + // Reference counted internal task object + detail::task_ptr internal_task; + + // Real result type, with void turned into fake_void + typedef typename detail::void_to_fake_void::type internal_result; + + // Type-specific task object + typedef detail::task_result internal_task_type; + + // Friend access + friend async::event_task; + template + friend typename T::internal_task_type * get_internal_task(const T & t); + + // Common code for set() + template + bool set_internal(T && result) const + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty event_task object"); + + // Only allow setting the value once + detail::task_state expected = detail::task_state::pending; + if (!internal_task->state.compare_exchange_strong(expected, detail::task_state::locked, std::memory_order_acquire, std::memory_order_relaxed)) + return false; + + LIBASYNC_TRY{ + // Store the result and finish + get_internal_task(*this)->set_result(std::forward(result)); + internal_task->finish(); + } LIBASYNC_CATCH(...) { + // At this point we have already committed to setting a value, so + // we can't return the exception to the caller. If we did then it + // could cause concurrent set() calls to fail, thinking a value has + // already been set. Instead, we simply cancel the task with the + // exception we just got. + get_internal_task(*this)->cancel_base(std::current_exception()); + } + return true; + } + +public: + // Movable but not copyable + basic_event(basic_event && other) LIBASYNC_NOEXCEPT + : internal_task(std::move(other.internal_task)) {} + basic_event & operator=(basic_event && other) LIBASYNC_NOEXCEPT + { + internal_task = std::move(other.internal_task); + return *this; + } + + // Main constructor + basic_event() + : internal_task(new internal_task_type) + { + internal_task->event_task_got_task = false; + } + + // Cancel events if they are destroyed before they are set + ~basic_event() + { + // This check isn't thread-safe but set_exception does a proper check + if (internal_task && !internal_task->ready() && !internal_task->is_unique_ref(std::memory_order_relaxed)) { +#ifdef LIBASYNC_NO_EXCEPTIONS + // This will result in an abort if the task result is read + set_exception(std::exception_ptr()); +#else + set_exception(std::make_exception_ptr(abandoned_event_task())); +#endif + } + } + + // Get the task linked to this event. This can only be called once. + task get_task() + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty event_task object"); + LIBASYNC_ASSERT(!internal_task->event_task_got_task, std::logic_error, "get_task() called twice on event_task"); + + // Even if we didn't trigger an assert, don't return a task if one has + // already been returned. + task out; + if (!internal_task->event_task_got_task) + set_internal_task(out, internal_task); + internal_task->event_task_got_task = true; + return out; + } + + // Cancel the event with an exception and cancel continuations + bool set_exception(std::exception_ptr except) const + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty event_task object"); + + // Only allow setting the value once + detail::task_state expected = detail::task_state::pending; + if (!internal_task->state.compare_exchange_strong(expected, detail::task_state::locked, std::memory_order_acquire, std::memory_order_relaxed)) + return false; + + // Cancel the task + get_internal_task(*this)->cancel_base(std::move(except)); + return true; + } +}; + +} // namespace detail + +template +class task : public detail::basic_task { +public: + // Movable but not copyable + task() = default; + task(task && other) LIBASYNC_NOEXCEPT + : detail::basic_task(std::move(other)) {} + task & operator=(task && other) LIBASYNC_NOEXCEPT + { + detail::basic_task::operator=(std::move(other)); + return *this; + } + + // Get the result of the task + Result get() + { + this->get_internal(); + + // Move the internal state pointer so that the task becomes invalid, + // even if an exception is thrown. + detail::task_ptr my_internal = std::move(this->internal_task); + return detail::fake_void_to_void(static_cast(my_internal.get())->get_result(*this)); + } + + // Add a continuation to the task + template + typename detail::continuation_traits::task_type then(Sched & sched, Func && f) + { + return this->then_internal(sched, std::forward(f), std::move(*this)); + } + template + typename detail::continuation_traits::task_type then(Func && f) + { + return then(::async::default_scheduler(), std::forward(f)); + } + + // Create a shared_task from this task + shared_task share() + { + LIBASYNC_ASSERT(this->internal_task, std::invalid_argument, "Use of empty task object"); + + shared_task out; + detail::set_internal_task(out, std::move(this->internal_task)); + return out; + } +}; + +template +class shared_task : public detail::basic_task { + // get() return value: const Result& -or- void + typedef typename std::conditional< + std::is_void::value, + void, + typename std::add_lvalue_reference< + typename std::add_const::type + >::type + >::type get_result; + +public: + // Movable and copyable + shared_task() = default; + + // Get the result of the task + get_result get() const + { + this->get_internal(); + return detail::fake_void_to_void(detail::get_internal_task(*this)->get_result(*this)); + } + + // Add a continuation to the task + template + typename detail::continuation_traits::task_type then(Sched & sched, Func && f) const + { + return this->then_internal(sched, std::forward(f), *this); + } + template + typename detail::continuation_traits::task_type then(Func && f) const + { + return then(::async::default_scheduler(), std::forward(f)); + } +}; + +// Special task type which can be triggered manually rather than when a function executes. +template +class event_task : public detail::basic_event { +public: + // Movable but not copyable + event_task() = default; + event_task(event_task && other) LIBASYNC_NOEXCEPT + : detail::basic_event(std::move(other)) {} + event_task & operator=(event_task && other) LIBASYNC_NOEXCEPT + { + detail::basic_event::operator=(std::move(other)); + return *this; + } + + // Set the result of the task, mark it as completed and run its continuations + bool set(const Result & result) const + { + return this->set_internal(result); + } + bool set(Result && result) const + { + return this->set_internal(std::move(result)); + } +}; + +// Specialization for references +template +class event_task : public detail::basic_event { +public: + // Movable but not copyable + event_task() = default; + event_task(event_task && other) LIBASYNC_NOEXCEPT + : detail::basic_event(std::move(other)) {} + event_task & operator=(event_task && other) LIBASYNC_NOEXCEPT + { + detail::basic_event::operator=(std::move(other)); + return *this; + } + + // Set the result of the task, mark it as completed and run its continuations + bool set(Result & result) const + { + return this->set_internal(result); + } +}; + +// Specialization for void +template<> +class event_task : public detail::basic_event { +public: + // Movable but not copyable + event_task() = default; + event_task(event_task && other) LIBASYNC_NOEXCEPT + : detail::basic_event(std::move(other)) {} + event_task & operator=(event_task && other) LIBASYNC_NOEXCEPT + { + detail::basic_event::operator=(std::move(other)); + return *this; + } + + // Set the result of the task, mark it as completed and run its continuations + bool set() + { + return this->set_internal(detail::fake_void()); + } +}; + +// Task type returned by local_spawn() +template +class local_task { + // Make sure the function type is callable + typedef typename std::decay::type decay_func; + static_assert(detail::is_callable::value, "Invalid function type passed to local_spawn()"); + + // Task result type + typedef typename detail::remove_task()())>::type result_type; + typedef typename detail::void_to_fake_void::type internal_result; + + // Task execution function type + typedef detail::root_exec_func()())>::value> exec_func; + + // Task object embedded directly. The ref-count is initialized to 1 so it + // will never be freed using delete, only when the local_task is destroyed. + detail::task_func internal_task; + + // Friend access for local_spawn + template + friend local_task local_spawn(S & sched, F && f); + template + friend local_task local_spawn(F && f); + + // Constructor, used by local_spawn + local_task(Sched & sched, Func && f) + : internal_task(std::forward(f)) + { + // Avoid an expensive ref-count modification since the task isn't shared yet + internal_task.add_ref_unlocked(); + detail::schedule_task(sched, detail::task_ptr(&internal_task)); + } + +public: + // Non-movable and non-copyable + local_task(const local_task &) = delete; + local_task & operator=(const local_task &) = delete; + + // Wait for the task to complete when destroying + ~local_task() + { + wait(); + + // Now spin until the reference count drops to 1, since the scheduler + // may still have a reference to the task. + while (!internal_task.is_unique_ref(std::memory_order_acquire)) { +#if defined(__GLIBCXX__) && __GLIBCXX__ <= 20140612 + // Some versions of libstdc++ (4.7 and below) don't include a + // definition of std::this_thread::yield(). + sched_yield(); +#else + std::this_thread::yield(); +#endif + } + } + + // Query whether the task has finished executing + bool ready() const + { + return internal_task.ready(); + } + + // Query whether the task has been canceled with an exception + bool canceled() const + { + return internal_task.state.load(std::memory_order_acquire) == detail::task_state::canceled; + } + + // Wait for the task to complete + void wait() + { + internal_task.wait(); + } + + // Get the result of the task + result_type get() + { + internal_task.wait_and_throw(); + return detail::fake_void_to_void(internal_task.get_result(task())); + } + + // Get the exception associated with a canceled task + std::exception_ptr get_exception() const + { + if (internal_task.wait() == detail::task_state::canceled) + return internal_task.get_exception(); + else + return std::exception_ptr(); + } +}; + +// Spawn a function asynchronously +template +task::type()>::type>::type> spawn(Sched & sched, Func && f) +{ + // Using result_of in the function return type to work around bugs in the Intel + // C++ compiler. + + // Make sure the function type is callable + typedef typename std::decay::type decay_func; + static_assert(detail::is_callable::value, "Invalid function type passed to spawn()"); + + // Create task + typedef typename detail::void_to_fake_void()())>::type>::type internal_result; + typedef detail::root_exec_func()())>::value> exec_func; + task()())>::type> out; + detail::set_internal_task(out, detail::task_ptr(new detail::task_func(std::forward(f)))); + + // Avoid an expensive ref-count modification since the task isn't shared yet + detail::get_internal_task(out)->add_ref_unlocked(); + detail::schedule_task(sched, detail::task_ptr(detail::get_internal_task(out))); + + return out; +} +template +decltype(async::spawn(::async::default_scheduler(), std::declval())) spawn(Func && f) +{ + return async::spawn(::async::default_scheduler(), std::forward(f)); +} + +// Create a completed task containing a value +template +task::type> make_task(T && value) +{ + task::type> out; + + detail::set_internal_task(out, detail::task_ptr(new detail::task_result::type>)); + detail::get_internal_task(out)->set_result(std::forward(value)); + detail::get_internal_task(out)->state.store(detail::task_state::completed, std::memory_order_relaxed); + + return out; +} +template +task make_task(std::reference_wrapper value) +{ + task out; + + detail::set_internal_task(out, detail::task_ptr(new detail::task_result)); + detail::get_internal_task(out)->set_result(value.get()); + detail::get_internal_task(out)->state.store(detail::task_state::completed, std::memory_order_relaxed); + + return out; +} +inline task make_task() +{ + task out; + + detail::set_internal_task(out, detail::task_ptr(new detail::task_result)); + detail::get_internal_task(out)->state.store(detail::task_state::completed, std::memory_order_relaxed); + + return out; +} + +// Create a canceled task containing an exception +template +task make_exception_task(std::exception_ptr except) +{ + task out; + + detail::set_internal_task(out, detail::task_ptr(new detail::task_result::type>)); + detail::get_internal_task(out)->set_exception(std::move(except)); + detail::get_internal_task(out)->state.store(detail::task_state::canceled, std::memory_order_relaxed); + + return out; +} + +// Spawn a very limited task which is restricted to the current function and +// joins on destruction. Because local_task is not movable, the result must +// be captured in a reference, like this: +// auto&& x = local_spawn(...); +template +#ifdef __GNUC__ +__attribute__((warn_unused_result)) +#endif +local_task local_spawn(Sched & sched, Func && f) +{ + // Since local_task is not movable, we construct it in-place and let the + // caller extend the lifetime of the returned object using a reference. + return { sched, std::forward(f) }; +} +template +#ifdef __GNUC__ +__attribute__((warn_unused_result)) +#endif +local_task local_spawn(Func && f) +{ + return { ::async::default_scheduler(), std::forward(f) }; +} + +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { + +// Result type for when_any +template +struct when_any_result { + // Index of the task that finished first + std::size_t index; + + // List of tasks that were passed in + Result tasks; +}; + +namespace detail { + +// Shared state for when_all +template +struct when_all_state : public ref_count_base> { + event_task event; + Result result; + + when_all_state(std::size_t count) + : ref_count_base>(count) {} + + // When all references are dropped, signal the event + ~when_all_state() + { + event.set(std::move(result)); + } +}; + +// Execution functions for when_all, for ranges and tuples +template +struct when_all_func_range { + std::size_t index; + ref_count_ptr> state; + + when_all_func_range(std::size_t index, ref_count_ptr> state) + : index(index), state(std::move(state)) {} + + // Copy the completed task object to the shared state. The event is + // automatically signaled when all references are dropped. + void operator()(Task t) const + { + state->result[index] = std::move(t); + } +}; +template +struct when_all_func_tuple { + ref_count_ptr> state; + + when_all_func_tuple(ref_count_ptr> state) + : state(std::move(state)) {} + + // Copy the completed task object to the shared state. The event is + // automatically signaled when all references are dropped. + void operator()(Task t) const + { + std::get(state->result) = std::move(t); + } +}; + +// Shared state for when_any +template +struct when_any_state : public ref_count_base> { + event_task> event; + Result result; + + when_any_state(std::size_t count) + : ref_count_base>(count) {} + + // Signal the event when the first task reaches here + void set(std::size_t i) + { + event.set({ i, std::move(result) }); + } +}; + +// Execution function for when_any +template +struct when_any_func { + std::size_t index; + ref_count_ptr> state; + + when_any_func(std::size_t index, ref_count_ptr> state) + : index(index), state(std::move(state)) {} + + // Simply tell the state that our task has finished, it already has a copy + // of the task object. + void operator()(Task) const + { + state->set(index); + } +}; + +// Internal implementation of when_all for variadic arguments +template +void when_all_variadic(when_all_state *) {} +template +void when_all_variadic(when_all_state * state, First && first, T&&... tasks) +{ + typedef typename std::decay::type task_type; + + // Add a continuation to the task + LIBASYNC_TRY{ + first.then(inline_scheduler(), detail::when_all_func_tuple(detail::ref_count_ptr>(state))); + } LIBASYNC_CATCH(...) { + // Make sure we don't leak memory if then() throws + state->remove_ref(sizeof...(T)); + LIBASYNC_RETHROW(); + } + + // Add continuations to remaining tasks + detail::when_all_variadic(state, std::forward(tasks)...); +} + +// Internal implementation of when_any for variadic arguments +template +void when_any_variadic(when_any_state *) {} +template +void when_any_variadic(when_any_state * state, First && first, T&&... tasks) +{ + typedef typename std::decay::type task_type; + + // Add a copy of the task to the results because the event may be + // set before all tasks have finished. + detail::task_base * t = detail::get_internal_task(first); + t->add_ref(); + detail::set_internal_task(std::get(state->result), detail::task_ptr(t)); + + // Add a continuation to the task + LIBASYNC_TRY{ + first.then(inline_scheduler(), detail::when_any_func(index, detail::ref_count_ptr>(state))); + } LIBASYNC_CATCH(...) { + // Make sure we don't leak memory if then() throws + state->remove_ref(sizeof...(T)); + LIBASYNC_RETHROW(); + } + + // Add continuations to remaining tasks + detail::when_any_variadic(state, std::forward(tasks)...); +} + +} // namespace detail + +// Combine a set of tasks into one task which is signaled when all specified tasks finish +template +task::value_type>::type>> when_all(Iter begin, Iter end) +{ + typedef typename std::decay::value_type>::type task_type; + typedef std::vector result_type; + + // Handle empty ranges + if (begin == end) + return make_task(result_type()); + + // Create shared state, initialized with the proper reference count + std::size_t count = std::distance(begin, end); + auto * state = new detail::when_all_state(count); + state->result.resize(count); + auto out = state->event.get_task(); + + // Add a continuation to each task to add its result to the shared state + // Last task sets the event result + for (std::size_t i = 0; begin != end; i++, ++begin) { + LIBASYNC_TRY{ + (*begin).then(inline_scheduler(), detail::when_all_func_range(i, detail::ref_count_ptr>(state))); + } LIBASYNC_CATCH(...) { + // Make sure we don't leak memory if then() throws + state->remove_ref(std::distance(begin, end) - 1); + LIBASYNC_RETHROW(); + } + } + + return out; +} + +// Combine a set of tasks into one task which is signaled when one of the tasks finishes +template +task::value_type>::type>>> when_any(Iter begin, Iter end) +{ + typedef typename std::decay::value_type>::type task_type; + typedef std::vector result_type; + + // Handle empty ranges + if (begin == end) + return make_task(when_any_result()); + + // Create shared state, initialized with the proper reference count + std::size_t count = std::distance(begin, end); + auto * state = new detail::when_any_state(count); + state->result.resize(count); + auto out = state->event.get_task(); + + // Add a continuation to each task to set the event. First one wins. + for (std::size_t i = 0; begin != end; i++, ++begin) { + // Add a copy of the task to the results because the event may be + // set before all tasks have finished. + detail::task_base * t = detail::get_internal_task(*begin); + t->add_ref(); + detail::set_internal_task(state->result[i], detail::task_ptr(t)); + + LIBASYNC_TRY{ + (*begin).then(inline_scheduler(), detail::when_any_func(i, detail::ref_count_ptr>(state))); + } LIBASYNC_CATCH(...) { + // Make sure we don't leak memory if then() throws + state->remove_ref(std::distance(begin, end) - 1); + LIBASYNC_RETHROW(); + } + } + + return out; +} + +// when_all wrapper accepting ranges +template +decltype(async::when_all(std::begin(std::declval()), std::end(std::declval()))) when_all(T && tasks) +{ + return async::when_all(std::begin(std::forward(tasks)), std::end(std::forward(tasks))); +} + +// when_any wrapper accepting ranges +template +decltype(async::when_any(std::begin(std::declval()), std::end(std::declval()))) when_any(T && tasks) +{ + return async::when_any(std::begin(std::forward(tasks)), std::end(std::forward(tasks))); +} + +// when_all with variadic arguments +inline task> when_all() +{ + return async::make_task(std::tuple<>()); +} +template +task::type...>> when_all(T&&... tasks) +{ + typedef std::tuple::type...> result_type; + + // Create shared state + auto state = new detail::when_all_state(sizeof...(tasks)); + auto out = state->event.get_task(); + + // Register all the tasks on the event + detail::when_all_variadic<0>(state, std::forward(tasks)...); + + return out; +} + +// when_any with variadic arguments +inline task>> when_any() +{ + return async::make_task(when_any_result>()); +} +template +task::type...>>> when_any(T&&... tasks) +{ + typedef std::tuple::type...> result_type; + + // Create shared state + auto state = new detail::when_any_state(sizeof...(tasks)); + auto out = state->event.get_task(); + + // Register all the tasks on the event + detail::when_any_variadic<0>(state, std::forward(tasks)...); + + return out; +} + +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { + +// Exception thrown by cancel_current_task() +struct LIBASYNC_EXPORT_EXCEPTION task_canceled {}; + +// A flag which can be used to request cancellation +class cancellation_token { + std::atomic state; + +public: + cancellation_token() + : state(false) {} + + // Non-copyable and non-movable + cancellation_token(const cancellation_token &) = delete; + cancellation_token & operator=(const cancellation_token &) = delete; + + bool is_canceled() const + { + bool s = state.load(std::memory_order_relaxed); + if (s) + std::atomic_thread_fence(std::memory_order_acquire); + return s; + } + + void cancel() + { + state.store(true, std::memory_order_release); + } + + void reset() + { + state.store(false, std::memory_order_relaxed); + } +}; + +// Interruption point, throws task_canceled if the specified token is set. +inline void interruption_point(const cancellation_token & token) +{ + if (token.is_canceled()) + LIBASYNC_THROW(task_canceled()); +} + +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { + +// Range type representing a pair of iterators +template +class range { + Iter iter_begin, iter_end; + +public: + range() = default; + range(Iter a, Iter b) + : iter_begin(a), iter_end(b) {} + + Iter begin() const + { + return iter_begin; + } + Iter end() const + { + return iter_end; + } +}; + +// Construct a range from 2 iterators +template +range make_range(Iter begin, Iter end) +{ + return { begin, end }; +} + +// A range of integers +template +class int_range { + T value_begin, value_end; + + static_assert(std::is_integral::value, "int_range can only be used with integral types"); + +public: + class iterator { + T current; + + explicit iterator(T a) + : current(a) {} + friend class int_range; + + public: + typedef T value_type; + typedef std::ptrdiff_t difference_type; + typedef iterator pointer; + typedef T reference; + typedef std::random_access_iterator_tag iterator_category; + + iterator() = default; + + T operator*() const + { + return current; + } + T operator[](difference_type offset) const + { + return current + offset; + } + + iterator & operator++() + { + ++current; + return *this; + } + iterator operator++(int) + { + return iterator(current++); + } + iterator & operator--() + { + --current; + return *this; + } + iterator operator--(int) + { + return iterator(current--); + } + + iterator & operator+=(difference_type offset) + { + current += offset; + return *this; + } + iterator & operator-=(difference_type offset) + { + current -= offset; + return *this; + } + + iterator operator+(difference_type offset) const + { + return iterator(current + offset); + } + iterator operator-(difference_type offset) const + { + return iterator(current - offset); + } + + friend iterator operator+(difference_type offset, iterator other) + { + return other + offset; + } + + friend difference_type operator-(iterator a, iterator b) + { + return a.current - b.current; + } + + friend bool operator==(iterator a, iterator b) + { + return a.current == b.current; + } + friend bool operator!=(iterator a, iterator b) + { + return a.current != b.current; + } + friend bool operator>(iterator a, iterator b) + { + return a.current > b.current; + } + friend bool operator<(iterator a, iterator b) + { + return a.current < b.current; + } + friend bool operator>=(iterator a, iterator b) + { + return a.current >= b.current; + } + friend bool operator<=(iterator a, iterator b) + { + return a.current <= b.current; + } + }; + + int_range(T begin, T end) + : value_begin(begin), value_end(end) {} + + iterator begin() const + { + return iterator(value_begin); + } + iterator end() const + { + return iterator(value_end); + } +}; + +// Construct an int_range between 2 values +template +int_range::type> irange(T begin, U end) +{ + return { begin, end }; +} + +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Partitioners are essentially ranges with an extra split() function. The +// split() function returns a partitioner containing a range to be executed in a +// child task and modifies the parent partitioner's range to represent the rest +// of the original range. If the range cannot be split any more then split() +// should return an empty range. + +// Detect whether a range is a partitioner +template().split())> +two & is_partitioner_helper(int); +template +one & is_partitioner_helper(...); +template +struct is_partitioner : public std::integral_constant(0)) - 1> {}; + +// Automatically determine a grain size for a sequence length +inline std::size_t auto_grain_size(std::size_t dist) +{ + // Determine the grain size automatically using a heuristic + std::size_t grain = dist / (8 * hardware_concurrency()); + if (grain < 1) + grain = 1; + if (grain > 2048) + grain = 2048; + return grain; +} + +template +class static_partitioner_impl { + Iter iter_begin, iter_end; + std::size_t grain; + +public: + static_partitioner_impl(Iter begin, Iter end, std::size_t grain) + : iter_begin(begin), iter_end(end), grain(grain) {} + Iter begin() const + { + return iter_begin; + } + Iter end() const + { + return iter_end; + } + static_partitioner_impl split() + { + // Don't split if below grain size + std::size_t length = std::distance(iter_begin, iter_end); + static_partitioner_impl out(iter_end, iter_end, grain); + if (length <= grain) + return out; + + // Split our range in half + iter_end = iter_begin; + std::advance(iter_end, (length + 1) / 2); + out.iter_begin = iter_end; + return out; + } +}; + +template +class auto_partitioner_impl { + Iter iter_begin, iter_end; + std::size_t grain; + std::size_t num_threads; + std::thread::id last_thread; + +public: + // thread_id is initialized to "no thread" and will be set on first split + auto_partitioner_impl(Iter begin, Iter end, std::size_t grain) + : iter_begin(begin), iter_end(end), grain(grain) {} + Iter begin() const + { + return iter_begin; + } + Iter end() const + { + return iter_end; + } + auto_partitioner_impl split() + { + // Don't split if below grain size + std::size_t length = std::distance(iter_begin, iter_end); + auto_partitioner_impl out(iter_end, iter_end, grain); + if (length <= grain) + return out; + + // Check if we are in a different thread than we were before + std::thread::id current_thread = std::this_thread::get_id(); + if (current_thread != last_thread) + num_threads = hardware_concurrency(); + + // If we only have one thread, don't split + if (num_threads <= 1) + return out; + + // Split our range in half + iter_end = iter_begin; + std::advance(iter_end, (length + 1) / 2); + out.iter_begin = iter_end; + out.last_thread = current_thread; + last_thread = current_thread; + out.num_threads = num_threads / 2; + num_threads -= out.num_threads; + return out; + } +}; + +} // namespace detail + +// A simple partitioner which splits until a grain size is reached. If a grain +// size is not specified, one is chosen automatically. +template +detail::static_partitioner_impl()))> static_partitioner(Range && range, std::size_t grain) +{ + return { std::begin(range), std::end(range), grain }; +} +template +detail::static_partitioner_impl()))> static_partitioner(Range && range) +{ + std::size_t grain = detail::auto_grain_size(std::distance(std::begin(range), std::end(range))); + return { std::begin(range), std::end(range), grain }; +} + +// A more advanced partitioner which initially divides the range into one chunk +// for each available thread. The range is split further if a chunk gets stolen +// by a different thread. +template +detail::auto_partitioner_impl()))> auto_partitioner(Range && range) +{ + std::size_t grain = detail::auto_grain_size(std::distance(std::begin(range), std::end(range))); + return { std::begin(range), std::end(range), grain }; +} + +// Wrap a range in a partitioner. If the input is already a partitioner then it +// is returned unchanged. This allows parallel algorithms to accept both ranges +// and partitioners as parameters. +template +typename std::enable_if::type>::value, Partitioner &&>::type to_partitioner(Partitioner && partitioner) +{ + return std::forward(partitioner); +} +template +typename std::enable_if::type>::value, detail::auto_partitioner_impl()))>>::type to_partitioner(Range && range) +{ + return async::auto_partitioner(std::forward(range)); +} + +// Overloads with std::initializer_list +template +detail::static_partitioner_impl>().begin())> static_partitioner(std::initializer_list range) +{ + return async::static_partitioner(async::make_range(range.begin(), range.end())); +} +template +detail::static_partitioner_impl>().begin())> static_partitioner(std::initializer_list range, std::size_t grain) +{ + return async::static_partitioner(async::make_range(range.begin(), range.end()), grain); +} +template +detail::auto_partitioner_impl>().begin())> auto_partitioner(std::initializer_list range) +{ + return async::auto_partitioner(async::make_range(range.begin(), range.end())); +} +template +detail::auto_partitioner_impl>().begin())> to_partitioner(std::initializer_list range) +{ + return async::auto_partitioner(async::make_range(range.begin(), range.end())); +} + +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Recursively split the arguments so tasks are spawned in parallel +template +struct parallel_invoke_internal { + template + static void run(Sched & sched, const Tuple & args) + { + auto && t = async::local_spawn(sched, [&sched, &args] { + parallel_invoke_internal::run(sched, args); + }); + parallel_invoke_internal::run(sched, args); + t.get(); + } +}; +template +struct parallel_invoke_internal { + template + static void run(Sched &, const Tuple & args) + { + // Make sure to preserve the rvalue/lvalue-ness of the original parameter + std::forward::type>(std::get(args))(); + } +}; +template +struct parallel_invoke_internal { + template + static void run(Sched &, const Tuple &) {} +}; + +} // namespace detail + +// Run several functions in parallel, optionally using the specified scheduler. +template +typename std::enable_if::value>::type parallel_invoke(Sched & sched, Args&&... args) +{ + detail::parallel_invoke_internal<0, sizeof...(Args)>::run(sched, std::forward_as_tuple(std::forward(args)...)); +} +template +void parallel_invoke(Args&&... args) +{ + async::parallel_invoke(::async::default_scheduler(), std::forward(args)...); +} + +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Internal implementation of parallel_for that only accepts a partitioner +// argument. +template +void internal_parallel_for(Sched & sched, Partitioner partitioner, const Func & func) +{ + // Split the partition, run inline if no more splits are possible + auto subpart = partitioner.split(); + if (subpart.begin() == subpart.end()) { + for (auto && i : partitioner) + func(std::forward(i)); + return; + } + + // Run the function over each half in parallel + auto && t = async::local_spawn(sched, [&sched, &subpart, &func] { + detail::internal_parallel_for(sched, std::move(subpart), func); + }); + detail::internal_parallel_for(sched, std::move(partitioner), func); + t.get(); +} + +} // namespace detail + +// Run a function for each element in a range +template +void parallel_for(Sched & sched, Range && range, const Func & func) +{ + detail::internal_parallel_for(sched, async::to_partitioner(std::forward(range)), func); +} + +// Overload with default scheduler +template +void parallel_for(Range && range, const Func & func) +{ + async::parallel_for(::async::default_scheduler(), range, func); +} + +// Overloads with std::initializer_list +template +void parallel_for(Sched & sched, std::initializer_list range, const Func & func) +{ + async::parallel_for(sched, async::make_range(range.begin(), range.end()), func); +} +template +void parallel_for(std::initializer_list range, const Func & func) +{ + async::parallel_for(async::make_range(range.begin(), range.end()), func); +} + +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Default map function which simply passes its parameter through unmodified +struct default_map { + template + T && operator()(T && x) const + { + return std::forward(x); + } +}; + +// Internal implementation of parallel_map_reduce that only accepts a +// partitioner argument. +template +Result internal_parallel_map_reduce(Sched & sched, Partitioner partitioner, Result init, const MapFunc & map, const ReduceFunc & reduce) +{ + // Split the partition, run inline if no more splits are possible + auto subpart = partitioner.split(); + if (subpart.begin() == subpart.end()) { + Result out = init; + for (auto && i : partitioner) + out = reduce(std::move(out), map(std::forward(i))); + return out; + } + + // Run the function over each half in parallel + auto && t = async::local_spawn(sched, [&sched, &subpart, init, &map, &reduce] { + return detail::internal_parallel_map_reduce(sched, std::move(subpart), init, map, reduce); + }); + Result out = detail::internal_parallel_map_reduce(sched, std::move(partitioner), init, map, reduce); + return reduce(std::move(out), t.get()); +} + +} // namespace detail + +// Run a function for each element in a range and then reduce the results of that function to a single value +template +Result parallel_map_reduce(Sched & sched, Range && range, Result init, const MapFunc & map, const ReduceFunc & reduce) +{ + return detail::internal_parallel_map_reduce(sched, async::to_partitioner(std::forward(range)), init, map, reduce); +} + +// Overload with default scheduler +template +Result parallel_map_reduce(Range && range, Result init, const MapFunc & map, const ReduceFunc & reduce) +{ + return async::parallel_map_reduce(::async::default_scheduler(), range, init, map, reduce); +} + +// Overloads with std::initializer_list +template +Result parallel_map_reduce(Sched & sched, std::initializer_list range, Result init, const MapFunc & map, const ReduceFunc & reduce) +{ + return async::parallel_map_reduce(sched, async::make_range(range.begin(), range.end()), init, map, reduce); +} +template +Result parallel_map_reduce(std::initializer_list range, Result init, const MapFunc & map, const ReduceFunc & reduce) +{ + return async::parallel_map_reduce(async::make_range(range.begin(), range.end()), init, map, reduce); +} + +// Variant with identity map operation +template +Result parallel_reduce(Sched & sched, Range && range, Result init, const ReduceFunc & reduce) +{ + return async::parallel_map_reduce(sched, range, init, detail::default_map(), reduce); +} +template +Result parallel_reduce(Range && range, Result init, const ReduceFunc & reduce) +{ + return async::parallel_reduce(::async::default_scheduler(), range, init, reduce); +} +template +Result parallel_reduce(Sched & sched, std::initializer_list range, Result init, const ReduceFunc & reduce) +{ + return async::parallel_reduce(sched, async::make_range(range.begin(), range.end()), init, reduce); +} +template +Result parallel_reduce(std::initializer_list range, Result init, const ReduceFunc & reduce) +{ + return async::parallel_reduce(async::make_range(range.begin(), range.end()), init, reduce); +} + +} // namespace async + + +#ifndef LIBASYNC_STATIC +#if defined(__GNUC__) && !defined(_WIN32) +# pragma GCC visibility pop +#endif +#endif + +#endif + + +// For posix_memalign/_aligned_malloc +#ifdef _WIN32 +# include +# ifdef __MINGW32__ +# define _aligned_malloc __mingw_aligned_malloc +# define _aligned_free __mingw_aligned_free +# endif +#else +# include +#endif + +// We don't make use of dynamic TLS initialization/destruction so we can just +// use the legacy TLS attributes. +#ifdef __GNUC__ +# define THREAD_LOCAL __thread +#elif defined (_MSC_VER) +# define THREAD_LOCAL __declspec(thread) +#else +# define THREAD_LOCAL thread_local +#endif + +// GCC, Clang and the Linux version of the Intel compiler and MSVC 2015 support +// thread-safe initialization of function-scope static variables. +#ifdef __GNUC__ +# define HAVE_THREAD_SAFE_STATIC +#elif _MSC_VER >= 1900 && !defined(__INTEL_COMPILER) +# define HAVE_THREAD_SAFE_STATIC +#endif + +// MSVC deadlocks when joining a thread from a static destructor. Use a +// workaround in that case to avoid the deadlock. +#if defined(_MSC_VER) && _MSC_VER < 1900 +# define BROKEN_JOIN_IN_DESTRUCTOR +#endif + +// Apple's iOS has no thread local support yet. They claim that they don't want to +// introduce a binary compatility issue when they got a better implementation available. +// Luckily, pthreads supports some kind of "emulation" for that. This detects if the we +// are compiling for iOS and enables the workaround accordingly. +// It is also possible enabling it forcibly by setting the EMULATE_PTHREAD_THREAD_LOCAL +// macro. Obviously, this will only works on platforms with pthread available. +#if __APPLE__ +# include "TargetConditionals.h" +# if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE +# define EMULATE_PTHREAD_THREAD_LOCAL +# endif +#endif + +// Force symbol visibility to hidden unless explicity exported +#ifndef LIBASYNC_STATIC +#if defined(__GNUC__) && !defined(_WIN32) +# pragma GCC visibility push(hidden) +#endif +#endif + +// Include other internal headers +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +namespace async { +namespace detail { + +// Thread-safe singleton wrapper class +#ifdef HAVE_THREAD_SAFE_STATIC +// C++11 guarantees thread safety for static initialization +template +class singleton { +public: + static T & get_instance() + { + static T instance; + return instance; + } +}; +#else +// Some compilers don't support thread-safe static initialization, so emulate it +template +class singleton { + std::mutex lock; + std::atomic init_flag; + typename std::aligned_storage::value>::type storage; + + static singleton instance; + + // Use a destructor instead of atexit() because the latter does not work + // properly when the singleton is in a library that is unloaded. + ~singleton() + { + if (init_flag.load(std::memory_order_acquire)) + reinterpret_cast(&storage)->~T(); + } + +public: + static T & get_instance() + { + T * ptr = reinterpret_cast(&instance.storage); + if (!instance.init_flag.load(std::memory_order_acquire)) { + std::lock_guard locked(instance.lock); + if (!instance.init_flag.load(std::memory_order_relaxed)) { + new(ptr) T; + instance.init_flag.store(true, std::memory_order_release); + } + } + return *ptr; + } +}; + +template singleton singleton::instance; +#endif + +} // namespace detail +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +namespace async { +namespace detail { + +// Set of events that an task_wait_event can hold +enum wait_type { + // The task that is being waited on has completed + task_finished = 1, + + // A task is available to execute from the scheduler + task_available = 2 +}; + +// OS-supported event object which can be used to wait for either a task to +// finish or for the scheduler to have more work for the current thread. +// +// The event object is lazily initialized to avoid unnecessary API calls. +class task_wait_event { + std::aligned_storage::value>::type m; + std::aligned_storage::value>::type c; + int event_mask; + bool initialized; + + std::mutex & mutex() + { + return *reinterpret_cast(&m); + } + std::condition_variable & cond() + { + return *reinterpret_cast(&c); + } + +public: + task_wait_event() + : event_mask(0), initialized(false) {} + + ~task_wait_event() + { + if (initialized) { + mutex().~mutex(); + cond().~condition_variable(); + } + } + + // Initialize the event, must be done before any other functions are called. + void init() + { + if (!initialized) { + new(&m) std::mutex; + new(&c) std::condition_variable; + initialized = true; + } + } + + // Wait for an event to occur. Returns the event(s) that occurred. This also + // clears any pending events afterwards. + int wait() + { + std::unique_lock lock(mutex()); + while (event_mask == 0) + cond().wait(lock); + int result = event_mask; + event_mask = 0; + return result; + } + + // Check if a specific event is ready + bool try_wait(int event) + { + std::lock_guard lock(mutex()); + int result = event_mask & event; + event_mask &= ~event; + return result != 0; + } + + // Signal an event and wake up a sleeping thread + void signal(int event) + { + std::unique_lock lock(mutex()); + event_mask |= event; + + // This must be done while holding the lock otherwise we may end up with + // a use-after-free due to a race with wait(). + cond().notify_one(); + lock.unlock(); + } +}; + +} // namespace detail +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Queue which holds tasks in FIFO order. Note that this queue is not +// thread-safe and must be protected by a lock. +class fifo_queue { + detail::aligned_array items; + std::size_t head, tail; + +public: + fifo_queue() + : items(32), head(0), tail(0) {} + ~fifo_queue() + { + // Free any unexecuted tasks + for (std::size_t i = head; i != tail; i = (i + 1) & (items.size() - 1)) + task_run_handle::from_void_ptr(items[i]); + } + + // Push a task to the end of the queue + void push(task_run_handle t) + { + // Resize queue if it is full + if (head == ((tail + 1) & (items.size() - 1))) { + detail::aligned_array new_items(items.size() * 2); + for (std::size_t i = 0; i != items.size(); i++) + new_items[i] = items[(i + head) & (items.size() - 1)]; + head = 0; + tail = items.size() - 1; + items = std::move(new_items); + } + + // Push the item + items[tail] = t.to_void_ptr(); + tail = (tail + 1) & (items.size() - 1); + } + + // Pop a task from the front of the queue + task_run_handle pop() + { + // See if an item is available + if (head == tail) + return task_run_handle(); + else { + void * x = items[head]; + head = (head + 1) & (items.size() - 1); + return task_run_handle::from_void_ptr(x); + } + } +}; + +} // namespace detail +} // namespace async + +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Chase-Lev work stealing deque +// +// Dynamic Circular Work-Stealing Deque +// http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.170.1097&rep=rep1&type=pdf +// +// Correct and Efficient Work-Stealing for Weak Memory Models +// http://www.di.ens.fr/~zappa/readings/ppopp13.pdf +class work_steal_queue { + // Circular array of void* + class circular_array { + detail::aligned_array items; + std::unique_ptr previous; + + public: + circular_array(std::size_t n) + : items(n) {} + + std::size_t size() const + { + return items.size(); + } + + void * get(std::size_t index) + { + return items[index & (size() - 1)]; + } + + void put(std::size_t index, void * x) + { + items[index & (size() - 1)] = x; + } + + // Growing the array returns a new circular_array object and keeps a + // linked list of all previous arrays. This is done because other threads + // could still be accessing elements from the smaller arrays. + circular_array * grow(std::size_t top, std::size_t bottom) + { + circular_array * new_array = new circular_array(size() * 2); + new_array->previous.reset(this); + for (std::size_t i = top; i != bottom; i++) + new_array->put(i, get(i)); + return new_array; + } + }; + + std::atomic array; + std::atomic top, bottom; + + // Convert a 2's complement unsigned value to a signed value. We need to do + // this because (b - t) may not always be positive. + static std::ptrdiff_t to_signed(std::size_t x) + { + // Unsigned to signed conversion is implementation-defined if the value + // doesn't fit, so we convert manually. + static_assert(static_cast(PTRDIFF_MAX) + 1 == static_cast(PTRDIFF_MIN), "Wrong integer wrapping behavior"); + if (x > static_cast(PTRDIFF_MAX)) + return static_cast(x - static_cast(PTRDIFF_MIN)) + PTRDIFF_MIN; + else + return static_cast(x); + } + +public: + work_steal_queue() + : array(new circular_array(32)), top(0), bottom(0) {} + ~work_steal_queue() + { + // Free any unexecuted tasks + std::size_t b = bottom.load(std::memory_order_relaxed); + std::size_t t = top.load(std::memory_order_relaxed); + circular_array * a = array.load(std::memory_order_relaxed); + for (std::size_t i = t; i != b; i++) + task_run_handle::from_void_ptr(a->get(i)); + delete a; + } + + // Push a task to the bottom of this thread's queue + void push(task_run_handle x) + { + std::size_t b = bottom.load(std::memory_order_relaxed); + std::size_t t = top.load(std::memory_order_acquire); + circular_array * a = array.load(std::memory_order_relaxed); + + // Grow the array if it is full + if (to_signed(b - t) >= to_signed(a->size())) { + a = a->grow(t, b); + array.store(a, std::memory_order_release); + } + + // Note that we only convert to void* here in case grow throws due to + // lack of memory. + a->put(b, x.to_void_ptr()); + std::atomic_thread_fence(std::memory_order_release); + bottom.store(b + 1, std::memory_order_relaxed); + } + + // Pop a task from the bottom of this thread's queue + task_run_handle pop() + { + std::size_t b = bottom.load(std::memory_order_relaxed); + + // Early exit if queue is empty + std::size_t t = top.load(std::memory_order_relaxed); + if (to_signed(b - t) <= 0) + return task_run_handle(); + + // Make sure bottom is stored before top is read + bottom.store(--b, std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_seq_cst); + t = top.load(std::memory_order_relaxed); + + // If the queue is empty, restore bottom and exit + if (to_signed(b - t) < 0) { + bottom.store(b + 1, std::memory_order_relaxed); + return task_run_handle(); + } + + // Fetch the element from the queue + circular_array * a = array.load(std::memory_order_relaxed); + void * x = a->get(b); + + // If this was the last element in the queue, check for races + if (b == t) { + if (!top.compare_exchange_strong(t, t + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { + bottom.store(b + 1, std::memory_order_relaxed); + return task_run_handle(); + } + bottom.store(b + 1, std::memory_order_relaxed); + } + return task_run_handle::from_void_ptr(x); + } + + // Steal a task from the top of this thread's queue + task_run_handle steal() + { + // Loop while the compare_exchange fails. This is still lock-free because + // a fail means that another thread has sucessfully stolen a task. + while (true) { + // Make sure top is read before bottom + std::size_t t = top.load(std::memory_order_acquire); + std::atomic_thread_fence(std::memory_order_seq_cst); + std::size_t b = bottom.load(std::memory_order_acquire); + + // Exit if the queue is empty + if (to_signed(b - t) <= 0) + return task_run_handle(); + + // Fetch the element from the queue + circular_array * a = array.load(std::memory_order_consume); + void * x = a->get(t); + + // Attempt to increment top + if (top.compare_exchange_weak(t, t + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) + return task_run_handle::from_void_ptr(x); + } + } +}; + +} // namespace detail +} // namespace async + + + +// for pthread thread_local emulation +#if defined(EMULATE_PTHREAD_THREAD_LOCAL) +# include +#endif + +namespace async { +namespace detail { + +LIBASYNC_EXPORT void* aligned_alloc(std::size_t size, std::size_t align) +{ +#ifdef _WIN32 + void* ptr = _aligned_malloc(size, align); + if (!ptr) + LIBASYNC_THROW(std::bad_alloc()); + return ptr; +#else + void* result; + if (posix_memalign(&result, align, size)) + LIBASYNC_THROW(std::bad_alloc()); + else + return result; +#endif +} + +LIBASYNC_EXPORT void aligned_free(void* addr) LIBASYNC_NOEXCEPT +{ +#ifdef _WIN32 + _aligned_free(addr); +#else + free(addr); +#endif +} + +// Wait for a task to complete (for threads outside thread pool) +static void generic_wait_handler(task_wait_handle wait_task) +{ + // Create an event to wait on + task_wait_event event; + event.init(); + + // Create a continuation for the task we are waiting for + wait_task.on_finish([&event] { + // Just signal the thread event + event.signal(wait_type::task_finished); + }); + + // Wait for the event to be set + event.wait(); +} + +#if defined(EMULATE_PTHREAD_THREAD_LOCAL) +// Wait handler function, per-thread, defaults to generic version +struct pthread_emulation_thread_wait_handler_key_initializer { + pthread_key_t key; + + pthread_emulation_thread_wait_handler_key_initializer() + { + pthread_key_create(&key, nullptr); + } + + ~pthread_emulation_thread_wait_handler_key_initializer() + { + pthread_key_delete(key); + } +}; + +static pthread_key_t get_thread_wait_handler_key() +{ + static pthread_emulation_thread_wait_handler_key_initializer initializer; + return initializer.key; +} + +#else +static THREAD_LOCAL wait_handler thread_wait_handler = generic_wait_handler; +#endif + +static void set_thread_wait_handler(wait_handler handler) +{ +#if defined(EMULATE_PTHREAD_THREAD_LOCAL) + // we need to call this here, because the pthread initializer is lazy, + // this means the it could be null and we need to set it before trying to + // get or set it + pthread_setspecific(get_thread_wait_handler_key(), reinterpret_cast(handler)); +#else + thread_wait_handler = handler; +#endif +} + +static wait_handler get_thread_wait_handler() +{ +#if defined(EMULATE_PTHREAD_THREAD_LOCAL) + // we need to call this here, because the pthread initializer is lazy, + // this means the it could be null and we need to set it before trying to + // get or set it + wait_handler handler = (wait_handler) pthread_getspecific(get_thread_wait_handler_key()); + if(handler == nullptr) { + return generic_wait_handler; + } + return handler; +#else + return thread_wait_handler; +#endif +} + +// Wait for a task to complete +LIBASYNC_EXPORT void wait_for_task(task_base* wait_task) +{ + // Dispatch to the current thread's wait handler + wait_handler thread_wait_handler = get_thread_wait_handler(); + thread_wait_handler(task_wait_handle(wait_task)); +} + +// The default scheduler is just a thread pool which can be configured +// using environment variables. +class default_scheduler_impl: public threadpool_scheduler { + static inline std::size_t num_threads_ = 0; +public: + static void set_num_threads(std::size_t thread_count) + { + num_threads_ = thread_count; + } +private: + + static std::size_t get_num_threads() + { + if (num_threads_ > 0) + return num_threads_; + // Get the requested number of threads from the environment + // If that fails, use the number of CPUs in the system. + std::size_t num_threads; +#ifdef _MSC_VER + char* s; +# ifdef __cplusplus_winrt + // Windows store applications do not support environment variables + s = nullptr; +# else + // MSVC gives an error when trying to use getenv, work around this + // by using _dupenv_s instead. + _dupenv_s(&s, nullptr, "LIBASYNC_NUM_THREADS"); +# endif +#else + const char *s = std::getenv("LIBASYNC_NUM_THREADS"); +#endif + if (s) + num_threads = std::strtoul(s, nullptr, 10); + else + num_threads = hardware_concurrency(); + +#if defined(_MSC_VER) && !defined(__cplusplus_winrt) + // Free the string allocated by _dupenv_s + free(s); +#endif + + // Make sure the thread count is reasonable + if (num_threads < 1) + num_threads = 1; + return num_threads; + } + +public: + default_scheduler_impl() + : threadpool_scheduler(get_num_threads()) {} +}; + +// Thread scheduler implementation +LIBASYNC_EXPORT void thread_scheduler_impl::schedule(task_run_handle t) +{ + // A shared_ptr is used here because not all implementations of + // std::thread support move-only objects. + std::thread([](const std::shared_ptr& t) { + t->run(); + }, std::make_shared(std::move(t))).detach(); +} + +} // namespace detail + +LIBASYNC_EXPORT threadpool_scheduler& default_threadpool_scheduler() +{ + return detail::singleton::get_instance(); +} + +// FIFO scheduler implementation +struct fifo_scheduler::internal_data { + detail::fifo_queue queue; + std::mutex lock; +}; +LIBASYNC_EXPORT fifo_scheduler::fifo_scheduler() + : impl(new internal_data) {} +LIBASYNC_EXPORT fifo_scheduler::~fifo_scheduler() {} +LIBASYNC_EXPORT void fifo_scheduler::schedule(task_run_handle t) +{ + std::lock_guard locked(impl->lock); + impl->queue.push(std::move(t)); +} +LIBASYNC_EXPORT bool fifo_scheduler::try_run_one_task() +{ + task_run_handle t; + { + std::lock_guard locked(impl->lock); + t = impl->queue.pop(); + } + if (t) { + t.run(); + return true; + } + return false; +} +LIBASYNC_EXPORT void fifo_scheduler::run_all_tasks() +{ + while (try_run_one_task()) {} +} + +LIBASYNC_EXPORT std::size_t hardware_concurrency() LIBASYNC_NOEXCEPT +{ + // Cache the value because calculating it may be expensive + static std::size_t value = std::thread::hardware_concurrency(); + + // Always return at least 1 core + return value == 0 ? 1 : value; +} + +LIBASYNC_EXPORT wait_handler set_thread_wait_handler(wait_handler handler) LIBASYNC_NOEXCEPT +{ + wait_handler old = detail::get_thread_wait_handler(); + detail::set_thread_wait_handler(handler); + return old; +} + + + + + + + +namespace detail { + +// Per-thread data, aligned to cachelines to avoid false sharing +struct LIBASYNC_CACHELINE_ALIGN thread_data_t { + work_steal_queue queue; + std::minstd_rand rng; + std::thread handle; +}; + +// Internal data used by threadpool_scheduler +struct threadpool_data { + threadpool_data(std::size_t num_threads) + : thread_data(num_threads), shutdown(false), num_waiters(0), waiters(new task_wait_event * [num_threads]) {} + + threadpool_data(std::size_t num_threads, std::function && prerun_, std::function && postrun_) + : thread_data(num_threads), shutdown(false), num_waiters(0), waiters(new task_wait_event * [num_threads]), + prerun(std::move(prerun_)), postrun(std::move(postrun_)) {} + + // Mutex protecting everything except thread_data + std::mutex lock; + + // Array of per-thread data + aligned_array thread_data; + + // Global queue for tasks from outside the pool + fifo_queue public_queue; + + // Shutdown request indicator + bool shutdown; + + // List of threads waiting for tasks to run. num_waiters needs to be atomic + // because it is sometimes read outside the mutex. + std::atomic num_waiters; + std::unique_ptr waiters; + + // Pre/Post run functions. + std::function prerun; + std::function postrun; + +#ifdef BROKEN_JOIN_IN_DESTRUCTOR + // Shutdown complete event, used instead of thread::join() + std::size_t shutdown_num_threads; + std::condition_variable shutdown_complete_event; +#endif +}; + +// this wrapper encapsulates both the owning_threadpool pointer and the thread id. +// this is done to improve performance on the emulated thread_local reducing the number +// of calls to "pthread_getspecific" +struct threadpool_data_wrapper { + threadpool_data * owning_threadpool; + std::size_t thread_id; + + threadpool_data_wrapper(threadpool_data * owning_threadpool, std::size_t thread_id) : + owning_threadpool(owning_threadpool), thread_id(thread_id) { } +}; + +#if defined(EMULATE_PTHREAD_THREAD_LOCAL) +struct pthread_emulation_threadpool_data_initializer { + pthread_key_t key; + + pthread_emulation_threadpool_data_initializer() + { + pthread_key_create(&key, [](void * wrapper_ptr) { + threadpool_data_wrapper * wrapper = static_cast(wrapper_ptr); + delete wrapper; + }); + } + + ~pthread_emulation_threadpool_data_initializer() + { + pthread_key_delete(key); + } +}; + +static pthread_key_t get_local_threadpool_data_key() +{ + static pthread_emulation_threadpool_data_initializer initializer; + return initializer.key; +} + +#else +// Thread pool this thread belongs to, or null if not in pool +static THREAD_LOCAL threadpool_data * owning_threadpool = nullptr; + +// Current thread's index in the pool +static THREAD_LOCAL std::size_t thread_id; +#endif + +static void create_threadpool_data(threadpool_data * owning_threadpool_, std::size_t thread_id_) +{ +#if defined(EMULATE_PTHREAD_THREAD_LOCAL) + // the memory allocated here gets deallocated by the lambda declared on the key creation + pthread_setspecific(get_local_threadpool_data_key(), new threadpool_data_wrapper(owning_threadpool_, thread_id_)); +#else + owning_threadpool = owning_threadpool_; + thread_id = thread_id_; +#endif +} + +static threadpool_data_wrapper get_threadpool_data_wrapper() +{ +#if defined(EMULATE_PTHREAD_THREAD_LOCAL) + threadpool_data_wrapper * wrapper = static_cast(pthread_getspecific(get_local_threadpool_data_key())); + if (wrapper == nullptr) { + // if, for some reason, the wrapper is not set, this won't cause a crash + return threadpool_data_wrapper(nullptr, 0); + } + return *wrapper; +#else + return threadpool_data_wrapper(owning_threadpool, thread_id); +#endif +} + +// Try to steal a task from another thread's queue +static task_run_handle steal_task(threadpool_data * impl, std::size_t thread_id) +{ + // Make a list of victim thread ids and shuffle it + std::vector victims(impl->thread_data.size()); + std::iota(victims.begin(), victims.end(), 0); + std::shuffle(victims.begin(), victims.end(), impl->thread_data[thread_id].rng); + + // Try to steal from another thread + for (std::size_t i : victims) { + // Don't try to steal from ourself + if (i == thread_id) + continue; + + if (task_run_handle t = impl->thread_data[i].queue.steal()) + return t; + } + + // No tasks found, but we might have missed one if it was just added. In + // practice this doesn't really matter since it will be handled by another + // thread. + return task_run_handle(); +} + +// Main task stealing loop which is used by worker threads when they have +// nothing to do. +static void thread_task_loop(threadpool_data * impl, std::size_t thread_id, task_wait_handle wait_task) +{ + // Get our thread's data + thread_data_t & current_thread = impl->thread_data[thread_id]; + + // Flag indicating if we have added a continuation to the task + bool added_continuation = false; + + // Event to wait on + task_wait_event event; + + // Loop while waiting for the task to complete + while (true) { + // Check if the task has finished. If we have added a continuation, we + // need to make sure the event has been signaled, otherwise the other + // thread may try to signal it after we have freed it. + if (wait_task && (added_continuation ? event.try_wait(wait_type::task_finished) : wait_task.ready())) + return; + + // Try to get a task from the local queue + if (task_run_handle t = current_thread.queue.pop()) { + t.run(); + continue; + } + + // Stealing loop + while (true) { + // Try to steal a task + if (task_run_handle t = steal_task(impl, thread_id)) { + t.run(); + break; + } + + // Try to fetch from the public queue + std::unique_lock locked(impl->lock); + if (task_run_handle t = impl->public_queue.pop()) { + // Don't hold the lock while running the task + locked.unlock(); + t.run(); + break; + } + + // If shutting down and we don't have a task to wait for, return. + if (!wait_task && impl->shutdown) { +#ifdef BROKEN_JOIN_IN_DESTRUCTOR + // Notify once all worker threads have exited + if (--impl->shutdown_num_threads == 0) + impl->shutdown_complete_event.notify_one(); +#endif + return; + } + + // Initialize the event object + event.init(); + + // No tasks found, so sleep until something happens. + // If a continuation has not been added yet, add it. + if (wait_task && !added_continuation) { + // Create a continuation for the task we are waiting for + wait_task.on_finish([&event]{ + // Signal the thread's event + event.signal(wait_type::task_finished); + }); + added_continuation = true; + } + + // Add our thread to the list of waiting threads + size_t num_waiters_val = impl->num_waiters.load(std::memory_order_relaxed); + impl->waiters[num_waiters_val] = &event; + impl->num_waiters.store(num_waiters_val + 1, std::memory_order_relaxed); + + // Wait for our event to be signaled when a task is scheduled or + // the task we are waiting for has completed. + locked.unlock(); + int events = event.wait(); + locked.lock(); + + // Remove our thread from the list of waiting threads + num_waiters_val = impl->num_waiters.load(std::memory_order_relaxed); + for (std::size_t i = 0; i < num_waiters_val; i++) { + if (impl->waiters[i] == &event) { + if (i != num_waiters_val - 1) + std::swap(impl->waiters[i], impl->waiters[num_waiters_val - 1]); + impl->num_waiters.store(num_waiters_val - 1, std::memory_order_relaxed); + break; + } + } + + // Check again if the task has finished. We have added a + // continuation at this point, so we need to check that the + // continuation has finished signaling the event. + if (wait_task && (events & wait_type::task_finished)) + return; + } + } +} + +// Wait for a task to complete (for worker threads inside thread pool) +static void threadpool_wait_handler(task_wait_handle wait_task) +{ + threadpool_data_wrapper wrapper = get_threadpool_data_wrapper(); + thread_task_loop(wrapper.owning_threadpool, wrapper.thread_id, wait_task); +} + +// Worker thread main loop +static void worker_thread(threadpool_data * owning_threadpool, std::size_t thread_id) +{ + // store on the local thread data + create_threadpool_data(owning_threadpool, thread_id); + + // Set the wait handler so threads from the pool do useful work while + // waiting for another task to finish. + async::detail::set_thread_wait_handler(threadpool_wait_handler); + + // Seed the random number generator with our id. This gives each thread a + // different steal order. + owning_threadpool->thread_data[thread_id].rng.seed(static_cast(thread_id)); + + // Prerun hook + if (owning_threadpool->prerun) owning_threadpool->prerun(); + + // Main loop, runs until the shutdown signal is recieved + thread_task_loop(owning_threadpool, thread_id, task_wait_handle()); + + // Postrun hook + if (owning_threadpool->postrun) owning_threadpool->postrun(); +} + +// Recursive function to spawn all worker threads in parallel +static void recursive_spawn_worker_thread(threadpool_data * impl, std::size_t index, std::size_t threads) +{ + // If we are down to one thread, go to the worker main loop + if (threads == 1) + worker_thread(impl, index); + else { + // Split thread range into 2 sub-ranges + std::size_t mid = index + threads / 2; + + // Spawn a thread for half of the range + impl->thread_data[mid].handle = std::thread(recursive_spawn_worker_thread, impl, mid, threads - threads / 2); +#ifdef BROKEN_JOIN_IN_DESTRUCTOR + impl->thread_data[mid].handle.detach(); +#endif + + // Tail-recurse to handle our half of the range + recursive_spawn_worker_thread(impl, index, threads / 2); + } +} + +} // namespace detail + + + + + + +LIBASYNC_EXPORT threadpool_scheduler::threadpool_scheduler(threadpool_scheduler && other) + : impl(std::move(other.impl)) {} + +LIBASYNC_EXPORT threadpool_scheduler::threadpool_scheduler(std::size_t num_threads) + : impl(new detail::threadpool_data(num_threads)) +{ + // Start worker threads + impl->thread_data[0].handle = std::thread(detail::recursive_spawn_worker_thread, impl.get(), 0, num_threads); +#ifdef BROKEN_JOIN_IN_DESTRUCTOR + impl->thread_data[0].handle.detach(); +#endif +} + +LIBASYNC_EXPORT threadpool_scheduler::threadpool_scheduler(std::size_t num_threads, + std::function && prerun, + std::function && postrun) + : impl(new detail::threadpool_data(num_threads, std::move(prerun), std::move(postrun))) +{ + // Start worker threads + impl->thread_data[0].handle = std::thread(detail::recursive_spawn_worker_thread, impl.get(), 0, num_threads); +#ifdef BROKEN_JOIN_IN_DESTRUCTOR + impl->thread_data[0].handle.detach(); +#endif +} + + +// Wait for all currently running tasks to finish +LIBASYNC_EXPORT threadpool_scheduler::~threadpool_scheduler() +{ + if (!impl) return; +#ifdef _WIN32 + // Windows kills all threads except one on process exit before calling + // global destructors in DLLs. Waiting for dead threads to exit will likely + // result in deadlocks, so we just exit early if we detect that the process + // is exiting. + auto RtlDllShutdownInProgress = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlDllShutdownInProgress")); + if (RtlDllShutdownInProgress && RtlDllShutdownInProgress()) { +# ifndef BROKEN_JOIN_IN_DESTRUCTOR + // We still need to detach the thread handles otherwise the std::thread + // destructor will throw an exception. + for (std::size_t i = 0; i < impl->thread_data.size(); i++) { + try { + impl->thread_data[i].handle.detach(); + } + catch (...) {} + } +# endif + return; + } +#endif + + { + std::unique_lock locked(impl->lock); + + // Signal shutdown + impl->shutdown = true; + + // Wake up any sleeping threads + size_t num_waiters_val = impl->num_waiters.load(std::memory_order_relaxed); + for (std::size_t i = 0; i < num_waiters_val; i++) + impl->waiters[i]->signal(detail::wait_type::task_available); + impl->num_waiters.store(0, std::memory_order_relaxed); + +#ifdef BROKEN_JOIN_IN_DESTRUCTOR + // Wait for the threads to exit + impl->shutdown_num_threads = impl->thread_data.size(); + impl->shutdown_complete_event.wait(locked); +#endif + } + +#ifndef BROKEN_JOIN_IN_DESTRUCTOR + // Wait for the threads to exit + for (std::size_t i = 0; i < impl->thread_data.size(); i++) + impl->thread_data[i].handle.join(); +#endif +} + +// Schedule a task on the thread pool +LIBASYNC_EXPORT void threadpool_scheduler::schedule(task_run_handle t) +{ + detail::threadpool_data_wrapper wrapper = detail::get_threadpool_data_wrapper(); + + // Check if we are in the thread pool + if (wrapper.owning_threadpool == impl.get()) { + // Push the task onto our task queue + impl->thread_data[wrapper.thread_id].queue.push(std::move(t)); + + // If there are no sleeping threads, just return. We check outside the + // lock to avoid locking overhead in the fast path. + if (impl->num_waiters.load(std::memory_order_relaxed) == 0) + return; + + // Get a thread to wake up from the list + std::lock_guard locked(impl->lock); + + // Check again if there are waiters + size_t num_waiters_val = impl->num_waiters.load(std::memory_order_relaxed); + if (num_waiters_val == 0) + return; + + // Pop a thread from the list and wake it up + impl->waiters[num_waiters_val - 1]->signal(detail::wait_type::task_available); + impl->num_waiters.store(num_waiters_val - 1, std::memory_order_relaxed); + } + else { + std::lock_guard locked(impl->lock); + + // Push task onto the public queue + impl->public_queue.push(std::move(t)); + + // Wake up a sleeping thread + size_t num_waiters_val = impl->num_waiters.load(std::memory_order_relaxed); + if (num_waiters_val == 0) + return; + impl->waiters[num_waiters_val - 1]->signal(detail::wait_type::task_available); + impl->num_waiters.store(num_waiters_val - 1, std::memory_order_relaxed); + } +} + +} // namespace async + +#ifndef LIBASYNC_STATIC +#if defined(__GNUC__) && !defined(_WIN32) +# pragma GCC visibility pop +#endif +#endif diff --git a/include/aegis/channel.hpp b/include/aegis/channel.hpp index 88898081..2d2527a6 100644 --- a/include/aegis/channel.hpp +++ b/include/aegis/channel.hpp @@ -51,7 +51,7 @@ struct create_message_t std::string _content; json _embed; int64_t _nonce = 0; - lib::optional _file; + std::optional _file; }; struct edit_message_t @@ -96,15 +96,15 @@ struct modify_channel_t } modify_channel_t & parent_id(snowflake param) { _parent_id = param; return *this; } modify_channel_t & rate_limit_per_user(int param) { _rate_limit_per_user = param; return *this; } - lib::optional _name = {}; - lib::optional _position = {}; - lib::optional _topic = {}; - lib::optional _nsfw = {}; - lib::optional _bitrate = {}; - lib::optional _user_limit = {}; - lib::optional> _permission_overwrites = {}; - lib::optional _parent_id = {}; - lib::optional _rate_limit_per_user = {}; + std::optional _name = {}; + std::optional _position = {}; + std::optional _topic = {}; + std::optional _nsfw = {}; + std::optional _bitrate = {}; + std::optional _user_limit = {}; + std::optional> _permission_overwrites = {}; + std::optional _parent_id = {}; + std::optional _rate_limit_per_user = {}; }; struct create_reaction_t @@ -159,10 +159,10 @@ struct create_channel_invite_t create_channel_invite_t & max_uses(int param) { _max_uses = param; return *this; } create_channel_invite_t & temporary(bool param) { _temporary = param; return *this; } create_channel_invite_t & unique(bool param) { _unique = param; return *this; } - lib::optional _max_age; - lib::optional _max_uses; - lib::optional _temporary; - lib::optional _unique; + std::optional _max_age; + std::optional _max_uses; + std::optional _temporary; + std::optional _unique; }; #pragma endregion @@ -215,7 +215,7 @@ class channel */ std::string get_name() const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _name = name; return std::move(_name); } @@ -226,7 +226,7 @@ class channel */ std::string get_topic() const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _topic = topic; return std::move(_topic); } @@ -287,103 +287,103 @@ class channel /// Send message to this channel /** * @param content A string of the message to send - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_message(const std::string & content, int64_t nonce = 0); + AEGIS_DECL async::task create_message(const std::string & content, int64_t nonce = 0); /// Send message to this channel /** * @see aegis::create_message_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_message(create_message_t obj); + AEGIS_DECL async::task create_message(create_message_t obj); /// Send an embed message to this channel /**\deprecated * @param content A string of the message to send * @param embed A json object of the embed object itself - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_message_embed(const std::string & content, const json & embed, int64_t nonce = 0); + AEGIS_DECL async::task create_message_embed(const std::string & content, const json & embed, int64_t nonce = 0); /// Send an embed message to this channel /**\deprecated * @see aegis::create_message_embed_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_message_embed(create_message_t obj); + AEGIS_DECL async::task create_message_embed(create_message_t obj); /// Get message from this channel /** * @param message_id Snowflake of the message to retrieve - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future get_message(snowflake message_id); + AEGIS_DECL async::task get_message(snowflake message_id); /// Get multiple messages from this channel /** * @see aegis::get_messages_t * @param obj Struct of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future get_messages(get_messages_t obj); + AEGIS_DECL async::task get_messages(get_messages_t obj); /// Edit a message in this channel /** * @param message_id Snowflake of the message to replace. Must be your own message * @param content A string of the message to set - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future edit_message(snowflake message_id, const std::string & content); + AEGIS_DECL async::task edit_message(snowflake message_id, const std::string & content); /// Edit a message in this channel /** * @see aegis::edit_message_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future edit_message(edit_message_t obj); + AEGIS_DECL async::task edit_message(edit_message_t obj); /// Edit an embed message in this channel /**\deprecated * @param message_id Snowflake of the message to replace. Must be your own message * @param content A string of the message to set * @param embed A json object of the embed object itself - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future edit_message_embed(snowflake message_id, const std::string & content, const json & embed); + AEGIS_DECL async::task edit_message_embed(snowflake message_id, const std::string & content, const json & embed); /// Edit an embed message in this channel /**\deprecated * @see aegis::edit_message_embed_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future edit_message_embed(edit_message_t obj); + AEGIS_DECL async::task edit_message_embed(edit_message_t obj); /// Delete a message /** * @param message_id Snowflake of the message to delete - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future delete_message(snowflake message_id); + AEGIS_DECL async::task delete_message(snowflake message_id); /// Delete up to 100 messages at once /** * @param message Vector of up to 100 message int64_t to delete - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future bulk_delete_message(const std::vector & messages); + AEGIS_DECL async::task bulk_delete_message(const std::vector & messages); /// Delete up to 100 messages at once /** * @param message Vector of up to 100 message snowflakes to delete - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future bulk_delete_message(const std::vector & messages); + AEGIS_DECL async::task bulk_delete_message(const std::vector & messages); /// Modify this channel (all parameters optional) /** @@ -395,22 +395,22 @@ class channel * @param _user_limit Integer of the channel max user limit (VOICE CHANNEL ONLY) * @param _permission_overwrites Vector of permission_overwrite objects for overriding permissions * @param _parent_id Snowflake of category channel belongs in (empty parent puts channel in no category) - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future modify_channel(lib::optional _name = {}, - lib::optional _position = {}, lib::optional _topic = {}, - lib::optional _nsfw = {}, lib::optional _bitrate = {}, - lib::optional _user_limit = {}, - lib::optional> _permission_overwrites = {}, - lib::optional _parent_id = {}, lib::optional _rate_limit_per_user = {}); + AEGIS_DECL async::task modify_channel(std::optional _name = {}, + std::optional _position = {}, std::optional _topic = {}, + std::optional _nsfw = {}, std::optional _bitrate = {}, + std::optional _user_limit = {}, + std::optional> _permission_overwrites = {}, + std::optional _parent_id = {}, std::optional _rate_limit_per_user = {}); /// Modify this channel (all parameters optional) /** * @see aegis::modify_channel_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future modify_channel(modify_channel_t obj) + AEGIS_DECL async::task modify_channel(modify_channel_t obj) { return modify_channel(obj._name, obj._position, obj._topic, obj._nsfw, obj._bitrate, obj._user_limit, obj._permission_overwrites, @@ -419,25 +419,25 @@ class channel /// Delete this channel /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future delete_channel(); + AEGIS_DECL async::task delete_channel(); /// Add new reaction on message /** * @param message_id Snowflake of message * @param emoji_text Text of emoji being added `name:snowflake` - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_reaction(snowflake message_id, const std::string & emoji_text); + AEGIS_DECL async::task create_reaction(snowflake message_id, const std::string & emoji_text); /// Add new reaction on message /** * @see aegis::create_reaction_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_reaction(create_reaction_t obj) + AEGIS_DECL async::task create_reaction(create_reaction_t obj) { return create_reaction(obj._message_id, obj._emoji_text); } @@ -446,17 +446,17 @@ class channel /** * @param message_id Snowflake of message * @param emoji_text Text of emoji being added `name:snowflake` - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future delete_own_reaction(snowflake message_id, const std::string & emoji_text); + AEGIS_DECL async::task delete_own_reaction(snowflake message_id, const std::string & emoji_text); /// Delete own reaction on message /** * @see aegis::delete_own_reaction_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future delete_own_reaction(delete_own_reaction_t obj) + AEGIS_DECL async::task delete_own_reaction(delete_own_reaction_t obj) { return delete_own_reaction(obj._message_id, obj._emoji_text); } @@ -466,17 +466,17 @@ class channel * @param message_id Snowflake of message * @param emoji_text Text of emoji being added `name:snowflake` * @param member_id Snowflake of member to remove emoji from - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future delete_user_reaction(snowflake message_id, const std::string & emoji_text, snowflake member_id); + AEGIS_DECL async::task delete_user_reaction(snowflake message_id, const std::string & emoji_text, snowflake member_id); /// Delete specified member reaction on message /** * @see aegis::delete_user_reaction_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future delete_user_reaction(delete_user_reaction_t obj) + AEGIS_DECL async::task delete_user_reaction(delete_user_reaction_t obj) { return delete_user_reaction(obj._message_id, obj._emoji_text, obj._member_id); } @@ -485,17 +485,17 @@ class channel /** * @param message_id Snowflake of message * @param emoji_text Text of emoji being added `name:snowflake` - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future get_reactions(snowflake message_id, const std::string & emoji_text); + AEGIS_DECL async::task get_reactions(snowflake message_id, const std::string & emoji_text); /// Get all reactions for this message /** * @see aegis::get_reactions_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future get_reactions(get_reactions_t obj) + AEGIS_DECL async::task get_reactions(get_reactions_t obj) { return get_reactions(obj._message_id, obj._emoji_text); } @@ -503,9 +503,9 @@ class channel /// Delete all reactions by message /** * @param message_id Snowflake of message - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future delete_all_reactions(snowflake message_id); + AEGIS_DECL async::task delete_all_reactions(snowflake message_id); /// Edit channel permission override /** @@ -513,17 +513,17 @@ class channel * @param _allow Int64 allow flags * @param _deny Int64 deny flags * @param _type Type of override (role/user) - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future edit_channel_permissions(snowflake _overwrite_id, int64_t _allow, int64_t _deny, const std::string & _type); + AEGIS_DECL async::task edit_channel_permissions(snowflake _overwrite_id, int64_t _allow, int64_t _deny, const std::string & _type); /// Edit channel permission override /** * @see aegis::edit_channel_permissions_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future edit_channel_permissions(edit_channel_permissions_t obj) + AEGIS_DECL async::task edit_channel_permissions(edit_channel_permissions_t obj) { return edit_channel_permissions(obj._overwrite_id, obj._allow, obj._deny, obj._type); } @@ -552,9 +552,9 @@ class channel /// Get active channel invites /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future get_channel_invites(); + AEGIS_DECL async::task get_channel_invites(); /// Create a new channel invite /** @@ -562,20 +562,20 @@ class channel * @param max_uses The max uses this invite code allows * @param temporary Is this invite code temporary * @param unique Is this invite code a unique one-use - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_channel_invite(const lib::optional max_age, - const lib::optional max_uses, - const lib::optional temporary, - const lib::optional unique); + AEGIS_DECL async::task create_channel_invite(const std::optional max_age, + const std::optional max_uses, + const std::optional temporary, + const std::optional unique); /// Create a new channel invite /** * @see aegis::create_channel_invite_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_channel_invite(create_channel_invite_t obj) + AEGIS_DECL async::task create_channel_invite(create_channel_invite_t obj) { return create_channel_invite(obj._max_age, obj._max_uses, obj._temporary, obj._unique); } @@ -583,45 +583,45 @@ class channel /// Delete channel permission override /** * @param overwrite_id Snowflake of the channel permission to delete - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future delete_channel_permission(snowflake overwrite_id); + AEGIS_DECL async::task delete_channel_permission(snowflake overwrite_id); /// Trigger typing indicator in channel (lasts 10 seconds) /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future trigger_typing_indicator(); + AEGIS_DECL async::task trigger_typing_indicator(); /// Get pinned messages in channel /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future get_pinned_messages(); + AEGIS_DECL async::task get_pinned_messages(); /// Add a pinned message in channel /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future add_pinned_channel_message(snowflake message_id); + AEGIS_DECL async::task add_pinned_channel_message(snowflake message_id); /// Delete a pinned message in channel /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future delete_pinned_channel_message(snowflake message_id); + AEGIS_DECL async::task delete_pinned_channel_message(snowflake message_id); /// Add member to a group direct message /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future group_dm_add_recipient(snowflake user_id); + AEGIS_DECL async::task group_dm_add_recipient(snowflake user_id); /// Remove member from a group direct message /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future group_dm_remove_recipient(snowflake user_id); + AEGIS_DECL async::task group_dm_remove_recipient(snowflake user_id); /// Get parent channel /** @@ -672,7 +672,7 @@ class channel /** * @returns Returns a std::shared_mutex reference for the channel object */ - shared_mutex & mtx() const noexcept + std::shared_mutex & mtx() const noexcept { return _m; } @@ -714,7 +714,7 @@ class channel uint16_t rate_limit_per_user = 0; /**< Amount of seconds a user must wait before sending another message */ #endif asio::io_context & _io_context; - mutable shared_mutex _m; + mutable std::shared_mutex _m; core * _bot = nullptr; ratelimit::ratelimit_mgr & _ratelimit; }; diff --git a/include/aegis/config.hpp b/include/aegis/config.hpp index 56e62324..2dfb1504 100644 --- a/include/aegis/config.hpp +++ b/include/aegis/config.hpp @@ -83,98 +83,15 @@ // Workaround for Microsoft Visual C++ __cplusplus value #if defined(AEGIS_MSVC) -# if (_MSVC_LANG < 201402) -# error AegisLib requires C++14 or greater +# if (_MSVC_LANG < 201703) +# error AegisLib requires C++17 or greater # endif #else -# if (__cplusplus < 201402) -# error AegisLib requires C++14 or greater +# if (__cplusplus < 201703) +# error AegisLib requires C++17 or greater # endif #endif -#if (__cplusplus >= 201703) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703) || (defined(_HAS_CXX17) && _HAS_CXX17 != 0) -# define AEGIS_CXX17 -#endif // (__cplusplus >= 201703) || (_MSVC_LANG >= 201703) - -// Support for std::optional over built-in -#if !defined(AEGIS_HAS_STD_OPTIONAL) -# if (__cplusplus >= 201703) -# if __has_include() -# include -namespace aegis::lib -{ -template using optional = std::optional; -constexpr auto nullopt = std::nullopt; -using bad_optional_access = std::bad_optional_access; -} -# define AEGIS_HAS_STD_OPTIONAL 1 -# elif __has_include() -# include -namespace aegis::lib -{ -template using optional = std::experimental::optional; -constexpr auto nullopt = std::experimental::nullopt; -using bad_optional_access = std::experimental::bad_optional_access; -} -# define AEGIS_HAS_STD_OPTIONAL 1 -# endif // __has_include() -# elif (__cplusplus >= 201402) // c++14 -# include "aegis/optional.hpp" -namespace aegis -{ -namespace lib -{ -template using optional = std::experimental::optional; -constexpr auto nullopt = std::experimental::nullopt; -using bad_optional_access = std::experimental::bad_optional_access; -} -} -# define AEGIS_HAS_STD_OPTIONAL 1 -# define AEGIS_HAS_BUILTIN_OPTIONAL 1 -# endif // (__cplusplus >= 201703) -# if defined(AEGIS_MSVC) -# if (_MSVC_LANG < 201703) -# include "aegis/optional.hpp" -namespace aegis -{ -namespace lib -{ -template using optional = std::experimental::optional; -constexpr auto nullopt = std::experimental::nullopt; -using bad_optional_access = std::experimental::bad_optional_access; -} -} -# define AEGIS_HAS_STD_OPTIONAL 1 -# define AEGIS_HAS_BUILTIN_OPTIONAL 1 -# else -# include -namespace aegis::lib -{ -template using optional = std::optional; -constexpr auto nullopt = std::nullopt; -using bad_optional_access = std::bad_optional_access; -} -# define AEGIS_HAS_STD_OPTIONAL 1 -# endif // (_MSC_VER >= 1910 && _HAS_CXX17) -# endif // defined(AEGIS_MSVC) -#endif // !defined(AEGIS_HAS_STD_OPTIONAL) - -#if !defined(AEGIS_HAS_STD_OPTIONAL) -# error Could not find a suitable optional library. -#endif - -// use std::shared_timed_mutex on C++14 or shared_mutex on C++17 -#if !defined(AEGIS_HAS_STD_SHARED_MUTEX) -# if !defined(AEGIS_DISABLE_STD_SHARED_MUTEX) -# if (__cplusplus >= 201703) || (_MSVC_LANG >= 201703) -# define AEGIS_HAS_STD_SHARED_MUTEX 1 -# endif // (__cplusplus >= 201703) || (_MSVC_LANG >= 201703) -# endif // !defined(AEGIS_DISABLE_STD_SHARED_MUTEX) -# if !defined(AEGIS_HAS_STD_SHARED_MUTEX) -# define AEGIS_HAS_STD_SHARED_MUTEX 0 -# endif // !defined(AEGIS_HAS_STD_SHARED_MUTEX) -#endif // !defined(AEGIS_HAS_STD_SHARED_MUTEX) - #if !defined(NDEBUG) #include #endif diff --git a/include/aegis/core.hpp b/include/aegis/core.hpp index 25385e82..f186baf9 100644 --- a/include/aegis/core.hpp +++ b/include/aegis/core.hpp @@ -42,14 +42,13 @@ #include #include +#include "async++.hpp" + +using namespace std::chrono_literals; + namespace aegis { using namespace nlohmann; -#if (AEGIS_HAS_STD_SHARED_MUTEX == 1) -using shared_mutex = std::shared_mutex; -#else -using shared_mutex = std::shared_timed_mutex; -#endif struct create_message_t; @@ -111,13 +110,13 @@ struct create_guild_t create_guild_t & channels(const std::vector> & param) { _channels = param; return *this; } std::string _name; - lib::optional _voice_region; - lib::optional _verification_level; - lib::optional _default_message_notifications; - lib::optional _explicit_content_filter; - lib::optional _icon; - lib::optional> _roles; - lib::optional>> _channels; + std::optional _voice_region; + std::optional _verification_level; + std::optional _default_message_notifications; + std::optional _explicit_content_filter; + std::optional _icon; + std::optional> _roles; + std::optional>> _channels; }; /// Class for fluent definition of bot parameters @@ -269,13 +268,13 @@ class core * * @param roles vector of roles to create * @param channels vector of channels to create - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_guild( - std::string name, lib::optional voice_region = {}, lib::optional verification_level = {}, - lib::optional default_message_notifications = {}, lib::optional explicit_content_filter = {}, - lib::optional icon = {}, lib::optional> roles = {}, - lib::optional>> channels = {} + AEGIS_DECL async::task create_guild( + std::string name, std::optional voice_region = {}, std::optional verification_level = {}, + std::optional default_message_notifications = {}, std::optional explicit_content_filter = {}, + std::optional icon = {}, std::optional> roles = {}, + std::optional>> channels = {} ); /// Create new guild - Unique case. Does not belong to any ratelimit bucket so it is run @@ -284,23 +283,23 @@ class core /** * @see aegis::create_guild_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_guild(create_guild_t obj); + AEGIS_DECL async::task create_guild(create_guild_t obj); /// Changes bot's username (not implemented yet. username can be changed in developer panel) /** * @param username String of the username to set - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future modify_bot_username(const std::string & username); + AEGIS_DECL async::task modify_bot_username(const std::string & username); /// Changes bot's avatar (not implemented yet. avatar can be changed in developer panel) /** * @param avatar String of the avatar to set - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future modify_bot_avatar(const std::string & avatar); + AEGIS_DECL async::task modify_bot_avatar(const std::string & avatar); /// Starts the shard manager, creates the shards, and connects to the gateway AEGIS_DECL void run(); @@ -482,17 +481,17 @@ class core * @param id Snowflake of member to message * @param content string of message to send * @param nonce Unique id to track when message verifies (can be omitted) - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_dm_message(snowflake member_id, const std::string & content, int64_t nonce = 0); + AEGIS_DECL async::task create_dm_message(snowflake member_id, const std::string & content, int64_t nonce = 0); /// Send a direct message to a user /** * @see aegis::create_message_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_dm_message(const create_message_t & obj); + AEGIS_DECL async::task create_dm_message(const create_message_t & obj); /// Return bot uptime as {days hours minutes seconds} /** @@ -826,19 +825,19 @@ class core */ AEGIS_DECL void reduce_threads(std::size_t count) noexcept; - AEGIS_DECL aegis::future create_message(snowflake channel_id, const std::string& msg, int64_t nonce = 0, bool perform_lookup = false) noexcept; + AEGIS_DECL async::task create_message(snowflake channel_id, const std::string& msg, int64_t nonce = 0, bool perform_lookup = false); - AEGIS_DECL aegis::future create_message_embed(snowflake channel_id, const std::string& msg, const json& _obj, int64_t nonce = 0, bool perform_lookup = false) noexcept; + AEGIS_DECL async::task create_message_embed(snowflake channel_id, const std::string& msg, const json& _obj, int64_t nonce = 0, bool perform_lookup = false); /// Run async task /** * This function will queue your task (a lambda or std::function) within Asio for execution at a later time - * This version will return an aegis::future of your type that the passed function returns that you may + * This version will return an async::task of your type that the passed function returns that you may * chain a continuation onto and receive that value within in * * Example: * @code{.cpp} - * aegis::future message = async([]{ + * async::task message = async([]{ * return 5; * }).then([](int value){ * return std::string("result received"); @@ -846,38 +845,22 @@ class core * @endcode * * @param f Function to run async - * @returns aegis::future + * @returns async::task */ template, typename = std::enable_if_t::value>> - aegis::future async(T f) noexcept + async::task async(T f) noexcept { - std::atomic_thread_fence(std::memory_order_acquire); - aegis::promise pr(_io_context.get(), &_global_m); - auto fut = pr.get_future(); - - asio::post(*_io_context, [pr = std::move(pr), f = std::move(f)]() mutable - { - try - { - pr.set_value(f()); - } - catch (std::exception &) - { - pr.set_exception(std::current_exception()); - } - }); - std::atomic_thread_fence(std::memory_order_release); - return fut; + return async::spawn(f); } /// Run async task /** * This function will queue your task (a lambda or std::function) within Asio for execution at a later time - * This version will return an aegis::future that will still allow chaining continuations on + * This version will return an async::task that will still allow chaining continuations on * * Example: * @code{.cpp} - * aegis::future message = async([]{ + * async::task message = async([]{ * return; * }).then([](){ * return std::string("continuation executed"); @@ -885,39 +868,22 @@ class core * @endcode * * @param f Function to run async - * @returns aegis::future + * @returns async::task */ template>::value>> - aegis::future async(T f) noexcept + async::task async(T f) noexcept { - std::atomic_thread_fence(std::memory_order_acquire); - aegis::promise pr(_io_context.get(), &_global_m); - auto fut = pr.get_future(); - - asio::post(*_io_context, [pr = std::move(pr), f = std::move(f)]() mutable - { - try - { - f(); - pr.set_value(); - } - catch (std::exception & e) - { - pr.set_exception(std::current_exception()); - } - }); - std::atomic_thread_fence(std::memory_order_release); - return fut; + return async::spawn(f); } /// Get the internal guild mutex - shared_mutex & get_guild_mutex() { return _guild_m; } + std::shared_mutex & get_guild_mutex() { return _guild_m; } /// Get the internal channel mutex - shared_mutex & get_channel_mutex() { return _channel_m; } + std::shared_mutex & get_channel_mutex() { return _channel_m; } /// Get the internal user mutex - shared_mutex & get_user_mutex() { return _user_m; } + std::shared_mutex & get_user_mutex() { return _user_m; } /// Get the guild map /** @@ -927,7 +893,7 @@ class core * * Example: * @code{.cpp} - * std::shared_lock l(get_guild_mutex()); + * std::shared_lock l(get_guild_mutex()); * @endcode * * @returns std::unordered_map> @@ -942,7 +908,7 @@ class core * * Example: * @code{.cpp} - * std::shared_lock l(get_channel_mutex()); + * std::shared_lock l(get_channel_mutex()); * @endcode * * @returns std::unordered_map> @@ -958,7 +924,7 @@ class core * * Example: * @code{.cpp} - * std::shared_lock l(get_user_mutex()); + * std::shared_lock l(get_user_mutex()); * @endcode * * @returns std::unordered_map> @@ -1136,10 +1102,10 @@ class core std::unordered_map> ws_handlers; spdlog::level::level_enum _loglevel = spdlog::level::level_enum::info; - mutable shared_mutex _shard_m; - mutable shared_mutex _guild_m; - mutable shared_mutex _channel_m; - mutable shared_mutex _user_m; + mutable std::shared_mutex _shard_m; + mutable std::shared_mutex _guild_m; + mutable std::shared_mutex _channel_m; + mutable std::shared_mutex _user_m; bool file_logging = false; bool external_io_context = true; diff --git a/include/aegis/futures.hpp b/include/aegis/futures.hpp index 000120e1..3d181eaa 100644 --- a/include/aegis/futures.hpp +++ b/include/aegis/futures.hpp @@ -1,3 +1,4 @@ +/* // // futures.hpp // *********** @@ -891,9 +892,9 @@ class future _promise->_future = nullptr; } } - /*if (failed()) + / *if (failed()) { - }*/ + }* / std::atomic_thread_fence(std::memory_order_release); } @@ -1337,3 +1338,4 @@ inline future make_exception_future(aegis::error ec) } } +*/ diff --git a/include/aegis/gateway/events/message_create.hpp b/include/aegis/gateway/events/message_create.hpp index ddd662c1..1e60f6b7 100644 --- a/include/aegis/gateway/events/message_create.hpp +++ b/include/aegis/gateway/events/message_create.hpp @@ -14,6 +14,7 @@ #include "aegis/snowflake.hpp" #include "aegis/gateway/objects/message.hpp" #include "aegis/error.hpp" +#include namespace aegis { @@ -29,7 +30,7 @@ namespace events struct message_create { shards::shard & shard; /**< Reference to shard object this message came from */ - lib::optional> user; /**< Reference to object of user that sent this message */ + std::optional> user; /**< Reference to object of user that sent this message */ aegis::channel & channel; /**< Reference to channel object this message was sent in */ objects::message msg; /**< Message object */ @@ -42,9 +43,9 @@ struct message_create if (has_user()) return user.value().get(); #if defined(AEGIS_HAS_BUILTIN_OPTIONAL) - throw lib::bad_optional_access("bad optional access"); + throw std::bad_optional_access("bad optional access"); #else - throw lib::bad_optional_access(); + throw std::bad_optional_access(); #endif } }; diff --git a/include/aegis/gateway/events/message_update.hpp b/include/aegis/gateway/events/message_update.hpp index 4ecf8c1e..f8c98628 100644 --- a/include/aegis/gateway/events/message_update.hpp +++ b/include/aegis/gateway/events/message_update.hpp @@ -29,7 +29,7 @@ struct message_update { shards::shard & shard; /**< Reference to shard object this message came from */ aegis::channel & channel; /**< Reference to channel object this message came from */ - lib::optional> user; /**< Cached user object */ + std::optional> user; /**< Cached user object */ objects::message msg; /**< Message object */ }; diff --git a/include/aegis/gateway/objects/impl/message.cpp b/include/aegis/gateway/objects/impl/message.cpp index c55e64bf..f6e5999b 100644 --- a/include/aegis/gateway/objects/impl/message.cpp +++ b/include/aegis/gateway/objects/impl/message.cpp @@ -62,72 +62,72 @@ AEGIS_DECL aegis::user & message::get_user() } #endif -AEGIS_DECL aegis::future message::delete_message() +AEGIS_DECL async::task message::delete_message() { populate_self(); assert(_channel != nullptr); if (_channel == nullptr) - return aegis::make_exception_future(std::make_exception_ptr(aegis::exception(make_error_code(error::channel_error)))); + throw aegis::exception(make_error_code(error::channel_error)); return get_channel().delete_message(_message_id); } -AEGIS_DECL aegis::future message::edit(const std::string & content) +AEGIS_DECL async::task message::edit(const std::string & content) { populate_self(); assert(_channel != nullptr); if (_channel == nullptr) - return aegis::make_exception_future(std::make_exception_ptr(aegis::exception(make_error_code(error::channel_error)))); + throw aegis::exception(make_error_code(error::channel_error)); return get_channel().edit_message(_message_id, content); } -AEGIS_DECL aegis::future message::edit(edit_message_t & obj) +AEGIS_DECL async::task message::edit(edit_message_t & obj) { populate_self(); assert(_channel != nullptr); if (_channel == nullptr) - return aegis::make_exception_future(std::make_exception_ptr(aegis::exception(make_error_code(error::channel_error)))); + throw aegis::exception(make_error_code(error::channel_error)); return get_channel().edit_message(obj.message_id(_message_id)); } -AEGIS_DECL aegis::future message::create_reaction(const std::string & content) +AEGIS_DECL async::task message::create_reaction(const std::string & content) { populate_self(); assert(_channel != nullptr); if (_channel == nullptr) - return aegis::make_exception_future(std::make_exception_ptr(aegis::exception(make_error_code(error::channel_error)))); + throw aegis::exception(make_error_code(error::channel_error)); return get_channel().create_reaction(_message_id, content); } -AEGIS_DECL aegis::future message::delete_own_reaction(const std::string & content) +AEGIS_DECL async::task message::delete_own_reaction(const std::string & content) { populate_self(); assert(_channel != nullptr); if (_channel == nullptr) - return aegis::make_exception_future(std::make_exception_ptr(aegis::exception(make_error_code(error::channel_error)))); + throw aegis::exception(make_error_code(error::channel_error)); return get_channel().delete_own_reaction(_message_id, content); } -AEGIS_DECL aegis::future message::delete_user_reaction(const std::string & content, const snowflake member_id) +AEGIS_DECL async::task message::delete_user_reaction(const std::string & content, const snowflake member_id) { populate_self(); assert(_channel != nullptr); if (_channel == nullptr) - return aegis::make_exception_future(std::make_exception_ptr(aegis::exception(make_error_code(error::channel_error)))); + throw aegis::exception(make_error_code(error::channel_error)); return get_channel().delete_user_reaction(_message_id, content, member_id); } -AEGIS_DECL aegis::future message::delete_all_reactions() +AEGIS_DECL async::task message::delete_all_reactions() { populate_self(); assert(_channel != nullptr); if (_channel == nullptr) - return aegis::make_exception_future(std::make_exception_ptr(aegis::exception(make_error_code(error::channel_error)))); + throw aegis::exception(make_error_code(error::channel_error)); return get_channel().delete_all_reactions(_message_id); } diff --git a/include/aegis/gateway/objects/member.hpp b/include/aegis/gateway/objects/member.hpp index 0bc04011..1ea57bfe 100644 --- a/include/aegis/gateway/objects/member.hpp +++ b/include/aegis/gateway/objects/member.hpp @@ -52,7 +52,7 @@ struct member std::vector roles; std::string nick; /**< nick of user */ std::string joined_at; - lib::optional _user; + std::optional _user; bool mute = false; bool deaf = false; @@ -65,9 +65,9 @@ struct member if (has_user()) return _user.value(); #if defined(AEGIS_HAS_BUILTIN_OPTIONAL) - throw lib::bad_optional_access("bad optional access"); + throw std::bad_optional_access("bad optional access"); #else - throw lib::bad_optional_access(); + throw std::bad_optional_access(); #endif } }; diff --git a/include/aegis/gateway/objects/message.hpp b/include/aegis/gateway/objects/message.hpp index b113d859..01240ae0 100644 --- a/include/aegis/gateway/objects/message.hpp +++ b/include/aegis/gateway/objects/message.hpp @@ -18,7 +18,6 @@ #include "aegis/gateway/objects/reaction.hpp" #include "aegis/gateway/objects/user.hpp" #include -#include "aegis/futures.hpp" namespace aegis { @@ -300,52 +299,52 @@ class message /// Delete this message /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future delete_message(); + AEGIS_DECL async::task delete_message(); /// Edit this message /** * @param content String to set the new content to - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future edit(const std::string & content); + AEGIS_DECL async::task edit(const std::string & content); /// Edit this message /** * @see edit_message_t * @param obj Struct of the edit message request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future edit(edit_message_t & obj); + AEGIS_DECL async::task edit(edit_message_t & obj); /// Add a reaction to this message /** * @param content String of the emoji to add. Unicode emoji or `emojiname:emoji_id` - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_reaction(const std::string & content); + AEGIS_DECL async::task create_reaction(const std::string & content); /// Delete your reaction to this message /** * @param content String of the emoji to set. Unicode emoji or `emojiname:emoji_id` - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future delete_own_reaction(const std::string & content); + AEGIS_DECL async::task delete_own_reaction(const std::string & content); /// Delete other user reaction to this message (not available for DMs) /** * @param content String of the emoji to set. Unicode emoji or `emojiname:emoji_id` * @param member_id Snowflake of the member to delete the reaction for - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future delete_user_reaction(const std::string & content, const snowflake member_id); + AEGIS_DECL async::task delete_user_reaction(const std::string & content, const snowflake member_id); /// Delete all reactions on this message (not available for DMs) /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future delete_all_reactions(); + AEGIS_DECL async::task delete_all_reactions(); /// Obtain the relevant snowflakes related to this message /** diff --git a/include/aegis/guild.hpp b/include/aegis/guild.hpp index 1bf9ca7a..d65925ff 100644 --- a/include/aegis/guild.hpp +++ b/include/aegis/guild.hpp @@ -24,17 +24,10 @@ #include #include #include -#include "aegis/futures.hpp" namespace aegis { -#if (AEGIS_HAS_STD_SHARED_MUTEX == 1) -using shared_mutex = std::shared_mutex; -#else -using shared_mutex = std::shared_timed_mutex; -#endif - using json = nlohmann::json; #pragma region rest params @@ -51,16 +44,16 @@ struct modify_guild_t modify_guild_t & owner_id(snowflake param) { _owner_id = param; return *this; } modify_guild_t & splash(const std::string & param) { _splash = param; return *this; } - lib::optional _name; - lib::optional _voice_region; - lib::optional _verification_level; - lib::optional _default_message_notifications; - lib::optional _explicit_content_filter; - lib::optional _afk_channel_id; - lib::optional _afk_timeout; - lib::optional _icon; - lib::optional _owner_id; - lib::optional _splash; + std::optional _name; + std::optional _voice_region; + std::optional _verification_level; + std::optional _default_message_notifications; + std::optional _explicit_content_filter; + std::optional _afk_channel_id; + std::optional _afk_timeout; + std::optional _icon; + std::optional _owner_id; + std::optional _splash; }; struct create_text_channel_t @@ -111,11 +104,11 @@ struct modify_guild_member_t modify_guild_member_t & roles(const std::vector & param) { _roles = param; return *this; } modify_guild_member_t & channel_id(snowflake param) { _channel_id = param; return *this; } snowflake _user_id; - lib::optional _nick; - lib::optional _mute; - lib::optional _deaf; - lib::optional> _roles; - lib::optional _channel_id; + std::optional _nick; + std::optional _mute; + std::optional _deaf; + std::optional> _roles; + std::optional _channel_id; }; struct create_guild_ban_t @@ -204,7 +197,7 @@ class guild */ std::string get_name() const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _name = name; return std::move(_name); } @@ -215,7 +208,7 @@ class guild */ std::string get_icon() const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _icon = icon; return std::move(_icon); } @@ -226,7 +219,7 @@ class guild */ std::string get_splash() const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _splash = splash; return std::move(_splash); } @@ -237,7 +230,7 @@ class guild */ std::string get_region() const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _region = region; return std::move(_region); } @@ -278,7 +271,7 @@ class guild */ int64_t base_permissions() const { - std::shared_lock l(_m); + std::shared_lock l(_m); return base_permissions(self()); } @@ -288,7 +281,7 @@ class guild */ int64_t base_permissions(const user * _member) const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); return base_permissions(*_member); } @@ -339,9 +332,9 @@ class guild /// Get guild information /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future get_guild(); + AEGIS_DECL async::task get_guild(); /// Modify guild information /** @@ -357,24 +350,24 @@ class guild * @param icon Set icon \todo * @param owner_id Transfer owner to someone else * @param splash \todo - * @returns aegis::future - */ - AEGIS_DECL aegis::future modify_guild( - lib::optional name = {}, - lib::optional voice_region = {}, lib::optional verification_level = {}, - lib::optional default_message_notifications = {}, lib::optional explicit_content_filter = {}, - lib::optional afk_channel_id = {}, lib::optional afk_timeout = {}, - lib::optional icon = {}, lib::optional owner_id = {}, - lib::optional splash = {} + * @returns async::task + */ + AEGIS_DECL async::task modify_guild( + std::optional name = {}, + std::optional voice_region = {}, std::optional verification_level = {}, + std::optional default_message_notifications = {}, std::optional explicit_content_filter = {}, + std::optional afk_channel_id = {}, std::optional afk_timeout = {}, + std::optional icon = {}, std::optional owner_id = {}, + std::optional splash = {} ); /// Modify guild information /** * @see aegis::modify_guild_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future modify_guild(modify_guild_t obj) + AEGIS_DECL async::task modify_guild(modify_guild_t obj) { return modify_guild(obj._name, obj._voice_region, obj._verification_level, obj._default_message_notifications, obj._explicit_content_filter, obj._afk_channel_id, obj._afk_timeout, obj._icon, @@ -383,9 +376,9 @@ class guild /// Delete a guild /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future delete_guild(); + AEGIS_DECL async::task delete_guild(); /// Create a text channel /** @@ -393,18 +386,18 @@ class guild * @param parent_id The channel or category to place this channel below * @param nsfw Whether the channel will be labeled as not safe for work * @param permission_overwrites Array of permission overwrites to apply to the channel - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_text_channel(const std::string & name, int64_t parent_id = 0, bool nsfw = false, + AEGIS_DECL async::task create_text_channel(const std::string & name, int64_t parent_id = 0, bool nsfw = false, const std::vector & permission_overwrites = {}); /// Create a text channel /** * @see aegis::create_text_channel_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_text_channel(create_text_channel_t obj) + AEGIS_DECL async::task create_text_channel(create_text_channel_t obj) { return create_text_channel(obj._name, obj._parent_id, obj._nsfw, obj._permission_overwrites); } @@ -417,18 +410,18 @@ class guild * @param parent_id The channel or category to place this channel below * @param nsfw Whether the channel will be labeled as not safe for work * @param permission_overwrites Array of permission overwrites to apply to the channel - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_voice_channel(const std::string & name, int32_t bitrate = 0, int32_t user_limit = 0, int64_t parent_id = 0, + AEGIS_DECL async::task create_voice_channel(const std::string & name, int32_t bitrate = 0, int32_t user_limit = 0, int64_t parent_id = 0, const std::vector & permission_overwrites = {}); /// Create a voice channel /** * @see aegis::create_voice_channel_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_voice_channel(create_voice_channel_t obj) + AEGIS_DECL async::task create_voice_channel(create_voice_channel_t obj) { return create_voice_channel(obj._name, obj._bitrate, obj._user_limit, obj._parent_id, obj._permission_overwrites); } @@ -438,27 +431,27 @@ class guild * @param name String of the name for the channel * @param parent_id The channel or category to place this channel below * @param permission_overwrites Array of permission overwrites to apply to the channel - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_category_channel(const std::string & name, int64_t parent_id, + AEGIS_DECL async::task create_category_channel(const std::string & name, int64_t parent_id, const std::vector & permission_overwrites); /// Create a category /** * @see aegis::create_category_channel_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_category_channel(create_category_channel_t obj) + AEGIS_DECL async::task create_category_channel(create_category_channel_t obj) { return create_category_channel(obj._name, obj._parent_id, obj._permission_overwrites); } /// Modify positions of channels /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future modify_channel_positions(); + AEGIS_DECL async::task modify_channel_positions(); /// Modify a member /// All fields are optional @@ -469,20 +462,20 @@ class guild * @param deaf Whether or not to voice deafen the member * @param roles Array of roles to apply to the member * @param channel_id Snowflake of the channel to move user to - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future modify_guild_member(snowflake user_id, lib::optional nick, lib::optional mute, - lib::optional deaf, lib::optional> roles, - lib::optional channel_id); + AEGIS_DECL async::task modify_guild_member(snowflake user_id, std::optional nick, std::optional mute, + std::optional deaf, std::optional> roles, + std::optional channel_id); /// Modify a member /// All fields are optional /** * @see aegis::modify_guild_member_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future modify_guild_member(modify_guild_member_t obj) + AEGIS_DECL async::task modify_guild_member(modify_guild_member_t obj) { return modify_guild_member(obj._user_id, obj._nick, obj._mute, obj._deaf, obj._roles, obj._channel_id); } @@ -490,48 +483,48 @@ class guild /// Modify own nickname /** * @param newname String of the new nickname to apply - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future modify_my_nick(const std::string & newname); + AEGIS_DECL async::task modify_my_nick(const std::string & newname); /// Add a role to guild member /** * @param user_id The snowflake of the user to add new role * @param role_id The snowflake of the role to add to member - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future add_guild_member_role(snowflake user_id, snowflake role_id); + AEGIS_DECL async::task add_guild_member_role(snowflake user_id, snowflake role_id); /// Remove a role from guild member /** * @param user_id The snowflake of the user to remove role * @param role_id The snowflake of the role to remove from member - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future remove_guild_member_role(snowflake user_id, snowflake role_id); + AEGIS_DECL async::task remove_guild_member_role(snowflake user_id, snowflake role_id); /// Remove guild member (kick) /** * @param user_id The snowflake of the member to kick - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future remove_guild_member(snowflake user_id); + AEGIS_DECL async::task remove_guild_member(snowflake user_id); /// Create a new guild ban /** * @param user_id The snowflake of the member to ban * @param delete_message_days How many days to delete member message history (0-7) - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_guild_ban(snowflake user_id, int8_t delete_message_days = 0, const std::string & reason = ""); + AEGIS_DECL async::task create_guild_ban(snowflake user_id, int8_t delete_message_days = 0, const std::string & reason = ""); /// Create a new guild ban /** * @see aegis::create_guild_ban_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_guild_ban(create_guild_ban_t obj) + AEGIS_DECL async::task create_guild_ban(create_guild_ban_t obj) { return create_guild_ban(obj._user_id, obj._delete_message_days, obj._reason); } @@ -539,22 +532,22 @@ class guild /// Remove a guild ban /** * @param user_id The snowflake of the member to unban - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future remove_guild_ban(snowflake user_id); + AEGIS_DECL async::task remove_guild_ban(snowflake user_id); /// Get guild bans /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future get_guild_bans(); + AEGIS_DECL async::task get_guild_bans(); /// Get guild ban /** * @param user_id The snowflake of the user to retrieve the ban object for - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future get_guild_ban(snowflake user_id); + AEGIS_DECL async::task get_guild_ban(snowflake user_id); /// Create a guild role /** @@ -564,18 +557,18 @@ class guild * @param color 32bit integer of the color * @param hoist Whether the role should be separated from other roles * @param mentionable Whether the role can be specifically mentioned - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_guild_role(const std::string & name, permission _perms, int32_t color, bool hoist, bool mentionable); + AEGIS_DECL async::task create_guild_role(const std::string & name, permission _perms, int32_t color, bool hoist, bool mentionable); /// Create a guild role /** * @see aegis::permission * @see aegis::create_guild_role_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_guild_role(create_guild_role_t obj) + AEGIS_DECL async::task create_guild_role(create_guild_role_t obj) { return create_guild_role(obj._name, obj._perms, obj._color, obj._hoist, obj._mentionable); } @@ -584,9 +577,9 @@ class guild /** * @param role_id Snowflake of role to change position * @param position Index of position to change role to - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future modify_guild_role_positions(snowflake role_id, int16_t position); + AEGIS_DECL async::task modify_guild_role_positions(snowflake role_id, int16_t position); /// Modify a guild role /** @@ -597,9 +590,9 @@ class guild * @param color 32bit integer of the color * @param hoist Whether the role should be separated from other roles * @param mentionable Whether the role can be specifically mentioned - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future modify_guild_role(snowflake role_id, const std::string & name, permission _perms, int32_t color, + AEGIS_DECL async::task modify_guild_role(snowflake role_id, const std::string & name, permission _perms, int32_t color, bool hoist, bool mentionable); /// Modify a guild role @@ -607,9 +600,9 @@ class guild * @see aegis::permission * @see aegis::modify_guild_role_t * @param obj Struct of the contents of the request - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future modify_guild_role(modify_guild_role_t obj) + AEGIS_DECL async::task modify_guild_role(modify_guild_role_t obj) { return modify_guild_role(obj._role_id, obj._name, obj._perms, obj._color, obj._hoist, obj._mentionable); } @@ -617,91 +610,91 @@ class guild /// Delete a guild role /** * @param role_id The snowflake of the role to delete - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future delete_guild_role(snowflake role_id); + AEGIS_DECL async::task delete_guild_role(snowflake role_id); /// Get a count of members that would be pruned /** * @param days The days of inactivity to prune the member - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future get_guild_prune_count(int16_t days); + AEGIS_DECL async::task get_guild_prune_count(int16_t days); /// Perform a guild prune /** * @param days The days of inactivity to prune the member - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future begin_guild_prune(int16_t days); + AEGIS_DECL async::task begin_guild_prune(int16_t days); /// Get active guild invite /** - * @param invite_code The invite code of the invite to retrieve - * @returns aegis::future - */ - AEGIS_DECL aegis::future get_guild_invite(std::string invite_code); + * @param invite_code The invite code of the invite to retrieve + * @returns async::task + */ + AEGIS_DECL async::task get_guild_invite(std::string invite_code); /// Get active guild invites /** - * @returns aegis::future - */ - AEGIS_DECL aegis::future get_guild_invites(); + * @returns async::task + */ + AEGIS_DECL async::task get_guild_invites(); /// Delete active guild invite /** * @param invite_code The invite code of the invite to delete * @returns aegis::future */ - AEGIS_DECL aegis::future delete_guild_invite(std::string invite_code); + AEGIS_DECL async::task delete_guild_invite(std::string invite_code); /// Get guild integrations /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future get_guild_integrations(); + AEGIS_DECL async::task get_guild_integrations(); /// Create a new guild integration /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future create_guild_integration(); + AEGIS_DECL async::task create_guild_integration(); /// Modify a guild integration /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future modify_guild_integration(); + AEGIS_DECL async::task modify_guild_integration(); /// Delete a guild integration /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future delete_guild_integration(); + AEGIS_DECL async::task delete_guild_integration(); /// Get the guild integrations /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future sync_guild_integration(); + AEGIS_DECL async::task sync_guild_integration(); /// Get the guild embed settings /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future get_guild_embed(); + AEGIS_DECL async::task get_guild_embed(); /// Modify the guild embed settings /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future modify_guild_embed(); + AEGIS_DECL async::task modify_guild_embed(); /// Leave the guild this object is associated with /** - * @returns aegis::future + * @returns async::task */ - AEGIS_DECL aegis::future leave(); + AEGIS_DECL async::task leave(); /// Gets the Bot object /** @@ -737,14 +730,14 @@ class guild * @param role_name String of role to search for * @returns Pointer to gateway::objects::role or nullptr */ - AEGIS_DECL lib::optional find_role(snowflake role_id) const noexcept; + AEGIS_DECL std::optional find_role(snowflake role_id) const noexcept; /// Obtain a role by name /** * @param role_name String of role to search for * @returns Pointer to gateway::objects::role or nullptr */ - AEGIS_DECL lib::optional find_role(std::string role_name) const noexcept; + AEGIS_DECL std::optional find_role(std::string role_name) const noexcept; /// Obtain map of channels /** @@ -752,7 +745,7 @@ class guild */ std::unordered_map get_channels() const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); std::unordered_map _list = channels; return std::move(_list); } @@ -764,7 +757,7 @@ class guild */ std::unordered_map get_members() const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); std::unordered_map _list = members; return std::move(_list); } @@ -775,7 +768,7 @@ class guild */ std::unordered_map get_roles() const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); std::unordered_map _list = roles; return std::move(_list); } @@ -786,7 +779,7 @@ class guild */ std::unordered_map get_voicestates() const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); std::unordered_map _list = voice_states; return std::move(_list); } @@ -797,7 +790,7 @@ class guild */ std::unordered_map get_emojis() const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); std::unordered_map _list = emojis; return std::move(_list); } @@ -839,7 +832,7 @@ class guild return channels; } - shared_mutex & mtx() + std::shared_mutex & mtx() { return _m; } @@ -906,7 +899,7 @@ class guild #endif core * _bot; asio::io_context & _io_context; - mutable shared_mutex _m; + mutable std::shared_mutex _m; }; } diff --git a/include/aegis/impl/channel.cpp b/include/aegis/impl/channel.cpp index d359f90a..750d6043 100644 --- a/include/aegis/impl/channel.cpp +++ b/include/aegis/impl/channel.cpp @@ -62,7 +62,7 @@ AEGIS_DECL permission channel::perms() const noexcept AEGIS_DECL void channel::_load_with_guild(guild & _guild, const json & obj, shards::shard * _shard) { - std::unique_lock l(_m); + std::unique_lock l(_m); _load_with_guild_nolock(_guild, obj, _shard); } @@ -143,21 +143,21 @@ AEGIS_DECL void channel::_load_with_guild_nolock(guild & _guild, const json & ob #else AEGIS_DECL void channel::_load_with_guild(guild & _guild, const json & obj, shards::shard * _shard) { - std::unique_lock l(_m); + std::unique_lock l(_m); channel_id = obj["id"]; guild_id = _guild.get_id(); } #endif -AEGIS_DECL aegis::future channel::create_message(const std::string & content, int64_t nonce) +AEGIS_DECL async::task channel::create_message(const std::string & content, int64_t nonce) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (_guild && !perms().can_send_messages()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); json obj; obj["content"] = content; @@ -168,27 +168,27 @@ AEGIS_DECL aegis::future channel::create_message(cons return _ratelimit.post_task({ _endpoint, rest::Post, obj.dump(-1, ' ', true) }); } -AEGIS_DECL aegis::future channel::get_message(snowflake message_id) +AEGIS_DECL async::task channel::get_message(snowflake message_id) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (_guild && !perms().can_read_history()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}/messages/{}", channel_id, message_id); return _ratelimit.post_task({ _endpoint, rest::Get }); } -AEGIS_DECL aegis::future channel::get_messages(get_messages_t obj) +AEGIS_DECL async::task channel::get_messages(get_messages_t obj) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (_guild && !perms().can_read_history()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); std::string type_, query_params{ "?" }, limit; @@ -215,16 +215,16 @@ AEGIS_DECL aegis::future channel::get_messages(get_m return _ratelimit.post_task({ _endpoint, rest::Get, {}, {}, {}, {}, query_params }); } -AEGIS_DECL aegis::future channel::create_message(create_message_t obj) +AEGIS_DECL async::task channel::create_message(create_message_t obj) { if (obj._file.has_value()) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (_guild && (!perms().can_send_messages() || !perms().can_attach_files())) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); json jobj; if (!obj._content.empty()) @@ -241,14 +241,14 @@ AEGIS_DECL aegis::future channel::create_messa return create_message_embed(obj._content, obj._embed, obj._nonce); }; -AEGIS_DECL aegis::future channel::create_message_embed(const std::string & content, const json & embed, int64_t nonce) +AEGIS_DECL async::task channel::create_message_embed(const std::string & content, const json & embed, int64_t nonce) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (_guild && !perms().can_send_messages()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); json obj; if (!content.empty()) @@ -262,19 +262,19 @@ AEGIS_DECL aegis::future channel::create_message_embe return _ratelimit.post_task({ _endpoint, rest::Post, obj.dump(-1, ' ', true) }); } -AEGIS_DECL aegis::future channel::create_message_embed(create_message_t obj) +AEGIS_DECL async::task channel::create_message_embed(create_message_t obj) { return create_message_embed(obj._content, obj._embed, obj._nonce); } -AEGIS_DECL aegis::future channel::edit_message(edit_message_t obj) +AEGIS_DECL async::task channel::edit_message(edit_message_t obj) { return edit_message_embed(obj); } -AEGIS_DECL aegis::future channel::edit_message(snowflake message_id, const std::string & content) +AEGIS_DECL async::task channel::edit_message(snowflake message_id, const std::string & content) { - std::shared_lock l(_m); + std::shared_lock l(_m); json obj; obj["content"] = content; @@ -284,18 +284,18 @@ AEGIS_DECL aegis::future channel::edit_message(snowfl return _ratelimit.post_task(_bucket, { _endpoint, rest::Patch, obj.dump() }); } -AEGIS_DECL aegis::future channel::edit_message_embed(edit_message_t obj) +AEGIS_DECL async::task channel::edit_message_embed(edit_message_t obj) { return edit_message_embed(obj._message_id, obj._content, obj._embed); } -AEGIS_DECL aegis::future channel::edit_message_embed(snowflake message_id, const std::string & content, const json & embed) +AEGIS_DECL async::task channel::edit_message_embed(snowflake message_id, const std::string & content, const json & embed) { - std::shared_lock l(_m); + std::shared_lock l(_m); json obj; if (content.empty() && embed.empty()) - return aegis::make_exception_future(error::bad_request); + throw aegis::exception(make_error_code(error::bad_request)); if (!content.empty()) obj["content"] = content; if (!embed.empty()) @@ -310,31 +310,31 @@ AEGIS_DECL aegis::future channel::edit_message_embed( * can delete your own messages freely - provide separate function or keep history of messages? * message::delete() can determine if author is bot for self-delete */ -AEGIS_DECL aegis::future channel::delete_message(snowflake message_id) +AEGIS_DECL async::task channel::delete_message(snowflake message_id) { //@todo check if bot owns message before denying delete #if !defined(AEGIS_DISABLE_ALL_CACHE) if (_guild && !perms().can_manage_messages()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}/messages/{}", channel_id, message_id); std::string _bucket = fmt::format("/channels/{}/messages/_/delete", channel_id); return _ratelimit.post_task(_bucket, { _endpoint, rest::Delete }); } -AEGIS_DECL aegis::future channel::bulk_delete_message(const std::vector & messages) +AEGIS_DECL async::task channel::bulk_delete_message(const std::vector & messages) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (_guild && !perms().can_manage_messages()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); if (messages.size() < 2 || messages.size() > 100) - return aegis::make_exception_future(error::bulk_delete_out_of_range); + throw aegis::exception(make_error_code(error::bulk_delete_out_of_range)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); json obj; json & msgs = obj["messages"]; @@ -345,16 +345,16 @@ AEGIS_DECL aegis::future channel::bulk_delete_message(const st return _ratelimit.post_task({ _endpoint, rest::Post, obj.dump() }); } -AEGIS_DECL aegis::future channel::bulk_delete_message(const std::vector & messages) +AEGIS_DECL async::task channel::bulk_delete_message(const std::vector & messages) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (_guild && !perms().can_manage_messages()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); if (messages.size() < 2 || messages.size() > 100) - return aegis::make_exception_future(error::bulk_delete_out_of_range); + throw aegis::exception(make_error_code(error::bulk_delete_out_of_range)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); json obj; json & msgs = obj["messages"]; @@ -365,15 +365,16 @@ AEGIS_DECL aegis::future channel::bulk_delete_message(const st return _ratelimit.post_task({ _endpoint, rest::Post, obj.dump() }); } -AEGIS_DECL aegis::future channel::modify_channel(lib::optional _name, lib::optional _position, lib::optional _topic, - lib::optional _nsfw, lib::optional _bitrate, lib::optional _user_limit, - lib::optional> _permission_overwrites, lib::optional _parent_id, - lib::optional _rate_limit_per_user) +AEGIS_DECL async::task channel::modify_channel(std::optional _name, std::optional _position, std::optional _topic, + std::optional _nsfw, std::optional _bitrate, std::optional _user_limit, + std::optional> _permission_overwrites, std::optional _parent_id, + std::optional _rate_limit_per_user) { #if !defined(AEGIS_DISABLE_ALL_CACHE) - if (!_guild) return aegis::make_exception_future(error::guild_error); + if (!_guild) + throw aegis::exception(make_error_code(error::guild_error)); if (!perms().can_manage_channels()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif json obj; @@ -410,33 +411,33 @@ AEGIS_DECL aegis::future channel::modify_channel(lib: if (_rate_limit_per_user.has_value()) obj["rate_limit_per_user"] = _rate_limit_per_user.value(); - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}", channel_id); return _ratelimit.post_task({ _endpoint, rest::Patch, obj.dump() }); } -AEGIS_DECL aegis::future channel::delete_channel() +AEGIS_DECL async::task channel::delete_channel() { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (_guild && !perms().can_manage_channels()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}", channel_id); return _ratelimit.post_task({ _endpoint, rest::Delete }); } -AEGIS_DECL aegis::future channel::create_reaction(snowflake message_id, const std::string & emoji_text) +AEGIS_DECL async::task channel::create_reaction(snowflake message_id, const std::string & emoji_text) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (_guild && !perms().can_add_reactions()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}/messages/{}/reactions/{}/@me", channel_id, message_id, utility::url_encode(emoji_text)); std::string _bucket = fmt::format("/guilds/{}/reactions", guild_id); @@ -444,14 +445,14 @@ AEGIS_DECL aegis::future channel::create_reaction(snowflake me return _ratelimit.post_task(_bucket, { _endpoint, rest::Put }); } -AEGIS_DECL aegis::future channel::delete_own_reaction(snowflake message_id, const std::string & emoji_text) +AEGIS_DECL async::task channel::delete_own_reaction(snowflake message_id, const std::string & emoji_text) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (_guild && !perms().can_add_reactions()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}/messages/{}/reactions/{}/@me", channel_id, message_id, utility::url_encode(emoji_text)); std::string _bucket = fmt::format("/guilds/{}/reactions", guild_id); @@ -459,15 +460,16 @@ AEGIS_DECL aegis::future channel::delete_own_reaction(snowflak return _ratelimit.post_task(_bucket, { _endpoint, rest::Delete }); } -AEGIS_DECL aegis::future channel::delete_user_reaction(snowflake message_id, const std::string & emoji_text, snowflake member_id) +AEGIS_DECL async::task channel::delete_user_reaction(snowflake message_id, const std::string & emoji_text, snowflake member_id) { #if !defined(AEGIS_DISABLE_ALL_CACHE) - if (!_guild) return aegis::make_exception_future(error::guild_error); + if (!_guild) + throw aegis::exception(make_error_code(error::guild_error)); if (!perms().can_manage_messages()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}/messages/{}/reactions/{}/{}", channel_id, message_id, utility::url_encode(emoji_text), member_id); std::string _bucket = fmt::format("/channels/{}/messages/_/reactions/", channel_id); @@ -477,39 +479,41 @@ AEGIS_DECL aegis::future channel::delete_user_reaction(snowfla /**\todo Support query parameters * \todo before[snowflake], after[snowflake], limit[int] */ -AEGIS_DECL aegis::future channel::get_reactions(snowflake message_id, const std::string & emoji_text) +AEGIS_DECL async::task channel::get_reactions(snowflake message_id, const std::string & emoji_text) { - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}/messages/{}/reactions/{}", channel_id, message_id, emoji_text); std::string _bucket = fmt::format("/channels/{}/messages/_/reactions/", channel_id); return _ratelimit.post_task(_bucket, { _endpoint, rest::Get }); } -AEGIS_DECL aegis::future channel::delete_all_reactions(snowflake message_id) +AEGIS_DECL async::task channel::delete_all_reactions(snowflake message_id) { #if !defined(AEGIS_DISABLE_ALL_CACHE) - if (!_guild) return aegis::make_exception_future(error::guild_error); + if (!_guild) + throw aegis::exception(make_error_code(error::guild_error)); if (!perms().can_manage_messages()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}/messages/{}/reactions", channel_id, message_id); std::string _bucket = fmt::format("/channels/{}/messages/_/reactions", channel_id); return _ratelimit.post_task(_bucket, { _endpoint, rest::Delete }); } -AEGIS_DECL aegis::future channel::edit_channel_permissions(snowflake _overwrite_id, int64_t _allow, int64_t _deny, const std::string & _type) +AEGIS_DECL async::task channel::edit_channel_permissions(snowflake _overwrite_id, int64_t _allow, int64_t _deny, const std::string & _type) { #if !defined(AEGIS_DISABLE_ALL_CACHE) - if (!_guild) return aegis::make_exception_future(error::guild_error); + if (!_guild) + throw aegis::exception(make_error_code(error::guild_error)); if (!perms().can_manage_roles()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); json obj; obj["allow"] = _allow; @@ -521,29 +525,31 @@ AEGIS_DECL aegis::future channel::edit_channel_permissions(sno return _ratelimit.post_task(_bucket, { _endpoint, rest::Put, obj.dump() }); } -AEGIS_DECL aegis::future channel::get_channel_invites() +AEGIS_DECL async::task channel::get_channel_invites() { #if !defined(AEGIS_DISABLE_ALL_CACHE) - if (!_guild) return aegis::make_exception_future(error::guild_error); + if (!_guild) + throw aegis::exception(make_error_code(error::guild_error)); if (!perms().can_manage_channels()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}/invites", channel_id); return _ratelimit.post_task({ _endpoint, rest::Get }); } -AEGIS_DECL aegis::future channel::create_channel_invite(lib::optional max_age, lib::optional max_uses, lib::optional temporary, lib::optional unique) +AEGIS_DECL async::task channel::create_channel_invite(std::optional max_age, std::optional max_uses, std::optional temporary, std::optional unique) { #if !defined(AEGIS_DISABLE_ALL_CACHE) - if (!_guild) return aegis::make_exception_future(error::guild_error); + if (!_guild) + throw aegis::exception(make_error_code(error::guild_error)); if (!perms().can_invite()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); json obj; if (max_age.has_value()) @@ -559,59 +565,60 @@ AEGIS_DECL aegis::future channel::create_channel_invite(lib::o return _ratelimit.post_task({ _endpoint, rest::Post, obj.dump() }); } -AEGIS_DECL aegis::future channel::delete_channel_permission(snowflake overwrite_id) +AEGIS_DECL async::task channel::delete_channel_permission(snowflake overwrite_id) { #if !defined(AEGIS_DISABLE_ALL_CACHE) - if (!_guild) return aegis::make_exception_future(error::guild_error); + if (!_guild) + throw aegis::exception(make_error_code(error::guild_error)); if (!perms().can_manage_roles()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}/permissions/{}", channel_id, overwrite_id); std::string _bucket = fmt::format("/channels/{}/permissions/", channel_id); return _ratelimit.post_task(_bucket, { _endpoint, rest::Delete }); } -AEGIS_DECL aegis::future channel::trigger_typing_indicator() +AEGIS_DECL async::task channel::trigger_typing_indicator() { - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}/typing", channel_id); return _ratelimit.post_task({ _endpoint }); } -AEGIS_DECL aegis::future channel::get_pinned_messages() +AEGIS_DECL async::task channel::get_pinned_messages() { - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}/pins", channel_id); return _ratelimit.post_task({ _endpoint, rest::Get }); } -AEGIS_DECL aegis::future channel::add_pinned_channel_message(snowflake message_id) +AEGIS_DECL async::task channel::add_pinned_channel_message(snowflake message_id) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (_guild && !perms().can_manage_messages()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}/pins/{}", channel_id, message_id); std::string _bucket = fmt::format("/channels/{}/pins/", channel_id); return _ratelimit.post_task(_bucket, { _endpoint, rest::Put }); } -AEGIS_DECL aegis::future channel::delete_pinned_channel_message(snowflake message_id) +AEGIS_DECL async::task channel::delete_pinned_channel_message(snowflake message_id) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (_guild && !perms().can_manage_messages()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}/pins/{}", channel_id, message_id); std::string _bucket = fmt::format("/channels/{}/pins/", channel_id); @@ -621,9 +628,9 @@ AEGIS_DECL aegis::future channel::delete_pinned_channel_messag /**\todo Will likely move to aegis class * requires OAuth permissions to perform */ -AEGIS_DECL aegis::future channel::group_dm_add_recipient(snowflake user_id)//will go in aegis::aegis +AEGIS_DECL async::task channel::group_dm_add_recipient(snowflake user_id)//will go in aegis::aegis { - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}/recipients/{}", channel_id, user_id); std::string _bucket = fmt::format("/channels/{}/recipients/", channel_id); @@ -633,9 +640,9 @@ AEGIS_DECL aegis::future channel::group_dm_add_recipient(snowf /**\todo Will likely move to aegis class * requires OAuth permissions to perform */ -AEGIS_DECL aegis::future channel::group_dm_remove_recipient(snowflake user_id)//will go in aegis::aegis +AEGIS_DECL async::task channel::group_dm_remove_recipient(snowflake user_id)//will go in aegis::aegis { - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _endpoint = fmt::format("/channels/{}/recipients/{}", channel_id, user_id); std::string _bucket = fmt::format("/channels/{}/recipients/", channel_id); diff --git a/include/aegis/impl/core.cpp b/include/aegis/impl/core.cpp index 16ec1b93..99cb5e57 100644 --- a/include/aegis/impl/core.cpp +++ b/include/aegis/impl/core.cpp @@ -218,7 +218,7 @@ AEGIS_DECL core::~core() #if !defined(AEGIS_DISABLE_ALL_CACHE) AEGIS_DECL int64_t core::get_member_count() const noexcept { - std::shared_lock l(_guild_m); + std::shared_lock l(_guild_m); int64_t count = 0; for (auto & kv : guilds) count += kv.second->get_member_count(); @@ -242,7 +242,7 @@ AEGIS_DECL int64_t core::get_guild_count() const noexcept AEGIS_DECL user * core::find_user(snowflake id) const noexcept { - std::shared_lock l(_user_m); + std::shared_lock l(_user_m); auto it = users.find(id); if (it == users.end()) return nullptr; @@ -259,7 +259,7 @@ AEGIS_DECL user* core::find_user_nolock(snowflake id) const noexcept AEGIS_DECL user * core::user_create(snowflake id) noexcept { - std::unique_lock l(_user_m); + std::unique_lock l(_user_m); auto it = users.find(id); if (it == users.end()) { @@ -272,13 +272,13 @@ AEGIS_DECL user * core::user_create(snowflake id) noexcept } #endif -AEGIS_DECL aegis::future core::create_dm_message(snowflake member_id, const std::string & content, int64_t nonce) +AEGIS_DECL async::task core::create_dm_message(snowflake member_id, const std::string & content, int64_t nonce) { #if !defined(AEGIS_DISABLE_ALL_CACHE) channel * c = nullptr; auto m = find_user(member_id); if (!m) - return aegis::make_exception_future(aegis::error::member_error); + throw aegis::exception(make_error_code(error::member_error)); if (m->get_dm_id()) c = channel_create(m->get_dm_id()); if (c) @@ -291,7 +291,8 @@ AEGIS_DECL aegis::future core::create_dm_message(snow .then([=](gateway::objects::channel && reply) { auto c = channel_create(reply.id); - if (!c) throw aegis::exception(make_error_code(error::member_error)); + if (!c) + throw aegis::exception(make_error_code(error::member_error)); #if !defined(AEGIS_DISABLE_ALL_CACHE) m->set_dm_id(reply.id); #endif @@ -300,13 +301,13 @@ AEGIS_DECL aegis::future core::create_dm_message(snow } } -AEGIS_DECL aegis::future core::create_dm_message(const create_message_t & obj) +AEGIS_DECL async::task core::create_dm_message(const create_message_t & obj) { #if !defined(AEGIS_DISABLE_ALL_CACHE) channel * c = nullptr; auto m = find_user(obj._user_id); if (!m) - return aegis::make_exception_future(aegis::error::member_error); + throw aegis::exception(make_error_code(error::member_error)); if (m->get_dm_id()) c = channel_create(m->get_dm_id()); if (c) @@ -319,7 +320,8 @@ AEGIS_DECL aegis::future core::create_dm_message(cons .then([=](gateway::objects::channel && reply) { auto c = channel_create(reply.id); - if (!c) throw aegis::exception(make_error_code(error::member_error)); + if (!c) + throw aegis::exception(make_error_code(error::member_error)); #if !defined(AEGIS_DISABLE_ALL_CACHE) m->set_dm_id(reply.id); #endif @@ -350,7 +352,7 @@ AEGIS_DECL aegis::rest::rest_reply core::call(rest::request_params && params) AEGIS_DECL channel * core::find_channel(snowflake id) const noexcept { - std::shared_lock l(_channel_m); + std::shared_lock l(_channel_m); auto it = channels.find(id); if (it == channels.end()) return nullptr; @@ -367,7 +369,7 @@ AEGIS_DECL channel* core::find_channel_nolock(snowflake id) const noexcept AEGIS_DECL channel * core::channel_create(snowflake id) noexcept { - std::unique_lock l(_channel_m); + std::unique_lock l(_channel_m); auto it = channels.find(id); if (it == channels.end()) { @@ -381,7 +383,7 @@ AEGIS_DECL channel * core::channel_create(snowflake id) noexcept AEGIS_DECL guild * core::find_guild(snowflake id) const noexcept { - std::shared_lock l(_guild_m); + std::shared_lock l(_guild_m); auto it = guilds.find(id); if (it == guilds.end()) return nullptr; @@ -398,7 +400,7 @@ AEGIS_DECL guild* core::find_guild_nolock(snowflake id) const noexcept AEGIS_DECL guild * core::guild_create(snowflake id, shards::shard * _shard) noexcept { - std::unique_lock l(_guild_m); + std::unique_lock l(_guild_m); auto it = guilds.find(id); if (it == guilds.end()) { @@ -412,7 +414,7 @@ AEGIS_DECL guild * core::guild_create(snowflake id, shards::shard * _shard) noex AEGIS_DECL void core::remove_guild(snowflake guild_id) noexcept { - std::unique_lock l(_guild_m); + std::unique_lock l(_guild_m); auto it = guilds.find(guild_id); if (it == guilds.end()) { @@ -437,7 +439,7 @@ AEGIS_DECL void core::remove_guild_nolock(snowflake guild_id) noexcept AEGIS_DECL void core::remove_channel(snowflake channel_id) noexcept { - std::unique_lock l(_channel_m); + std::unique_lock l(_channel_m); auto it = channels.find(channel_id); if (it == channels.end()) { @@ -463,7 +465,7 @@ AEGIS_DECL void core::remove_channel_nolock(snowflake channel_id) noexcept #if !defined(AEGIS_DISABLE_ALL_CACHE) AEGIS_DECL void core::remove_member(snowflake user_id) noexcept { - std::unique_lock l(_user_m); + std::unique_lock l(_user_m); auto it = users.find(user_id); if (it == users.end()) { @@ -731,7 +733,7 @@ AEGIS_DECL void core::process_ready(const json & d, shards::shard * _shard) if (!unavailable) { - std::unique_lock l(_guild->mtx()); + std::unique_lock l(_guild->mtx()); _guild->_load(guildobj, _shard); AEGIS_DEBUG(log, "Shard#{} : CREATED Guild: {} [T:{}] [{}]" , _shard->get_id() @@ -742,28 +744,28 @@ AEGIS_DECL void core::process_ready(const json & d, shards::shard * _shard) } } -AEGIS_DECL aegis::future core::create_guild(create_guild_t obj) +AEGIS_DECL async::task core::create_guild(create_guild_t obj) { return create_guild(obj._name, obj._voice_region, obj._verification_level, obj._default_message_notifications, obj._explicit_content_filter, obj._icon, obj._roles, obj._channels); } -AEGIS_DECL aegis::future core::modify_bot_username(const std::string & username) +AEGIS_DECL async::task core::modify_bot_username(const std::string & username) { if (!username.empty()) { - return make_ready_future(); + return async::task(); } - return make_ready_future(); + return async::task(); } -AEGIS_DECL aegis::future core::modify_bot_avatar(const std::string & avatar) +AEGIS_DECL async::task core::modify_bot_avatar(const std::string & avatar) { if (!avatar.empty()) { - return make_ready_future(); + return async::task(); } - return make_ready_future(); + return async::task(); } ///\todo @@ -774,7 +776,7 @@ AEGIS_DECL channel * core::dm_channel_create(const json & obj, shards::shard * _ try { - std::unique_lock l(_channel->mtx()); + std::unique_lock l(_channel->mtx()); #if !defined(AEGIS_DISABLE_ALL_CACHE) AEGIS_DEBUG(log, "Shard#{} : Channel[{}] created for DirectMessage", _shard->get_id(), channel_id); if (obj.count("name") && !obj["name"].is_null()) _channel->name = obj["name"].get(); @@ -844,7 +846,7 @@ AEGIS_DECL void core::on_message(websocketpp::connection_hdl hdl, std::string ms { //message id found ++message_count[cmd]; - asio::post(*_io_context, [=, res = std::move(result)]() + async([=, res = std::move(result)]() { if (get_state() == aegis::bot_status::shutdown) return; @@ -959,8 +961,8 @@ AEGIS_DECL void core::on_message(websocketpp::connection_hdl hdl, std::string ms if (result["op"] == 7) { //reconnect request - _shard_mgr->close(_shard, 1003); log->trace("Reconnecting shard {} by op7 - {}", _shard->get_id(), _shard->session_id); + _shard_mgr->close(_shard, 1003); return; } @@ -1187,7 +1189,7 @@ AEGIS_DECL std::size_t core::add_run_thread() noexcept AEGIS_DECL void core::reduce_threads(std::size_t count) noexcept { for (uint32_t i = 0; i < count; ++i) - asio::post(*_io_context, [] + async([] { throw 1; }); @@ -1220,8 +1222,8 @@ AEGIS_DECL void core::ws_presence_update(const json & result, shards::shard * _s return; } { - std::unique_lock l(_member->mtx(), std::defer_lock); - std::unique_lock l2(_guild->mtx(), std::defer_lock); + std::unique_lock l(_member->mtx(), std::defer_lock); + std::unique_lock l2(_guild->mtx(), std::defer_lock); std::lock(l, l2); _member->_load_nolock(_guild, result["d"], _shard, true, false); } @@ -1323,7 +1325,7 @@ AEGIS_DECL void core::ws_message_create(const json & result, shards::shard * _sh } //user was previously created via presence update, but presence update only contains id - gateway::events::message_create obj{ *_shard, lib::nullopt, std::ref(*c) }; + gateway::events::message_create obj{ *_shard, std::nullopt, std::ref(*c) }; obj.msg = result["d"]; obj.msg._core = this; @@ -1354,7 +1356,7 @@ AEGIS_DECL void core::ws_message_update(const json & result, shards::shard * _sh gateway::events::message_update obj{ *_shard, *_channel }; - obj.user = lib::nullopt; + obj.user = std::nullopt; if (result["d"].count("author") && result["d"].count("member") && !result["d"]["member"].is_null()) { @@ -1487,7 +1489,7 @@ AEGIS_DECL void core::ws_guild_delete(const json & result, shards::shard * _shar if (i_guild_delete) i_guild_delete(obj); - std::unique_lock l(_guild_m); + std::unique_lock l(_guild_m); //kicked or left //websocket_o.set_timer(5000, [this, id, _shard](const asio::error_code & ec) //{ @@ -1664,9 +1666,9 @@ AEGIS_DECL void core::ws_channel_create(const json & result, shards::shard * _sh if (_guild == nullptr)//TODO: errors return; auto _channel = channel_create(channel_id); - std::unique_lock l(_channel->mtx(), std::defer_lock); - std::unique_lock l2(_channel_m, std::defer_lock); - std::unique_lock l3(_guild->mtx(), std::defer_lock); + std::unique_lock l(_channel->mtx(), std::defer_lock); + std::unique_lock l2(_channel_m, std::defer_lock); + std::unique_lock l3(_guild->mtx(), std::defer_lock); std::lock(l, l2, l3); _channel->_load_with_guild_nolock(*_guild, result["d"], _shard); _guild->channels.emplace(channel_id, _channel); @@ -1716,8 +1718,8 @@ AEGIS_DECL void core::ws_channel_update(const json & result, shards::shard * _sh if (_guild == nullptr)//TODO: errors return; auto _channel = channel_create(channel_id); - std::unique_lock l(_channel->mtx(), std::defer_lock); - std::unique_lock l2(_channel_m, std::defer_lock); + std::unique_lock l(_channel->mtx(), std::defer_lock); + std::unique_lock l2(_channel_m, std::defer_lock); std::lock(l, l2); _channel->_load_with_guild_nolock(*_guild, result["d"], _shard); _guild->channels.emplace(channel_id, _channel); @@ -1755,8 +1757,8 @@ AEGIS_DECL void core::ws_channel_delete(const json & result, shards::shard * _sh auto _channel = find_channel(channel_id); if (_channel == nullptr)//TODO: errors return; - std::unique_lock l(_channel->mtx(), std::defer_lock); - std::unique_lock l2(_channel_m, std::defer_lock); + std::unique_lock l(_channel->mtx(), std::defer_lock); + std::unique_lock l2(_channel_m, std::defer_lock); std::lock(l, l2); _guild->_remove_channel(channel_id); remove_channel_nolock(channel_id); @@ -1849,8 +1851,8 @@ AEGIS_DECL void core::ws_guild_member_add(const json & result, shards::shard * _ auto _member = user_create(member_id); auto _guild = find_guild(guild_id); - std::unique_lock l(_member->mtx(), std::defer_lock); - std::unique_lock l2(_guild->mtx(), std::defer_lock); + std::unique_lock l(_member->mtx(), std::defer_lock); + std::unique_lock l2(_guild->mtx(), std::defer_lock); std::lock(l, l2); _member->_load_nolock(_guild, result["d"], _shard, true, false); #endif @@ -1875,7 +1877,7 @@ AEGIS_DECL void core::ws_guild_member_remove(const json & result, shards::shard snowflake guild_id = result["d"]["guild_id"]; { - std::unique_lock l(_guild_m); + std::unique_lock l(_guild_m); auto _member = find_user(member_id); auto _guild = find_guild_nolock(guild_id); @@ -1939,8 +1941,8 @@ AEGIS_DECL void core::ws_guild_member_update(const json & result, shards::shard } { - std::unique_lock l(_member->mtx(), std::defer_lock); - std::unique_lock l2(_guild->mtx(), std::defer_lock); + std::unique_lock l(_member->mtx(), std::defer_lock); + std::unique_lock l2(_guild->mtx(), std::defer_lock); std::lock(l, l2); _member->_load_nolock(_guild, result["d"], _shard, true, false); } @@ -1982,8 +1984,8 @@ AEGIS_DECL void core::ws_guild_members_chunk(const json & result, shards::shard auto _member_ptr = user_create(member_id); - std::unique_lock l(_member_ptr->mtx(), std::defer_lock); - std::unique_lock l2(_guild->mtx(), std::defer_lock); + std::unique_lock l(_member_ptr->mtx(), std::defer_lock); + std::unique_lock l2(_guild->mtx(), std::defer_lock); std::lock(l, l2); _member_ptr->_load_nolock(_guild, _member, _shard, true, false); } @@ -2189,11 +2191,11 @@ AEGIS_DECL void core::ws_webhooks_update(const json & result, shards::shard * _s i_webhooks_update(obj); } -AEGIS_DECL aegis::future core::create_guild( - std::string name, lib::optional voice_region, lib::optional verification_level, - lib::optional default_message_notifications, lib::optional explicit_content_filter, - lib::optional icon, lib::optional> roles, - lib::optional>> channels +AEGIS_DECL async::task core::create_guild( + std::string name, std::optional voice_region, std::optional verification_level, + std::optional default_message_notifications, std::optional explicit_content_filter, + std::optional icon, std::optional> roles, + std::optional>> channels ) { json obj; @@ -2214,10 +2216,10 @@ AEGIS_DECL aegis::future core::create_guild( for (auto & c : channels.value()) obj["channels"].push_back(json({ { "name", std::get<0>(c) }, { "type", std::get<1>(c) } })); - return aegis::make_ready_future(gateway::objects::guild(json::parse(get_ratelimit().post_task({ "/guilds", rest::Post, obj.dump() }).get().content))); + return async::make_task(gateway::objects::guild(json::parse(get_ratelimit().post_task({ "/guilds", rest::Post, obj.dump() }).get().content))); } -AEGIS_DECL aegis::future core::create_message(snowflake channel_id, const std::string & msg, int64_t nonce, bool perform_lookup) noexcept +AEGIS_DECL async::task core::create_message(snowflake channel_id, const std::string & msg, int64_t nonce, bool perform_lookup) { #if !defined(AEGIS_DISABLE_ALL_CACHE) channel* _channel = nullptr; @@ -2226,12 +2228,12 @@ AEGIS_DECL aegis::future core::create_message(snowfla { auto it = channels.find(channel_id); if (it == channels.end()) - return aegis::make_exception_future(error::channel_not_found); + throw aegis::exception(make_error_code(error::channel_not_found)); _channel = it->second.get(); _guild = &it->second->get_guild(); if (_guild != nullptr)//probably a DM if (!_channel->perms().can_send_messages()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); } #endif @@ -2244,7 +2246,7 @@ AEGIS_DECL aegis::future core::create_message(snowfla return get_ratelimit().post_task({ fmt::format("/channels/{}/messages", channel_id), rest::Post, obj.dump() }); } -AEGIS_DECL aegis::future core::create_message_embed(snowflake channel_id, const std::string& msg, const json& _obj, int64_t nonce, bool perform_lookup) noexcept +AEGIS_DECL async::task core::create_message_embed(snowflake channel_id, const std::string& msg, const json& _obj, int64_t nonce, bool perform_lookup) { #if !defined(AEGIS_DISABLE_ALL_CACHE) channel* _channel = nullptr; @@ -2253,12 +2255,12 @@ AEGIS_DECL aegis::future core::create_message_embed(s { auto it = channels.find(channel_id); if (it == channels.end()) - return aegis::make_exception_future(error::channel_not_found); + throw aegis::exception(make_error_code(error::channel_not_found)); _channel = it->second.get(); _guild = &it->second->get_guild(); if (_guild != nullptr)//probably a DM if (!_channel->perms().can_send_messages()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); } #endif diff --git a/include/aegis/impl/guild.cpp b/include/aegis/impl/guild.cpp index fece8397..b08a05fa 100644 --- a/include/aegis/impl/guild.cpp +++ b/include/aegis/impl/guild.cpp @@ -54,7 +54,7 @@ AEGIS_DECL user * guild::self() const AEGIS_DECL void guild::_add_member(user * _member) noexcept { - std::unique_lock l(_m); + std::unique_lock l(_m); members.emplace(_member->_member_id, _member); } @@ -65,7 +65,7 @@ AEGIS_DECL void guild::_add_member_nolock(user * _member) noexcept AEGIS_DECL void guild::_remove_member(snowflake member_id) noexcept { - std::unique_lock l(_m); + std::unique_lock l(_m); auto _member = members.find(member_id); if (_member == members.end()) { @@ -78,7 +78,7 @@ AEGIS_DECL void guild::_remove_member(snowflake member_id) noexcept AEGIS_DECL bool guild::member_has_role(snowflake member_id, snowflake role_id) const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); auto _member = find_member(member_id); if (_member == nullptr) return false; @@ -148,7 +148,7 @@ AEGIS_DECL const snowflake guild::get_owner() const noexcept AEGIS_DECL user * guild::find_member(snowflake member_id) const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); auto m = members.find(member_id); if (m == members.end()) return nullptr; @@ -173,7 +173,7 @@ AEGIS_DECL channel* guild::_find_channel(snowflake channel_id) const noexcept AEGIS_DECL channel * guild::find_channel(snowflake channel_id) const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); auto m = channels.find(channel_id); if (m == channels.end()) return nullptr; @@ -182,25 +182,25 @@ AEGIS_DECL channel * guild::find_channel(snowflake channel_id) const noexcept AEGIS_DECL channel * guild::find_channel(std::string channel_name) const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); for (auto & c : channels) if (c.second->get_name() == channel_name) return c.second; return nullptr; } -AEGIS_DECL lib::optional guild::find_role(snowflake role_id) const noexcept +AEGIS_DECL std::optional guild::find_role(snowflake role_id) const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); auto r = roles.find(role_id); if (r == roles.end()) return {}; return r->second; } -AEGIS_DECL lib::optional guild::find_role(std::string role_name) const noexcept +AEGIS_DECL std::optional guild::find_role(std::string role_name) const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); for (auto & r : roles) if (r.second.name == role_name) return r.second; @@ -209,7 +209,7 @@ AEGIS_DECL lib::optional guild::find_role(std::string ro AEGIS_DECL permission guild::get_permissions(snowflake member_id, snowflake channel_id) const { - std::shared_lock l(_m); + std::shared_lock l(_m); if (!members.count(member_id) || !channels.count(channel_id)) return 0; return get_permissions(_find_member(member_id), _find_channel(channel_id)); @@ -327,7 +327,7 @@ AEGIS_DECL int64_t guild::compute_overwrites(const int64_t _base_permissions, co AEGIS_DECL const gateway::objects::role guild::get_role(int64_t r) const { - std::shared_lock l(_m); + std::shared_lock l(_m); for (auto & kv : roles) if (kv.second.role_id == r) return kv.second; @@ -336,7 +336,7 @@ AEGIS_DECL const gateway::objects::role guild::get_role(int64_t r) const AEGIS_DECL void guild::_remove_role(snowflake role_id) noexcept { - std::unique_lock l(_m); + std::unique_lock l(_m); try { for (auto & kv : members) @@ -368,7 +368,7 @@ AEGIS_DECL int32_t guild::get_member_count() const noexcept AEGIS_DECL void guild::_load(const json & obj, shards::shard * _shard) { - std::unique_lock l(_m); + std::unique_lock l(_m); //uint64_t application_id = obj->get("application_id").convert(); snowflake g_id = obj["id"]; @@ -556,7 +556,7 @@ AEGIS_DECL void guild::_load(const json & obj, shards::shard * _shard) noexcept AEGIS_DECL void guild::_remove_channel(snowflake channel_id) noexcept { - std::unique_lock l(_m); + std::unique_lock l(_m); auto it = channels.find(channel_id); if (it == channels.end()) { @@ -568,7 +568,7 @@ AEGIS_DECL void guild::_remove_channel(snowflake channel_id) noexcept AEGIS_DECL channel * guild::get_channel(snowflake id) const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); auto it = channels.find(id); if (it == channels.end()) return nullptr; @@ -577,22 +577,22 @@ AEGIS_DECL channel * guild::get_channel(snowflake id) const noexcept /**\todo Incomplete. Signature may change. Location may change. */ -AEGIS_DECL aegis::future guild::get_guild() +AEGIS_DECL async::task guild::get_guild() { - std::shared_lock l(_m); + std::shared_lock l(_m); return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}", guild_id), rest::Get }); } -AEGIS_DECL aegis::future guild::modify_guild(lib::optional name, lib::optional voice_region, lib::optional verification_level, - lib::optional default_message_notifications, lib::optional explicit_content_filter, lib::optional afk_channel_id, lib::optional afk_timeout, - lib::optional icon, lib::optional owner_id, lib::optional splash) +AEGIS_DECL async::task guild::modify_guild(std::optional name, std::optional voice_region, std::optional verification_level, + std::optional default_message_notifications, std::optional explicit_content_filter, std::optional afk_channel_id, std::optional afk_timeout, + std::optional icon, std::optional owner_id, std::optional splash) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if ((!perms().can_manage_guild()) || (owner_id.has_value() && owner_id != self()->_member_id)) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); json obj; if (name.has_value()) @@ -619,29 +619,29 @@ AEGIS_DECL aegis::future guild::modify_guild(lib::optio return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}", guild_id), rest::Patch, obj.dump() }); } -AEGIS_DECL aegis::future guild::delete_guild() +AEGIS_DECL async::task guild::delete_guild() { #if !defined(AEGIS_DISABLE_ALL_CACHE) //requires OWNER if (owner_id != self()->_member_id) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}", guild_id), rest::Delete }); } -AEGIS_DECL aegis::future guild::create_text_channel(const std::string & name, +AEGIS_DECL async::task guild::create_text_channel(const std::string & name, int64_t parent_id, bool nsfw, const std::vector & permission_overwrites) { #if !defined(AEGIS_DISABLE_ALL_CACHE) //requires MANAGE_CHANNELS if (!perms().can_manage_channels()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); json obj; obj["name"] = name; @@ -677,16 +677,16 @@ AEGIS_DECL aegis::future guild::create_text_channel(c #endif } -AEGIS_DECL aegis::future guild::create_voice_channel(const std::string & name, +AEGIS_DECL async::task guild::create_voice_channel(const std::string & name, int32_t bitrate, int32_t user_limit, int64_t parent_id, const std::vector & permission_overwrites) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_channels()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); json obj; obj["name"] = name; @@ -722,15 +722,15 @@ AEGIS_DECL aegis::future guild::create_voice_channel( #endif } -AEGIS_DECL aegis::future guild::create_category_channel(const std::string & name, +AEGIS_DECL async::task guild::create_category_channel(const std::string & name, int64_t parent_id, const std::vector & permission_overwrites) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_channels()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); json obj; obj["name"] = name; @@ -760,18 +760,18 @@ AEGIS_DECL aegis::future guild::create_category_chann /**\todo Incomplete. Signature may change */ -AEGIS_DECL aegis::future guild::modify_channel_positions() +AEGIS_DECL async::task guild::modify_channel_positions() { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_channels()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - return aegis::make_exception_future(error::not_implemented); + throw aegis::exception(make_error_code(error::not_implemented)); } -AEGIS_DECL aegis::future guild::modify_guild_member(snowflake user_id, lib::optional nick, lib::optional mute, - lib::optional deaf, lib::optional> roles, lib::optional channel_id) +AEGIS_DECL async::task guild::modify_guild_member(snowflake user_id, std::optional nick, std::optional mute, + std::optional deaf, std::optional> roles, std::optional channel_id) { json obj; #if !defined(AEGIS_DISABLE_ALL_CACHE) @@ -779,32 +779,32 @@ AEGIS_DECL aegis::future guild::modify_guild_member(sn if (nick.has_value()) { if (!perm.can_manage_names()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); obj["nick"] = nick.value();//requires MANAGE_NICKNAMES } if (mute.has_value()) { if (!perm.can_voice_mute()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); obj["mute"] = mute.value();//requires MUTE_MEMBERS } if (deaf.has_value()) { if (!perm.can_voice_deafen()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); obj["deaf"] = deaf.value();//requires DEAFEN_MEMBERS } if (roles.has_value()) { if (!perm.can_manage_roles()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); obj["roles"] = roles.value();//requires MANAGE_ROLES } if (channel_id.has_value()) { //TODO: This needs to calculate whether or not the bot has access to the voice channel as well if (!perm.can_voice_move()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); obj["channel_id"] = channel_id.value();//requires MOVE_MEMBERS } #else @@ -820,302 +820,302 @@ AEGIS_DECL aegis::future guild::modify_guild_member(sn obj["channel_id"] = channel_id.value();//requires MOVE_MEMBERS #endif - std::shared_lock l(_m); + std::shared_lock l(_m); return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}/members/{}", guild_id, user_id), rest::Patch, obj.dump() }); } -AEGIS_DECL aegis::future guild::modify_my_nick(const std::string & newname) +AEGIS_DECL async::task guild::modify_my_nick(const std::string & newname) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_change_name()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); json obj = { { "nick", newname } }; return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}/members/@me/nick", guild_id), rest::Patch, obj.dump() }); } -AEGIS_DECL aegis::future guild::add_guild_member_role(snowflake user_id, snowflake role_id) +AEGIS_DECL async::task guild::add_guild_member_role(snowflake user_id, snowflake role_id) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_roles()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}/members/{}/roles/{}", guild_id, user_id, role_id), rest::Put }); } -AEGIS_DECL aegis::future guild::remove_guild_member_role(snowflake user_id, snowflake role_id) +AEGIS_DECL async::task guild::remove_guild_member_role(snowflake user_id, snowflake role_id) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_roles()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}/members/{}/roles/{}", guild_id, user_id, role_id), rest::Delete }); } -AEGIS_DECL aegis::future guild::remove_guild_member(snowflake user_id) +AEGIS_DECL async::task guild::remove_guild_member(snowflake user_id) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_kick()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}/members/{}", guild_id, user_id), rest::Delete }); } -AEGIS_DECL aegis::future guild::create_guild_ban(snowflake user_id, int8_t delete_message_days, const std::string & reason) +AEGIS_DECL async::task guild::create_guild_ban(snowflake user_id, int8_t delete_message_days, const std::string & reason) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_ban()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); json obj = { { "delete_message_days", delete_message_days }, { "reason", reason } }; return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}/bans/{}", guild_id, user_id), rest::Put, obj.dump()}); } -AEGIS_DECL aegis::future guild::remove_guild_ban(snowflake user_id) +AEGIS_DECL async::task guild::remove_guild_ban(snowflake user_id) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_ban()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}/bans/{}", guild_id, user_id), rest::Delete }); } -AEGIS_DECL aegis::future guild::get_guild_bans() +AEGIS_DECL async::task guild::get_guild_bans() { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_ban()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}/bans", guild_id), rest::Get }); } -AEGIS_DECL aegis::future guild::get_guild_ban(snowflake user_id) +AEGIS_DECL async::task guild::get_guild_ban(snowflake user_id) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_ban()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}/bans/{}", guild_id, user_id), rest::Get }); } -AEGIS_DECL aegis::future guild::create_guild_role(const std::string & name, permission _perms, int32_t color, bool hoist, bool mentionable) +AEGIS_DECL async::task guild::create_guild_role(const std::string & name, permission _perms, int32_t color, bool hoist, bool mentionable) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_roles()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); json obj = { { "name", name },{ "permissions", _perms },{ "color", color },{ "hoist", hoist },{ "mentionable", mentionable } }; return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}/roles", guild_id), rest::Post, obj.dump() }); } -AEGIS_DECL aegis::future guild::modify_guild_role_positions(snowflake role_id, int16_t position) +AEGIS_DECL async::task guild::modify_guild_role_positions(snowflake role_id, int16_t position) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_roles()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); json obj = { { "id", role_id },{ "position", position } }; return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}/roles", guild_id), rest::Patch, obj.dump() }); } -AEGIS_DECL aegis::future guild::modify_guild_role(snowflake role_id, const std::string & name, permission _perms, int32_t color, bool hoist, bool mentionable) +AEGIS_DECL async::task guild::modify_guild_role(snowflake role_id, const std::string & name, permission _perms, int32_t color, bool hoist, bool mentionable) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_roles()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); json obj = { { "name", name },{ "permissions", _perms },{ "color", color },{ "hoist", hoist },{ "mentionable", mentionable } }; return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}/roles/{}", guild_id, role_id), rest::Post, obj.dump() }); } -AEGIS_DECL aegis::future guild::delete_guild_role(snowflake role_id) +AEGIS_DECL async::task guild::delete_guild_role(snowflake role_id) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_roles()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - std::shared_lock l(_m); + std::shared_lock l(_m); return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}/roles/{}", guild_id, role_id), rest::Delete }); } /**\todo Incomplete. Signature may change */ -AEGIS_DECL aegis::future guild::get_guild_prune_count(int16_t days) +AEGIS_DECL async::task guild::get_guild_prune_count(int16_t days) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_kick()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - return aegis::make_exception_future(error::not_implemented); + throw aegis::exception(make_error_code(error::not_implemented)); } /**\todo Incomplete. Signature may change */ -AEGIS_DECL aegis::future guild::begin_guild_prune(int16_t days) +AEGIS_DECL async::task guild::begin_guild_prune(int16_t days) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_kick()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - return aegis::make_exception_future(error::not_implemented); + throw aegis::exception(make_error_code(error::not_implemented)); } -AEGIS_DECL aegis::future guild::get_guild_invite(std::string invite_code) +AEGIS_DECL async::task guild::get_guild_invite(std::string invite_code) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_guild()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif return _bot->get_ratelimit().post_task({ fmt::format("/invites/{}", invite_code), rest::Get }); } -AEGIS_DECL aegis::future guild::get_guild_invites() +AEGIS_DECL async::task guild::get_guild_invites() { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_guild()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif return _bot->get_ratelimit().post_task({ fmt::format("/guilds/{}/invites", guild_id), rest::Get }); } -AEGIS_DECL aegis::future guild::delete_guild_invite(std::string invite_code) +AEGIS_DECL async::task guild::delete_guild_invite(std::string invite_code) { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_guild()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - return _bot->get_ratelimit().post_task({ fmt::format("/invites/{}", invite_code), rest::Delete }); + throw aegis::exception(make_error_code(error::not_implemented)); } /**\todo Incomplete. Signature may change */ -AEGIS_DECL aegis::future guild::get_guild_integrations() +AEGIS_DECL async::task guild::get_guild_integrations() { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_guild()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - return aegis::make_exception_future(error::not_implemented); + throw aegis::exception(make_error_code(error::not_implemented)); } /**\todo Incomplete. Signature may change */ -AEGIS_DECL aegis::future guild::create_guild_integration() +AEGIS_DECL async::task guild::create_guild_integration() { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_guild()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - return aegis::make_exception_future(error::not_implemented); + throw aegis::exception(make_error_code(error::not_implemented)); } /**\todo Incomplete. Signature may change */ -AEGIS_DECL aegis::future guild::modify_guild_integration() +AEGIS_DECL async::task guild::modify_guild_integration() { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_guild()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - return aegis::make_exception_future(error::not_implemented); + throw aegis::exception(make_error_code(error::not_implemented)); } /**\todo Incomplete. Signature may change */ -AEGIS_DECL aegis::future guild::delete_guild_integration() +AEGIS_DECL async::task guild::delete_guild_integration() { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_guild()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - return aegis::make_exception_future(error::not_implemented); + throw aegis::exception(make_error_code(error::not_implemented)); } /**\todo Incomplete. Signature may change */ -AEGIS_DECL aegis::future guild::sync_guild_integration() +AEGIS_DECL async::task guild::sync_guild_integration() { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_guild()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - return aegis::make_exception_future(error::not_implemented); + throw aegis::exception(make_error_code(error::not_implemented)); } /**\todo Incomplete. Signature may change */ -AEGIS_DECL aegis::future guild::get_guild_embed() +AEGIS_DECL async::task guild::get_guild_embed() { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_guild()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - return aegis::make_exception_future(error::not_implemented); + throw aegis::exception(make_error_code(error::not_implemented)); } /**\todo Incomplete. Signature may change */ -AEGIS_DECL aegis::future guild::modify_guild_embed() +AEGIS_DECL async::task guild::modify_guild_embed() { #if !defined(AEGIS_DISABLE_ALL_CACHE) if (!perms().can_manage_guild()) - return aegis::make_exception_future(error::no_permission); + throw aegis::exception(make_error_code(error::no_permission)); #endif - return aegis::make_exception_future(error::not_implemented); + throw aegis::exception(make_error_code(error::not_implemented)); } -AEGIS_DECL aegis::future guild::leave() +AEGIS_DECL async::task guild::leave() { - std::unique_lock l(_m); + std::unique_lock l(_m); return _bot->get_ratelimit().post_task({ fmt::format("/users/@me/guilds/{0}", guild_id), rest::Delete }); } diff --git a/include/aegis/impl/user.cpp b/include/aegis/impl/user.cpp index 4c022d60..270e103a 100644 --- a/include/aegis/impl/user.cpp +++ b/include/aegis/impl/user.cpp @@ -23,14 +23,14 @@ namespace aegis AEGIS_DECL std::string user::get_full_name() const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); return fmt::format("{}#{:0=4}", std::string(_name), _discriminator); } AEGIS_DECL void user::_load(guild * _guild, const json & obj, shards::shard * _shard, bool self_add) { - std::unique_lock l(_m); + std::unique_lock l(_m); _load_nolock(_guild, obj, _shard, self_add); } @@ -105,7 +105,7 @@ AEGIS_DECL void user::_load_nolock(guild * _guild, const json & obj, shards::sha AEGIS_DECL user::guild_info & user::get_guild_info(snowflake guild_id) noexcept { - std::unique_lock l(_m); + std::unique_lock l(_m); auto g = std::find_if(std::begin(guilds), std::end(guilds), [&guild_id](const std::unique_ptr & gi) { @@ -115,11 +115,7 @@ AEGIS_DECL user::guild_info & user::get_guild_info(snowflake guild_id) noexcept }); if (g == guilds.end()) { -#if defined(AEGIS_CXX17) return *guilds.emplace_back(std::make_unique(guild_id)); -#else - return **guilds.insert(guilds.end(), std::make_unique(guild_id)); -#endif } return **g; } @@ -134,18 +130,14 @@ AEGIS_DECL user::guild_info & user::get_guild_info_nolock(snowflake guild_id) no }); if (g == guilds.end()) { -#if defined(AEGIS_CXX17) return *guilds.emplace_back(std::make_unique(guild_id)); -#else - return **guilds.insert(guilds.end(), std::make_unique(guild_id)); -#endif } return **g; } AEGIS_DECL user::guild_info * user::get_guild_info_nocreate(snowflake guild_id) const noexcept { - std::unique_lock l(_m); + std::unique_lock l(_m); auto g = std::find_if(std::begin(guilds), std::end(guilds), [&guild_id](const std::unique_ptr & gi) { @@ -162,7 +154,7 @@ AEGIS_DECL user::guild_info * user::get_guild_info_nocreate(snowflake guild_id) AEGIS_DECL std::string user::get_name(snowflake guild_id) noexcept { - std::unique_lock l(_m); + std::unique_lock l(_m); const auto & def = get_guild_info_nolock(guild_id).nickname; return def.has_value() ? def.value() : ""; @@ -170,7 +162,7 @@ AEGIS_DECL std::string user::get_name(snowflake guild_id) noexcept AEGIS_DECL user::guild_info & user::_join(snowflake guild_id) { - std::unique_lock l(_m); + std::unique_lock l(_m); return _join_nolock(guild_id); } @@ -184,18 +176,14 @@ AEGIS_DECL user::guild_info & user::_join_nolock(snowflake guild_id) }); if (g == guilds.end()) { -#if defined(AEGIS_CXX17) return *guilds.emplace_back(std::make_unique(guild_id)); -#else - return **guilds.insert(guilds.end(), std::make_unique(guild_id)); -#endif } return **g; } AEGIS_DECL void user::_load_data(gateway::objects::user mbr) { - std::unique_lock l(_m); + std::unique_lock l(_m); if (!mbr.avatar.empty()) _avatar = mbr.avatar; diff --git a/include/aegis/optional.hpp b/include/aegis/optional.hpp deleted file mode 100644 index 5dca1896..00000000 --- a/include/aegis/optional.hpp +++ /dev/null @@ -1,1088 +0,0 @@ -// Copyright (C) 2011 - 2012 Andrzej Krzemienski. -// -// Use, modification, and distribution is subject to the Boost Software -// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// The idea and interface is based on Boost.Optional library -// authored by Fernando Luis Cacciola Carballal - -# ifndef ___OPTIONAL_HPP___ -# define ___OPTIONAL_HPP___ - -# include -# include -# include -# include -# include -# include -# include - -# define TR2_OPTIONAL_REQUIRES(...) typename enable_if<__VA_ARGS__::value, bool>::type = false - -# if defined __GNUC__ // NOTE: GNUC is also defined for Clang -# if (__GNUC__ == 4) && (__GNUC_MINOR__ >= 8) -# define TR2_OPTIONAL_GCC_4_8_AND_HIGHER___ -# elif (__GNUC__ > 4) -# define TR2_OPTIONAL_GCC_4_8_AND_HIGHER___ -# endif -# -# if (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7) -# define TR2_OPTIONAL_GCC_4_7_AND_HIGHER___ -# elif (__GNUC__ > 4) -# define TR2_OPTIONAL_GCC_4_7_AND_HIGHER___ -# endif -# -# if (__GNUC__ == 4) && (__GNUC_MINOR__ == 8) && (__GNUC_PATCHLEVEL__ >= 1) -# define TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___ -# elif (__GNUC__ == 4) && (__GNUC_MINOR__ >= 9) -# define TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___ -# elif (__GNUC__ > 4) -# define TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___ -# endif -# endif -# -# if defined __clang_major__ -# if (__clang_major__ == 3 && __clang_minor__ >= 5) -# define TR2_OPTIONAL_CLANG_3_5_AND_HIGHTER_ -# elif (__clang_major__ > 3) -# define TR2_OPTIONAL_CLANG_3_5_AND_HIGHTER_ -# endif -# if defined TR2_OPTIONAL_CLANG_3_5_AND_HIGHTER_ -# define TR2_OPTIONAL_CLANG_3_4_2_AND_HIGHER_ -# elif (__clang_major__ == 3 && __clang_minor__ == 4 && __clang_patchlevel__ >= 2) -# define TR2_OPTIONAL_CLANG_3_4_2_AND_HIGHER_ -# endif -# endif -# -# if defined _MSC_VER -# if (_MSC_VER >= 1900) -# define TR2_OPTIONAL_MSVC_2015_AND_HIGHER___ -# endif -# endif - -# if defined __clang__ -# if (__clang_major__ > 2) || (__clang_major__ == 2) && (__clang_minor__ >= 9) -# define OPTIONAL_HAS_THIS_RVALUE_REFS 1 -# else -# define OPTIONAL_HAS_THIS_RVALUE_REFS 0 -# endif -# elif defined TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___ -# define OPTIONAL_HAS_THIS_RVALUE_REFS 1 -# elif defined TR2_OPTIONAL_MSVC_2015_AND_HIGHER___ -# define OPTIONAL_HAS_THIS_RVALUE_REFS 1 -# else -# define OPTIONAL_HAS_THIS_RVALUE_REFS 0 -# endif - - -# if defined TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___ -# define OPTIONAL_HAS_CONSTEXPR_INIT_LIST 1 -# define OPTIONAL_CONSTEXPR_INIT_LIST constexpr -# else -# define OPTIONAL_HAS_CONSTEXPR_INIT_LIST 0 -# define OPTIONAL_CONSTEXPR_INIT_LIST -# endif - -# if defined TR2_OPTIONAL_CLANG_3_5_AND_HIGHTER_ && (defined __cplusplus) && (__cplusplus != 201103L) -# define OPTIONAL_HAS_MOVE_ACCESSORS 1 -# else -# define OPTIONAL_HAS_MOVE_ACCESSORS 0 -# endif - -# // In C++11 constexpr implies const, so we need to make non-const members also non-constexpr -# if (defined __cplusplus) && (__cplusplus == 201103L) -# define OPTIONAL_MUTABLE_CONSTEXPR -# else -# define OPTIONAL_MUTABLE_CONSTEXPR constexpr -# endif - -namespace std -{ - -namespace experimental -{ - -// BEGIN workaround for missing is_trivially_destructible -# if defined TR2_OPTIONAL_GCC_4_8_AND_HIGHER___ -// leave it: it is already there -# elif defined TR2_OPTIONAL_CLANG_3_4_2_AND_HIGHER_ -// leave it: it is already there -# elif defined TR2_OPTIONAL_MSVC_2015_AND_HIGHER___ -// leave it: it is already there -# elif defined TR2_OPTIONAL_DISABLE_EMULATION_OF_TYPE_TRAITS -// leave it: the user doesn't want it -# else -template -using is_trivially_destructible = std::has_trivial_destructor; -# endif -// END workaround for missing is_trivially_destructible - -# if (defined TR2_OPTIONAL_GCC_4_7_AND_HIGHER___) -// leave it; our metafunctions are already defined. -# elif defined TR2_OPTIONAL_CLANG_3_4_2_AND_HIGHER_ -// leave it; our metafunctions are already defined. -# elif defined TR2_OPTIONAL_MSVC_2015_AND_HIGHER___ -// leave it: it is already there -# elif defined TR2_OPTIONAL_DISABLE_EMULATION_OF_TYPE_TRAITS -// leave it: the user doesn't want it -# else - - -// workaround for missing traits in GCC and CLANG -template -struct is_nothrow_move_constructible -{ - constexpr static bool value = std::is_nothrow_constructible::value; -}; - - -template -struct is_assignable -{ - template - constexpr static bool has_assign(...) { return false; } - - template () = std::declval(), true)) > - // the comma operator is necessary for the cases where operator= returns void - constexpr static bool has_assign(bool) { return true; } - - constexpr static bool value = has_assign(true); -}; - - -template -struct is_nothrow_move_assignable -{ - template - struct has_nothrow_move_assign - { - constexpr static bool value = false; - }; - - template - struct has_nothrow_move_assign - { - constexpr static bool value = noexcept(std::declval() = std::declval()); - }; - - constexpr static bool value = has_nothrow_move_assign::value>::value; -}; -// end workaround - - -# endif - - - -// 20.5.4, optional for object types -template class optional; - -// 20.5.5, optional for lvalue reference types -template class optional; - - -// workaround: std utility functions aren't constexpr yet -template inline constexpr T&& constexpr_forward(typename std::remove_reference::type& t) noexcept -{ - return static_cast(t); -} - -template inline constexpr T&& constexpr_forward(typename std::remove_reference::type&& t) noexcept -{ - static_assert(!std::is_lvalue_reference::value, "!!"); - return static_cast(t); -} - -template inline constexpr typename std::remove_reference::type&& constexpr_move(T&& t) noexcept -{ - return static_cast::type&&>(t); -} - - -#if defined NDEBUG -# define TR2_OPTIONAL_ASSERTED_EXPRESSION(CHECK, EXPR) (EXPR) -#else -# define TR2_OPTIONAL_ASSERTED_EXPRESSION(CHECK, EXPR) ((CHECK) ? (EXPR) : ([]{assert(!#CHECK);}(), (EXPR))) -#endif - - -namespace detail_ -{ - -// static_addressof: a constexpr version of addressof -template -struct has_overloaded_addressof -{ - template - constexpr static bool has_overload(...) { return false; } - - template ().operator&()) > - constexpr static bool has_overload(bool) { return true; } - - constexpr static bool value = has_overload(true); -}; - -template )> -constexpr T* static_addressof(T& ref) -{ - return &ref; -} - -template )> -T* static_addressof(T& ref) -{ - return std::addressof(ref); -} - - -// the call to convert(b) has return type A and converts b to type A iff b decltype(b) is implicitly convertible to A -template -constexpr U convert(U v) { return v; } - -} // namespace detail - - -constexpr struct trivial_init_t {} trivial_init{}; - - -// 20.5.6, In-place construction -constexpr struct in_place_t {} in_place{}; - - -// 20.5.7, Disengaged state indicator -struct nullopt_t -{ - struct init {}; - constexpr explicit nullopt_t(init) {} -}; -constexpr nullopt_t nullopt{ nullopt_t::init() }; - - -// 20.5.8, class bad_optional_access -class bad_optional_access : public logic_error -{ -public: - explicit bad_optional_access(const string& what_arg) : logic_error{ what_arg } {} - explicit bad_optional_access(const char* what_arg) : logic_error{ what_arg } {} -}; - - -template -union storage_t -{ - unsigned char dummy_; - T value_; - - constexpr storage_t(trivial_init_t) noexcept : dummy_() {}; - - template - constexpr storage_t(Args&&... args) : value_(constexpr_forward(args)...) {} - - ~storage_t() {} -}; - - -template -union constexpr_storage_t -{ - unsigned char dummy_; - T value_; - - constexpr constexpr_storage_t(trivial_init_t) noexcept : dummy_() {}; - - template - constexpr constexpr_storage_t(Args&&... args) : value_(constexpr_forward(args)...) {} - - ~constexpr_storage_t() = default; -}; - - -template -struct optional_base -{ - bool init_; - storage_t storage_; - - constexpr optional_base() noexcept : init_(false), storage_(trivial_init) {}; - - explicit constexpr optional_base(const T& v) : init_(true), storage_(v) {} - - explicit constexpr optional_base(T&& v) : init_(true), storage_(constexpr_move(v)) {} - - template explicit optional_base(in_place_t, Args&&... args) - : init_(true), storage_(constexpr_forward(args)...) - { - } - - template >)> - explicit optional_base(in_place_t, std::initializer_list il, Args&&... args) - : init_(true), storage_(il, std::forward(args)...) - { - } - - ~optional_base() { if (init_) storage_.value_.T::~T(); } -}; - - -template -struct constexpr_optional_base -{ - bool init_; - constexpr_storage_t storage_; - - constexpr constexpr_optional_base() noexcept : init_(false), storage_(trivial_init) {}; - - explicit constexpr constexpr_optional_base(const T& v) : init_(true), storage_(v) {} - - explicit constexpr constexpr_optional_base(T&& v) : init_(true), storage_(constexpr_move(v)) {} - - template explicit constexpr constexpr_optional_base(in_place_t, Args&&... args) - : init_(true), storage_(constexpr_forward(args)...) - { - } - - template >)> - OPTIONAL_CONSTEXPR_INIT_LIST explicit constexpr_optional_base(in_place_t, std::initializer_list il, Args&&... args) - : init_(true), storage_(il, std::forward(args)...) - { - } - - ~constexpr_optional_base() = default; -}; - -template -using OptionalBase = typename std::conditional< - is_trivially_destructible::value, // if possible - constexpr_optional_base::type>, // use base with trivial destructor - optional_base::type> ->::type; - - - -template -class optional : private OptionalBase -{ - static_assert(!std::is_same::type, nullopt_t>::value, "bad T"); - static_assert(!std::is_same::type, in_place_t>::value, "bad T"); - - - constexpr bool initialized() const noexcept { return OptionalBase::init_; } - typename std::remove_const::type* dataptr() { return std::addressof(OptionalBase::storage_.value_); } - constexpr const T* dataptr() const { return detail_::static_addressof(OptionalBase::storage_.value_); } - -# if OPTIONAL_HAS_THIS_RVALUE_REFS == 1 - constexpr const T& contained_val() const& { return OptionalBase::storage_.value_; } -# if OPTIONAL_HAS_MOVE_ACCESSORS == 1 - OPTIONAL_MUTABLE_CONSTEXPR T&& contained_val() && { return std::move(OptionalBase::storage_.value_); } - OPTIONAL_MUTABLE_CONSTEXPR T& contained_val() & { return OptionalBase::storage_.value_; } -# else - T& contained_val() & { return OptionalBase::storage_.value_; } - T&& contained_val() && { return std::move(OptionalBase::storage_.value_); } -# endif -# else - constexpr const T& contained_val() const { return OptionalBase::storage_.value_; } - T& contained_val() { return OptionalBase::storage_.value_; } -# endif - - void clear() noexcept - { - if (initialized()) dataptr()->T::~T(); - OptionalBase::init_ = false; - } - - template - void initialize(Args&&... args) noexcept(noexcept(T(std::forward(args)...))) - { - assert(!OptionalBase::init_); - ::new (static_cast(dataptr())) T(std::forward(args)...); - OptionalBase::init_ = true; - } - - template - void initialize(std::initializer_list il, Args&&... args) noexcept(noexcept(T(il, std::forward(args)...))) - { - assert(!OptionalBase::init_); - ::new (static_cast(dataptr())) T(il, std::forward(args)...); - OptionalBase::init_ = true; - } - -public: - typedef T value_type; - - // 20.5.5.1, constructors - constexpr optional() noexcept : OptionalBase() {}; - constexpr optional(nullopt_t) noexcept : OptionalBase() {}; - - optional(const optional& rhs) - : OptionalBase() - { - if (rhs.initialized()) - { - ::new (static_cast(dataptr())) T(*rhs); - OptionalBase::init_ = true; - } - } - - optional(optional&& rhs) noexcept(is_nothrow_move_constructible::value) - : OptionalBase() - { - if (rhs.initialized()) - { - ::new (static_cast(dataptr())) T(std::move(*rhs)); - OptionalBase::init_ = true; - } - } - - constexpr optional(const T& v) : OptionalBase(v) {} - - constexpr optional(T&& v) : OptionalBase(constexpr_move(v)) {} - - template - explicit constexpr optional(in_place_t, Args&&... args) - : OptionalBase(in_place_t{}, constexpr_forward(args)...) {} - - template >)> - OPTIONAL_CONSTEXPR_INIT_LIST explicit optional(in_place_t, std::initializer_list il, Args&&... args) - : OptionalBase(in_place_t{}, il, constexpr_forward(args)...) {} - - // 20.5.4.2, Destructor - ~optional() = default; - - // 20.5.4.3, assignment - optional& operator=(nullopt_t) noexcept - { - clear(); - return *this; - } - - optional& operator=(const optional& rhs) - { - if (initialized() == true && rhs.initialized() == false) clear(); - else if (initialized() == false && rhs.initialized() == true) initialize(*rhs); - else if (initialized() == true && rhs.initialized() == true) contained_val() = *rhs; - return *this; - } - - optional& operator=(optional&& rhs) - noexcept(is_nothrow_move_assignable::value && is_nothrow_move_constructible::value) - { - if (initialized() == true && rhs.initialized() == false) clear(); - else if (initialized() == false && rhs.initialized() == true) initialize(std::move(*rhs)); - else if (initialized() == true && rhs.initialized() == true) contained_val() = std::move(*rhs); - return *this; - } - - template - auto operator=(U&& v) - -> typename enable_if - < - is_same::type, T>::value, - optional& - >::type - { - if (initialized()) { contained_val() = std::forward(v); } - else { initialize(std::forward(v)); } - return *this; - } - - - template - void emplace(Args&&... args) - { - clear(); - initialize(std::forward(args)...); - } - - template - void emplace(initializer_list il, Args&&... args) - { - clear(); - initialize(il, std::forward(args)...); - } - - // 20.5.4.4, Swap - void swap(optional& rhs) noexcept(is_nothrow_move_constructible::value && noexcept(swap(declval(), declval()))) - { - if (initialized() == true && rhs.initialized() == false) { rhs.initialize(std::move(**this)); clear(); } - else if (initialized() == false && rhs.initialized() == true) { initialize(std::move(*rhs)); rhs.clear(); } - else if (initialized() == true && rhs.initialized() == true) { using std::swap; swap(**this, *rhs); } - } - - // 20.5.4.5, Observers - - explicit constexpr operator bool() const noexcept { return initialized(); } - constexpr bool has_value() const noexcept { return initialized(); } - - constexpr T const* operator ->() const - { - return TR2_OPTIONAL_ASSERTED_EXPRESSION(initialized(), dataptr()); - } - -# if OPTIONAL_HAS_MOVE_ACCESSORS == 1 - - OPTIONAL_MUTABLE_CONSTEXPR T* operator ->() - { - assert(initialized()); - return dataptr(); - } - - constexpr T const& operator *() const& - { - return TR2_OPTIONAL_ASSERTED_EXPRESSION(initialized(), contained_val()); - } - - OPTIONAL_MUTABLE_CONSTEXPR T& operator *() & - { - assert(initialized()); - return contained_val(); - } - - OPTIONAL_MUTABLE_CONSTEXPR T&& operator *() && { - assert(initialized()); - return constexpr_move(contained_val()); - } - - constexpr T const& value() const& - { - return initialized() ? contained_val() : (throw bad_optional_access("bad optional access"), contained_val()); - } - - OPTIONAL_MUTABLE_CONSTEXPR T& value() & - { - return initialized() ? contained_val() : (throw bad_optional_access("bad optional access"), contained_val()); - } - - OPTIONAL_MUTABLE_CONSTEXPR T&& value() && { - if (!initialized()) throw bad_optional_access("bad optional access"); - return std::move(contained_val()); - } - -# else - - T* operator ->() - { - assert(initialized()); - return dataptr(); - } - - constexpr T const& operator *() const - { - return TR2_OPTIONAL_ASSERTED_EXPRESSION(initialized(), contained_val()); - } - - T& operator *() - { - assert(initialized()); - return contained_val(); - } - - constexpr T const& value() const - { - return initialized() ? contained_val() : (throw bad_optional_access("bad optional access"), contained_val()); - } - - T& value() - { - return initialized() ? contained_val() : (throw bad_optional_access("bad optional access"), contained_val()); - } - -# endif - -# if OPTIONAL_HAS_THIS_RVALUE_REFS == 1 - - template - constexpr T value_or(V&& v) const& - { - return *this ? **this : detail_::convert(constexpr_forward(v)); - } - -# if OPTIONAL_HAS_MOVE_ACCESSORS == 1 - - template - OPTIONAL_MUTABLE_CONSTEXPR T value_or(V&& v) && - { - return *this ? constexpr_move(const_cast&>(*this).contained_val()) : detail_::convert(constexpr_forward(v)); - } - -# else - - template - T value_or(V&& v) && - { - return *this ? constexpr_move(const_cast&>(*this).contained_val()) : detail_::convert(constexpr_forward(v)); - } - -# endif - -# else - - template - constexpr T value_or(V&& v) const - { - return *this ? **this : detail_::convert(constexpr_forward(v)); - } - -# endif - - // 20.6.3.6, modifiers - void reset() noexcept { clear(); } -}; - - -template -class optional -{ - static_assert(!std::is_same::value, "bad T"); - static_assert(!std::is_same::value, "bad T"); - T* ref; - -public: - - // 20.5.5.1, construction/destruction - constexpr optional() noexcept : ref(nullptr) {} - - constexpr optional(nullopt_t) noexcept : ref(nullptr) {} - - constexpr optional(T& v) noexcept : ref(detail_::static_addressof(v)) {} - - optional(T&&) = delete; - - constexpr optional(const optional& rhs) noexcept : ref(rhs.ref) {} - - explicit constexpr optional(in_place_t, T& v) noexcept : ref(detail_::static_addressof(v)) {} - - explicit optional(in_place_t, T&&) = delete; - - ~optional() = default; - - // 20.5.5.2, mutation - optional& operator=(nullopt_t) noexcept - { - ref = nullptr; - return *this; - } - - // optional& operator=(const optional& rhs) noexcept { - // ref = rhs.ref; - // return *this; - // } - - // optional& operator=(optional&& rhs) noexcept { - // ref = rhs.ref; - // return *this; - // } - - template - auto operator=(U&& rhs) noexcept - -> typename enable_if - < - is_same::type, optional>::value, - optional& - >::type - { - ref = rhs.ref; - return *this; - } - - template - auto operator=(U&& rhs) noexcept - -> typename enable_if - < - !is_same::type, optional>::value, - optional& - >::type - = delete; - - void emplace(T& v) noexcept - { - ref = detail_::static_addressof(v); - } - - void emplace(T&&) = delete; - - - void swap(optional& rhs) noexcept - { - std::swap(ref, rhs.ref); - } - - // 20.5.5.3, observers - constexpr T* operator->() const - { - return TR2_OPTIONAL_ASSERTED_EXPRESSION(ref, ref); - } - - constexpr T& operator*() const - { - return TR2_OPTIONAL_ASSERTED_EXPRESSION(ref, *ref); - } - - constexpr T& value() const - { - return ref ? *ref : (throw bad_optional_access("bad optional access"), *ref); - } - - explicit constexpr operator bool() const noexcept - { - return ref != nullptr; - } - - constexpr bool has_value() const noexcept - { - return ref != nullptr; - } - - template - constexpr typename decay::type value_or(V&& v) const - { - return *this ? **this : detail_::convert::type>(constexpr_forward(v)); - } - - // x.x.x.x, modifiers - void reset() noexcept { ref = nullptr; } -}; - - -template -class optional -{ - static_assert(sizeof(T) == 0, "optional rvalue references disallowed"); -}; - - -// 20.5.8, Relational operators -template constexpr bool operator==(const optional& x, const optional& y) -{ - return bool(x) != bool(y) ? false : bool(x) == false ? true : *x == *y; -} - -template constexpr bool operator!=(const optional& x, const optional& y) -{ - return !(x == y); -} - -template constexpr bool operator<(const optional& x, const optional& y) -{ - return (!y) ? false : (!x) ? true : *x < *y; -} - -template constexpr bool operator>(const optional& x, const optional& y) -{ - return (y < x); -} - -template constexpr bool operator<=(const optional& x, const optional& y) -{ - return !(y < x); -} - -template constexpr bool operator>=(const optional& x, const optional& y) -{ - return !(x < y); -} - - -// 20.5.9, Comparison with nullopt -template constexpr bool operator==(const optional& x, nullopt_t) noexcept -{ - return (!x); -} - -template constexpr bool operator==(nullopt_t, const optional& x) noexcept -{ - return (!x); -} - -template constexpr bool operator!=(const optional& x, nullopt_t) noexcept -{ - return bool(x); -} - -template constexpr bool operator!=(nullopt_t, const optional& x) noexcept -{ - return bool(x); -} - -template constexpr bool operator<(const optional&, nullopt_t) noexcept -{ - return false; -} - -template constexpr bool operator<(nullopt_t, const optional& x) noexcept -{ - return bool(x); -} - -template constexpr bool operator<=(const optional& x, nullopt_t) noexcept -{ - return (!x); -} - -template constexpr bool operator<=(nullopt_t, const optional&) noexcept -{ - return true; -} - -template constexpr bool operator>(const optional& x, nullopt_t) noexcept -{ - return bool(x); -} - -template constexpr bool operator>(nullopt_t, const optional&) noexcept -{ - return false; -} - -template constexpr bool operator>=(const optional&, nullopt_t) noexcept -{ - return true; -} - -template constexpr bool operator>=(nullopt_t, const optional& x) noexcept -{ - return (!x); -} - - - -// 20.5.10, Comparison with T -template constexpr bool operator==(const optional& x, const T& v) -{ - return bool(x) ? *x == v : false; -} - -template constexpr bool operator==(const T& v, const optional& x) -{ - return bool(x) ? v == *x : false; -} - -template constexpr bool operator!=(const optional& x, const T& v) -{ - return bool(x) ? *x != v : true; -} - -template constexpr bool operator!=(const T& v, const optional& x) -{ - return bool(x) ? v != *x : true; -} - -template constexpr bool operator<(const optional& x, const T& v) -{ - return bool(x) ? *x < v : true; -} - -template constexpr bool operator>(const T& v, const optional& x) -{ - return bool(x) ? v > *x : true; -} - -template constexpr bool operator>(const optional& x, const T& v) -{ - return bool(x) ? *x > v : false; -} - -template constexpr bool operator<(const T& v, const optional& x) -{ - return bool(x) ? v < *x : false; -} - -template constexpr bool operator>=(const optional& x, const T& v) -{ - return bool(x) ? *x >= v : false; -} - -template constexpr bool operator<=(const T& v, const optional& x) -{ - return bool(x) ? v <= *x : false; -} - -template constexpr bool operator<=(const optional& x, const T& v) -{ - return bool(x) ? *x <= v : true; -} - -template constexpr bool operator>=(const T& v, const optional& x) -{ - return bool(x) ? v >= *x : true; -} - - -// Comparison of optional with T -template constexpr bool operator==(const optional& x, const T& v) -{ - return bool(x) ? *x == v : false; -} - -template constexpr bool operator==(const T& v, const optional& x) -{ - return bool(x) ? v == *x : false; -} - -template constexpr bool operator!=(const optional& x, const T& v) -{ - return bool(x) ? *x != v : true; -} - -template constexpr bool operator!=(const T& v, const optional& x) -{ - return bool(x) ? v != *x : true; -} - -template constexpr bool operator<(const optional& x, const T& v) -{ - return bool(x) ? *x < v : true; -} - -template constexpr bool operator>(const T& v, const optional& x) -{ - return bool(x) ? v > *x : true; -} - -template constexpr bool operator>(const optional& x, const T& v) -{ - return bool(x) ? *x > v : false; -} - -template constexpr bool operator<(const T& v, const optional& x) -{ - return bool(x) ? v < *x : false; -} - -template constexpr bool operator>=(const optional& x, const T& v) -{ - return bool(x) ? *x >= v : false; -} - -template constexpr bool operator<=(const T& v, const optional& x) -{ - return bool(x) ? v <= *x : false; -} - -template constexpr bool operator<=(const optional& x, const T& v) -{ - return bool(x) ? *x <= v : true; -} - -template constexpr bool operator>=(const T& v, const optional& x) -{ - return bool(x) ? v >= *x : true; -} - -// Comparison of optional with T -template constexpr bool operator==(const optional& x, const T& v) -{ - return bool(x) ? *x == v : false; -} - -template constexpr bool operator==(const T& v, const optional& x) -{ - return bool(x) ? v == *x : false; -} - -template constexpr bool operator!=(const optional& x, const T& v) -{ - return bool(x) ? *x != v : true; -} - -template constexpr bool operator!=(const T& v, const optional& x) -{ - return bool(x) ? v != *x : true; -} - -template constexpr bool operator<(const optional& x, const T& v) -{ - return bool(x) ? *x < v : true; -} - -template constexpr bool operator>(const T& v, const optional& x) -{ - return bool(x) ? v > *x : true; -} - -template constexpr bool operator>(const optional& x, const T& v) -{ - return bool(x) ? *x > v : false; -} - -template constexpr bool operator<(const T& v, const optional& x) -{ - return bool(x) ? v < *x : false; -} - -template constexpr bool operator>=(const optional& x, const T& v) -{ - return bool(x) ? *x >= v : false; -} - -template constexpr bool operator<=(const T& v, const optional& x) -{ - return bool(x) ? v <= *x : false; -} - -template constexpr bool operator<=(const optional& x, const T& v) -{ - return bool(x) ? *x <= v : true; -} - -template constexpr bool operator>=(const T& v, const optional& x) -{ - return bool(x) ? v >= *x : true; -} - - -// 20.5.12, Specialized algorithms -template -void swap(optional& x, optional& y) noexcept(noexcept(x.swap(y))) -{ - x.swap(y); -} - - -template -constexpr optional::type> make_optional(T&& v) -{ - return optional::type>(constexpr_forward(v)); -} - -template -constexpr optional make_optional(reference_wrapper v) -{ - return optional(v.get()); -} - - -} // namespace experimental -} // namespace std - -namespace std -{ -template -struct hash> -{ - typedef typename hash::result_type result_type; - typedef std::experimental::optional argument_type; - - constexpr result_type operator()(argument_type const& arg) const - { - return arg ? std::hash{}(*arg) : result_type{}; - } -}; - -template -struct hash> -{ - typedef typename hash::result_type result_type; - typedef std::experimental::optional argument_type; - - constexpr result_type operator()(argument_type const& arg) const - { - return arg ? std::hash{}(*arg) : result_type{}; - } -}; -} - -# undef TR2_OPTIONAL_REQUIRES -# undef TR2_OPTIONAL_ASSERTED_EXPRESSION - -# endif //___OPTIONAL_HPP___ \ No newline at end of file diff --git a/include/aegis/permission.hpp b/include/aegis/permission.hpp index 087393de..8af79057 100644 --- a/include/aegis/permission.hpp +++ b/include/aegis/permission.hpp @@ -97,6 +97,7 @@ class permission bool can_manage_guild() const noexcept { return (_allow_permissions & 0x20) > 0; } bool can_add_reactions() const noexcept { return (_allow_permissions & 0x40) > 0; } bool can_view_audit_logs() const noexcept { return (_allow_permissions & 0x80) > 0; } + bool has_priority_speaker() const noexcept { return (_allow_permissions & 0x100) > 0; } bool can_read_messages() const noexcept { return (_allow_permissions & 0x400) > 0; } bool can_send_messages() const noexcept { return (_allow_permissions & 0x800) > 0; } bool can_tts() const noexcept { return (_allow_permissions & 0x1000) > 0; } @@ -118,7 +119,6 @@ class permission bool can_voice_deafen() const noexcept { return (_allow_permissions & 0x800000) > 0; } bool can_voice_move() const noexcept { return (_allow_permissions & 0x1000000) > 0; } bool can_voice_activity() const noexcept { return (_allow_permissions & 0x2000000) > 0; } - bool has_priority_speaker() const noexcept { return (_allow_permissions & 0x100) > 0; } void set_invite() noexcept { _allow_permissions = (_allow_permissions & 0x1); } void set_kick() noexcept { _allow_permissions = (_allow_permissions & 0x2); } @@ -128,6 +128,7 @@ class permission void set_manage_guild() noexcept { _allow_permissions = (_allow_permissions & 0x20); } void set_add_reactions() noexcept { _allow_permissions = (_allow_permissions & 0x40); } void set_view_audit_logs() noexcept { _allow_permissions = (_allow_permissions & 0x80); } + void set_priority_speaker() noexcept { _allow_permissions = (_allow_permissions & 0x100); } void set_read_messages() noexcept { _allow_permissions = (_allow_permissions & 0x400); } void set_send_messages() noexcept { _allow_permissions = (_allow_permissions & 0x800); } void set_tts() noexcept { _allow_permissions = (_allow_permissions & 0x1000); } diff --git a/include/aegis/ratelimit/ratelimit.hpp b/include/aegis/ratelimit/ratelimit.hpp index 97bad52a..a24bafc8 100644 --- a/include/aegis/ratelimit/ratelimit.hpp +++ b/include/aegis/ratelimit/ratelimit.hpp @@ -85,7 +85,7 @@ class ratelimit_mgr } template::value>> - aegis::future post_task(rest::request_params params) noexcept + async::task post_task(rest::request_params params) noexcept { return _bot->async([=]() -> ResultType { @@ -97,7 +97,7 @@ class ratelimit_mgr }); } - aegis::future post_task(rest::request_params params) noexcept + async::task post_task(rest::request_params params) noexcept { return _bot->async([=]() -> rest::rest_reply { @@ -107,7 +107,7 @@ class ratelimit_mgr } template::value>> - aegis::future post_task(std::string _bucket, rest::request_params params) noexcept + async::task post_task(std::string _bucket, rest::request_params params) noexcept { return _bot->async([=]() -> ResultType { @@ -119,7 +119,7 @@ class ratelimit_mgr }); } - aegis::future post_task(std::string _bucket, rest::request_params params) noexcept + async::task post_task(std::string _bucket, rest::request_params params) noexcept { return _bot->async([=]() -> rest::rest_reply { diff --git a/include/aegis/rest/rest_controller.hpp b/include/aegis/rest/rest_controller.hpp index bf588050..8c93d340 100644 --- a/include/aegis/rest/rest_controller.hpp +++ b/include/aegis/rest/rest_controller.hpp @@ -34,6 +34,7 @@ #include #include #include +#include namespace aegis { @@ -66,7 +67,7 @@ struct request_params std::string port = "443"; std::vector headers; std::string _path_ex; - lib::optional file; + std::optional file; }; class rest_controller diff --git a/include/aegis/shards/impl/shard.cpp b/include/aegis/shards/impl/shard.cpp index b2d5e3a5..b51f714e 100644 --- a/include/aegis/shards/impl/shard.cpp +++ b/include/aegis/shards/impl/shard.cpp @@ -9,6 +9,7 @@ #include "aegis/shards/shard.hpp" #include "aegis/error.hpp" +#include "aegis/async++.hpp" namespace aegis { @@ -40,7 +41,7 @@ AEGIS_DECL void shard::do_reset(shard_status _status) noexcept ++counters.reconnects; return; } - asio::post(asio::bind_executor(*_connection->get_strand(), [this, _status]() + async::spawn([this, _status]() { try { @@ -75,7 +76,7 @@ AEGIS_DECL void shard::do_reset(shard_status _status) noexcept { std::cout << "error in shard::do_reset()\n"; } - })); + }); } AEGIS_DECL void shard::_reset() @@ -98,7 +99,7 @@ AEGIS_DECL void shard::connect() if (!state_valid()) return; - asio::post(asio::bind_executor(*_strand, [this]() + async::spawn([this]() { try { @@ -120,7 +121,7 @@ AEGIS_DECL void shard::connect() { std::cout << "error in shard::connect()\n"; } - })); + }); } AEGIS_DECL void shard::set_connected() @@ -178,14 +179,14 @@ AEGIS_DECL void shard::start_heartbeat(int32_t heartbeat) noexcept return; keepalivetimer.expires_after(std::chrono::milliseconds(heartbeat)); - keepalivetimer.async_wait(asio::bind_executor(*_strand, [=](const asio::error_code & ec) + keepalivetimer.async_wait([=](const asio::error_code & ec) { if (ec == asio::error::operation_aborted) return; keepalivefunc(ec, std::chrono::milliseconds(heartbeat), this); start_heartbeat(heartbeat); - })); + }); } AEGIS_DECL void shard::send(const std::string & payload, websocketpp::frame::opcode::value op) @@ -194,10 +195,10 @@ AEGIS_DECL void shard::send(const std::string & payload, websocketpp::frame::opc return; if (!is_connected()) return; - asio::post(asio::bind_executor(*_strand, [=]() + async::spawn([=]() { write_queue.push(std::make_tuple(payload, op)); - })); + }); } AEGIS_DECL void shard::send_now(const std::string & payload, websocketpp::frame::opcode::value op) @@ -206,13 +207,13 @@ AEGIS_DECL void shard::send_now(const std::string & payload, websocketpp::frame: return; if (!is_connected()) return; - asio::post(asio::bind_executor(*_connection->get_strand(), [=]() + async::spawn([=]() { last_ws_write = std::chrono::steady_clock::now(); if (!_connection) return; _connection->send(payload, op); - })); + }); } AEGIS_DECL void shard::process_writes(const asio::error_code & ec) diff --git a/include/aegis/shards/impl/shard_mgr.cpp b/include/aegis/shards/impl/shard_mgr.cpp index 511c8918..6f56e2ff 100644 --- a/include/aegis/shards/impl/shard_mgr.cpp +++ b/include/aegis/shards/impl/shard_mgr.cpp @@ -11,6 +11,7 @@ #include "aegis/shards/shard_mgr.hpp" #include +#include "aegis/async++.hpp" namespace aegis { @@ -59,8 +60,8 @@ AEGIS_DECL void shard_mgr::start() starttime = std::chrono::steady_clock::now(); - int shard_start = 0; - int shard_end = shard_max_count; + uint32_t shard_start = 0; + uint32_t shard_end = shard_max_count; std::string cluster; if (_max_clusters) { @@ -162,7 +163,7 @@ AEGIS_DECL void shard_mgr::_on_message(websocketpp::connection_hdl hdl, message_ if (_shard->zlib_ctx == nullptr) { log->error("Shard#{}: zlib failure. Context null.", _shard->get_id()); - close(*_shard, 1001, "", aegis::shard_status::reconnecting); + close(*_shard, 1003, "", aegis::shard_status::reconnecting); return; } _shard->zlib_ctx->clear(); @@ -175,7 +176,7 @@ AEGIS_DECL void shard_mgr::_on_message(websocketpp::connection_hdl hdl, message_ catch (std::exception & e) { log->error("Shard#{}: zlib failure. Context invalid. {}", _shard->get_id(), e.what()); - close(*_shard, 1001, "", aegis::shard_status::reconnecting); + close(*_shard, 1003, "", aegis::shard_status::reconnecting); return; } } @@ -379,7 +380,6 @@ AEGIS_DECL void shard_mgr::ws_status(const asio::error_code & ec) asio::error_code ec; _shard->_connection = websocket_o.get_connection(gateway_url, ec); - _shard->_strand = _shard->_connection->get_strand(); if (ec) throw ec; @@ -414,7 +414,7 @@ AEGIS_DECL void shard_mgr::ws_status(const asio::error_code & ec) AEGIS_DECL void shard_mgr::connect(shard * _shard) noexcept { - asio::post(asio::bind_executor(*_shard->_connection->get_strand(), [this, _shard]() + async::spawn([this, _shard]() { setup_callbacks(_shard); _shard->connection_state = shard_status::connecting; @@ -424,11 +424,12 @@ AEGIS_DECL void shard_mgr::connect(shard * _shard) noexcept _shard->lastwsevent = std::chrono::steady_clock::now(); _shard->connect(); - })); + }); } AEGIS_DECL void shard_mgr::queue_reconnect(shard * _shard) noexcept { + AEGIS_DEBUG(log, "Shard#{}: queue_reconnect()", _shard->get_id()); auto it = std::find(_shards_to_connect.cbegin(), _shards_to_connect.cend(), _shard); if (it != _shards_to_connect.cend()) { @@ -485,6 +486,7 @@ AEGIS_DECL shard & shard_mgr::get_shard(uint16_t shard_id) AEGIS_DECL void shard_mgr::close(shard * _shard, int32_t code, const std::string & reason, shard_status connection_state) noexcept { + AEGIS_DEBUG(log, "Shard#{}: close()", _shard->get_id()); _shard->connection_state = connection_state; _shard->closing_time = std::chrono::steady_clock::now(); if (_shard->_connection != nullptr) diff --git a/include/aegis/shards/shard.hpp b/include/aegis/shards/shard.hpp index a7575c68..b7df116b 100644 --- a/include/aegis/shards/shard.hpp +++ b/include/aegis/shards/shard.hpp @@ -233,7 +233,7 @@ class shard bool state_valid() { - if (_connection == nullptr || _strand == nullptr) + if (_connection == nullptr) return false; return true; } @@ -263,7 +263,6 @@ class shard // Websocket++ socket connection websocketpp::connection_hdl hdl; std::vector _trace; - std::shared_ptr _strand; heartbeat_status _heartbeat_status = heartbeat_status::normal; }; diff --git a/include/aegis/snowflake.hpp b/include/aegis/snowflake.hpp index d28723b9..17689e2e 100644 --- a/include/aegis/snowflake.hpp +++ b/include/aegis/snowflake.hpp @@ -26,9 +26,7 @@ class snowflake constexpr snowflake(const snowflake & _snowflake) noexcept : _id(_snowflake._id) {} explicit snowflake(const char * _snowflake) noexcept : _id(std::stoll(std::string(_snowflake))) {} explicit snowflake(const std::string & _snowflake) noexcept : _id(std::stoll(_snowflake)) {} -#if defined(AEGIS_CXX17) explicit snowflake(const std::string_view _snowflake) noexcept : _id(std::stoll(std::string{ _snowflake })) {} -#endif explicit snowflake(const nlohmann::json & _snowflake) noexcept : _id(std::stoll(_snowflake.get())) {} AEGIS_DECL snowflake(const aegis::user & _user) noexcept; AEGIS_DECL snowflake(const aegis::guild & _guild) noexcept; diff --git a/include/aegis/user.hpp b/include/aegis/user.hpp index 44c367d5..6ff5c185 100644 --- a/include/aegis/user.hpp +++ b/include/aegis/user.hpp @@ -21,16 +21,11 @@ #include #include #include +#include namespace aegis { -#if (AEGIS_HAS_STD_SHARED_MUTEX == 1) -using shared_mutex = std::shared_mutex; -#else -using shared_mutex = std::shared_timed_mutex; -#endif - using json = nlohmann::json; /// Stores user-specific and guild-specific attributes of users @@ -47,7 +42,7 @@ class user guild_info(snowflake _id) : id(_id) {}; snowflake id;/**< Snowflake of the guild for this data */ std::vector roles; - lib::optional nickname;/**< Nickname of the user in this guild */ + std::optional nickname;/**< Nickname of the user in this guild */ uint64_t joined_at = 0;/**< Unix timestamp of when member joined this guild */ bool deaf = false;/**< Whether member is deafened in a voice channel */ bool mute = false;/**< Whether member is muted in a voice channel */ @@ -66,7 +61,7 @@ class user */ std::string get_username() const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); std::string _username = _name; return std::move(_username); } @@ -86,7 +81,7 @@ class user */ std::string get_avatar() const noexcept { - std::shared_lock l(_m); + std::shared_lock l(_m); std::string t_avatar = _avatar; return std::move(t_avatar); } @@ -177,9 +172,9 @@ class user /// /** - * @returns shared_mutex The mutex for the user + * @returns std::shared_mutex The mutex for the user */ - shared_mutex & mtx() noexcept + std::shared_mutex & mtx() noexcept { return _m; } @@ -200,7 +195,7 @@ class user bool _is_bot = false; /**< true if member is a bot */ bool _mfa_enabled = false; /**< true if member has Two-factor authentication enabled */ std::vector> guilds; /**< Vector of snowflakes to member owned guild information */ - mutable shared_mutex _m; + mutable std::shared_mutex _m; /// requires the caller to handle locking AEGIS_DECL void _load(guild * _guild, const json & obj, shards::shard * _shard, bool self_add = true); @@ -217,7 +212,7 @@ class user /// remove this member from the specified guild void leave(snowflake guild_id) { - std::unique_lock l(mtx()); + std::unique_lock l(mtx()); guilds.erase(std::find_if(std::begin(guilds), std::end(guilds), [&guild_id](const std::unique_ptr & gi) { if (gi->id == guild_id) From 6488aeacf449c17d05c8ad50d915a237637ffc51 Mon Sep 17 00:00:00 2001 From: Sharon Fox Date: Wed, 13 Jan 2021 05:21:21 -0500 Subject: [PATCH 2/6] Cleaned up code --- include/aegis/channel.hpp | 7 ------ include/aegis/config.hpp | 2 ++ include/aegis/core.hpp | 2 -- include/aegis/impl/permission.cpp | 32 ------------------------- include/aegis/permission.hpp | 32 ++++++++++++++++++++++++- include/aegis/shards/impl/shard_mgr.cpp | 11 ++++++--- 6 files changed, 41 insertions(+), 45 deletions(-) diff --git a/include/aegis/channel.hpp b/include/aegis/channel.hpp index 2d2527a6..5d147af3 100644 --- a/include/aegis/channel.hpp +++ b/include/aegis/channel.hpp @@ -18,17 +18,10 @@ #include "aegis/gateway/objects/permission_overwrite.hpp" #include "aegis/gateway/objects/channel.hpp" #include -#include "aegis/futures.hpp" namespace aegis { -#if (AEGIS_HAS_STD_SHARED_MUTEX == 1) -using shared_mutex = std::shared_mutex; -#else -using shared_mutex = std::shared_timed_mutex; -#endif - using json = nlohmann::json; using channel_type = gateway::objects::channel::channel_type; diff --git a/include/aegis/config.hpp b/include/aegis/config.hpp index 2dfb1504..03a0656e 100644 --- a/include/aegis/config.hpp +++ b/include/aegis/config.hpp @@ -18,6 +18,8 @@ #include #endif +#include "async++.hpp" + #if !defined(ASIO_NO_DEPRECATED) #define ASIO_NO_DEPRECATED #endif diff --git a/include/aegis/core.hpp b/include/aegis/core.hpp index f186baf9..3fdae0df 100644 --- a/include/aegis/core.hpp +++ b/include/aegis/core.hpp @@ -42,8 +42,6 @@ #include #include -#include "async++.hpp" - using namespace std::chrono_literals; namespace aegis diff --git a/include/aegis/impl/permission.cpp b/include/aegis/impl/permission.cpp index dee90b92..c724fb30 100644 --- a/include/aegis/impl/permission.cpp +++ b/include/aegis/impl/permission.cpp @@ -30,36 +30,4 @@ AEGIS_DECL void to_json(nlohmann::json& j, const permission& s) } /// \endcond -AEGIS_DECL const std::unordered_map permission::perm_strs { - {0x1, "Create invites"}, - {0x2, "Kick members"}, - {0x4, "Ban members"}, - {0x8, "Administrator"}, - {0x10, "Manage channels"}, - {0x20, "Manage server"}, - {0x40, "Add reactions"}, - {0x80, "View audit log"}, - {0x400, "Read messages"}, - {0x800, "Send messages"}, - {0x1000, "Use TTS"}, - {0x2000, "Manage messages"}, - {0x4000, "Send embeds"}, - {0x8000, "Attach files"}, - {0x10000, "Read message history"}, - {0x20000, "Mention everyone"}, - {0x40000, "Use external emoji"}, - {0x4000000, "Change nickname"}, - {0x8000000, "Manage nicknames"}, - {0x10000000, "Manage roles"}, - {0x20000000, "Manage webhooks"}, - {0x40000000, "Manage emojis"}, - {0x100000, "Connect to VC"}, - {0x400000, "Server mute"}, - {0x200000, "Speak in VC"}, - {0x800000, "Server deafen"}, - {0x1000000, "Move VC members"}, - {0x2000000, "Use voice activity"}, - {0x100, "Priority speaker"} -}; - } diff --git a/include/aegis/permission.hpp b/include/aegis/permission.hpp index 8af79057..0d60a90c 100644 --- a/include/aegis/permission.hpp +++ b/include/aegis/permission.hpp @@ -153,7 +153,37 @@ class permission private: int64_t _allow_permissions = 0; - AEGIS_DECL static const std::unordered_map perm_strs; + static inline const std::unordered_map perm_strs = { + { 0x1, "Create invites" }, + { 0x2, "Kick members" }, + { 0x4, "Ban members" }, + { 0x8, "Administrator" }, + { 0x10, "Manage channels" }, + { 0x20, "Manage server" }, + { 0x40, "Add reactions" }, + { 0x80, "View audit log" }, + { 0x100, "Priority speaker" }, + { 0x400, "Read messages" }, + { 0x800, "Send messages" }, + { 0x1000, "Use TTS" }, + { 0x2000, "Manage messages" }, + { 0x4000, "Send embeds" }, + { 0x8000, "Attach files" }, + { 0x10000, "Read message history" }, + { 0x20000, "Mention everyone" }, + { 0x40000, "Use external emoji" }, + { 0x4000000, "Change nickname" }, + { 0x8000000, "Manage nicknames" }, + { 0x10000000, "Manage roles" }, + { 0x20000000, "Manage webhooks" }, + { 0x40000000, "Manage emojis" }, + { 0x100000, "Connect to VC" }, + { 0x400000, "Server mute" }, + { 0x200000, "Speak in VC" }, + { 0x800000, "Server deafen" }, + { 0x1000000, "Move VC members" }, + { 0x2000000, "Use voice activity" } + }; }; /// \cond TEMPLATES diff --git a/include/aegis/shards/impl/shard_mgr.cpp b/include/aegis/shards/impl/shard_mgr.cpp index 6f56e2ff..e5abd4ff 100644 --- a/include/aegis/shards/impl/shard_mgr.cpp +++ b/include/aegis/shards/impl/shard_mgr.cpp @@ -64,9 +64,8 @@ AEGIS_DECL void shard_mgr::start() uint32_t shard_end = shard_max_count; std::string cluster; - if (_max_clusters) { + if (_max_clusters) cluster = fmt::format(" (cluster ID {}, max clusters: {})", _cluster_id, _max_clusters); - } log->info("Starting bot with {} shards{}", shard_max_count, cluster); { @@ -416,6 +415,12 @@ AEGIS_DECL void shard_mgr::connect(shard * _shard) noexcept { async::spawn([this, _shard]() { + if (_shard->get_connection() == nullptr) + { + _shard->do_reset(); + return; + } + setup_callbacks(_shard); _shard->connection_state = shard_status::connecting; _shard->heartbeat_ack = std::chrono::steady_clock::time_point(); @@ -429,7 +434,7 @@ AEGIS_DECL void shard_mgr::connect(shard * _shard) noexcept AEGIS_DECL void shard_mgr::queue_reconnect(shard * _shard) noexcept { - AEGIS_DEBUG(log, "Shard#{}: queue_reconnect()", _shard->get_id()); + //AEGIS_DEBUG(log, "Shard#{}: queue_reconnect()", _shard->get_id()); auto it = std::find(_shards_to_connect.cbegin(), _shards_to_connect.cend(), _shard); if (it != _shards_to_connect.cend()) { From 83dfc0643de3ed4d4d7bcdd950ebe988246ff5a4 Mon Sep 17 00:00:00 2001 From: Sharon Fox Date: Wed, 13 Jan 2021 05:22:00 -0500 Subject: [PATCH 3/6] Added JavaScript-style timers --- include/aegis/core.hpp | 98 +++++++++++++++++++++++++++++++----------- 1 file changed, 73 insertions(+), 25 deletions(-) diff --git a/include/aegis/core.hpp b/include/aegis/core.hpp index 3fdae0df..89c866a2 100644 --- a/include/aegis/core.hpp +++ b/include/aegis/core.hpp @@ -41,6 +41,7 @@ #include #include #include +#include using namespace std::chrono_literals; @@ -845,31 +846,8 @@ class core * @param f Function to run async * @returns async::task */ - template, typename = std::enable_if_t::value>> - async::task async(T f) noexcept - { - return async::spawn(f); - } - - /// Run async task - /** - * This function will queue your task (a lambda or std::function) within Asio for execution at a later time - * This version will return an async::task that will still allow chaining continuations on - * - * Example: - * @code{.cpp} - * async::task message = async([]{ - * return; - * }).then([](){ - * return std::string("continuation executed"); - * }); - * @endcode - * - * @param f Function to run async - * @returns async::task - */ - template>::value>> - async::task async(T f) noexcept + template + decltype(async::spawn(::async::default_scheduler(), std::declval())) async(T f) noexcept { return async::spawn(f); } @@ -930,6 +908,76 @@ class core std::unordered_map> & get_user_map() { return users; }; #endif + + + struct aegis_timer + { + aegis_timer(asio::io_context & _io) : timer(_io) {} + uint64_t id; + asio::steady_timer timer; + std::function fn; + std::chrono::duration interval; + }; + + std::set> timers; + std::mutex timer_m; + uint64_t timer_count = 1; + + template + uint64_t set_timeout(std::function fn, const std::chrono::duration & t) + { + std::unique_lock l(timer_m); + std::shared_ptr tmr = std::make_shared(*io_context_); + tmr->id = timer_count++; + tmr->fn = fn; + tmr->timer.async_wait(std::bind(&core::timer_cb, this, tmr)); + timers.insert(tmr); + tmr->timer.expires_after(t); + return tmr->id; + } + + template + uint64_t set_interval(std::function fn, const std::chrono::duration & t) + { + std::unique_lock l(timer_m); + std::shared_ptr tmr = std::make_shared(*io_context_); + tmr->id = timer_count++; + tmr->fn = fn; + tmr->interval = t; + tmr->timer.async_wait(std::bind(&core::interval_cb, this, tmr)); + timers.insert(tmr); + tmr->timer.expires_after(t); + return tmr->id; + } + + void clear_timeout(uint64_t t) + { + std::unique_lock l(timer_m); + for (const auto & timer : timers) + { + if (timer->id == t) + { + timers.erase(timer); + return; + } + } + } + + void timer_cb(std::shared_ptr tmr) + { + std::unique_lock l(timer_m); + timers.erase(tmr); + tmr->fn(); + } + + void interval_cb(std::shared_ptr tmr) + { + std::unique_lock l(timer_m); + tmr->fn(); + tmr->timer.async_wait(std::bind(&core::interval_cb, this, tmr)); + tmr->timer.expires_after(tmr->interval); + } + private: AEGIS_DECL void _thread_track(thread_state * t_state); From e900789fcd24ace21c9e8659c1307f8e438d82c4 Mon Sep 17 00:00:00 2001 From: Sharon Fox Date: Wed, 13 Jan 2021 05:22:30 -0500 Subject: [PATCH 4/6] Added experimental ping-reconnect handler --- include/aegis/shards/impl/shard.cpp | 49 +++++++++++++++++++++++-- include/aegis/shards/impl/shard_mgr.cpp | 11 ++++++ include/aegis/shards/shard.hpp | 4 ++ 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/include/aegis/shards/impl/shard.cpp b/include/aegis/shards/impl/shard.cpp index b51f714e..1b987314 100644 --- a/include/aegis/shards/impl/shard.cpp +++ b/include/aegis/shards/impl/shard.cpp @@ -17,6 +17,8 @@ namespace aegis namespace shards { +using namespace std::chrono_literals; + AEGIS_DECL shard::shard(asio::io_context & _io, websocketpp::client & _ws, int32_t id) : keepalivetimer(_io) , delayedauth(_io) @@ -29,11 +31,36 @@ AEGIS_DECL shard::shard(asio::io_context & _io, websocketpp::clientping("ping-a-ling"); + } + catch (std::exception ex) + { + std::cout << ex.what() << '\n'; + } + + ping_timer.expires_after(3000ms); + + ping_timer.async_wait(std::bind(&shard::do_the_ping, this, std::placeholders::_1)); } AEGIS_DECL void shard::do_reset(shard_status _status) noexcept { + ping_timer.cancel(); if (!state_valid()) { connection_state = _status; @@ -106,21 +133,21 @@ AEGIS_DECL void shard::connect() //_reset(); _websocket.connect(_connection); connection_state = shard_status::connecting; + return; } catch (std::exception & e) { std::cout << "Shard#" << get_id() << ": worst happened connect() - std::exception " << e.what() << '\n'; - _reset(); } catch (asio::error_code & e) { std::cout << "Shard#" << get_id() << ": worst happened connect() - asio::error_code " << e.value() << ':' << e.message() << '\n'; - _reset(); } catch (...) { std::cout << "error in shard::connect()\n"; } + do_reset(); }); } @@ -129,15 +156,29 @@ AEGIS_DECL void shard::set_connected() if (!state_valid()) return; using namespace std::chrono_literals; + + if (ping_timer.expiry() > std::chrono::steady_clock::now()) + ping_timer.cancel(); + + ping_timer.expires_after(20000ms); + + ping_timer.async_wait(std::bind(&shard::do_the_ping, this, std::placeholders::_1)); + if (zlib_ctx) { //already has an existing context - throw aegis::exception("set_connected() zlib context already exists"); + //throw aegis::exception("set_connected() zlib context already exists"); + std::cout << "set_connected() zlib context already exists\n"; + do_reset(); + return; } if (_connection == nullptr) { //error - throw aegis::exception("set_connected() connection = nullptr"); + //throw aegis::exception("set_connected() connection = nullptr"); + std::cout << "set_connected() connection = nullptr\n"; + do_reset(); + return; } ws_buffer.str(""); zlib_ctx = std::make_unique(ws_buffer); diff --git a/include/aegis/shards/impl/shard_mgr.cpp b/include/aegis/shards/impl/shard_mgr.cpp index e5abd4ff..3dc9cba7 100644 --- a/include/aegis/shards/impl/shard_mgr.cpp +++ b/include/aegis/shards/impl/shard_mgr.cpp @@ -92,6 +92,17 @@ AEGIS_DECL void shard_mgr::setup_callbacks(shard * _shard) noexcept std::bind(&shard_mgr::_on_connect, this, std::placeholders::_1, _shard)); _shard->_connection->set_close_handler( std::bind(&shard_mgr::_on_close, this, std::placeholders::_1, _shard)); + + _shard->_connection->set_pong_timeout(3000); + + _shard->_connection->set_pong_timeout_handler([&, _shard](websocketpp::connection_hdl hdl, std::string str) + { + auto now = std::chrono::steady_clock::now(); + log->warn("Shard#{}: Timeout called - lastheartbeat({}) lastwsevent({}) lastheartbeatack({}) ms ago\n", _shard->get_id(), utility::to_ms(now - _shard->lastheartbeat), + utility::to_ms(now - _shard->lastwsevent), + utility::to_ms(now - _shard->heartbeat_ack)); + close(_shard, 1003, ""); + }); } AEGIS_DECL void shard_mgr::shutdown() diff --git a/include/aegis/shards/shard.hpp b/include/aegis/shards/shard.hpp index b7df116b..22bf7f9d 100644 --- a/include/aegis/shards/shard.hpp +++ b/include/aegis/shards/shard.hpp @@ -178,6 +178,10 @@ class shard asio::steady_timer keepalivetimer; asio::steady_timer delayedauth; asio::steady_timer write_timer; + asio::steady_timer ping_timer; + std::mutex parse_mutex; + + AEGIS_DECL void do_the_ping(const asio::error_code & ec); std::queue> write_queue; From 0c029d1494c44654a7196cc1820f7b671db3fde6 Mon Sep 17 00:00:00 2001 From: Sharon Fox Date: Wed, 13 Jan 2021 11:26:09 -0500 Subject: [PATCH 5/6] Added outgoing REST call queue counters --- include/aegis/ratelimit/ratelimit.hpp | 83 ++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/include/aegis/ratelimit/ratelimit.hpp b/include/aegis/ratelimit/ratelimit.hpp index a24bafc8..51e29a8c 100644 --- a/include/aegis/ratelimit/ratelimit.hpp +++ b/include/aegis/ratelimit/ratelimit.hpp @@ -87,49 +87,104 @@ class ratelimit_mgr template::value>> async::task post_task(rest::request_params params) noexcept { + current_queue++; return _bot->async([=]() -> ResultType { - auto & bkt = get_bucket(params.path); - auto res = bkt.perform(params); - if (res.reply_code < rest::ok || res.reply_code >= rest::multiple_choices)//error - throw aegis::exception(fmt::format("REST Reply Code: {}", static_cast(res.reply_code)), bad_request); - return res.content.empty() ? ResultType(_bot) : ResultType(res.content, _bot); + try + { + auto & bkt = get_bucket(params.path); + auto res = bkt.perform(params); + if (res.reply_code < rest::ok || res.reply_code >= rest::multiple_choices)//error + { + current_queue--; + throw aegis::exception(fmt::format("REST Reply Code: {}", static_cast(res.reply_code)), bad_request); + } + current_queue--; + return res.content.empty() ? ResultType(_bot) : ResultType(res.content, _bot); + } + catch (...) + { + current_queue--; + return {}; + } }); } async::task post_task(rest::request_params params) noexcept { + current_queue++; return _bot->async([=]() -> rest::rest_reply { - auto & bkt = get_bucket(params.path); - return bkt.perform(params); + try + { + auto & bkt = get_bucket(params.path); + current_queue--; + return bkt.perform(params); + } + catch (...) + { + current_queue--; + return {}; + } }); } template::value>> async::task post_task(std::string _bucket, rest::request_params params) noexcept { + current_queue++; return _bot->async([=]() -> ResultType { - auto & bkt = get_bucket(_bucket); - auto res = bkt.perform(params); - if (res.reply_code < rest::ok || res.reply_code >= rest::multiple_choices)//error - throw aegis::exception(fmt::format("REST Reply Code: {}", static_cast(res.reply_code)), bad_request); - return res.content.empty() ? ResultType(_bot) : ResultType(res.content, _bot); + try + { + auto & bkt = get_bucket(_bucket); + auto res = bkt.perform(params); + if (res.reply_code < rest::ok || res.reply_code >= rest::multiple_choices)//error + { + current_queue--; + throw aegis::exception(fmt::format("REST Reply Code: {}", static_cast(res.reply_code)), bad_request); + } + current_queue--; + return res.content.empty() ? ResultType(_bot) : ResultType(res.content, _bot); + } + catch (...) + { + current_queue--; + return {}; + } }); } async::task post_task(std::string _bucket, rest::request_params params) noexcept { + current_queue++; return _bot->async([=]() -> rest::rest_reply { - auto & bkt = get_bucket(_bucket); - return bkt.perform(params); + try + { + auto & bkt = get_bucket(_bucket); + current_queue--; + return bkt.perform(params); + } + catch (...) + { + current_queue--; + return {}; + } }); } + uint64_t get_current_queue() const noexcept + { + return current_queue.load(); + } + private: friend class bucket; + friend class shard; + friend class shard_mgr; + + std::atomic current_queue; /**< Current count of outgoing requests awaiting processing */ std::atomic global_limit; /**< Timestamp in seconds when global ratelimit expires */ From 65d29c0d81dafe92bb779a4c22f73f52752be9d8 Mon Sep 17 00:00:00 2001 From: Sharon Fox Date: Thu, 14 Jan 2021 04:45:05 -0500 Subject: [PATCH 6/6] Fixed timers --- include/aegis/core.hpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/include/aegis/core.hpp b/include/aegis/core.hpp index 89c866a2..8c7dfb51 100644 --- a/include/aegis/core.hpp +++ b/include/aegis/core.hpp @@ -908,11 +908,9 @@ class core std::unordered_map> & get_user_map() { return users; }; #endif - - struct aegis_timer { - aegis_timer(asio::io_context & _io) : timer(_io) {} + aegis_timer(asio::io_context & _io) : timer(_io), id(0), interval(0) {} uint64_t id; asio::steady_timer timer; std::function fn; @@ -927,12 +925,12 @@ class core uint64_t set_timeout(std::function fn, const std::chrono::duration & t) { std::unique_lock l(timer_m); - std::shared_ptr tmr = std::make_shared(*io_context_); + std::shared_ptr tmr = std::make_shared(*_io_context); tmr->id = timer_count++; tmr->fn = fn; + tmr->timer.expires_after(t); tmr->timer.async_wait(std::bind(&core::timer_cb, this, tmr)); timers.insert(tmr); - tmr->timer.expires_after(t); return tmr->id; } @@ -940,13 +938,13 @@ class core uint64_t set_interval(std::function fn, const std::chrono::duration & t) { std::unique_lock l(timer_m); - std::shared_ptr tmr = std::make_shared(*io_context_); + std::shared_ptr tmr = std::make_shared(*_io_context); tmr->id = timer_count++; tmr->fn = fn; tmr->interval = t; + tmr->timer.expires_after(t); tmr->timer.async_wait(std::bind(&core::interval_cb, this, tmr)); timers.insert(tmr); - tmr->timer.expires_after(t); return tmr->id; } @@ -974,8 +972,8 @@ class core { std::unique_lock l(timer_m); tmr->fn(); - tmr->timer.async_wait(std::bind(&core::interval_cb, this, tmr)); tmr->timer.expires_after(tmr->interval); + tmr->timer.async_wait(std::bind(&core::interval_cb, this, tmr)); } private: