ddnet/src/engine/e_ringbuffer.c
2008-02-03 23:55:37 +00:00

217 lines
4.3 KiB
C

#include <engine/e_system.h>
enum
{
RBFLAG_FREE=1
};
typedef struct RBITEM_t
{
struct RBITEM_t *prev;
struct RBITEM_t *next;
int flags;
int size;
} RBITEM;
typedef struct
{
/* what you need */
RBITEM *next_alloc;
RBITEM *last_alloc;
RBITEM *first;
RBITEM *last;
void *memory;
int size;
} RINGBUFFER;
RINGBUFFER *ringbuf_init(void *memory, int size)
{
RINGBUFFER *rb = (RINGBUFFER *)memory;
rb->memory = rb+1;
rb->size = (size-sizeof(RINGBUFFER))/sizeof(RBITEM)*sizeof(RBITEM);
rb->first = (RBITEM *)rb->memory;
rb->first->flags = RBFLAG_FREE;
rb->first->prev = 0;
rb->first->size = rb->size;
rb->last = rb->first;
rb->next_alloc = rb->first;
rb->last_alloc = 0;
return rb;
}
static RBITEM *ringbuf_free(RINGBUFFER *rb, RBITEM *item)
{
dbg_assert(!(item->flags&RBFLAG_FREE), "trying to free element that is already freed");
item->flags |= RBFLAG_FREE;
/* merge with all free items backwards */
while(item->prev && (item->prev->flags&RBFLAG_FREE))
{
item->prev->size += item->size;
item->prev->next = item->next;
item = item->prev;
if(item->next)
item->next->prev = item;
}
/* merge with all free items forwards */
while(item->next && (item->next->flags&RBFLAG_FREE))
{
item->size += item->next->size;
item->next = item->next->next;
if(item->next)
item->next->prev = item;
}
if(!item->next)
rb->last = item;
return item;
}
void ringbuf_validate(RINGBUFFER *rb)
{
RBITEM *prev = 0;
RBITEM *cur = rb->first;
int freechunks = 0;
while(cur)
{
if(cur->flags&RBFLAG_FREE)
freechunks++;
dbg_assert(freechunks <= 2, "too many free chunks");
dbg_assert(cur->prev == prev, "pointers doesn't match");
dbg_assert(!prev || prev->next == cur, "pointers doesn't match");
dbg_assert(cur->next || cur == rb->last, "last isn't last");
prev = cur;
cur = cur->next;
}
}
static RBITEM *ringbuf_try_allocate(RINGBUFFER *rb, int wanted_size)
{
RBITEM *item = rb->next_alloc;
/* check for space */
if(!(item->flags&RBFLAG_FREE) || item->size < wanted_size)
return 0x0;
/* split the block if needed */
if(item->size > wanted_size)
{
RBITEM *new_item = (RBITEM *)((char *)item + wanted_size);
new_item->prev = item;
new_item->next = item->next;
if(new_item->next)
new_item->next->prev = new_item;
item->next = new_item;
new_item->flags = RBFLAG_FREE;
new_item->size = item->size - wanted_size;
item->size = wanted_size;
if(!new_item->next)
rb->last = new_item;
}
item->flags &= ~RBFLAG_FREE;
rb->next_alloc = item->next;
if(!rb->next_alloc)
rb->next_alloc = rb->first;
rb->last_alloc = item;
return item;
}
void *ringbuf_allocate(RINGBUFFER *rb, int size)
{
int wanted_size = (size+sizeof(RBITEM)+sizeof(RBITEM)-1)/sizeof(RBITEM)*sizeof(RBITEM);
RBITEM *block = 0;
/* check if we even can fit this block */
if(wanted_size > rb->size)
return 0;
/* free the block if it is in use */
if(!(rb->next_alloc->flags&RBFLAG_FREE))
rb->next_alloc = ringbuf_free(rb, rb->next_alloc);
/* first attempt */
block = ringbuf_try_allocate(rb, wanted_size);
if(block)
return block+1;
/* ok, we need to wipe some blocks in order to get space */
while(1)
{
if(rb->next_alloc->next)
rb->next_alloc = ringbuf_free(rb, rb->next_alloc->next);
else
{
rb->next_alloc = rb->first;
rb->next_alloc = ringbuf_free(rb, rb->next_alloc);
}
/* try allocate again */
block = ringbuf_try_allocate(rb, wanted_size);
if(block)
return block+1;
}
}
void *ringbuf_prev(RINGBUFFER *rb, void *current)
{
RBITEM *item = ((RBITEM *)current) - 1;
while(1)
{
/* back up one step */
item = item->prev;
if(!item)
item = rb->last;
/* we have gone around */
if(item == rb->last_alloc)
return 0;
if(!(item->flags&RBFLAG_FREE))
return item+1;
}
}
void *ringbuf_next(RINGBUFFER *rb, void *current)
{
RBITEM *item = ((RBITEM *)current) - 1;
while(1)
{
/* back up one step */
item = item->next;
if(!item)
item = rb->first;
/* we have gone around */
if(item == rb->last_alloc)
return 0;
if(!(item->flags&RBFLAG_FREE))
return item+1;
}
}
void *ringbuf_item_ptr(void *p)
{
return ((RBITEM *)p) - 1;
}
void *ringbuf_first(RINGBUFFER *rb)
{
return ringbuf_next(rb, rb->last_alloc+1);
}
void *ringbuf_last(RINGBUFFER *rb)
{
return rb->last_alloc+1;
}