#pragma once // // Magic Ring Buffer // https://gist.github.com/mmozeiko/3b09a340f3c53e5eaed699a1aea95250 // // Sets up memory mapping so the same memory block follows itself in virtual address space: // // [abcd...xyz][abc...xyz] // // This allows reading or writing memory to ringbuffer without worrying of accessing // memory past the end of buffer. Normally you would need to split such reads or writes // into two read/write operations. But because memory is duplicated in virtual address // space, the buffer reads/writes can always happen on sequential bytes in one operation. // // Typical usage: // // MagicRB Buffer; // MRB_Init(&Buffer, 4096); // buffer will have space for at least 4096 bytes // // size_t WriteAvailable = MRB_GetFree(&Buffer); // how many bytes available to write // void* WritePtr = MRB_WritePtr(&Buffer); // get pointer to write to // ... write up to WriteAvailable bytes to WritePtr // MRB_WriteEnd(&Buffer, WriteSize); // finish writing, now reader is allowed // // to read WriteSize amount of bytes // // size_t ReadAvailable = MRB_GetUsed(&Buffer); // how many bytes available to read // void* ReadPtr = MRB_ReadPtr(&Buffer); // get pointer to read from // ... read up to ReadAvailable bytes from ReadPtr // MRB_ReadEnd(&Buffer, ReadSize); // finish reading, now writer is allowed // // to write extra ReadSize amount of bytes // // Basically writer is allowed to write up to "free" amount of bytes. // And reader is allowed to read up to "used" amount of bytes. // It's up to you to check how many free or used bytes are in buffer before reading and writing. // #include #include // // interface // typedef struct { void* Data; size_t Size; size_t Read; size_t Write; } MagicRB; // allocation & freeing, size will be rounded up to multiple of page size static inline void MRB_Init(MagicRB* RingBuffer, size_t Size); static inline void MRB_Done(MagicRB* RingBuffer); // size queries: // used = how many bytes are available for reading // free = how many bytes are available for writing static inline size_t MRB_GetUsed(const MagicRB* RingBuffer); static inline size_t MRB_GetFree(const MagicRB* RingBuffer); // check if buffer is empty or full static inline bool MRB_IsEmpty(const MagicRB* RingBuffer); static inline bool MRB_IsFull(const MagicRB* RingBuffer); // reading, must not read more than "used" amount of bytes static inline void* MRB_ReadPtr(MagicRB* RingBuffer); static inline void MRB_ReadEnd(MagicRB* RingBuffer, size_t ReadSize); // writing, must not write more than "free" amount of bytes static inline void* MRB_WritePtr(MagicRB* RingBuffer); static inline void MRB_WriteEnd(MagicRB* RingBuffer, size_t WriteSize); // // implementation // #if defined(_WIN32) # include # pragma comment (lib, "onecore") #elif defined(__linux__) # include # include # include # include #elif defined(__APPLE__) # include # include # include # include #else # error target not supported #endif #if !defined(MRB_ASSERT) # if defined(_MSC_VER) # if !defined(NDEBUG) # include # define MRB_ASSERT(cond) do { if (!(cond)) __debugbreak(); } while (0) # else # define MRB_ASSERT(cond) do { (void)sizeof(cond); } while (0) # endif # else # include # define MRB_ASSERT(cond) assert(cond) # endif #endif void MRB_Init(MagicRB* RingBuffer, size_t Size) { MRB_ASSERT(Size != 0 && Size <= ~((size_t)0) >> 2); #if defined(_WIN32) SYSTEM_INFO SystemInfo; GetSystemInfo(&SystemInfo); const size_t PageSize = SystemInfo.dwPageSize; Size = (Size + PageSize - 1) & ~(PageSize - 1); char* Placeholder1 = (char*)VirtualAlloc2(NULL, NULL, 2 * Size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, NULL, 0); char* Placeholder2 = (char*)Placeholder1 + Size; MRB_ASSERT(Placeholder1 && "failed to reserve placeholder"); BOOL Ok = VirtualFree(Placeholder1, Size, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER); MRB_ASSERT(Ok && "failed to split reservation into two placeholders"); HANDLE Section = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, (DWORD)(Size >> 32), (DWORD)Size, NULL); MRB_ASSERT(Section && "failed to create mapping"); void* View1 = MapViewOfFile3(Section, NULL, Placeholder1, 0, Size, MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, NULL, 0); MRB_ASSERT(View1 && "failed to map first half of mapping"); void* View2 = MapViewOfFile3(Section, NULL, Placeholder2, 0, Size, MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, NULL, 0); MRB_ASSERT(View2 && "failed to map second half of mapping"); CloseHandle(Section); VirtualFree(Placeholder1, 0, MEM_RELEASE); VirtualFree(Placeholder2, 0, MEM_RELEASE); RingBuffer->Data = View1; #elif defined(__linux__) const size_t PageSize = sysconf(_SC_PAGESIZE); Size = (Size + PageSize - 1) & ~(PageSize - 1); int MemFd = syscall(__NR_memfd_create, "MagicRingBuffer", FD_CLOEXEC); MRB_ASSERT(MemFd > 0 && "failed to create memfd"); int Ok = ftruncate(MemFd, Size); MRB_ASSERT(Ok == 0 && "failed to set memfd size"); char* Base = (char*)mmap(NULL, 2 * Size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); MRB_ASSERT(Base != MAP_FAILED && "failed to create memory mapping"); void* Mapped1 = mmap(Base + 0 * Size, Size, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, MemFd, 0); MRB_ASSERT(Mapped1 != MAP_FAILED && "failed to map first half of mapping"); void* Mapped2 = mmap(Base + 1 * Size, Size, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, MemFd, 0); MRB_ASSERT(Mapped2 != MAP_FAILED && "failed to map second half of mapping"); close(MemFd); RingBuffer->Data = Base; #elif defined(__APPLE__) Size = mach_vm_round_page(Size); mach_port_t Task = mach_task_self(); mach_vm_address_t Address; int AllocateOk = mach_vm_allocate(Task, &Address, 2 * Size, VM_FLAGS_ANYWHERE); MRB_ASSERT(AllocateOk == 0 && "failed to allocate memory"); int Mapping1 = mach_vm_allocate(Task, &Address, Size, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE); MRB_ASSERT(Mapping1 == 0 && "failed to map first half of mapping"); const vm_prot_t PageProtection = VM_PROT_READ | VM_PROT_WRITE; mach_port_t MemoryPort; mach_vm_size_t MemorySize = Size; int PortOk = mach_make_memory_entry_64(Task, &MemorySize, Address, PageProtection, &MemoryPort, MACH_PORT_NULL); MRB_ASSERT(PortOk == 0 && "failed to create mach port"); mach_vm_address_t Address2 = Address + Size; int Mapping2 = mach_vm_map(Task, &Address2, Size, 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, MemoryPort, 0, FALSE, PageProtection, PageProtection, VM_INHERIT_NONE); MRB_ASSERT(Mapping2 == 0 && "failed to map second half of mapping"); mach_port_deallocate(Task, MemoryPort); RingBuffer->Data = (void*)Address; #endif RingBuffer->Size = Size; RingBuffer->Read = 0; RingBuffer->Write = 0; } void MRB_Done(MagicRB* RingBuffer) { #if defined(_WIN32) UnmapViewOfFileEx((char*)RingBuffer->Data, 0); UnmapViewOfFileEx((char*)RingBuffer->Data + RingBuffer->Size, 0); #elif defined(__linux__) munmap(RingBuffer->Data, 2 * RingBuffer->Size); #elif defined(__APPLE__) mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)RingBuffer->Data, 2 * RingBuffer->Size); #endif } size_t MRB_GetUsed(const MagicRB* RingBuffer) { return RingBuffer->Write - RingBuffer->Read; } size_t MRB_GetFree(const MagicRB* RingBuffer) { return RingBuffer->Size - MRB_GetUsed(RingBuffer); } bool MRB_IsEmpty(const MagicRB* RingBuffer) { return MRB_GetUsed(RingBuffer) == 0; } bool MRB_IsFull(const MagicRB* RingBuffer) { return MRB_GetFree(RingBuffer) == 0; } void* MRB_ReadPtr(MagicRB* RingBuffer) { MRB_ASSERT(MRB_GetUsed(RingBuffer) != 0); size_t Offset = RingBuffer->Read & (RingBuffer->Size - 1); return (char*)RingBuffer->Data + Offset; } void MRB_ReadEnd(MagicRB* RingBuffer, size_t ReadSize) { MRB_ASSERT(ReadSize <= MRB_GetUsed(RingBuffer)); RingBuffer->Read += ReadSize; } void* MRB_WritePtr(MagicRB* RingBuffer) { MRB_ASSERT(MRB_GetFree(RingBuffer) != 0); size_t Offset = RingBuffer->Write & (RingBuffer->Size - 1); return (char*)RingBuffer->Data + Offset; } void MRB_WriteEnd(MagicRB* RingBuffer, size_t WriteSize) { MRB_ASSERT(WriteSize <= MRB_GetFree(RingBuffer)); RingBuffer->Write += WriteSize; }