diff --git a/file_list.c b/file_list.c index 7d02c549a6..7d973a2d2a 100644 --- a/file_list.c +++ b/file_list.c @@ -14,6 +14,7 @@ * If not, see . */ +#define _GNU_SOURCE #include #include #include "file_list.h" diff --git a/rewind-alcaro.c b/rewind-alcaro.c index 576c9a4c8a..e69de29bb2 100644 --- a/rewind-alcaro.c +++ b/rewind-alcaro.c @@ -1,377 +0,0 @@ -#include "rewind-alcaro.h" -#include -#include - -//format per frame: -//size nextstart; -//repeat { -// uint16 numchanged; // everything is counted in units of uint16 -// if (numchanged) { -// uint16 numunchanged; // skip these before handling numchanged -// uint16[numchanged] changeddata; -// } -// else -// { -// uint32 numunchanged; -// if (!numunchanged) break; -// } -//} -//size thisstart; -// -//the start offsets point to 'nextstart' of any given compressed frame -//multibyte values are stored native endian if alignment is not enforced; if it is, little endian -//the start of the buffer contains a size pointing to the end of the buffer; the end points to its start -//wrapping is handled by returning to the start of the buffer if the compressed data could potentially hit the edge -//if the compressed data could potentially overwrite the tail pointer, the tail retreats until it can no longer collide -//so on average, ~2*maxcompsize is unused at any given moment -// -//if unaligned memory access is illegal, define NO_UNALIGNED_MEM - -#if SIZE_MAX == 0xFFFFFFFF -extern char test[(sizeof(size_t)==4)?1:-1]; -#elif SIZE_MAX == 0xFFFFFFFFFFFFFFFF -extern char test[(sizeof(size_t)==8)?1:-1]; -#define USE_64BIT -#else -#error your compiler is insane. -#endif - -#ifdef NO_UNALIGNED_MEM -//These functions assume 16bit alignment. -//They do not make any attempt at matching system native endian; values written by these can only be read by the matching partner. -#ifdef USE_64BIT -static inline void write_size_t(uint16_t* ptr, size_t val) -{ - ptr[0]=val>>0; - ptr[1]=val>>16; - ptr[2]=val>>32; - ptr[3]=val>>48; -} - -static inline size_t read_size_t(uint16_t* ptr) -{ - return ((size_t)ptr[0]<<0 | - (size_t)ptr[1]<<16 | - (size_t)ptr[2]<<32 | - (size_t)ptr[3]<<48); -} -#else -static inline void write_size_t(uint16_t* ptr, size_t val) -{ - ptr[0]=val; - ptr[1]=val>>16; -} - -static inline size_t read_size_t(uint16_t* ptr) -{ - return (ptr[0] | (size_t)ptr[1]<<16); -} -#endif - -#else -#define read_size_t(ptr) (*(size_t*)(ptr)) -#define write_size_t(ptr, val) (*(size_t*)(ptr) = (val)) -#endif - -struct rewindstack_impl { - struct rewindstack i; - - char * data; - size_t capacity; - char * head;//read and write here - char * tail;//delete here if head is close - - char * thisblock; - char * nextblock; - bool thisblock_valid; - - size_t blocksize;//rounded up from reset::blocksize - size_t maxcompsize;//size_t+(blocksize+131071)/131072*(blocksize+u16+u16)+u16+u32+size_t - - unsigned int entries; -}; - -static void reset(struct rewindstack * this_, size_t blocksize, size_t capacity) -{ - struct rewindstack_impl * this=(struct rewindstack_impl*)this_; - - int newblocksize=((blocksize-1)|(sizeof(uint16_t)-1))+1; - if (this->blocksize!=newblocksize) - { - this->blocksize=newblocksize; - - const int maxcblkcover=UINT16_MAX*sizeof(uint16_t); - const int maxcblks=(this->blocksize+maxcblkcover-1)/maxcblkcover; - this->maxcompsize=this->blocksize + maxcblks*sizeof(uint16_t)*2 + sizeof(uint16_t)+sizeof(uint32_t) + sizeof(size_t)*2; - - free(this->thisblock); - free(this->nextblock); - this->thisblock=calloc(this->blocksize+sizeof(uint16_t)*8, 1); - this->nextblock=calloc(this->blocksize+sizeof(uint16_t)*8, 1); - //force in a different byte at the end, so we don't need to look for the buffer end in the innermost loop - //there is also a large amount of data that's the same, to stop the other scan - //and finally some padding so we don't read outside the buffer end if we're reading in large blocks - *(uint16_t*)(this->thisblock+this->blocksize+sizeof(uint16_t)*3)=0xFFFF; - *(uint16_t*)(this->nextblock+this->blocksize+sizeof(uint16_t)*3)=0x0000; - } - - if (capacity!=this->capacity) - { - free(this->data); - this->data=malloc(capacity); - this->capacity=capacity; - } - - this->head=this->data+sizeof(size_t); - this->tail=this->data+sizeof(size_t); - - this->thisblock_valid=false; - - this->entries=0; -} - -static void * push_begin(struct rewindstack * this_) -{ - struct rewindstack_impl * this=(struct rewindstack_impl*)this_; - return this->nextblock; -} - -static void push_end(struct rewindstack * this_) -{ - struct rewindstack_impl * this=(struct rewindstack_impl*)this_; - if (this->thisblock_valid) - { - if (this->capacitymaxcompsize) return; - - recheckcapacity:; - size_t headpos=(this->head-this->data); - size_t tailpos=(this->tail-this->data); - size_t remaining=(tailpos+this->capacity-sizeof(size_t)-headpos-1)%this->capacity + 1; - if (remaining<=this->maxcompsize) - { - this->tail=this->data + read_size_t((uint16_t*)this->tail); - this->entries--; - goto recheckcapacity; - } - - const char* old=this->thisblock; - const char* new=this->nextblock; - char* compressed=this->head+sizeof(size_t); - - //at the end, 'compressed' must point to the end of the compressed data - //do not include the next/prev pointers - //begin compression code - const uint16_t * old16=(const uint16_t*)old; - const uint16_t * new16=(const uint16_t*)new; - uint16_t * compressed16=(uint16_t*)compressed; - size_t num16s=this->blocksize/sizeof(uint16_t); - while (num16s) - { - const uint16_t * oldprev=old16; -#ifdef NO_UNALIGNED_MEM - while ((uintptr_t)old16 & (sizeof(size_t)-1) && *old16==*new16) - { - old16++; - new16++; - } - if (*old16==*new16) -#endif - { - const size_t* olds=(const size_t*)old16; - const size_t* news=(const size_t*)new16; - - while (*olds==*news) - { - olds++; - news++; - } - old16=(const uint16_t*)olds; - new16=(const uint16_t*)news; - - while (*old16==*new16) - { - old16++; - new16++; - } - } - size_t skip=(old16-oldprev); - - if (skip>=num16s) break; - num16s-=skip; - - if (skip>UINT16_MAX) - { - if (skip>UINT32_MAX) - { - old16-=skip; - new16-=skip; - skip=UINT32_MAX; - old16+=skip; - new16+=skip; - } - *(compressed16++)=0; - *(compressed16++)=skip; - *(compressed16++)=skip>>16; - compressed16+=2; - skip=0; - continue; - } - - size_t changed; - const uint16_t * old16prev=old16; - //comparing two or three words makes no real difference - //with two, the smaller blocks are less likely to be chopped up elsewhere due to 64KB - //with three, we get larger blocks which should be a minuscle bit faster to decompress, but probably a little slower to compress - while (old16[0]!=new16[0] || old16[1]!=new16[1]) - { - old16++; - new16++; - while (*old16!=*new16) - { - old16++; - new16++; - } - } - changed=(old16-old16prev); - if (!changed) continue; - if (changed>UINT16_MAX) - { - old16-=changed; - new16-=changed; - changed=UINT16_MAX; - old16+=changed; - new16+=changed; - } - num16s-=changed; - *(compressed16++)=changed; - *(compressed16++)=skip; - memcpy(compressed16, old16prev, changed*sizeof(uint16_t)); - compressed16+=changed; - } - compressed16[0]=0; - compressed16[1]=0; - compressed16[2]=0; - compressed=(char*)(compressed16+3); - //end compression code - - if (compressed-this->data+this->maxcompsize > this->capacity) - { - compressed=this->data; - if (this->tail==this->data+sizeof(size_t)) this->tail=this->data + *(size_t*)this->tail; - } - write_size_t((uint16_t*)compressed, this->head-this->data); - compressed+=sizeof(size_t); - write_size_t((uint16_t*)this->head, compressed-this->data); - this->head=compressed; - } - else - { - this->thisblock_valid=true; - } - - char * swap=this->thisblock; - this->thisblock=this->nextblock; - this->nextblock=swap; - - this->entries++; -} - -static void push_cancel(struct rewindstack * this_) -{ - //struct rewindstack_impl * this=(struct rewindstack_impl*)this_; - //do nothing - push_begin just returns a pointer anyways -} - -static const void * pull(struct rewindstack * this_) -{ - struct rewindstack_impl * this=(struct rewindstack_impl*)this_; - - if (this->thisblock_valid) - { - this->thisblock_valid=false; - this->entries--; - return this->thisblock; - } - - if (this->head==this->tail) return NULL; - - size_t start=read_size_t((uint16_t*)(this->head - sizeof(size_t))); - this->head=this->data+start; - - const char * compressed=this->data+start+sizeof(size_t); - char * out=this->thisblock; - //begin decompression code - //out is the previously returned state - const uint16_t * compressed16=(const uint16_t*)compressed; - uint16_t * out16=(uint16_t*)out; - while (true) - { - uint16_t numchanged=*(compressed16++); - if (numchanged) - { - out16+=*(compressed16++); - //we could do memcpy, but it seems that function call overhead is high - // enough that memcpy's higher speed for large blocks won't matter - for (int i=0;ientries--; - - return this->thisblock; -} - -static void capacity_f(struct rewindstack * this_, unsigned int * entries, size_t * bytes, bool * full) -{ - struct rewindstack_impl * this=(struct rewindstack_impl*)this_; - - size_t headpos=(this->head-this->data); - size_t tailpos=(this->tail-this->data); - size_t remaining=(tailpos+this->capacity-sizeof(size_t)-headpos-1)%this->capacity + 1; - - if (entries) *entries=this->entries; - if (bytes) *bytes=(this->capacity-remaining); - if (full) *full=(remaining<=this->maxcompsize*2); -} - -static void free_(struct rewindstack * this_) -{ - struct rewindstack_impl * this=(struct rewindstack_impl*)this_; - free(this->data); - free(this->thisblock); - free(this->nextblock); - free(this); -} - -struct rewindstack * rewindstack_create(size_t blocksize, size_t capacity) -{ - struct rewindstack_impl * this=malloc(sizeof(struct rewindstack_impl)); - this->i.reset=reset; - this->i.push_begin=push_begin; - this->i.push_end=push_end; - this->i.push_cancel=push_cancel; - this->i.pull=pull; - this->i.capacity=capacity_f; - this->i.free=free_; - - this->data=NULL; - this->thisblock=NULL; - this->nextblock=NULL; - - this->capacity=0; - this->blocksize=0; - - reset((struct rewindstack*)this, blocksize, capacity); - - return (struct rewindstack*)this; -} diff --git a/rewind-alcaro.h b/rewind-alcaro.h index 8925f88792..e69de29bb2 100644 --- a/rewind-alcaro.h +++ b/rewind-alcaro.h @@ -1,43 +0,0 @@ -//#define NO_UNALIGNED_MEM -//Uncomment the above if alignment is enforced. - -#include "boolean.h" - -//A compressing, lossy stack. Optimized for large, mostly similar, blocks of data; optimized for -// writing, less so for reading. Will discard old data if its capacity is exhausted. -struct rewindstack { - //This is equivalent to deleting and recreating the structure, with the exception that - // it won't reallocate the big block if the capacity is unchanged. It is safe to set the capacity - // to 0, though this will make the structure rather useless. - //The structure may hand out bigger blocks of data than requested. This is not detectable; just - // ignore the extra bytes. - //The structure may allocate a reasonable multiple of blocksize, in addition to capacity. - //It is not possible to accurately predict how many blocks will fit in the structure; it varies - // depending on how much the data changes. Emulator savestates are usually compressed to about - // 0.5-2% of their original size. You can stick in some data and use capacity(). - void (*reset)(struct rewindstack * this, size_t blocksize, size_t capacity); - - //Asks where to put a new block. Size is same as blocksize. Don't read from it; contents are undefined. - //push_end or push_cancel must be the first function called on the structure after this; not even free() is allowed. - //This function cannot fail, though a pull() directly afterwards may fail. - void * (*push_begin)(struct rewindstack * this); - //Tells that the savestate has been written. Don't use the pointer from push_begin after this point. - void (*push_end)(struct rewindstack * this); - //Tells that things were not written to the pointer from push_begin. Equivalent - // to push_end+pull, but faster, and may avoid discarding something. It is allowed to have written to the pointer. - void (*push_cancel)(struct rewindstack * this); - - //Pulls off a block. Don't change it; it'll be used to generate the next one. The returned pointer is only - // guaranteed valid until the first call to any function in this structure, with the exception that capacity() - // will not invalidate anything. If the requested block has been discarded, or was never pushed, it returns NULL. - const void * (*pull)(struct rewindstack * this); - - //Tells how many entries are in the structure, how many bytes are used, and whether the structure - // is likely to discard something if a new item is appended. The full flag is guaranteed true if - // it has discarded anything since the last pull() or reset(); however, it may be set even before - // discarding, if the implementation feels like doing that. - void (*capacity)(struct rewindstack * this, unsigned int * entries, size_t * bytes, bool * full); - - void (*free)(struct rewindstack * this); -}; -struct rewindstack * rewindstack_create(size_t blocksize, size_t capacity); diff --git a/rewind.c b/rewind.c index 856a6ad603..258b22983b 100644 --- a/rewind.c +++ b/rewind.c @@ -13,10 +13,56 @@ * If not, see . */ +#define __STDC_LIMIT_MACROS #include "rewind.h" -#include "rewind-alcaro.h" #include #include +#include "msvc/msvc-stdint/stdint.h" + +//#define NO_UNALIGNED_MEM +//Uncomment the above if alignment is enforced. + +#include "boolean.h" + +//A compressing, lossy stack. Optimized for large, mostly similar, blocks of data; optimized for +// writing, less so for reading. Will discard old data if its capacity is exhausted. +struct rewindstack { + //This is equivalent to deleting and recreating the structure, with the exception that + // it won't reallocate the big block if the capacity is unchanged. It is safe to set the capacity + // to 0, though this will make the structure rather useless. + //The structure may hand out bigger blocks of data than requested. This is not detectable; just + // ignore the extra bytes. + //The structure may allocate a reasonable multiple of blocksize, in addition to capacity. + //It is not possible to accurately predict how many blocks will fit in the structure; it varies + // depending on how much the data changes. Emulator savestates are usually compressed to about + // 0.5-2% of their original size. You can stick in some data and use capacity(). + void (*reset)(struct rewindstack * this, size_t blocksize, size_t capacity); + + //Asks where to put a new block. Size is same as blocksize. Don't read from it; contents are undefined. + //push_end or push_cancel must be the first function called on the structure after this; not even free() is allowed. + //This function cannot fail, though a pull() directly afterwards may fail. + void * (*push_begin)(struct rewindstack * this); + //Tells that the savestate has been written. Don't use the pointer from push_begin after this point. + void (*push_end)(struct rewindstack * this); + //Tells that things were not written to the pointer from push_begin. Equivalent + // to push_end+pull, but faster, and may avoid discarding something. It is allowed to have written to the pointer. + void (*push_cancel)(struct rewindstack * this); + + //Pulls off a block. Don't change it; it'll be used to generate the next one. The returned pointer is only + // guaranteed valid until the first call to any function in this structure, with the exception that capacity() + // will not invalidate anything. If the requested block has been discarded, or was never pushed, it returns NULL. + const void * (*pull)(struct rewindstack * this); + + //Tells how many entries are in the structure, how many bytes are used, and whether the structure + // is likely to discard something if a new item is appended. The full flag is guaranteed true if + // it has discarded anything since the last pull() or reset(); however, it may be set even before + // discarding, if the implementation feels like doing that. + void (*capacity)(struct rewindstack * this, unsigned int * entries, size_t * bytes, bool * full); + + void (*free)(struct rewindstack * this); +}; +struct rewindstack * rewindstack_create(size_t blocksize, size_t capacity); + struct state_manager { struct rewindstack * core; @@ -62,3 +108,381 @@ bool state_manager_push(state_manager_t *state, const void *data) state->core->push_end(state->core); return true; } + +#include "rewind-alcaro.h" +#include +#include + +//format per frame: +//size nextstart; +//repeat { +// uint16 numchanged; // everything is counted in units of uint16 +// if (numchanged) { +// uint16 numunchanged; // skip these before handling numchanged +// uint16[numchanged] changeddata; +// } +// else +// { +// uint32 numunchanged; +// if (!numunchanged) break; +// } +//} +//size thisstart; +// +//the start offsets point to 'nextstart' of any given compressed frame +//multibyte values are stored native endian if alignment is not enforced; if it is, little endian +//the start of the buffer contains a size pointing to the end of the buffer; the end points to its start +//wrapping is handled by returning to the start of the buffer if the compressed data could potentially hit the edge +//if the compressed data could potentially overwrite the tail pointer, the tail retreats until it can no longer collide +//so on average, ~2*maxcompsize is unused at any given moment +// +//if unaligned memory access is illegal, define NO_UNALIGNED_MEM + +#if SIZE_MAX == 0xFFFFFFFF +extern char test[(sizeof(size_t)==4)?1:-1]; +#elif SIZE_MAX == 0xFFFFFFFFFFFFFFFF +extern char test[(sizeof(size_t)==8)?1:-1]; +#define USE_64BIT +#else +#error your compiler is insane. +#endif + +#ifdef NO_UNALIGNED_MEM +//These functions assume 16bit alignment. +//They do not make any attempt at matching system native endian; values written by these can only be read by the matching partner. +#ifdef USE_64BIT +static inline void write_size_t(uint16_t* ptr, size_t val) +{ + ptr[0]=val>>0; + ptr[1]=val>>16; + ptr[2]=val>>32; + ptr[3]=val>>48; +} + +static inline size_t read_size_t(uint16_t* ptr) +{ + return ((size_t)ptr[0]<<0 | + (size_t)ptr[1]<<16 | + (size_t)ptr[2]<<32 | + (size_t)ptr[3]<<48); +} +#else +static inline void write_size_t(uint16_t* ptr, size_t val) +{ + ptr[0]=val; + ptr[1]=val>>16; +} + +static inline size_t read_size_t(uint16_t* ptr) +{ + return (ptr[0] | (size_t)ptr[1]<<16); +} +#endif + +#else +#define read_size_t(ptr) (*(size_t*)(ptr)) +#define write_size_t(ptr, val) (*(size_t*)(ptr) = (val)) +#endif + +struct rewindstack_impl { + struct rewindstack i; + + char * data; + size_t capacity; + char * head;//read and write here + char * tail;//delete here if head is close + + char * thisblock; + char * nextblock; + bool thisblock_valid; + + size_t blocksize;//rounded up from reset::blocksize + size_t maxcompsize;//size_t+(blocksize+131071)/131072*(blocksize+u16+u16)+u16+u32+size_t + + unsigned int entries; +}; + +static void reset(struct rewindstack * this_, size_t blocksize, size_t capacity) +{ + struct rewindstack_impl * this=(struct rewindstack_impl*)this_; + + int newblocksize=((blocksize-1)|(sizeof(uint16_t)-1))+1; + if (this->blocksize!=newblocksize) + { + this->blocksize=newblocksize; + + const int maxcblkcover=UINT16_MAX*sizeof(uint16_t); + const int maxcblks=(this->blocksize+maxcblkcover-1)/maxcblkcover; + this->maxcompsize=this->blocksize + maxcblks*sizeof(uint16_t)*2 + sizeof(uint16_t)+sizeof(uint32_t) + sizeof(size_t)*2; + + free(this->thisblock); + free(this->nextblock); + this->thisblock=calloc(this->blocksize+sizeof(uint16_t)*8, 1); + this->nextblock=calloc(this->blocksize+sizeof(uint16_t)*8, 1); + //force in a different byte at the end, so we don't need to look for the buffer end in the innermost loop + //there is also a large amount of data that's the same, to stop the other scan + //and finally some padding so we don't read outside the buffer end if we're reading in large blocks + *(uint16_t*)(this->thisblock+this->blocksize+sizeof(uint16_t)*3)=0xFFFF; + *(uint16_t*)(this->nextblock+this->blocksize+sizeof(uint16_t)*3)=0x0000; + } + + if (capacity!=this->capacity) + { + free(this->data); + this->data=malloc(capacity); + this->capacity=capacity; + } + + this->head=this->data+sizeof(size_t); + this->tail=this->data+sizeof(size_t); + + this->thisblock_valid=false; + + this->entries=0; +} + +static void * push_begin(struct rewindstack * this_) +{ + struct rewindstack_impl * this=(struct rewindstack_impl*)this_; + return this->nextblock; +} + +static void push_end(struct rewindstack * this_) +{ + struct rewindstack_impl * this=(struct rewindstack_impl*)this_; + if (this->thisblock_valid) + { + if (this->capacitymaxcompsize) return; + + recheckcapacity:; + size_t headpos=(this->head-this->data); + size_t tailpos=(this->tail-this->data); + size_t remaining=(tailpos+this->capacity-sizeof(size_t)-headpos-1)%this->capacity + 1; + if (remaining<=this->maxcompsize) + { + this->tail=this->data + read_size_t((uint16_t*)this->tail); + this->entries--; + goto recheckcapacity; + } + + const char* old=this->thisblock; + const char* new=this->nextblock; + char* compressed=this->head+sizeof(size_t); + + //at the end, 'compressed' must point to the end of the compressed data + //do not include the next/prev pointers + //begin compression code + const uint16_t * old16=(const uint16_t*)old; + const uint16_t * new16=(const uint16_t*)new; + uint16_t * compressed16=(uint16_t*)compressed; + size_t num16s=this->blocksize/sizeof(uint16_t); + while (num16s) + { + const uint16_t * oldprev=old16; +#ifdef NO_UNALIGNED_MEM + while ((uintptr_t)old16 & (sizeof(size_t)-1) && *old16==*new16) + { + old16++; + new16++; + } + if (*old16==*new16) +#endif + { + const size_t* olds=(const size_t*)old16; + const size_t* news=(const size_t*)new16; + + while (*olds==*news) + { + olds++; + news++; + } + old16=(const uint16_t*)olds; + new16=(const uint16_t*)news; + + while (*old16==*new16) + { + old16++; + new16++; + } + } + size_t skip=(old16-oldprev); + + if (skip>=num16s) break; + num16s-=skip; + + if (skip>UINT16_MAX) + { + if (skip>UINT32_MAX) + { + old16-=skip; + new16-=skip; + skip=UINT32_MAX; + old16+=skip; + new16+=skip; + } + *(compressed16++)=0; + *(compressed16++)=skip; + *(compressed16++)=skip>>16; + compressed16+=2; + skip=0; + continue; + } + + size_t changed; + const uint16_t * old16prev=old16; + //comparing two or three words makes no real difference + //with two, the smaller blocks are less likely to be chopped up elsewhere due to 64KB + //with three, we get larger blocks which should be a minuscle bit faster to decompress, but probably a little slower to compress + while (old16[0]!=new16[0] || old16[1]!=new16[1]) + { + old16++; + new16++; + while (*old16!=*new16) + { + old16++; + new16++; + } + } + changed=(old16-old16prev); + if (!changed) continue; + if (changed>UINT16_MAX) + { + old16-=changed; + new16-=changed; + changed=UINT16_MAX; + old16+=changed; + new16+=changed; + } + num16s-=changed; + *(compressed16++)=changed; + *(compressed16++)=skip; + memcpy(compressed16, old16prev, changed*sizeof(uint16_t)); + compressed16+=changed; + } + compressed16[0]=0; + compressed16[1]=0; + compressed16[2]=0; + compressed=(char*)(compressed16+3); + //end compression code + + if (compressed-this->data+this->maxcompsize > this->capacity) + { + compressed=this->data; + if (this->tail==this->data+sizeof(size_t)) this->tail=this->data + *(size_t*)this->tail; + } + write_size_t((uint16_t*)compressed, this->head-this->data); + compressed+=sizeof(size_t); + write_size_t((uint16_t*)this->head, compressed-this->data); + this->head=compressed; + } + else + { + this->thisblock_valid=true; + } + + char * swap=this->thisblock; + this->thisblock=this->nextblock; + this->nextblock=swap; + + this->entries++; +} + +static void push_cancel(struct rewindstack * this_) +{ + //struct rewindstack_impl * this=(struct rewindstack_impl*)this_; + //do nothing - push_begin just returns a pointer anyways +} + +static const void * pull(struct rewindstack * this_) +{ + struct rewindstack_impl * this=(struct rewindstack_impl*)this_; + + if (this->thisblock_valid) + { + this->thisblock_valid=false; + this->entries--; + return this->thisblock; + } + + if (this->head==this->tail) return NULL; + + size_t start=read_size_t((uint16_t*)(this->head - sizeof(size_t))); + this->head=this->data+start; + + const char * compressed=this->data+start+sizeof(size_t); + char * out=this->thisblock; + //begin decompression code + //out is the previously returned state + const uint16_t * compressed16=(const uint16_t*)compressed; + uint16_t * out16=(uint16_t*)out; + while (true) + { + uint16_t numchanged=*(compressed16++); + if (numchanged) + { + out16+=*(compressed16++); + //we could do memcpy, but it seems that function call overhead is high + // enough that memcpy's higher speed for large blocks won't matter + for (int i=0;ientries--; + + return this->thisblock; +} + +static void capacity_f(struct rewindstack * this_, unsigned int * entries, size_t * bytes, bool * full) +{ + struct rewindstack_impl * this=(struct rewindstack_impl*)this_; + + size_t headpos=(this->head-this->data); + size_t tailpos=(this->tail-this->data); + size_t remaining=(tailpos+this->capacity-sizeof(size_t)-headpos-1)%this->capacity + 1; + + if (entries) *entries=this->entries; + if (bytes) *bytes=(this->capacity-remaining); + if (full) *full=(remaining<=this->maxcompsize*2); +} + +static void free_(struct rewindstack * this_) +{ + struct rewindstack_impl * this=(struct rewindstack_impl*)this_; + free(this->data); + free(this->thisblock); + free(this->nextblock); + free(this); +} + +struct rewindstack * rewindstack_create(size_t blocksize, size_t capacity) +{ + struct rewindstack_impl * this=malloc(sizeof(struct rewindstack_impl)); + this->i.reset=reset; + this->i.push_begin=push_begin; + this->i.push_end=push_end; + this->i.push_cancel=push_cancel; + this->i.pull=pull; + this->i.capacity=capacity_f; + this->i.free=free_; + + this->data=NULL; + this->thisblock=NULL; + this->nextblock=NULL; + + this->capacity=0; + this->blocksize=0; + + reset((struct rewindstack*)this, blocksize, capacity); + + return (struct rewindstack*)this; +}