Re: [AD] implementing seek

[ Thread Index | Date Index | More lists.liballeg.org/allegro-developers Archives ]


On Thu, Apr 8, 2010 at 12:01 AM, Matthew Leverton <meffer@xxxxxxxxxx> wrote:
>
> However, I dislike magic numbers, so maybe it's just better like:
>
> al_fopen_slice(fp, length, bounded);
>

Attached is my current favorite proposal:

ALLEGRO_FILE *al_fopen_slice(ALLEGRO_FILE *fh, size_t initial_size,
bool expandable)

(The third parameter could be a set of flags instead.)

It's meant to allow you to treat a subsection of a random access file
as a new, self-contained file.

The slice origin is set to the current spot in the file. No reading or
writing can happen before that point. The initial size of the slice
can be set, and it is used to calculate any SEEK_END movements. If
expandable is set to true, then seeking and writing past the initial
size is allowed. Upon closing the slice, the parent seek position is
set to the end of the slice's current size. (initial_size + whatever
extra length was written)

The file mode (read/write) is inherited from the parent.

If the initialize size is set past the actual size of a read-only
file, the behavior is undefined.

It does not work with sequential files, but it could if a memory
buffer were implemented upon detecting (if possible) that random
access was not allowed.

al_fopen_slice(fh, 0, false) => a useless slice

al_fopen_slice(fh, 0, true) => a slice that can be written to. upon
closing, the parent will be sent to the end of the slice

al_fopen_slice(fh, 10000, false) => access some data known to be
10,000 bytes long

al_fopen_slice(fh, al_fsize(fh) - al_ftell(fh), true) => SEEK_END
would be the end of the actual file. writing would be allowed past it.

The example included with the source works with the following binary
test.dat file:

$ cp mysha.pcx test.dat
$ cat mysha.tga >> test.dat
$ cat mysha256x256.png >> test.dat

My testing on Linux shows, when trying to load them successively:

Without slice: bmp1 => 0x8836ae0 bmp2 => 0x8836068 bmp3 => (nil)
With slice: bmp1 => 0x8836068 bmp2 => 0x88364e0 bmp3 => 0x8836550

i.e., the third bitmap (PNG file) in the binary blob fails to load
without a slice.

--
Matthew Leverton
/* Allegro File Slices
 *   by Matthew Leverton 
 */

#include <stdio.h>
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>

typedef struct ALLEGRO_FILE_SLICE ALLEGRO_FILE_SLICE;

struct ALLEGRO_FILE_SLICE {
   ALLEGRO_FILE file;    /* must be first */
   
   ALLEGRO_FILE *parent; /* handle to the parent file */

   bool eof;             /* true if end of file and ungetc is empty */
   bool expandable;      /* determines if writing will expand the slice */
   
   int64_t origin;       /* the start position with re: to the parent */
   int64_t size;         /* the active size of the slice (it may grow) */
   int64_t pos;          /* the current position, always [0, size] */
   
   int ungetc;           /* allow for one ungetc */
};

static void slice_fclose(ALLEGRO_FILE *fp)
{
   ALLEGRO_FILE_SLICE *s = (ALLEGRO_FILE_SLICE *)fp;
   
   /* seek to the end of the slice before exiting */
	 al_fseek(s->parent, s->origin + s->size, ALLEGRO_SEEK_SET);
   free(fp);
}

static size_t slice_fread(ALLEGRO_FILE *fp, void *ptr, size_t size)
{
	 ALLEGRO_FILE_SLICE *s = (ALLEGRO_FILE_SLICE *)fp;
	 size_t n  = 0;
	 
	 /* nothing to read */
	 if (!size) {
      return 0;
   }
   
   /* use the ungetc buffer if non-empty */
   if (s->ungetc != -1) {
   	  /* fill the first byte of the buffer */
      *((char *)ptr) = s->ungetc;
      ptr = ((char *)ptr) + 1;
      s->ungetc = -1;
      
      /* skip over the actual byte */
      al_fgetc(s->parent);      
      n = 1;
      --size;
      
      /* now we might be at the end of file */
      if (s->pos == s->size) {
         s->eof = true;
      }
   }
	 
	 /* read up to size bytes, or end of file */
	 if (!s->eof) { 
	 	  if (size + s->pos > s->size) {
	 	     size = s->size - s->pos;
	 	  }
	 
	 	  n += al_fread(s->parent, ptr, size);
	 	  s->pos += n;	 	  
	 	  if (s->pos == s->size) {
	 	     s->eof = true;
	 	  }
   }
   
   ALLEGRO_ASSERT(s->pos <= s->size);
	 
   return n;
}

static size_t slice_fwrite(ALLEGRO_FILE *fp, const void *ptr, size_t size)
{
	 ALLEGRO_FILE_SLICE *s = (ALLEGRO_FILE_SLICE *)fp;
	 size_t n = 0;
	 
	 /* nothing to write */
	 if (size == 0) {
      return 0;
   }
	 
	 /* if ungetc is not empty, we need to overwrite it */
	 if (s->ungetc != -1) {
      s->ungetc = -1;
      if (s->pos >= 0) {
         if (al_fseek(s->parent, -1, ALLEGRO_SEEK_CUR)) {
            --s->pos;         
         }
      }
	 }
	 
	 /* if the slice is expandable, then we are allowed to write past the end */
	 if (s->expandable) {
      n = al_fwrite(s->parent, ptr, size);
      s->pos += n;
      if (s->pos >= s->size) {
         /* the slice is now larger than it was */
         s->size = s->pos;
         s->eof = true;
      }
   }
	 else if (!s->eof) { 
	 	  if (size + s->pos > s->size) {
         /* adjust the size to fit the slice */
	 	     size = s->size - s->pos;
	 	  }
	 
	 	  n = al_fwrite(s->parent, ptr, size);
	 	  s->pos += n;
	 	  if (s->pos == s->size) {
	 	     s->eof = true;
	 	  }
   }
   
   ALLEGRO_ASSERT(s->pos >= 0 && s->pos <= s->size);
   
   return n;
}

static bool slice_fflush(ALLEGRO_FILE *fp)
{
	 ALLEGRO_FILE_SLICE *s = (ALLEGRO_FILE_SLICE *)fp;
	 
	 s->ungetc = -1;

   return al_fflush(s->parent);
}

static int64_t slice_ftell(ALLEGRO_FILE *fp)
{
	 ALLEGRO_FILE_SLICE *s = (ALLEGRO_FILE_SLICE *)fp;
	 
	 ALLEGRO_ASSERT(s->pos >= 0 && s->pos <= s->size);
	 
	 /* Return the virtual position, with consideration to
	    the ungetc buffer. But never return -1. */	 
	 if (s->ungetc != -1) {	    
      if (s->pos == 0) {
         s->ungetc = -1;
      }
      else {
         return s->pos - 1;
      }
   }
   
   return s->pos;
}

static bool slice_fseek(ALLEGRO_FILE *fp, int64_t offset,
   int whence)
{
	 ALLEGRO_FILE_SLICE *s = (ALLEGRO_FILE_SLICE *)fp;
	 int64_t ppos = -1;
	 
	 if (whence == ALLEGRO_SEEK_SET) {
      ppos = s->origin + offset;
	 }
	 else if (whence == ALLEGRO_SEEK_CUR) {
	 	  if (s->ungetc != -1) {
         /* seek with regard to the ungetc buffer, but never
            go before the beginning of the slice */
	       --s->pos;
	       if (s->pos + offset < 0) {
	          s->pos = 0;
	       }
	    }
      ppos = s->origin + s->pos + offset;
	 }
	 else if (whence == ALLEGRO_SEEK_END) {
	    ppos = s->origin + s->size + offset;
	 }
	 
	 s->ungetc = -1;
	 
	 /* never allowed to seek before the beginning of the slice */
	 if (ppos < s->origin) {
      return false;
   }
   
   /* not allowed to seek past the end of a bounded slice */
   if (!s->expandable && ppos > s->origin + s->size) {
      return false;
   }
	 	
   /* try to seek via the parent */
	 if (!al_fseek(s->parent, ppos, ALLEGRO_SEEK_SET)) {
      return false;
   }
      
   /* update our relative position */
   s->pos = ppos - s->origin;
	 s->eof = (s->pos >= s->size);
	 
	 /* update the size if moved past it (unbounded slices) */
	 if (s->pos > s->size) {
      ALLEGRO_ASSERT(s->expandable);
      s->size = s->pos;
   }
   
   ALLEGRO_ASSERT(s->pos >= 0 && s->pos <= s->size);
	 
   return true;
}

static bool slice_feof(ALLEGRO_FILE *fp)
{
	 ALLEGRO_FILE_SLICE *s = (ALLEGRO_FILE_SLICE *)fp;
   return s->eof;
}

static bool slice_ferror(ALLEGRO_FILE *fp)
{
   ALLEGRO_FILE_SLICE *s = (ALLEGRO_FILE_SLICE *)fp;
   return al_ferror(s->parent);
}

static int slice_fungetc(ALLEGRO_FILE *fp, int c)
{
	 ALLEGRO_FILE_SLICE *s = (ALLEGRO_FILE_SLICE *)fp;
	 
	 s->ungetc = c;
   s->eof = false;
   	 
   return c;
}

static off_t slice_fsize(ALLEGRO_FILE *fp)
{
   ALLEGRO_FILE_SLICE *s = (ALLEGRO_FILE_SLICE *)fp;
   return s->size;
}

static struct ALLEGRO_FILE_INTERFACE slice_vtable = {
   NULL,    /* open */
   slice_fclose,
   slice_fread,
   slice_fwrite,
   slice_fflush,
   slice_ftell,
   slice_fseek,
   slice_feof,
   slice_ferror,
   slice_fungetc,
   slice_fsize
};

ALLEGRO_FILE *al_fopen_slice(ALLEGRO_FILE *fh, size_t initial_size, bool expandable)
{
   ALLEGRO_FILE_SLICE *s = malloc(sizeof(ALLEGRO_FILE_SLICE));
	
   memset(s, 0, sizeof(*s));	
	
   s->file.vtable = &slice_vtable;
   s->parent = fh;   
   
   s->pos = 0;
   s->origin = al_ftell(fh); 			
   s->size = initial_size;
   s->expandable = expandable;
   s->ungetc = -1;
   	
	 return (ALLEGRO_FILE *)s;
}

int main()
{
	ALLEGRO_BITMAP *bmp1, *bmp2, *bmp3;
	ALLEGRO_FILE *f, *s;
	
	al_init();

// $ cp mysha.pcx test.dat
// $ cat mysha.tga >> test.dat
// $ cat mysha256x256.png >> test.dat
	
	f = al_fopen("test.dat", "rb");
	if (!f) return -1;
		
	bmp1 = al_load_pcx_f(f);
	bmp2 = al_load_tga_f(f);
	bmp3 = al_load_png_f(f);
	
	printf("Without slice: bmp1 => %p bmp2 => %p bmp3 => %p \n", bmp1, bmp2, bmp3);
	
	if (bmp1) al_destroy_bitmap(bmp1);
	if (bmp2) al_destroy_bitmap(bmp2);
	if (bmp3) al_destroy_bitmap(bmp3);
		
	al_fseek(f, 0, SEEK_SET);
	
	s = al_fopen_slice(f, 61581, false);
	bmp1 = al_load_pcx_f(s);
	al_fclose(s);
	
	s = al_fopen_slice(f, 125191, false);
	bmp2 = al_load_tga_f(s);
	al_fclose(s);
	
	s = al_fopen_slice(f, 65579, false);
	bmp3 = al_load_png_f(s);
	al_fclose(s);
	
	printf("With slice: bmp1 => %p bmp2 => %p bmp3 => %p \n", bmp1, bmp2, bmp3);
	
  if (bmp1) al_destroy_bitmap(bmp1);
  if (bmp2) al_destroy_bitmap(bmp2);
  if (bmp3) al_destroy_bitmap(bmp3);
  	
  al_fclose(f);
	
	return 0;
}


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/