76 lines
2.2 KiB
C
76 lines
2.2 KiB
C
|
|
#pragma once
|
||
|
|
#include <atomic>
|
||
|
|
#include <vector>
|
||
|
|
#include <memory>
|
||
|
|
|
||
|
|
// message queue
|
||
|
|
struct BroadcastMessage {
|
||
|
|
std::vector<uint8_t> frameData;
|
||
|
|
int excludeClientId;
|
||
|
|
|
||
|
|
BroadcastMessage() = default;
|
||
|
|
BroadcastMessage(std::vector<uint8_t> data, int excludeId)
|
||
|
|
: frameData(std::move(data)), excludeClientId(excludeId) {}
|
||
|
|
};
|
||
|
|
|
||
|
|
class LockFreeBroadcastQueue {
|
||
|
|
private:
|
||
|
|
static const size_t QUEUE_SIZE = 65536; // Must be power of 2
|
||
|
|
static const size_t QUEUE_MASK = QUEUE_SIZE - 1;
|
||
|
|
|
||
|
|
struct alignas(64) QueueSlot {
|
||
|
|
std::atomic<bool> ready{false};
|
||
|
|
BroadcastMessage message;
|
||
|
|
};
|
||
|
|
|
||
|
|
alignas(64) std::atomic<size_t> head{0};
|
||
|
|
alignas(64) std::atomic<size_t> tail{0};
|
||
|
|
QueueSlot queue[QUEUE_SIZE];
|
||
|
|
|
||
|
|
public:
|
||
|
|
bool push(BroadcastMessage&& message) {
|
||
|
|
const size_t currentTail = tail.load(std::memory_order_relaxed);
|
||
|
|
const size_t nextTail = (currentTail + 1) & QUEUE_MASK;
|
||
|
|
|
||
|
|
// queue full
|
||
|
|
if (nextTail == head.load(std::memory_order_acquire)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
QueueSlot& slot = queue[currentTail];
|
||
|
|
slot.message = std::move(message);
|
||
|
|
slot.ready.store(true, std::memory_order_release);
|
||
|
|
tail.store(nextTail, std::memory_order_release);
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool pop(BroadcastMessage& message) {
|
||
|
|
const size_t currentHead = head.load(std::memory_order_relaxed);
|
||
|
|
|
||
|
|
// empty
|
||
|
|
if (currentHead == tail.load(std::memory_order_acquire)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
QueueSlot& slot = queue[currentHead];
|
||
|
|
if (!slot.ready.load(std::memory_order_acquire)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
message = std::move(slot.message);
|
||
|
|
slot.ready.store(false, std::memory_order_release);
|
||
|
|
head.store((currentHead + 1) & QUEUE_MASK, std::memory_order_release);
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
size_t size() const {
|
||
|
|
return (tail.load(std::memory_order_relaxed) - head.load(std::memory_order_relaxed)) & QUEUE_MASK;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool empty() const {
|
||
|
|
return head.load(std::memory_order_relaxed) == tail.load(std::memory_order_relaxed);
|
||
|
|
}
|
||
|
|
};
|