mirror of
https://github.com/azerothcore/mod-ale
synced 2025-11-29 15:38:17 +08:00
Merge pull request #2 from r-o-b-o-t-o/feat/http-requests
feat: add HttpRequest method
This commit is contained in:
@@ -2637,6 +2637,90 @@ namespace LuaGlobalFunctions
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a non-blocking HTTP request.
|
||||
*
|
||||
* When the passed callback function is called, the parameters `(status, body, headers)` are passed to it.
|
||||
*
|
||||
* -- GET example (prints a random word)
|
||||
* HttpRequest("GET", "https://random-word-api.herokuapp.com/word", function(status, body, headers)
|
||||
* print("Random word: " .. string.sub(body, 3, body:len() - 2))
|
||||
* end)
|
||||
*
|
||||
* -- POST example with JSON request body
|
||||
* HttpRequest("POST", "https://jsonplaceholder.typicode.com/posts", '{"userId": 1,"title": "Foo","body": "Bar!"}', "application/json", function(status, body, headers)
|
||||
* print(body)
|
||||
* end)
|
||||
*
|
||||
* -- Example with request headers
|
||||
* HttpRequest("GET", "https://postman-echo.com/headers", { Accept = "application/json", ["User-Agent"] = "Eluna Lua Engine" }, function(status, body, headers)
|
||||
* print(body)
|
||||
* end)
|
||||
*
|
||||
* @proto (httpMethod, url, function)
|
||||
* @proto (httpMethod, url, headers, function)
|
||||
* @proto (httpMethod, url, body, contentType, function)
|
||||
* @proto (httpMethod, url, body, contentType, headers, function)
|
||||
*
|
||||
* @param string httpMethod : the HTTP method to use (possible values are: `"GET"`, `"HEAD"`, `"POST"`, `"PUT"`, `"PATCH"`, `"DELETE"`, `"OPTIONS"`)
|
||||
* @param string url : the URL to query
|
||||
* @param table headers : a table with string key-value pairs containing the request headers
|
||||
* @param string body : the request's body (only used for POST, PUT and PATCH requests)
|
||||
* @param string contentType : the body's content-type
|
||||
* @param function function : function that will be called when the request is executed
|
||||
*/
|
||||
int HttpRequest(lua_State* L)
|
||||
{
|
||||
std::string httpVerb = Eluna::CHECKVAL<std::string>(L, 1);
|
||||
std::string url = Eluna::CHECKVAL<std::string>(L, 2);
|
||||
std::string body;
|
||||
std::string bodyContentType;
|
||||
httplib::Headers headers;
|
||||
|
||||
int headersIdx = 3;
|
||||
int callbackIdx = 3;
|
||||
|
||||
if (!lua_istable(L, headersIdx) && lua_isstring(L, headersIdx) && lua_isstring(L, headersIdx + 1))
|
||||
{
|
||||
body = Eluna::CHECKVAL<std::string>(L, 3);
|
||||
bodyContentType = Eluna::CHECKVAL<std::string>(L, 4);
|
||||
headersIdx = 5;
|
||||
callbackIdx = 5;
|
||||
}
|
||||
|
||||
if (lua_istable(L, headersIdx))
|
||||
{
|
||||
++callbackIdx;
|
||||
|
||||
lua_pushnil(L); // First key
|
||||
while (lua_next(L, headersIdx) != 0)
|
||||
{
|
||||
// Uses 'key' (at index -2) and 'value' (at index -1)
|
||||
if (lua_isstring(L, -2))
|
||||
{
|
||||
std::string key(lua_tostring(L, -2));
|
||||
std::string value(lua_tostring(L, -1));
|
||||
headers.insert(std::pair<std::string, std::string>(key, value));
|
||||
}
|
||||
// Removes 'value'; keeps 'key' for next iteration
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
lua_pushvalue(L, callbackIdx);
|
||||
int funcRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
if (funcRef >= 0)
|
||||
{
|
||||
Eluna::GEluna->httpManager.PushRequest(new HttpWorkItem(funcRef, httpVerb, url, body, bodyContentType, headers));
|
||||
}
|
||||
else
|
||||
{
|
||||
luaL_argerror(L, callbackIdx, "unable to make a ref to function");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object representing a `long long` (64-bit) value.
|
||||
*
|
||||
|
||||
276
HttpManager.cpp
Normal file
276
HttpManager.cpp
Normal file
@@ -0,0 +1,276 @@
|
||||
#include <thread>
|
||||
extern "C"
|
||||
{
|
||||
#include "lua.h"
|
||||
#include "lauxlib.h"
|
||||
};
|
||||
|
||||
#if defined TRINITY || defined AZEROTHCORE
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#endif
|
||||
#include "libs/httplib.h"
|
||||
#include "HttpManager.h"
|
||||
#include "LuaEngine.h"
|
||||
|
||||
HttpWorkItem::HttpWorkItem(int funcRef, const std::string& httpVerb, const std::string& url, const std::string& body, const std::string& contentType, const httplib::Headers& headers)
|
||||
: funcRef(funcRef),
|
||||
httpVerb(httpVerb),
|
||||
url(url),
|
||||
body(body),
|
||||
contentType(contentType),
|
||||
headers(headers)
|
||||
{ }
|
||||
|
||||
HttpResponse::HttpResponse(int funcRef, int statusCode, const std::string& body, const httplib::Headers& headers)
|
||||
: funcRef(funcRef),
|
||||
statusCode(statusCode),
|
||||
body(body),
|
||||
headers(headers)
|
||||
{ }
|
||||
|
||||
HttpManager::HttpManager()
|
||||
: workQueue(16),
|
||||
responseQueue(16),
|
||||
startedWorkerThread(false),
|
||||
cancelationToken(false),
|
||||
condVar(),
|
||||
condVarMutex(),
|
||||
parseUrlRegex("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?")
|
||||
{
|
||||
StartHttpWorker();
|
||||
}
|
||||
|
||||
HttpManager::~HttpManager()
|
||||
{
|
||||
StopHttpWorker();
|
||||
}
|
||||
|
||||
void HttpManager::PushRequest(HttpWorkItem* item)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(condVarMutex);
|
||||
workQueue.push(item);
|
||||
condVar.notify_one();
|
||||
}
|
||||
|
||||
void HttpManager::StartHttpWorker()
|
||||
{
|
||||
ClearQueues();
|
||||
|
||||
if (!startedWorkerThread)
|
||||
{
|
||||
cancelationToken.store(false);
|
||||
workerThread = std::thread(&HttpManager::HttpWorkerThread, this);
|
||||
startedWorkerThread = true;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpManager::ClearQueues()
|
||||
{
|
||||
while (workQueue.front())
|
||||
{
|
||||
HttpWorkItem* item = *workQueue.front();
|
||||
if (item != nullptr)
|
||||
{
|
||||
delete item;
|
||||
}
|
||||
workQueue.pop();
|
||||
}
|
||||
|
||||
while (responseQueue.front())
|
||||
{
|
||||
HttpResponse* item = *responseQueue.front();
|
||||
if (item != nullptr)
|
||||
{
|
||||
delete item;
|
||||
}
|
||||
responseQueue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpManager::StopHttpWorker()
|
||||
{
|
||||
if (!startedWorkerThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cancelationToken.store(true);
|
||||
condVar.notify_one();
|
||||
workerThread.join();
|
||||
ClearQueues();
|
||||
startedWorkerThread = false;
|
||||
}
|
||||
|
||||
void HttpManager::HttpWorkerThread()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(condVarMutex);
|
||||
condVar.wait(lock, [&] { return workQueue.front() != nullptr || cancelationToken.load(); });
|
||||
}
|
||||
|
||||
if (cancelationToken.load())
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (!workQueue.front())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
HttpWorkItem* req = *workQueue.front();
|
||||
workQueue.pop();
|
||||
if (!req)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::string host;
|
||||
std::string path;
|
||||
|
||||
if (!ParseUrl(req->url, host, path)) {
|
||||
ELUNA_LOG_ERROR("[Eluna]: Could not parse URL %s", req->url.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
httplib::Client cli(host);
|
||||
cli.set_connection_timeout(0, 3000000); // 3 seconds
|
||||
cli.set_read_timeout(5, 0); // 5 seconds
|
||||
cli.set_write_timeout(5, 0); // 5 seconds
|
||||
|
||||
httplib::Result res = DoRequest(cli, req, path);
|
||||
httplib::Error err = res.error();
|
||||
if (err != httplib::Error::Success)
|
||||
{
|
||||
ELUNA_LOG_ERROR("[Eluna]: HTTP request error: %s", httplib::to_string(err).c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (res->status == 301)
|
||||
{
|
||||
std::string location = res->get_header_value("Location");
|
||||
std::string host;
|
||||
std::string path;
|
||||
|
||||
if (!ParseUrl(location, host, path))
|
||||
{
|
||||
ELUNA_LOG_ERROR("[Eluna]: Could not parse URL after redirect: %s", location.c_str());
|
||||
continue;
|
||||
}
|
||||
httplib::Client cli2(host);
|
||||
cli2.set_connection_timeout(0, 3000000); // 3 seconds
|
||||
cli2.set_read_timeout(5, 0); // 5 seconds
|
||||
cli2.set_write_timeout(5, 0); // 5 seconds
|
||||
res = DoRequest(cli2, req, path);
|
||||
}
|
||||
|
||||
responseQueue.push(new HttpResponse(req->funcRef, res->status, res->body, res->headers));
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
ELUNA_LOG_ERROR("[Eluna]: HTTP request error: %s", ex.what());
|
||||
}
|
||||
|
||||
delete req;
|
||||
}
|
||||
}
|
||||
|
||||
httplib::Result HttpManager::DoRequest(httplib::Client& client, HttpWorkItem* req, const std::string& urlPath)
|
||||
{
|
||||
const char* path = urlPath.c_str();
|
||||
if (req->httpVerb == "GET")
|
||||
{
|
||||
return client.Get(path, req->headers);
|
||||
}
|
||||
if (req->httpVerb == "HEAD")
|
||||
{
|
||||
return client.Head(path, req->headers);
|
||||
}
|
||||
if (req->httpVerb == "POST")
|
||||
{
|
||||
return client.Post(path, req->headers, req->body, req->contentType.c_str());
|
||||
}
|
||||
if (req->httpVerb == "PUT")
|
||||
{
|
||||
return client.Put(path, req->headers, req->body, req->contentType.c_str());
|
||||
}
|
||||
if (req->httpVerb == "PATCH")
|
||||
{
|
||||
return client.Patch(path, req->headers, req->body, req->contentType.c_str());
|
||||
}
|
||||
if (req->httpVerb == "DELETE")
|
||||
{
|
||||
return client.Delete(path, req->headers);
|
||||
}
|
||||
if (req->httpVerb == "OPTIONS")
|
||||
{
|
||||
return client.Options(path, req->headers);
|
||||
}
|
||||
|
||||
ELUNA_LOG_ERROR("[Eluna]: HTTP request error: invalid HTTP verb %s", req->httpVerb.c_str());
|
||||
return client.Get(path, req->headers);
|
||||
}
|
||||
|
||||
bool HttpManager::ParseUrl(const std::string& url, std::string& host, std::string& path)
|
||||
{
|
||||
std::smatch matches;
|
||||
|
||||
if (!std::regex_search(url, matches, parseUrlRegex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string scheme = matches[2];
|
||||
std::string authority = matches[4];
|
||||
std::string query = matches[7];
|
||||
host = scheme + "://" + authority;
|
||||
path = matches[5];
|
||||
if (path.empty())
|
||||
{
|
||||
path = "/";
|
||||
}
|
||||
path += (query.empty() ? "" : "?") + query;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpManager::HandleHttpResponses()
|
||||
{
|
||||
while (!responseQueue.empty())
|
||||
{
|
||||
HttpResponse* res = *responseQueue.front();
|
||||
responseQueue.pop();
|
||||
|
||||
if (res == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
LOCK_ELUNA;
|
||||
|
||||
lua_State* L = Eluna::GEluna->L;
|
||||
|
||||
// Get function
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, res->funcRef);
|
||||
|
||||
// Push parameters
|
||||
Eluna::Push(L, res->statusCode);
|
||||
Eluna::Push(L, res->body);
|
||||
lua_newtable(L);
|
||||
for (const auto& item : res->headers) {
|
||||
Eluna::Push(L, item.first);
|
||||
Eluna::Push(L, item.second);
|
||||
lua_settable(L, -3);
|
||||
}
|
||||
|
||||
// Call function
|
||||
Eluna::GEluna->ExecuteCall(3, 0);
|
||||
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, res->funcRef);
|
||||
|
||||
delete res;
|
||||
}
|
||||
}
|
||||
61
HttpManager.h
Normal file
61
HttpManager.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#ifndef ELUNA_HTTP_MANAGER_H
|
||||
#define ELUNA_HTTP_MANAGER_H
|
||||
|
||||
#include <regex>
|
||||
|
||||
#include "libs/httplib.h"
|
||||
#include "libs/rigtorp/SPSCQueue.h"
|
||||
|
||||
struct HttpWorkItem
|
||||
{
|
||||
public:
|
||||
HttpWorkItem(int funcRef, const std::string& httpVerb, const std::string& url, const std::string& body, const std::string &contentType, const httplib::Headers& headers);
|
||||
|
||||
int funcRef;
|
||||
std::string httpVerb;
|
||||
std::string url;
|
||||
std::string body;
|
||||
std::string contentType;
|
||||
httplib::Headers headers;
|
||||
};
|
||||
|
||||
struct HttpResponse
|
||||
{
|
||||
public:
|
||||
HttpResponse(int funcRef, int statusCode, const std::string& body, const httplib::Headers& headers);
|
||||
|
||||
int funcRef;
|
||||
int statusCode;
|
||||
std::string body;
|
||||
httplib::Headers headers;
|
||||
};
|
||||
|
||||
|
||||
class HttpManager
|
||||
{
|
||||
public:
|
||||
HttpManager();
|
||||
~HttpManager();
|
||||
|
||||
void StartHttpWorker();
|
||||
void StopHttpWorker();
|
||||
void PushRequest(HttpWorkItem* item);
|
||||
void HandleHttpResponses();
|
||||
|
||||
private:
|
||||
void ClearQueues();
|
||||
void HttpWorkerThread();
|
||||
bool ParseUrl(const std::string& url, std::string& host, std::string& path);
|
||||
httplib::Result DoRequest(httplib::Client& client, HttpWorkItem* req, const std::string& path);
|
||||
|
||||
rigtorp::SPSCQueue<HttpWorkItem*> workQueue;
|
||||
rigtorp::SPSCQueue<HttpResponse*> responseQueue;
|
||||
std::thread workerThread;
|
||||
bool startedWorkerThread;
|
||||
std::atomic_bool cancelationToken;
|
||||
std::condition_variable condVar;
|
||||
std::mutex condVarMutex;
|
||||
std::regex parseUrlRegex;
|
||||
};
|
||||
|
||||
#endif // #ifndef ELUNA_HTTP_MANAGER_H
|
||||
@@ -155,6 +155,7 @@ enabled(false),
|
||||
|
||||
L(NULL),
|
||||
eventMgr(NULL),
|
||||
httpManager(),
|
||||
|
||||
ServerEventBindings(NULL),
|
||||
PlayerEventBindings(NULL),
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "World.h"
|
||||
#include "Hooks.h"
|
||||
#include "ElunaUtility.h"
|
||||
#include "HttpManager.h"
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
@@ -178,7 +179,6 @@ private:
|
||||
void DestroyBindStores();
|
||||
void CreateBindStores();
|
||||
void InvalidateObjects();
|
||||
bool ExecuteCall(int params, int res);
|
||||
|
||||
// Use ReloadEluna() to make eluna reload
|
||||
// This is called on world update to reload eluna
|
||||
@@ -237,6 +237,7 @@ public:
|
||||
|
||||
lua_State* L;
|
||||
EventMgr* eventMgr;
|
||||
HttpManager httpManager;
|
||||
|
||||
BindingMap< EventKey<Hooks::ServerEvents> >* ServerEventBindings;
|
||||
BindingMap< EventKey<Hooks::PlayerEvents> >* PlayerEventBindings;
|
||||
@@ -301,6 +302,8 @@ public:
|
||||
ElunaTemplate<T>::Push(luastate, ptr);
|
||||
}
|
||||
|
||||
bool ExecuteCall(int params, int res);
|
||||
|
||||
/*
|
||||
* Returns `true` if Eluna has instance data for `map`.
|
||||
*/
|
||||
|
||||
@@ -145,6 +145,7 @@ luaL_Reg GlobalMethods[] =
|
||||
{ "CreateUint64", &LuaGlobalFunctions::CreateULongLong },
|
||||
{ "StartGameEvent", &LuaGlobalFunctions::StartGameEvent },
|
||||
{ "StopGameEvent", &LuaGlobalFunctions::StopGameEvent },
|
||||
{ "HttpRequest", &LuaGlobalFunctions::HttpRequest },
|
||||
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
@@ -321,6 +321,7 @@ void Eluna::OnWorldUpdate(uint32 diff)
|
||||
}
|
||||
|
||||
eventMgr->globalProcessor->Update(diff);
|
||||
httpManager.HandleHttpResponses();
|
||||
|
||||
START_HOOK(WORLD_EVENT_ON_UPDATE);
|
||||
Push(diff);
|
||||
|
||||
7832
libs/httplib.h
Normal file
7832
libs/httplib.h
Normal file
File diff suppressed because it is too large
Load Diff
227
libs/rigtorp/SPSCQueue.h
Normal file
227
libs/rigtorp/SPSCQueue.h
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
Copyright (c) 2020 Erik Rigtorp <erik@rigtorp.se>
|
||||
|
||||
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 <atomic>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <memory> // std::allocator
|
||||
#include <new> // std::hardware_destructive_interference_size
|
||||
#include <stdexcept>
|
||||
#include <type_traits> // std::enable_if, std::is_*_constructible
|
||||
|
||||
namespace rigtorp {
|
||||
|
||||
template <typename T, typename Allocator = std::allocator<T>> class SPSCQueue {
|
||||
|
||||
#if defined(__cpp_if_constexpr) && defined(__cpp_lib_void_t)
|
||||
template <typename Alloc2, typename = void>
|
||||
struct has_allocate_at_least : std::false_type {};
|
||||
|
||||
template <typename Alloc2>
|
||||
struct has_allocate_at_least<
|
||||
Alloc2, std::void_t<typename Alloc2::value_type,
|
||||
decltype(std::declval<Alloc2 &>().allocate_at_least(
|
||||
size_t{}))>> : std::true_type {};
|
||||
#endif
|
||||
|
||||
public:
|
||||
explicit SPSCQueue(const size_t capacity,
|
||||
const Allocator &allocator = Allocator())
|
||||
: capacity_(capacity), allocator_(allocator) {
|
||||
// The queue needs at least one element
|
||||
if (capacity_ < 1) {
|
||||
capacity_ = 1;
|
||||
}
|
||||
capacity_++; // Needs one slack element
|
||||
// Prevent overflowing size_t
|
||||
if (capacity_ > SIZE_MAX - 2 * kPadding) {
|
||||
capacity_ = SIZE_MAX - 2 * kPadding;
|
||||
}
|
||||
|
||||
#if defined(__cpp_if_constexpr) && defined(__cpp_lib_void_t)
|
||||
if constexpr (has_allocate_at_least<Allocator>::value) {
|
||||
auto res = allocator_.allocate_at_least(capacity_ + 2 * kPadding);
|
||||
slots_ = res.ptr;
|
||||
capacity_ = res.count - 2 * kPadding;
|
||||
} else {
|
||||
slots_ = std::allocator_traits<Allocator>::allocate(
|
||||
allocator_, capacity_ + 2 * kPadding);
|
||||
}
|
||||
#else
|
||||
slots_ = std::allocator_traits<Allocator>::allocate(
|
||||
allocator_, capacity_ + 2 * kPadding);
|
||||
#endif
|
||||
|
||||
static_assert(alignof(SPSCQueue<T>) == kCacheLineSize, "");
|
||||
static_assert(sizeof(SPSCQueue<T>) >= 3 * kCacheLineSize, "");
|
||||
assert(reinterpret_cast<char *>(&readIdx_) -
|
||||
reinterpret_cast<char *>(&writeIdx_) >=
|
||||
static_cast<std::ptrdiff_t>(kCacheLineSize));
|
||||
}
|
||||
|
||||
~SPSCQueue() {
|
||||
while (front()) {
|
||||
pop();
|
||||
}
|
||||
std::allocator_traits<Allocator>::deallocate(allocator_, slots_,
|
||||
capacity_ + 2 * kPadding);
|
||||
}
|
||||
|
||||
// non-copyable and non-movable
|
||||
SPSCQueue(const SPSCQueue &) = delete;
|
||||
SPSCQueue &operator=(const SPSCQueue &) = delete;
|
||||
|
||||
template <typename... Args>
|
||||
void emplace(Args &&...args) noexcept(
|
||||
std::is_nothrow_constructible<T, Args &&...>::value) {
|
||||
static_assert(std::is_constructible<T, Args &&...>::value,
|
||||
"T must be constructible with Args&&...");
|
||||
auto const writeIdx = writeIdx_.load(std::memory_order_relaxed);
|
||||
auto nextWriteIdx = writeIdx + 1;
|
||||
if (nextWriteIdx == capacity_) {
|
||||
nextWriteIdx = 0;
|
||||
}
|
||||
while (nextWriteIdx == readIdxCache_) {
|
||||
readIdxCache_ = readIdx_.load(std::memory_order_acquire);
|
||||
}
|
||||
new (&slots_[writeIdx + kPadding]) T(std::forward<Args>(args)...);
|
||||
writeIdx_.store(nextWriteIdx, std::memory_order_release);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
bool try_emplace(Args &&...args) noexcept(
|
||||
std::is_nothrow_constructible<T, Args &&...>::value) {
|
||||
static_assert(std::is_constructible<T, Args &&...>::value,
|
||||
"T must be constructible with Args&&...");
|
||||
auto const writeIdx = writeIdx_.load(std::memory_order_relaxed);
|
||||
auto nextWriteIdx = writeIdx + 1;
|
||||
if (nextWriteIdx == capacity_) {
|
||||
nextWriteIdx = 0;
|
||||
}
|
||||
if (nextWriteIdx == readIdxCache_) {
|
||||
readIdxCache_ = readIdx_.load(std::memory_order_acquire);
|
||||
if (nextWriteIdx == readIdxCache_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
new (&slots_[writeIdx + kPadding]) T(std::forward<Args>(args)...);
|
||||
writeIdx_.store(nextWriteIdx, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
void push(const T &v) noexcept(std::is_nothrow_copy_constructible<T>::value) {
|
||||
static_assert(std::is_copy_constructible<T>::value,
|
||||
"T must be copy constructible");
|
||||
emplace(v);
|
||||
}
|
||||
|
||||
template <typename P, typename = typename std::enable_if<
|
||||
std::is_constructible<T, P &&>::value>::type>
|
||||
void push(P &&v) noexcept(std::is_nothrow_constructible<T, P &&>::value) {
|
||||
emplace(std::forward<P>(v));
|
||||
}
|
||||
|
||||
bool
|
||||
try_push(const T &v) noexcept(std::is_nothrow_copy_constructible<T>::value) {
|
||||
static_assert(std::is_copy_constructible<T>::value,
|
||||
"T must be copy constructible");
|
||||
return try_emplace(v);
|
||||
}
|
||||
|
||||
template <typename P, typename = typename std::enable_if<
|
||||
std::is_constructible<T, P &&>::value>::type>
|
||||
bool try_push(P &&v) noexcept(std::is_nothrow_constructible<T, P &&>::value) {
|
||||
return try_emplace(std::forward<P>(v));
|
||||
}
|
||||
|
||||
T *front() noexcept {
|
||||
auto const readIdx = readIdx_.load(std::memory_order_relaxed);
|
||||
if (readIdx == writeIdxCache_) {
|
||||
writeIdxCache_ = writeIdx_.load(std::memory_order_acquire);
|
||||
if (writeIdxCache_ == readIdx) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return &slots_[readIdx + kPadding];
|
||||
}
|
||||
|
||||
void pop() noexcept {
|
||||
static_assert(std::is_nothrow_destructible<T>::value,
|
||||
"T must be nothrow destructible");
|
||||
auto const readIdx = readIdx_.load(std::memory_order_relaxed);
|
||||
assert(writeIdx_.load(std::memory_order_acquire) != readIdx);
|
||||
slots_[readIdx + kPadding].~T();
|
||||
auto nextReadIdx = readIdx + 1;
|
||||
if (nextReadIdx == capacity_) {
|
||||
nextReadIdx = 0;
|
||||
}
|
||||
readIdx_.store(nextReadIdx, std::memory_order_release);
|
||||
}
|
||||
|
||||
size_t size() const noexcept {
|
||||
std::ptrdiff_t diff = writeIdx_.load(std::memory_order_acquire) -
|
||||
readIdx_.load(std::memory_order_acquire);
|
||||
if (diff < 0) {
|
||||
diff += capacity_;
|
||||
}
|
||||
return static_cast<size_t>(diff);
|
||||
}
|
||||
|
||||
bool empty() const noexcept { return size() == 0; }
|
||||
|
||||
size_t capacity() const noexcept { return capacity_ - 1; }
|
||||
|
||||
private:
|
||||
#ifdef __cpp_lib_hardware_interference_size
|
||||
static constexpr size_t kCacheLineSize =
|
||||
std::hardware_destructive_interference_size;
|
||||
#else
|
||||
static constexpr size_t kCacheLineSize = 64;
|
||||
#endif
|
||||
|
||||
// Padding to avoid false sharing between slots_ and adjacent allocations
|
||||
static constexpr size_t kPadding = (kCacheLineSize - 1) / sizeof(T) + 1;
|
||||
|
||||
private:
|
||||
size_t capacity_;
|
||||
T *slots_;
|
||||
#if defined(__has_cpp_attribute) && __has_cpp_attribute(no_unique_address)
|
||||
Allocator allocator_ [[no_unique_address]];
|
||||
#else
|
||||
Allocator allocator_;
|
||||
#endif
|
||||
|
||||
// Align to cache line size in order to avoid false sharing
|
||||
// readIdxCache_ and writeIdxCache_ is used to reduce the amount of cache
|
||||
// coherency traffic
|
||||
alignas(kCacheLineSize) std::atomic<size_t> writeIdx_ = {0};
|
||||
alignas(kCacheLineSize) size_t readIdxCache_ = 0;
|
||||
alignas(kCacheLineSize) std::atomic<size_t> readIdx_ = {0};
|
||||
alignas(kCacheLineSize) size_t writeIdxCache_ = 0;
|
||||
|
||||
// Padding to avoid adjacent allocations to share cache line with
|
||||
// writeIdxCache_
|
||||
char padding_[kCacheLineSize - sizeof(writeIdxCache_)];
|
||||
};
|
||||
} // namespace rigtorp
|
||||
Reference in New Issue
Block a user