diff --git a/src/engine/e_ringbuffer.c b/src/engine/e_ringbuffer.c index ed3326d41..39dfa3e9c 100644 --- a/src/engine/e_ringbuffer.c +++ b/src/engine/e_ringbuffer.c @@ -2,33 +2,29 @@ #include "e_ringbuffer.h" -enum +typedef struct RBITEM { - RBFLAG_FREE=1 -}; + struct RBITEM *prev; + struct RBITEM *next; + int free; + int size; +} RBITEM; /* */ struct RINGBUFFER { - struct RBITEM_t *next_alloc; - struct RBITEM_t *last_alloc; - struct RBITEM_t *first; - struct RBITEM_t *last; + RBITEM *produce; + RBITEM *consume; + + RBITEM *first; + RBITEM *last; void *memory; int size; int flags; }; -typedef struct RBITEM_t -{ - struct RBITEM_t *prev; - struct RBITEM_t *next; - int flags; - int size; -} RBITEM; - RINGBUFFER *ringbuf_init(void *memory, int size, int flags) { RINGBUFFER *rb = (RINGBUFFER *)memory; @@ -37,115 +33,80 @@ RINGBUFFER *ringbuf_init(void *memory, int size, int flags) 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->free = 1; rb->first->size = rb->size; rb->last = rb->first; - rb->next_alloc = rb->first; + rb->produce = rb->first; + rb->consume = rb->first; rb->flags = flags; return rb; } -static RBITEM *ringbuf_free(RINGBUFFER *rb, RBITEM *item) +static RBITEM *ringbuf_nextblock(RINGBUFFER *rb, RBITEM *item) { - dbg_assert(!(item->flags&RBFLAG_FREE), "trying to free element that is already freed"); - item->flags |= RBFLAG_FREE; - - /* TODO: this should be handled better */ - if(item == rb->last_alloc) - rb->last_alloc = 0; - - /* 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; + if(item->next) + return item->next; + return rb->first; } -void ringbuf_validate(RINGBUFFER *rb) +static RBITEM *ringbuf_prevblock(RINGBUFFER *rb, RBITEM *item) { - 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; - } + if(item->prev) + return item->prev; + return rb->last; } -static RBITEM *ringbuf_try_allocate(RINGBUFFER *rb, int wanted_size) +static RBITEM *ringbuf_mergeback(RINGBUFFER *rb, RBITEM *item) { - 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; -} + /* make sure that this block and previous block is free */ + if(!item->free || !item->prev || !item->prev->free) + return item; -void ringbuf_popfirst(RINGBUFFER *rb) -{ - if(rb->next_alloc->next) - rb->next_alloc = ringbuf_free(rb, rb->next_alloc->next); + /* merge the blocks */ + item->prev->size += item->size; + item->prev->next = item->next; + + /* fixup pointers */ + if(item->next) + item->next->prev = item->prev; else + rb->last = item->prev; + + if(item == rb->produce) + rb->produce = item->prev; + + if(item == rb->consume) + rb->consume = item->prev; + + /* return the current block */ + return item->prev; +} + +int ringbuf_popfirst(RINGBUFFER *rb) +{ + if(rb->consume->free) + return 0; + + /* set the free flag */ + rb->consume->free = 1; + + /* previous block is also free, merge them */ + rb->consume = ringbuf_mergeback(rb, rb->consume); + + /* advance the consume pointer */ + rb->consume = ringbuf_nextblock(rb, rb->consume); + while(rb->consume->free && rb->consume != rb->produce) { - rb->next_alloc = rb->first; - rb->next_alloc = ringbuf_free(rb, rb->next_alloc); + rb->consume = ringbuf_mergeback(rb, rb->consume); + rb->consume = ringbuf_nextblock(rb, rb->consume); } + + /* in the case that we have catched up with the produce pointer */ + /* we might stand on a free block so merge em */ + ringbuf_mergeback(rb, rb->consume); + return 1; } void *ringbuf_allocate(RINGBUFFER *rb, int size) @@ -157,87 +118,245 @@ void *ringbuf_allocate(RINGBUFFER *rb, int size) 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; - - /* check if we just should return null */ - if(!(rb->flags&RINGBUF_FLAG_RECYCLE)) - return 0; - - /* ok, we need to wipe some blocks in order to get space */ - while(1) + while(1) { - /* pop one */ - ringbuf_popfirst(rb); - - /* try allocate again */ - block = ringbuf_try_allocate(rb, wanted_size); + /* check for space */ + if(rb->produce->free) + { + if(rb->produce->size >= wanted_size) + block = rb->produce; + else + { + /* wrap around to try to find a block */ + if(rb->first->free && rb->first->size >= wanted_size) + block = rb->first; + } + } + if(block) - return block+1; + break; + else + { + /* we have no block, check our policy and see what todo */ + if(rb->flags&RINGBUF_FLAG_RECYCLE) + { + if(!ringbuf_popfirst(rb)) + return 0; + } + else + return 0; + } } + + /* okey, we have our block */ + + /* split the block if needed */ + if(block->size > wanted_size) + { + RBITEM *new_item = (RBITEM *)((char *)block + wanted_size); + new_item->prev = block; + new_item->next = block->next; + if(new_item->next) + new_item->next->prev = new_item; + block->next = new_item; + + new_item->free = 1; + new_item->size = block->size - wanted_size; + block->size = wanted_size; + + if(!new_item->next) + rb->last = new_item; + } + + + /* set next block */ + rb->produce = ringbuf_nextblock(rb, block); + + /* set as used and return the item pointer */ + block->free = 0; + 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) + item = ringbuf_prevblock(rb, item); + if(item == rb->produce) return 0; - - if(!(item->flags&RBFLAG_FREE)) + if(!item->free) return item+1; } } - void *ringbuf_next(RINGBUFFER *rb, void *current) { RBITEM *item = ((RBITEM *)current) - 1; - - /* we have gone around */ - if(item == rb->last_alloc) - return 0; - + while(1) { - /* back up one step */ - item = item->next; - if(!item) - item = rb->first; - - if(!(item->flags&RBFLAG_FREE)) + item = ringbuf_nextblock(rb, item); + if(item == rb->produce) + return 0; + if(!item->free) return item+1; } } -void *ringbuf_item_ptr(void *p) -{ - return ((RBITEM *)p) - 1; -} - void *ringbuf_first(RINGBUFFER *rb) { - if(rb->last_alloc && rb->last_alloc->next) - return ringbuf_next(rb, rb->last_alloc->next+1); - return 0x0; + if(rb->consume->free) + return 0; + return rb->consume+1; } void *ringbuf_last(RINGBUFFER *rb) { - if(rb->last_alloc == 0) - return 0; - return rb->last_alloc+1; + if(!rb->produce->free) + return rb->produce+1; + return ringbuf_prev(rb, rb->produce+1); +} + + +/* debugging and testing stuff */ + +static void ringbuf_debugdump(RINGBUFFER *rb, const char *msg) +{ + RBITEM *cur = rb->first; + + dbg_msg("ringbuf", "-- dumping --"); + + while(cur) + { + char flags[4] = " "; + if(cur->free) + flags[0] = 'F'; + if(cur == rb->consume) + flags[1] = '>'; + if(cur == rb->produce) + flags[2] = '<'; + dbg_msg("ringbuf", "%s %d", flags, cur->size); + cur = cur->next; + } + + dbg_msg("ringbuf", "-- --"); + + if(msg) + dbg_assert(0, msg); +} + + + +static void ringbuf_validate(RINGBUFFER *rb) +{ + RBITEM *prev = 0; + RBITEM *cur = rb->first; + int freechunks = 0; + int got_consume = 0; + int got_produce = 0; + + while(cur) + { + + if(cur->free) + freechunks++; + + if(freechunks > 2) ringbuf_debugdump(rb, "too many free chunks"); + if(prev && prev->free && cur->free) ringbuf_debugdump(rb, "two free chunks next to each other"); + if(cur == rb->consume) got_consume = 1; + if(cur == rb->produce) got_produce = 1; + + dbg_assert(cur->prev == prev, "prev pointers doesn't match"); + dbg_assert(!prev || prev->next == cur, "next pointers doesn't match"); + dbg_assert(cur->next || cur == rb->last, "last isn't last"); + + prev = cur; + cur = cur->next; + } + + if(!got_consume) ringbuf_debugdump(rb, "consume pointer isn't pointing at a valid block"); + if(!got_produce) ringbuf_debugdump(rb, "produce pointer isn't pointing at a valid block"); +} + +int ringbuf_test() +{ + char buffer[256]; + RINGBUFFER *rb; + int i, s, k, m; + int count; + int testcount = 0; + + void *item; + int before; + + + for(k = 100; k < sizeof(buffer); k++) + { + if((k%10) == 0) + dbg_msg("ringbuf", "testing at %d", k); + rb = ringbuf_init(buffer, k, 0); + count = 0; + + for(s = 1; s < sizeof(buffer); s++) + { + for(i = 0; i < k*8; i++, testcount++) + { + for(m = 0, item = ringbuf_first(rb); item; item = ringbuf_next(rb, item), m++); + before = m; + + if(ringbuf_allocate(rb, s)) + { + count++; + for(m = 0, item = ringbuf_first(rb); item; item = ringbuf_next(rb, item), m++); + if(before+1 != m) ringbuf_debugdump(rb, "alloc error"); + if(count != m) ringbuf_debugdump(rb, "count error"); + } + else + { + if(ringbuf_popfirst(rb)) + { + count--; + + for(m = 0, item = ringbuf_first(rb); item; item = ringbuf_next(rb, item), m++); + if(before-1 != m) dbg_msg("", "popping error %d %d", before, m); + if(count != m) ringbuf_debugdump(rb, "count error"); + } + } + + /* remove an item every 10 */ + if((i%10) == 0) + { + for(m = 0, item = ringbuf_first(rb); item; item = ringbuf_next(rb, item), m++); + before = m; + + if(ringbuf_popfirst(rb)) + { + count--; + for(m = 0, item = ringbuf_first(rb); item; item = ringbuf_next(rb, item), m++); + if(before-1 != m) dbg_msg("", "popping error %d %d", before, m); + dbg_assert(count == m, "count error"); + } + } + + /* count items */ + for(m = 0, item = ringbuf_first(rb); item; item = ringbuf_next(rb, item), m++); + if(m != count) ringbuf_debugdump(rb, "wrong number of items during forward count"); + + for(m = 0, item = ringbuf_last(rb); item; item = ringbuf_prev(rb, item), m++); + if(m != count) ringbuf_debugdump(rb, "wrong number of items during backward count"); + + ringbuf_validate(rb); + } + + /* empty the ring buffer */ + while(ringbuf_first(rb)) + ringbuf_popfirst(rb); + ringbuf_validate(rb); + count = 0; + } + } + + return 0; } diff --git a/src/engine/e_ringbuffer.h b/src/engine/e_ringbuffer.h index 71b5831b8..6113c19b8 100644 --- a/src/engine/e_ringbuffer.h +++ b/src/engine/e_ringbuffer.h @@ -12,15 +12,12 @@ enum RINGBUFFER *ringbuf_init(void *memory, int size, int flags); void ringbuf_clear(RINGBUFFER *rb); void *ringbuf_allocate(RINGBUFFER *rb, int size); -void ringbuf_validate(RINGBUFFER *rb); - -void *ringbuf_item_ptr(void *p); void *ringbuf_prev(RINGBUFFER *rb, void *current); void *ringbuf_next(RINGBUFFER *rb, void *current); void *ringbuf_first(RINGBUFFER *rb); void *ringbuf_last(RINGBUFFER *rb); -void ringbuf_popfirst(RINGBUFFER *rb); +int ringbuf_popfirst(RINGBUFFER *rb); #endif