
--lock-free ring buffer.
--Written by Cosmin Apreutesei. Public Domain.
--Origins in libsound.io by Andrew Kelley. MIT License.

local ffi = require'ffi'

ffi.cdef[[
struct lfrb_state {
	long _write_offset;
	long _read_offset;
	int capacity;
	char data[?];
};
int lfrb_write_index(struct lfrb_state*);
void lfrb_advance_write_index(struct lfrb_state*, int count);
int lfrb_read_index(struct lfrb_state*);
void lfrb_advance_read_index(struct lfrb_state*, int count);
int lfrb_fill_count(struct lfrb_state*);
int lfrb_free_count(struct lfrb_state*);
void lfrb_clear(struct lfrb_state*);
]]

local C = ffi.load'lfrb'
local M = {C = C}

function M.new(capacity)
	local rb = ffi.new('struct lfrb_state', capacity)
	rb.capacity = capacity
	return rb
end

function M.wrap(buffer_addr)
	return ffi.cast('struct lfrb_state*', buffer_addr)
end

--TODO: read_segments, write_segments

ffi.metatype('struct lfrb_state', {__index = {
	write_index = C.lfrb_write_index,
	advance_write_index = C.lfrb_advance_write_index,
	read_index = C.lfrb_read_index,
	advance_read_index = C.lfrb_advance_read_index,
	fill_count = C.lfrb_fill_count,
	free_count = C.lfrb_free_count,
	clear = C.lfrb_clear,
}})

if not ... then
	io.stdout:setvbuf'full'
	local lfrb = M
	local pthread = require'pthread'
	local lua = require'luastate'
	local time = require'time'

	local state = lua.open()
	state:openlibs()
	state:push(function(rb_addr, trials, maxpause)
		local ffi = require'ffi'
		local lfrb = require'lfrb'
		local time = require'time'
		local rb = lfrb.wrap(rb_addr)
		local function func()
			for i = 1, trials do
				local n = rb:fill_count()
				local n = math.floor(math.random() * n)
				if n > 0 then
					io.stdout:write(string.format('read  %4d [%3d%%]\n',
						n, rb:fill_count() / rb.capacity * 100))
					rb:advance_read_index(n)
				end
				time.sleep(math.random() * maxpause)
			end
		end
		local cb = ffi.cast('void *(*)(void *)', func)
		local cb_addr = tonumber(ffi.cast('intptr_t', cb))
		return cb_addr
	end)

	local trials = 10^6
	local maxpause = 0

	local rb = lfrb.new(1000)
	local rb_addr = tonumber(ffi.cast('intptr_t', ffi.cast('void*', rb)))
	local cb_addr = state:call(rb_addr, trials, maxpause)
	local cb = ffi.cast('void*', cb_addr)
	local thread = pthread.new(cb)

	for i = 1, trials do
		local n = rb:free_count()
		local n = math.floor(math.random() * n)
		if n > 0 then
			io.stdout:write(string.format('write %4d [%3d%%]\n',
				n, rb:fill_count() / rb.capacity * 100))
			rb:advance_write_index(n)
		end
		time.sleep(math.random() * maxpause)
	end

	thread:join()
end

return M
