#include #include #include #include #include #include #define MAX_OPEN_SWAP_FILES 16 #include "tile_swap.h" typedef struct _SwapFile SwapFile; typedef struct _DefSwapFile DefSwapFile; typedef struct _Gap Gap; struct _SwapFile { char *filename; int swap_num; SwapFunc swap_func; gpointer user_data; int fd; }; struct _DefSwapFile { GList *gaps; long swap_file_end; off_t cur_position; }; struct _Gap { long start; long end; }; static void tile_swap_init (void); static guint tile_swap_hash (int *key); static gint tile_swap_compare (int *a, int *b); static void tile_swap_command (Tile *tile, int command); static void tile_swap_open (SwapFile *swap_file); static int tile_swap_default (int fd, Tile *tile, int cmd, gpointer user_data); static void tile_swap_default_in (DefSwapFile *def_swap_file, int fd, Tile *tile); static void tile_swap_default_out (DefSwapFile *def_swap_file, int fd, Tile *tile); static void tile_swap_default_delete (DefSwapFile *def_swap_file, int fd, Tile *tile); static long tile_swap_find_offset (DefSwapFile *def_swap_file, int fd, int bytes); static void tile_swap_resize (DefSwapFile *def_swap_file, int fd, long new_size); static Gap* tile_swap_gap_new (long start, long end); static void tile_swap_gap_destroy (Gap *gap); static initialize = TRUE; static GHashTable *swap_files = NULL; static GList *open_swap_files = NULL; static int nopen_swap_files = 0; static int next_swap_num = 1; static long swap_file_grow = 16 * TILE_WIDTH * TILE_HEIGHT * 4; static void tile_swap_print_gaps (DefSwapFile *def_swap_file) { GList *gaps; Gap *gap; gaps = def_swap_file->gaps; while (gaps) { gap = gaps->data; gaps = gaps->next; g_print (" %6ld - %6ld\n", gap->start, gap->end); } } static void tile_swap_exit1 (gpointer key, gpointer value, gpointer data) { extern int tile_ref_count; SwapFile *swap_file; DefSwapFile *def_swap_file; if (tile_ref_count != 0) g_print ("tile ref count balance: %d\n", tile_ref_count); swap_file = value; if (swap_file->swap_func == tile_swap_default) { def_swap_file = swap_file->user_data; if (def_swap_file->swap_file_end != 0) { g_print ("swap file not empty: \"%s\"\n", swap_file->filename); tile_swap_print_gaps (def_swap_file); } unlink (swap_file->filename); } } void tile_swap_exit () { if (swap_files) g_hash_table_foreach (swap_files, tile_swap_exit1, NULL); } int tile_swap_add (char *filename, SwapFunc swap_func, gpointer user_data) { SwapFile *swap_file; DefSwapFile *def_swap_file; if (initialize) tile_swap_init (); swap_file = g_new (SwapFile, 1); swap_file->filename = g_strdup (filename); swap_file->swap_num = next_swap_num++; if (!swap_func) { swap_func = tile_swap_default; def_swap_file = g_new (DefSwapFile, 1); def_swap_file->gaps = NULL; def_swap_file->swap_file_end = 0; def_swap_file->cur_position = 0; user_data = def_swap_file; } swap_file->swap_func = swap_func; swap_file->user_data = user_data; swap_file->fd = -1; g_hash_table_insert (swap_files, &swap_file->swap_num, swap_file); return swap_file->swap_num; } void tile_swap_remove (int swap_num) { SwapFile *swap_file; if (initialize) tile_swap_init (); swap_file = g_hash_table_lookup (swap_files, &swap_num); if (!swap_file) return; g_hash_table_remove (swap_files, &swap_num); if (swap_file->fd != -1) close (swap_file->fd); g_free (swap_file); } void tile_swap_in (Tile *tile) { if (tile->swap_offset == -1) { tile_alloc (tile); return; } tile_swap_command (tile, SWAP_IN); } void tile_swap_out (Tile *tile) { tile_swap_command (tile, SWAP_OUT); } void tile_swap_delete (Tile *tile) { tile_swap_command (tile, SWAP_DELETE); } void tile_swap_compress (int swap_num) { tile_swap_command (NULL, SWAP_COMPRESS); } static void tile_swap_init () { if (initialize) { initialize = FALSE; swap_files = g_hash_table_new ((GHashFunc) tile_swap_hash, (GCompareFunc) tile_swap_compare); } } static guint tile_swap_hash (int *key) { return ((guint) *key); } static gint tile_swap_compare (int *a, int *b) { return (*a == *b); } static void tile_swap_command (Tile *tile, int command) { SwapFile *swap_file; if (initialize) tile_swap_init (); do { swap_file = g_hash_table_lookup (swap_files, &tile->swap_num); if (!swap_file) { g_warning ("could not find swap file for tile"); return; } if (swap_file->fd == -1) { tile_swap_open (swap_file); if (swap_file->fd == -1) return; } } while ((* swap_file->swap_func) (swap_file->fd, tile, command, swap_file->user_data)); } static void tile_swap_open (SwapFile *swap_file) { SwapFile *tmp; if (swap_file->fd != -1) return; if (nopen_swap_files == MAX_OPEN_SWAP_FILES) { tmp = open_swap_files->data; close (tmp->fd); tmp->fd = -1; open_swap_files = g_list_remove (open_swap_files, open_swap_files->data); nopen_swap_files -= 1; } swap_file->fd = open (swap_file->filename, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR); if (swap_file->fd == -1) { g_warning ("unable to open swap file...BAD THINGS WILL HAPPEN SOON"); return; } open_swap_files = g_list_append (open_swap_files, swap_file); nopen_swap_files += 1; } /* The actual swap file code. The swap file consists of tiles * which have been moved out to disk in order to conserve memory. * The swap file format is free form. Any tile in memory may * end up anywhere on disk. * An actual tile in the swap file consists only of the tile data. * The offset of the tile on disk is stored in the tile data structure * in memory. */ static int tile_swap_default (int fd, Tile *tile, int cmd, gpointer user_data) { DefSwapFile *def_swap_file; def_swap_file = (DefSwapFile*) user_data; switch (cmd) { case SWAP_IN: tile_swap_default_in (def_swap_file, fd, tile); break; case SWAP_OUT: tile_swap_default_out (def_swap_file, fd, tile); break; case SWAP_DELETE: tile_swap_default_delete (def_swap_file, fd, tile); break; case SWAP_COMPRESS: g_warning ("tile_swap_default: SWAP_COMPRESS: UNFINISHED"); break; } return FALSE; } static void tile_swap_default_in (DefSwapFile *def_swap_file, int fd, Tile *tile) { int bytes; int err; int nleft; off_t offset; err = -1; if (def_swap_file->cur_position != tile->swap_offset) { def_swap_file->cur_position = tile->swap_offset; offset = lseek (fd, tile->swap_offset, SEEK_SET); if (offset == -1) { g_warning ("unable to seek to tile location on disk: %d", err); return; } } bytes = tile_size (tile); tile_alloc (tile); nleft = bytes; while (nleft > 0) { do { err = read (fd, tile->data + bytes - nleft, nleft); } while ((err == -1) && ((errno == EAGAIN) || (errno == EINTR))); if (err <= 0) { g_warning ("unable to read tile data from disk: %d ( %d ) bytes read", err, nleft); return; } nleft -= err; } def_swap_file->cur_position += bytes; /* Do not delete the swap from the file */ /* tile_swap_default_delete (def_swap_file, fd, tile); */ } static void tile_swap_default_out (DefSwapFile *def_swap_file, int fd, Tile *tile) { int bytes; int rbytes; int err; int nleft; off_t offset; bytes = TILE_WIDTH * TILE_HEIGHT * tile->bpp; rbytes = tile_size (tile); /* If there is already a valid swap_offset, use it */ if (tile->swap_offset == -1) tile->swap_offset = tile_swap_find_offset (def_swap_file, fd, bytes); if (def_swap_file->cur_position != tile->swap_offset) { def_swap_file->cur_position = tile->swap_offset; offset = lseek (fd, tile->swap_offset, SEEK_SET); if (offset == -1) { g_warning ("unable to seek to tile location on disk: %d", errno); return; } } nleft = rbytes; while (nleft > 0) { err = write (fd, tile->data + rbytes - nleft, rbytes); if (err <= 0) { g_warning ("unable to write tile data to disk: %d ( %d ) bytes written", err, nleft); return; } nleft -= err; } def_swap_file->cur_position += rbytes; g_free (tile->data); tile->data = NULL; } static void tile_swap_default_delete (DefSwapFile *def_swap_file, int fd, Tile *tile) { GList *tmp; GList *tmp2; Gap *gap; Gap *gap2; long start; long end; if (tile->swap_offset == -1) return; start = tile->swap_offset; end = start + TILE_WIDTH * TILE_HEIGHT * tile->bpp; tile->swap_offset = -1; tmp = def_swap_file->gaps; while (tmp) { gap = tmp->data; if (end == gap->start) { gap->start = start; if (tmp->prev) { gap2 = tmp->prev->data; if (gap->start == gap2->end) { gap2->end = gap->end; tile_swap_gap_destroy (gap); def_swap_file->gaps = g_list_remove_link (def_swap_file->gaps, tmp); g_list_free (tmp); } } break; } else if (start == gap->end) { gap->end = end; if (tmp->next) { gap2 = tmp->next->data; if (gap->end == gap2->start) { gap2->start = gap->start; tile_swap_gap_destroy (gap); def_swap_file->gaps = g_list_remove_link (def_swap_file->gaps, tmp); g_list_free (tmp); } } break; } else if (end < gap->start) { gap = tile_swap_gap_new (start, end); tmp2 = g_list_alloc (); tmp2->data = gap; tmp2->next = tmp; tmp2->prev = tmp->prev; if (tmp->prev) tmp->prev->next = tmp2; tmp->prev = tmp2; if (tmp == def_swap_file->gaps) def_swap_file->gaps = tmp2; break; } else if (!tmp->next) { gap = tile_swap_gap_new (start, end); tmp->next = g_list_alloc (); tmp->next->data = gap; tmp->next->prev = tmp; break; } tmp = tmp->next; } if (!def_swap_file->gaps) { gap = tile_swap_gap_new (start, end); def_swap_file->gaps = g_list_append (def_swap_file->gaps, gap); } tmp = g_list_last (def_swap_file->gaps); gap = tmp->data; if (gap->end == def_swap_file->swap_file_end) { tile_swap_resize (def_swap_file, fd, gap->start); tile_swap_gap_destroy (gap); def_swap_file->gaps = g_list_remove_link (def_swap_file->gaps, tmp); g_list_free (tmp); } } static void tile_swap_resize (DefSwapFile *def_swap_file, int fd, long new_size) { if (def_swap_file->swap_file_end > new_size) ftruncate (fd, new_size); def_swap_file->swap_file_end = new_size; } static long tile_swap_find_offset (DefSwapFile *def_swap_file, int fd, int bytes) { GList *tmp; Gap *gap; long offset; tmp = def_swap_file->gaps; while (tmp) { gap = tmp->data; if ((gap->end - gap->start) >= bytes) { offset = gap->start; gap->start += bytes; if (gap->start == gap->end) { tile_swap_gap_destroy (gap); def_swap_file->gaps = g_list_remove_link (def_swap_file->gaps, tmp); g_list_free (tmp); } return offset; } tmp = tmp->next; } offset = def_swap_file->swap_file_end; tile_swap_resize (def_swap_file, fd, def_swap_file->swap_file_end + swap_file_grow); if ((offset + bytes) < (def_swap_file->swap_file_end)) { gap = tile_swap_gap_new (offset + bytes, def_swap_file->swap_file_end); def_swap_file->gaps = g_list_append (def_swap_file->gaps, gap); } return offset; } static Gap* tile_swap_gap_new (long start, long end) { Gap *gap; gap = g_new (Gap, 1); gap->start = start; gap->end = end; return gap; } static void tile_swap_gap_destroy (Gap *gap) { g_free (gap); }