#ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #ifdef DEBUG_LOAD_TIME #include #endif #include "evas_cserve2.h" #include "evas_cs2_utils.h" #include #define ENTRY Entry base #define ASENTRY(a) (&(a->base)) #define ENTRYID(a) ((a)->base.id) typedef struct _Entry Entry; typedef struct _Reference Reference; typedef struct _File_Entry File_Entry; typedef struct _Image_Entry Image_Entry; typedef struct _File_Watch File_Watch; typedef struct _Font_Source Font_Source; typedef struct _Font_Entry Font_Entry; static const Evas_Image_Load_Opts empty_lo = { { 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0.0, 0, 0, 0, 0, EINA_FALSE }; typedef enum { CSERVE2_IMAGE_FILE, CSERVE2_IMAGE_DATA, CSERVE2_FONT_ENTRY } Entry_Type; struct _Entry { unsigned int id; Eina_List *references; Slave_Request *request; Entry_Type type; #ifdef DEBUG_LOAD_TIME struct timeval load_start; struct timeval load_finish; int load_time; int saved_time; #endif }; struct _File_Entry { ENTRY; File_Watch *watcher; Eina_List *images; }; struct _Image_Entry { ENTRY; Shm_Handle *shm; }; struct _Font_Source { string_t key; string_t name; string_t file; int refcount; void *ft; // Font_Source_Info }; struct _Font_Entry { ENTRY; unsigned int rend_flags; unsigned int size; unsigned int dpi; unsigned int font_data_id; Font_Source *src; void *ft; // Font_Info Fash_Glyph2 *glyph_entries[3]; // Fast access to the Glyph_Entry objects Shared_Array *glyph_datas; // Contains the Glyph_Data objects unsigned int nglyphs; Eina_Bool unused : 1; Shared_Mempool *mempool; // Contains the rendered glyphs #ifdef DEBUG_LOAD_TIME struct timeval rstart; // start of the glyphs load request struct timeval rfinish; // finish of the glyphs load request int gl_request_time; // time spent to process glyph requests int gl_load_time; int gl_render_time; int gl_slave_time; int gl_saved_time; #endif }; struct _Glyph_Entry { unsigned int gldata_id; Font_Entry *fe; }; struct _Glyphs_Request { Client *client; Font_Entry *fe; unsigned int current; unsigned int nglyphs; unsigned int *glyphs; unsigned int nrender; unsigned int *render; unsigned int nanswer; Glyph_Entry **answer; unsigned int hint; }; typedef struct _Glyphs_Request Glyphs_Request; struct _Reference { Client *client; Entry *entry; unsigned int client_entry_id; // for reverse lookup int count; }; struct _File_Watch { const char *path; Eina_List *entries; }; static unsigned int _generation_id = 0; static unsigned int _entry_id = 0; static unsigned int _glyph_id = 0; static unsigned int _font_data_id = 0; static unsigned int _freed_file_entry_count = 0; static unsigned int _freed_image_entry_count = 0; static unsigned int _freed_font_entry_count = 0; static Eina_Bool _shutdown = EINA_FALSE; static Shared_Array *_file_data_array = NULL; static Shared_Array *_image_data_array = NULL; static Shared_Array *_font_data_array = NULL; static Eina_Hash *file_ids = NULL; // maps path + key --> file_id static Eina_Hash *file_entries = NULL; // maps file_id --> entry static Eina_Hash *image_ids = NULL; // maps file id + load opts --> image id static Eina_Hash *image_entries = NULL; // maps image_id --> entry static Eina_Hash *font_sources = NULL; // font path --> font source static Eina_Hash *font_entries = NULL; // maps font path + options --> entry static Eina_Hash *file_watch = NULL; static Eina_List *image_entries_lru = NULL; static Eina_List *font_shm_lru = NULL; static int max_unused_mem_usage = 5 * 4 * 1024; /* in kbytes */ static int unused_mem_usage = 0; static int max_font_usage = 10 * 4 * 1024; /* in kbytes */ static int font_mem_usage = 0; #define MAX_PREEMPTIVE_LOAD_SIZE (320*320*4) #ifdef DEBUG_LOAD_TIME static int _timeval_sub(const struct timeval *tv2, const struct timeval *tv1) { int t1, t2; t1 = tv1->tv_usec + tv1->tv_sec * 1000000; t2 = tv2->tv_usec + tv2->tv_sec * 1000000; // Make sure that we don't add negative values. Some images may have // been not loaded yet, so it would mess with the stats. if (t2 > t1) return t2 - t1; return 0; } #endif static inline void _entry_load_start(Entry *e) { #ifdef DEBUG_LOAD_TIME gettimeofday(&e->load_start, NULL); #endif } static inline void _entry_load_finish(Entry *e) { #ifdef DEBUG_LOAD_TIME gettimeofday(&e->load_finish, NULL); e->load_time = _timeval_sub(&e->load_finish, &e->load_start); #endif } static inline void _entry_load_reused(Entry *e) { #ifdef DEBUG_LOAD_TIME e->saved_time += e->load_time; #endif } static int _shm_object_id_cmp_cb(const void *data1, const void *data2) { const Shm_Object *obj; unsigned int key; if (data1 == data2) return 0; if (!data1) return 1; if (!data2) return -1; obj = data1; key = *((unsigned int *) data2); if (obj->id == key) return 0; if (obj->id < key) return -1; else return +1; } static File_Data * _file_data_find(unsigned int file_id) { File_Data *fd; fd = cserve2_shared_array_item_data_find(_file_data_array, &file_id, _shm_object_id_cmp_cb); if (fd && !fd->refcount) { ERR("Can not access object %u with refcount 0", file_id); return NULL; } else if (!fd) ERR("Could not find file %u", file_id); return fd; } static File_Entry * _file_entry_find(unsigned int entry_id) { Entry *e; e = (Entry *) eina_hash_find(file_entries, &entry_id); if (!e || e->type != CSERVE2_IMAGE_FILE) { ERR("Could not find file entry %u", entry_id); return NULL; } return (File_Entry *) e; } static Image_Data * _image_data_find(unsigned int image_id) { Image_Data *idata; idata = cserve2_shared_array_item_data_find(_image_data_array, &image_id, _shm_object_id_cmp_cb); if (idata && !idata->refcount) { ERR("Can not access object %u with refcount 0", image_id); return NULL; } else if (!idata) ERR("Could not find image %u", image_id); return idata; } static Image_Entry * _image_entry_find(unsigned int entry_id) { Entry *e; e = (Entry *) eina_hash_find(image_entries, &entry_id); if (!e || e->type != CSERVE2_IMAGE_DATA) { ERR("Could not find image entry %u", entry_id); return NULL; } return (Image_Entry *) e; } static Font_Data * _font_data_find(unsigned int fs_id) { Font_Data *fdata; fdata = cserve2_shared_array_item_data_find(_font_data_array, &fs_id, _shm_object_id_cmp_cb); if (!fdata) { ERR("Could not find font data %u", fs_id); return NULL; } return fdata; } static Glyph_Data * _glyph_data_find(Shared_Array *sa, unsigned int glyph_id) { Glyph_Data *gldata; gldata = cserve2_shared_array_item_data_find(sa, &glyph_id, _shm_object_id_cmp_cb); if (!gldata) { ERR("Could not find glyph %u", glyph_id); return NULL; } return gldata; } static Eina_Bool _repack_skip_cb(Shared_Array *sa EINA_UNUSED, const void *elem, void *user_data EINA_UNUSED) { const Shm_Object *obj = elem; return (!obj->refcount); } static void _repack() { Shared_Array *sa; int count; Eina_Bool updated = EINA_FALSE; if (_shutdown) return; // Repack when we have 10% fragmentation over the whole shm buffer count = cserve2_shared_array_size_get(_file_data_array); if ((count > 0) && (_freed_file_entry_count > 100 || ((_freed_file_entry_count * 100) / count >= 10))) { DBG("Repacking file data array: %s", cserve2_shared_array_name_get(_file_data_array)); _generation_id++; sa = cserve2_shared_array_repack(_file_data_array, _generation_id, _repack_skip_cb, _shm_object_id_cmp_cb, NULL); if (!sa) { ERR("Failed to repack files array. Keeping previous references!"); goto skip_files; } cserve2_shared_array_del(_file_data_array); _freed_file_entry_count = 0; _file_data_array = sa; updated = EINA_TRUE; } skip_files: count = cserve2_shared_array_size_get(_image_data_array); if ((count > 0) && (_freed_image_entry_count > 100 || ((_freed_image_entry_count * 100) / count >= 10))) { DBG("Repacking image data array: %s", cserve2_shared_array_name_get(_image_data_array)); if (!updated) _generation_id++; sa = cserve2_shared_array_repack(_image_data_array, _generation_id, _repack_skip_cb, _shm_object_id_cmp_cb, NULL); if (!sa) { ERR("Failed to repack images array. Keeping previous references!"); goto skip_images; } cserve2_shared_array_del(_image_data_array); _freed_image_entry_count = 0; _image_data_array = sa; updated = EINA_TRUE; } skip_images: count = cserve2_shared_array_size_get(_font_data_array); if ((count > 0) && (_freed_font_entry_count > 100 || ((_freed_font_entry_count * 100) / count >= 10))) { DBG("Repacking font data array: %s", cserve2_shared_array_name_get(_font_data_array)); if (!updated) _generation_id++; sa = cserve2_shared_array_repack(_font_data_array, _generation_id, _repack_skip_cb, _shm_object_id_cmp_cb, NULL); if (!sa) { ERR("Failed to repack fonts array. Keeping previous references!"); goto skip_fonts; } cserve2_shared_array_del(_font_data_array); _freed_font_entry_count = 0; _font_data_array = sa; updated = EINA_TRUE; } skip_fonts: if (cserve2_shared_strings_repack(_repack_skip_cb, _shm_object_id_cmp_cb) == 1) updated = EINA_TRUE; if (updated) { // TODO: Update strings table generation_id? cserve2_shared_array_generation_id_set(_font_data_array, _generation_id); cserve2_shared_array_generation_id_set(_image_data_array, _generation_id); cserve2_shared_array_generation_id_set(_file_data_array, _generation_id); cserve2_index_list_send(_generation_id, cserve2_shared_strings_index_name_get(), cserve2_shared_strings_table_name_get(), cserve2_shared_array_name_get(_file_data_array), cserve2_shared_array_name_get(_image_data_array), cserve2_shared_array_name_get(_font_data_array), NULL); } } static Msg_Opened * _image_opened_msg_create(File_Data *fd, int *size) { Msg_Opened *msg; msg = calloc(1, sizeof(*msg)); msg->base.type = CSERVE2_OPENED; msg->image.w = fd->w; msg->image.h = fd->h; msg->image.frame_count = fd->frame_count; msg->image.loop_count = fd->loop_count; msg->image.loop_hint = fd->loop_hint; msg->image.alpha = fd->alpha; msg->image.animated = fd->animated; *size = sizeof(*msg); return msg; } static void _image_opened_send(Client *client, File_Data *fd, unsigned int rid) { int size; Msg_Opened *msg; DBG("Sending OPENED reply for entry: %d and RID: %d.", fd->id, rid); // clear the struct with possible paddings, since it is not aligned. msg = _image_opened_msg_create(fd, &size); msg->base.rid = rid; cserve2_client_send(client, &size, sizeof(size)); cserve2_client_send(client, msg, size); free(msg); } static Msg_Loaded * _image_loaded_msg_create(Image_Entry *ientry, Image_Data *idata, int *size) { Msg_Loaded *msg; const char *shmpath = cserve2_shm_name_get(ientry->shm); int path_len; char *buf; path_len = strlen(shmpath) + 1; *size = sizeof(*msg) + path_len; msg = calloc(1, *size); msg->base.type = CSERVE2_LOADED; msg->shm.mmap_offset = cserve2_shm_map_offset_get(ientry->shm); msg->shm.use_offset = cserve2_shm_offset_get(ientry->shm); msg->shm.mmap_size = cserve2_shm_map_size_get(ientry->shm); msg->shm.image_size = cserve2_shm_size_get(ientry->shm); msg->alpha_sparse = idata->alpha_sparse; msg->image.w = idata->w; msg->image.h = idata->h; if (idata->shm_id) { const char *old = cserve2_shared_string_get(idata->shm_id); if (strcmp(old, shmpath)) { cserve2_shared_string_del(idata->shm_id); idata->shm_id = cserve2_shared_string_add(shmpath); } } else idata->shm_id = cserve2_shared_string_add(shmpath); idata->valid = EINA_TRUE; buf = (char *)msg + sizeof(*msg); memcpy(buf, shmpath, path_len); return msg; } static void _image_loaded_send(Client *client, Image_Entry *ientry, Image_Data *idata, unsigned int rid) { int size; Msg_Loaded *msg; DBG("Sending LOADED reply for entry %d and RID: %d.", idata->id, rid); msg = _image_loaded_msg_create(ientry, idata, &size); msg->base.rid = rid; cserve2_client_send(client, &size, sizeof(size)); cserve2_client_send(client, msg, size); free(msg); } static void _font_loaded_send(Client *client, unsigned int rid) { int size; Msg_Font_Loaded msg; DBG("Sending FONT_LOADED reply for RID: %d.", rid); size = sizeof(msg); memset(&msg, 0, size); msg.base.rid = rid; msg.base.type = CSERVE2_FONT_LOADED; size = sizeof(msg); cserve2_client_send(client, &size, sizeof(size)); cserve2_client_send(client, &msg, size); } static void * _open_request_build(Entry *entry, int *bufsize) { Slave_Msg_Image_Open msg = { { { 0,0,0,0}, 0,0,0,0,0, } }; const char *loader_data, *key, *path; void *buf; size_t pathlen, keylen, loaderlen; File_Data *fd; Eina_Binbuf *bb; if (!entry || entry->type != CSERVE2_IMAGE_FILE) return NULL; fd = _file_data_find(entry->id); if (!fd) { ERR("Could not find file data for entry %u", entry->id); return NULL; } path = cserve2_shared_string_get(fd->path); key = cserve2_shared_string_get(fd->key); loader_data = cserve2_shared_string_get(fd->loader_data); if (!path) path = ""; if (!key) key = ""; if (!loader_data) loader_data = ""; pathlen = strlen(path) + 1; keylen = strlen(key) + 1; loaderlen = strlen(loader_data) + 1; msg.lo.region.x = fd->lo.region.x; msg.lo.region.y = fd->lo.region.y; msg.lo.region.w = fd->lo.region.w; msg.lo.region.h = fd->lo.region.h; msg.lo.dpi = fd->lo.dpi; msg.lo.w = fd->lo.w; msg.lo.h = fd->lo.h; msg.lo.scale_down_by = fd->lo.scale_down_by; msg.lo.orientation = fd->lo.orientation; bb = eina_binbuf_new(); eina_binbuf_append_length(bb, (unsigned char *) &msg, sizeof(msg)); eina_binbuf_append_length(bb, (unsigned char *) path, pathlen); eina_binbuf_append_length(bb, (unsigned char *) key, keylen); eina_binbuf_append_length(bb, (unsigned char *) loader_data, loaderlen); *bufsize = eina_binbuf_length_get(bb); buf = eina_binbuf_string_steal(bb); eina_binbuf_free(bb); return buf; } static void _request_free(void *msg, void *data EINA_UNUSED) { free(msg); } static Msg_Opened * _open_request_response(Entry *entry, Slave_Msg_Image_Opened *resp, int *size) { File_Data *fd; Slave_Request *req; _entry_load_finish(entry); req = entry->request; entry->request = NULL; fd = _file_data_find(entry->id); if (!fd) { ERR("Could not find file data for entry %u", entry->id); return NULL; } fd->w = resp->w; fd->h = resp->h; fd->animated = resp->animated; fd->frame_count = resp->frame_count; fd->loop_count = resp->loop_count; fd->loop_hint = resp->loop_hint; fd->alpha = resp->alpha; if (resp->has_loader_data) { const char *ldata = (const char *)resp + sizeof(Slave_Msg_Image_Opened); fd->loader_data = cserve2_shared_string_add(ldata); } fd->valid = EINA_TRUE; // If the image is too large, cancel pre-emptive load. if (fd->w * fd->h * 4 >= MAX_PREEMPTIVE_LOAD_SIZE) { DBG("Not pre-loading this image "); cserve2_request_dependents_drop(req, CSERVE2_REQ_IMAGE_SPEC_LOAD); } return _image_opened_msg_create(fd, size); } void cserve2_entry_request_drop(void *data, Slave_Request_Type type EINA_UNUSED) { Entry *e = data; if (!e) return; e->request = NULL; } static void _request_failed(Entry *e, Error_Type type EINA_UNUSED) { Eina_List *l, *l_next; Reference *ref; e->request = NULL; EINA_LIST_FOREACH_SAFE(e->references, l, l_next, ref) { Eina_Hash *hash = NULL; if (e->type == CSERVE2_IMAGE_FILE) hash = ref->client->files.referencing; else if (e->type == CSERVE2_IMAGE_DATA) hash = ref->client->images.referencing; else continue; if (type != CSERVE2_REQUEST_CANCEL) { DBG("removing entry %u from hash", ref->client_entry_id); eina_hash_del_by_key(hash, &(ref->client_entry_id)); } } } static Slave_Request_Funcs _open_funcs = { .msg_create = (Slave_Request_Msg_Create)_open_request_build, .msg_free = _request_free, .response = (Slave_Request_Response)_open_request_response, .error = (Slave_Request_Error)_request_failed }; static void * _load_request_build(Image_Entry *ientry, int *bufsize) { char *buf, *ptr; const char *shmpath, *loader_data, *path, *key; int size; int shmlen, filelen, keylen, loaderlen; Slave_Msg_Image_Load msg; File_Data *fd; Image_Data *idata; idata = _image_data_find(ENTRYID(ientry)); if (!idata) return NULL; fd = _file_data_find(idata->file_id); if (!fd) { ERR("Could not find file data %u for image %u", idata->file_id, idata->id); return NULL; } // opening shm for this file ientry->shm = cserve2_shm_request("img", fd->w * fd->h * 4); if (!ientry->shm) return NULL; shmpath = cserve2_shm_name_get(ientry->shm); shmlen = strlen(shmpath) + 1; path = cserve2_shared_string_get(fd->path); key = cserve2_shared_string_get(fd->key); if (!path) path = ""; if (!key) key = ""; filelen = strlen(path) + 1; keylen = strlen(key) + 1; loader_data = cserve2_shared_string_get(fd->loader_data); if (loader_data) loaderlen = strlen(loader_data) + 1; else loaderlen = 0; size = sizeof(msg) + shmlen + filelen + keylen + loaderlen; buf = malloc(size); if (!buf) return NULL; memset(&msg, 0, sizeof(msg)); msg.w = fd->w; msg.h = fd->h; msg.alpha = fd->alpha; // NOTE: Not passing scale_load options msg.opts.w = idata->opts.w; msg.opts.h = idata->opts.h; msg.opts.region = idata->opts.region; msg.opts.scale_down_by = idata->opts.scale_down_by; msg.opts.dpi = idata->opts.dpi; msg.opts.degree = idata->opts.degree; msg.opts.orientation = idata->opts.orientation; msg.shm.mmap_offset = cserve2_shm_map_offset_get(ientry->shm); msg.shm.image_offset = cserve2_shm_offset_get(ientry->shm); msg.shm.mmap_size = cserve2_shm_map_size_get(ientry->shm); msg.shm.image_size = cserve2_shm_size_get(ientry->shm); msg.has_loader_data = !!loaderlen; memcpy(buf, &msg, sizeof(msg)); ptr = buf + sizeof(msg); memcpy(ptr, shmpath, shmlen); ptr += shmlen; memcpy(ptr, path, filelen); ptr += filelen; memcpy(ptr, key, keylen); ptr += keylen; if (loaderlen > 0) memcpy(ptr, loader_data, loaderlen); *bufsize = size; _entry_load_start(&ientry->base); return buf; } static inline Eina_Bool _scaling_needed(Image_Data *idata, Slave_Msg_Image_Loaded *resp) { return (((idata->opts.scale_load.dst_w) && (idata->opts.scale_load.dst_h)) && ((idata->opts.scale_load.dst_w != resp->w) || (idata->opts.scale_load.dst_h != resp->h))); } static int _scaling_do(Shm_Handle *scale_shm, Image_Data *idata, Image_Entry *original) { char *scale_map, *orig_map; void *src_data, *dst_data; File_Data *fd; Image_Data *orig_idata; #warning FIXME Remove this call, add alpha flag to Image_Data fd = _file_data_find(idata->file_id); if (!fd) { ERR("Could not find file data %u for image %u", idata->file_id, idata->id); return -1; } orig_idata = _image_data_find(original->base.id); if (!orig_idata) { ERR("Could not find image %u", original->base.id); return -1; } scale_map = cserve2_shm_map(scale_shm); if (scale_map == MAP_FAILED) { ERR("Failed to memory map file for scale image."); return -1; } orig_map = cserve2_shm_map(original->shm); if (orig_map == MAP_FAILED) { ERR("Failed to memory map file for original image."); cserve2_shm_unmap(scale_shm); return -1; } src_data = orig_map + cserve2_shm_map_offset_get(original->shm); dst_data = scale_map + cserve2_shm_map_offset_get(scale_shm); DBG("Scaling image ([%dx%d]:[%d,%d:%dx%d] --> [%d,%d:%dx%d])", orig_idata->w, orig_idata->h, idata->opts.scale_load.src_x, idata->opts.scale_load.src_y, idata->opts.scale_load.src_w, idata->opts.scale_load.src_h, 0, 0, idata->opts.scale_load.dst_w, idata->opts.scale_load.dst_h); cserve2_rgba_image_scale_do( src_data, orig_idata->w, orig_idata->h, dst_data, idata->opts.scale_load.src_x, idata->opts.scale_load.src_y, idata->opts.scale_load.src_w, idata->opts.scale_load.src_h, 0, 0, idata->opts.scale_load.dst_w, idata->opts.scale_load.dst_h, fd->alpha, idata->opts.scale_load.smooth); cserve2_shm_unmap(original->shm); cserve2_shm_unmap(scale_shm); return 0; } static int _scaling_prepare_and_do(Image_Entry *ientry, Image_Data *idata) { Shm_Handle *scale_shm; scale_shm = cserve2_shm_request("img", idata->opts.scale_load.dst_w * idata->opts.scale_load.dst_h * 4); if (!scale_shm) return -1; DBG("Scaling image from shm %s to shm %s", cserve2_shm_name_get(ientry->shm), cserve2_shm_name_get(scale_shm)); if (_scaling_do(scale_shm, idata, ientry) != 0) return -1; // Switch shm in original image cserve2_shm_unref(ientry->shm); cserve2_shared_string_del(idata->shm_id); ientry->shm = scale_shm; idata->shm_id = 0; idata->w = idata->opts.scale_load.dst_w; idata->h = idata->opts.scale_load.dst_h; return 0; } static Msg_Loaded * _load_request_response(Image_Entry *ientry, Slave_Msg_Image_Loaded *resp, int *size) { Image_Data *idata; idata = _image_data_find(ENTRYID(ientry)); if (!idata) return NULL; _entry_load_finish(ASENTRY(ientry)); ASENTRY(ientry)->request = NULL; idata->alpha_sparse = resp->alpha_sparse; if (!idata->doload) DBG("Entry %d loaded by speculative preload.", idata->id); idata->w = resp->w; idata->h = resp->h; if (_scaling_needed(idata, resp)) { DBG("About to scale image %u", idata->id); if (!_scaling_prepare_and_do(ientry, idata)) DBG("Image %u has been scaled.", idata->id); else ERR("Failed to scale image %u", idata->id); } else DBG("No scaling needed for image %u", idata->id); return _image_loaded_msg_create(ientry, idata, size); } static Slave_Request_Funcs _load_funcs = { .msg_create = (Slave_Request_Msg_Create)_load_request_build, .msg_free = _request_free, .response = (Slave_Request_Response)_load_request_response, .error = (Slave_Request_Error)_request_failed }; static void _image_key_set(unsigned int file_id, const Evas_Image_Load_Opts *opts, char *buf, int size) { const char empty[sizeof(Evas_Image_Load_Opts)] = {0}; if (!opts) opts = (Evas_Image_Load_Opts *) ∅ snprintf(buf, size, "%u:%0.3f:%dx%d:%d:%d,%d+%dx%d:!([%d,%d:%dx%d]-[%dx%d:%d]):%d:%d", file_id, opts->dpi, opts->w, opts->h, opts->scale_down_by, opts->region.x, opts->region.y, opts->region.w, opts->region.h, opts->scale_load.src_x, opts->scale_load.src_y, opts->scale_load.src_w, opts->scale_load.src_h, opts->scale_load.dst_w, opts->scale_load.dst_h, opts->scale_load.smooth, opts->degree, opts->orientation); } static unsigned int _image_opts_id_get(unsigned int file_id, Evas_Image_Load_Opts *opts, char *buf, int size) { uintptr_t image_id; _image_key_set(file_id, opts, buf, size); image_id = (uintptr_t) eina_hash_find(image_ids, buf); return (unsigned int) image_id; } static int _image_entry_size_get(Image_Entry *ientry) { int size = sizeof(Image_Data); /* XXX: get the overhead of the shm handler too */ if (ientry->shm) size += cserve2_shm_size_get(ientry->shm); return size / 1024; } static Eina_Bool _evas_image_load_opts_empty(Evas_Image_Load_Opts *lo) { if (!lo) return EINA_TRUE; return ((lo->scale_down_by == 0) && (lo->dpi == 0.0) && (lo->w == 0) && (lo->h == 0) && (lo->region.x == 0) && (lo->region.y == 0) && (lo->region.w == 0) && (lo->region.h == 0) && (lo->orientation == 0)); } static void _file_hkey_get(char *buf, size_t sz, const char *path, const char *key, Evas_Image_Load_Opts *lo) { // Same as _evas_cache_image_loadopts_append() but not optimized :) if (lo && _evas_image_load_opts_empty(lo)) lo = NULL; if (!lo) snprintf(buf, sz, "%s:%s", path, key); else { if (lo->orientation) { snprintf(buf, sz, "%s:%s//@/%d/%f/%dx%d/%d+%d.%dx%d", path, key, lo->scale_down_by, lo->dpi, lo->w, lo->h, lo->region.x, lo->region.y, lo->region.w, lo->region.h); } else { snprintf(buf, sz, "%s:%s//@/%d/%f/%dx%d/%d+%d.%dx%d/o", path, key, lo->scale_down_by, lo->dpi, lo->w, lo->h, lo->region.x, lo->region.y, lo->region.w, lo->region.h); } } } static void _file_id_free(File_Data *fd) { Evas_Image_Load_Opts lo = empty_lo; char buf[4096]; lo.region.x = fd->lo.region.x; lo.region.y = fd->lo.region.y; lo.region.w = fd->lo.region.w; lo.region.h = fd->lo.region.h; lo.dpi = fd->lo.dpi; lo.w = fd->lo.w; lo.h = fd->lo.h; lo.scale_down_by = fd->lo.scale_down_by; lo.orientation = fd->lo.orientation; _file_hkey_get(buf, sizeof(buf), cserve2_shared_string_get(fd->path), cserve2_shared_string_get(fd->key), &lo); DBG("Removing entry file id: %u, file: \"%s\"", fd->id, buf); eina_hash_del_by_key(file_ids, buf); } static void _image_id_free(Image_Data *idata) { char buf[4096]; DBG("Removing entry image id: %d", idata->id); _image_opts_id_get(idata->file_id, &idata->opts, buf, sizeof(buf)); eina_hash_del_by_key(image_ids, buf); } static void _image_entry_free(Image_Entry *ientry) { File_Data *fd; File_Entry *fentry; Image_Data *idata; idata = _image_data_find(ENTRYID(ientry)); if (!idata || !idata->refcount) { CRIT("Trying to free already freed object: %u", ENTRYID(ientry)); return; } if (--idata->refcount > 0) return; if (ientry->base.request) cserve2_request_cancel_all(ientry->base.request, CSERVE2_REQUEST_CANCEL); if (idata->unused) { image_entries_lru = eina_list_remove(image_entries_lru, ientry); unused_mem_usage -= _image_entry_size_get(ientry); } fd = _file_data_find(idata->file_id); if (fd) { fentry = _file_entry_find(fd->id); fentry->images = eina_list_remove(fentry->images, ientry); if (fentry && !fentry->images && !ASENTRY(fentry)->references) eina_hash_del_by_key(file_entries, &fd->id); } else ERR("Could not find file data %u for image %u", idata->file_id, idata->id); if (ientry->shm) cserve2_shm_unref(ientry->shm); cserve2_shared_string_del(idata->shm_id); idata->shm_id = 0; free(ientry); } static void _hash_image_entry_free(void *data) { Image_Entry *ientry = data; Image_Data *idata; idata = _image_data_find(ENTRYID(ientry)); if (idata && idata->refcount > 0) { _image_id_free(idata); _image_entry_free(ientry); _freed_image_entry_count++; _repack(); } else ERR("Could not find image entry %u", ENTRYID(ientry)); } static void _file_entry_free(File_Entry *fentry) { File_Watch *fw; if (!fentry) return; // Should we call free for each of the images too? // If everything goes fine, it's not necessary. if (fentry->images) { ERR("Freeing file %u image data still referenced.", ENTRYID(fentry)); eina_list_free(fentry->images); } if (ASENTRY(fentry)->request) cserve2_request_cancel_all(ASENTRY(fentry)->request, CSERVE2_REQUEST_CANCEL); if ((fw = fentry->watcher)) { fw->entries = eina_list_remove(fw->entries, fentry); if (!fw->entries) eina_hash_del_by_key(file_watch, fw->path); } free(fentry); } static void _file_data_free(File_Data *fd) { if (!fd) return; if (!fd->refcount) return; if (--fd->refcount == 0) { cserve2_shared_string_del(fd->key); cserve2_shared_string_del(fd->path); cserve2_shared_string_del(fd->loader_data); memset((char *) fd + sizeof(fd->id), 0, sizeof(*fd) - sizeof(fd->id)); } } static void _hash_file_entry_free(void *data) { File_Entry *fentry = data; File_Data *fd; // TODO: Add some checks to make sure that we are freeing an // unused entry. fd = _file_data_find(ENTRYID(fentry)); _file_id_free(fd); _file_data_free(fd); _file_entry_free(fentry); _freed_file_entry_count++; _repack(); } static void _file_watch_free(void *data) { File_Watch *fw = data; cserve2_file_change_watch_del(fw->path); eina_stringshare_del(fw->path); eina_list_free(fw->entries); free(fw); } static int _font_entry_cmp(const Font_Entry *k1, int k1_length EINA_UNUSED, const Font_Entry *k2, int k2_length EINA_UNUSED) { const char *key1, *key2; if (k1->src->key == k2->src->key) { if (k1->size == k2->size) { if (k1->rend_flags == k2->rend_flags) return k1->dpi - k2->dpi; return k1->rend_flags - k2->rend_flags; } return k1->size - k2->size; } key1 = cserve2_shared_string_get(k1->src->key); key2 = cserve2_shared_string_get(k2->src->key); return strcmp(key1, key2); } static int _font_entry_key_hash(const Font_Entry *key, int key_length EINA_UNUSED) { const char *keystr; int hash; keystr = cserve2_shared_string_get(key->src->key); hash = eina_hash_djb2(keystr, strlen(keystr)); hash ^= eina_hash_int32(&key->rend_flags, sizeof(int)); hash ^= eina_hash_int32(&key->size, sizeof(int)); hash ^= eina_hash_int32(&key->dpi, sizeof(int)); return hash; } static int _font_entry_memory_usage_get(Font_Entry *fe) { int size; if (!fe) return 0; if (!fe->mempool && !fe->glyph_datas) return 0; size = cserve2_shared_mempool_size_get(fe->mempool); size += cserve2_shared_array_map_size_get(fe->glyph_datas); size += fe->nglyphs * sizeof(Glyph_Entry); return size; } static void _font_entry_free(Font_Entry *fe) { Font_Data *fd; int size, k; if (!fe) return; size = _font_entry_memory_usage_get(fe); DBG("Font memory usage down: %d -> %d / %d", font_mem_usage, font_mem_usage - size, max_font_usage); font_mem_usage -= size; fd = _font_data_find(fe->font_data_id); if (fd) { fd->refcount = 0; cserve2_shared_string_del(fd->glyph_index_shm); cserve2_shared_string_del(fd->file); cserve2_shared_string_del(fd->name); } for (k = 0; k < 3; k++) fash_gl_free(fe->glyph_entries[k]); cserve2_shared_array_del(fe->glyph_datas); cserve2_shared_mempool_del(fe->mempool); cserve2_font_ft_free(fe->ft); fe->src->refcount--; if (fe->src->refcount <= 0) { const char *key = cserve2_shared_string_get(fe->src->key); eina_hash_del_by_key(font_sources, key); } free(fe); _freed_font_entry_count++; _repack(); } static void _glyph_free_cb(void *data) { Glyph_Entry *gl = data; Glyph_Data *gldata; if (!gl || !gl->fe) return; gldata = _glyph_data_find(gl->fe->glyph_datas, gl->gldata_id); if (gldata) { cserve2_shared_string_del(gldata->shm_id); gldata->refcount--; } free(gl); } static void _font_source_free(Font_Source *fs) { if (!fs) return; cserve2_shared_string_del(fs->key); cserve2_shared_string_del(fs->name); cserve2_shared_string_del(fs->file); cserve2_font_source_ft_free(fs->ft); free(fs); } static void _font_lru_update(Font_Entry *fe) { Eina_List *l; l = eina_list_data_find_list(font_shm_lru, fe); if (l) { if (fe->unused) font_shm_lru = eina_list_promote_list(font_shm_lru, l); else font_shm_lru = eina_list_demote_list(font_shm_lru, l); } else font_shm_lru = eina_list_append(font_shm_lru, fe); } static void _font_lru_flush(void) { Eina_List *l, *l_next; l = font_shm_lru; l_next = eina_list_next(l); DBG("Font memory usage [begin]: %d / %d", font_mem_usage, max_font_usage); while (l && font_mem_usage > max_font_usage) { Font_Entry *fe; fe = eina_list_data_get(l); if (fe->unused) { font_shm_lru = eina_list_remove_list(font_shm_lru, l); eina_hash_del_by_key(font_entries, fe); } l = l_next; l_next = eina_list_next(l); } DBG("Font memory usage [end]: %d / %d", font_mem_usage, max_font_usage); } void cserve2_cache_init(void) { file_ids = eina_hash_string_superfast_new(NULL); file_entries = eina_hash_int32_new(_hash_file_entry_free); image_ids = eina_hash_string_superfast_new(NULL); image_entries = eina_hash_int32_new(_hash_image_entry_free); file_watch = eina_hash_string_superfast_new(_file_watch_free); font_sources = eina_hash_string_small_new(EINA_FREE_CB(_font_source_free)); font_entries = eina_hash_new(NULL, EINA_KEY_CMP(_font_entry_cmp), EINA_KEY_HASH(_font_entry_key_hash), EINA_FREE_CB(_font_entry_free), 5); _generation_id++; _file_data_array = cserve2_shared_array_new(FILE_DATA_ARRAY_TAG, _generation_id, sizeof(File_Data), 0); _image_data_array = cserve2_shared_array_new(IMAGE_DATA_ARRAY_TAG, _generation_id, sizeof(Image_Data), 0); _font_data_array = cserve2_shared_array_new(FONT_DATA_ARRAY_TAG, _generation_id, sizeof(Font_Data), 0); } void cserve2_cache_shutdown(void) { _shutdown = EINA_TRUE; eina_hash_free(image_entries); eina_hash_free(image_ids); eina_hash_free(file_entries); eina_hash_free(file_ids); eina_hash_free(file_watch); eina_hash_free(font_entries); eina_hash_free(font_sources); cserve2_shared_array_del(_file_data_array); cserve2_shared_array_del(_image_data_array); cserve2_shared_array_del(_font_data_array); } static Reference * _entry_reference_add(Entry *entry, Client *client, unsigned int client_entry_id) { Reference *ref; // increase reference for this file ref = malloc(sizeof(*ref)); ref->client = client; ref->entry = entry; ref->client_entry_id = client_entry_id; ref->count = 1; entry->references = eina_list_append(entry->references, ref); return ref; } static void _entry_unused_push(Image_Entry *ientry) { Image_Data *idata; int size; idata = _image_data_find(ENTRYID(ientry)); if (!idata) return; idata->unused = EINA_TRUE; size = _image_entry_size_get(ientry); if ((size > max_unused_mem_usage) || !(idata->doload)) { eina_hash_del_by_key(image_entries, &idata->id); return; } while (image_entries_lru && (size > (max_unused_mem_usage - unused_mem_usage))) { Image_Entry *ie = eina_list_data_get(eina_list_last(image_entries_lru)); Eina_Bool ok = eina_hash_del_by_key(image_entries, &(ENTRYID(ie))); if (!ok) { DBG("Image %d was not found in the hash table!", ENTRYID(ie)); image_entries_lru = eina_list_remove(image_entries_lru, ie); _image_entry_free(ie); } } if (!image_entries_lru && (unused_mem_usage != 0)) { DBG("Invalid accounting of LRU size (was empty but size: %d)", unused_mem_usage); unused_mem_usage = 0; } image_entries_lru = eina_list_append(image_entries_lru, ientry); unused_mem_usage += size; } static void _entry_reference_del(Entry *entry, Reference *ref) { entry->references = eina_list_remove(entry->references, ref); if (entry->references) goto free_ref; if (entry->type == CSERVE2_IMAGE_FILE) { File_Entry *fentry = (File_Entry *) entry; File_Data *fd = _file_data_find(entry->id); if (fd) { // FIXME: Check difference with master (2 cases vs. only one) if (fd->invalid || !fentry->images) eina_hash_del_by_key(file_entries, &entry->id); } else ERR("File data not found for id %u", entry->id); /* don't free file entries that have images attached to it, they will * be freed when the last unused image is freed */ } else if (entry->type == CSERVE2_IMAGE_DATA) { Image_Entry *ientry = (Image_Entry *) entry; Image_Data *idata = _image_data_find(entry->id); if (!idata || !idata->file_id) eina_hash_del_by_key(image_entries, &entry->id); else { File_Data *fdata = _file_data_find(idata->file_id); if (fdata->invalid) _image_entry_free(ientry); else _entry_unused_push(ientry); } } else if (entry->type == CSERVE2_FONT_ENTRY) { Font_Entry *fe = (Font_Entry *) entry; fe->unused = EINA_TRUE; _font_lru_update(fe); _font_lru_flush(); } else ERR("Wrong type of entry."); free_ref: free(ref); } static void _entry_free_cb(void *data) { Reference *ref = data; Entry *entry; DBG("Removing client reference for entry id: %d, client: %d", ref->entry->id, ref->client->id); entry = ref->entry; /* XXX: handle the case of requests being processed gracefully */ if (entry->request /*&& !entry->request->processing*/) { if (entry->type == CSERVE2_IMAGE_FILE) cserve2_request_cancel(entry->request, ref->client, CSERVE2_REQUEST_CANCEL); else if (entry->type == CSERVE2_IMAGE_DATA) cserve2_request_cancel(entry->request, ref->client, CSERVE2_REQUEST_CANCEL); } _entry_reference_del(entry, ref); } static void _font_entry_reference_del(Client *client, Font_Entry *fe) { Eina_List *l, *l_next; Reference *ref; EINA_LIST_FOREACH_SAFE(client->fonts.referencing, l, l_next, ref) { if (ref->entry == (Entry *)fe) { ref->count--; if (ref->count > 0) break; client->fonts.referencing = eina_list_remove_list( client->fonts.referencing, l); _entry_reference_del(&fe->base, ref); return; } } } void cserve2_cache_client_new(Client *client) { client->files.referencing = eina_hash_int32_new(_entry_free_cb); client->images.referencing = eina_hash_int32_new(_entry_free_cb); client->fonts.referencing = NULL; cserve2_index_list_send(_generation_id, cserve2_shared_strings_index_name_get(), cserve2_shared_strings_table_name_get(), cserve2_shared_array_name_get(_file_data_array), cserve2_shared_array_name_get(_image_data_array), cserve2_shared_array_name_get(_font_data_array), client); } void cserve2_cache_client_del(Client *client) { Reference *ref; // will call _entry_free_cb() for every entry eina_hash_free(client->images.referencing); // will call _entry_free_cb() for every entry eina_hash_free(client->files.referencing); EINA_LIST_FREE(client->fonts.referencing, ref) { _entry_reference_del(ref->entry, ref); } } static Image_Entry * _image_entry_new(Client *client, int rid, unsigned int client_file_id, unsigned int client_image_id, Evas_Image_Load_Opts *opts, char *buf, size_t buf_size) { Reference *ref, *oldref; Image_Entry *ientry; Image_Data *idata; File_Data *fd; int idata_id; unsigned int image_id; ref = eina_hash_find(client->files.referencing, &client_file_id); if (!ref) { ERR("Couldn't find file id: %d, for image id: %d", client_file_id, image_id); cserve2_client_error_send(client, rid, CSERVE2_INVALID_CACHE); return NULL; } fd = _file_data_find(ref->entry->id); if (!fd || fd->invalid) { cserve2_client_error_send(client, rid, CSERVE2_FILE_CHANGED); return NULL; } idata_id = cserve2_shared_array_item_new(_image_data_array); idata = cserve2_shared_array_item_data_get(_image_data_array, idata_id); if (idata_id < 0 || !idata) { ERR("Could not create new image entry in shared array"); cserve2_client_error_send(client, rid, CSERVE2_RESOURCE_ALLOCATION_FAILED); return NULL; } image_id = ++_entry_id; while (eina_hash_find(image_entries, &image_id)) image_id = ++_entry_id; DBG("Creating new image entry: %u at index %d in shm %s", image_id, idata_id, cserve2_shared_array_name_get(_image_data_array)); ientry = calloc(1, sizeof(*ientry)); ientry->base.id = image_id; ientry->base.type = CSERVE2_IMAGE_DATA; if (opts) { idata->opts.dpi = opts->dpi; idata->opts.w = opts->w; idata->opts.h = opts->h; idata->opts.scale_down_by = opts->scale_down_by; idata->opts.region.x = opts->region.x; idata->opts.region.y = opts->region.y; idata->opts.region.w = opts->region.w; idata->opts.region.h = opts->region.h; idata->opts.scale_load.src_x = opts->scale_load.src_x; idata->opts.scale_load.src_y = opts->scale_load.src_y; idata->opts.scale_load.src_w = opts->scale_load.src_w; idata->opts.scale_load.src_h = opts->scale_load.src_h; idata->opts.scale_load.dst_w = opts->scale_load.dst_w; idata->opts.scale_load.dst_h = opts->scale_load.dst_h; idata->opts.scale_load.smooth = opts->scale_load.smooth; idata->opts.scale_load.scale_hint = opts->scale_load.scale_hint; idata->opts.degree = opts->degree; idata->opts.orientation = opts->orientation; } idata->valid = EINA_FALSE; idata->file_id = ref->entry->id; idata->refcount = 1; idata->id = image_id; _image_key_set(idata->file_id, opts, buf, buf_size); eina_hash_add(image_entries, &image_id, ientry); eina_hash_add(image_ids, buf, (void *)(intptr_t) image_id); if (client_image_id) { oldref = eina_hash_find(client->images.referencing, &client_image_id); ref = _entry_reference_add(ASENTRY(ientry), client, client_image_id); if (oldref) eina_hash_del_by_key(client->images.referencing, &client_image_id); eina_hash_add(client->images.referencing, &client_image_id, ref); } // else: See _cserve2_cache_fast_scaling_check() return ientry; } static void _file_changed_cb(const char *path EINA_UNUSED, Eina_Bool deleted EINA_UNUSED, void *data) { File_Watch *fw = data; File_Data *fd; Eina_List *l; EINA_LIST_FOREACH(fw->entries, l, fd) { Eina_List *ll; Image_Entry *ie; File_Entry *fentry; fd->invalid = EINA_TRUE; fentry = _file_entry_find(fd->id); if (!fentry) continue; fentry->watcher = NULL; EINA_LIST_FOREACH(fentry->images, ll, ie) { Image_Data *idata; eina_hash_set(image_entries, &ENTRYID(ie), NULL); if (ASENTRY(ie)->request /*&& !ie->base.request->processing*/) cserve2_request_cancel_all(ASENTRY(ie)->request, CSERVE2_FILE_CHANGED); ASENTRY(ie)->request = NULL; idata = _image_data_find(ENTRYID(ie)); if (idata) { _image_id_free(idata); if (idata->unused) _image_entry_free(ie); } } _file_id_free(fd); eina_hash_set(file_entries, &fd->id, NULL); if (ASENTRY(fentry)->request /*&& !ASENTRY(fentry)->request->processing*/) { cserve2_request_cancel_all(ASENTRY(fentry)->request, CSERVE2_FILE_CHANGED); ASENTRY(fentry)->request = NULL; } if (!fentry->images && !ASENTRY(fentry)->references) _hash_file_entry_free(fentry); } eina_hash_del_by_key(file_watch, fw->path); } static Font_Source * _cserve2_font_source_find(const char *name) { Font_Source *fs; fs = eina_hash_find(font_sources, name); return fs; } static Font_Entry * _cserve2_font_entry_find(const char *name, unsigned int size, unsigned int rend_flags, unsigned int dpi) { Font_Entry tmp_fe; Font_Source tmp_fs; Font_Entry *fe; tmp_fs.key = cserve2_shared_string_add(name); tmp_fe.src = &tmp_fs; tmp_fe.size = size; tmp_fe.rend_flags = rend_flags; tmp_fe.dpi = dpi; fe = eina_hash_find(font_entries, &tmp_fe); cserve2_shared_string_del(tmp_fs.key); return fe; } static void * _font_load_request_build(void *data, int *size) { Font_Entry *fe = data; Slave_Msg_Font_Load *msg = calloc(1, sizeof(*msg)); msg->ftdata1 = fe->src->ft; msg->ftdata2 = fe->ft; msg->rend_flags = fe->rend_flags; msg->size = fe->size; msg->dpi = fe->dpi; msg->name = cserve2_shared_string_get(fe->src->name); msg->file = cserve2_shared_string_get(fe->src->file); *size = 0; _entry_load_start(&fe->base); return msg; } static void _font_load_request_free(void *msg, void *data EINA_UNUSED) { free(msg); } static Msg_Font_Loaded * _font_load_request_response(Font_Entry *fe, Slave_Msg_Font_Loaded *msg, int *size) { Msg_Font_Loaded *resp; if (!fe->src->ft) fe->src->ft = msg->ftdata1; if (!fe->ft) { fe->ft = msg->ftdata2; _entry_load_finish(&fe->base); } if (fe->base.request) fe->base.request = NULL; resp = calloc(1, sizeof(*resp)); resp->base.type = CSERVE2_FONT_LOADED; *size = sizeof(*resp); return resp; } static void _font_load_request_failed(Font_Entry *fe, Error_Type error EINA_UNUSED) { Eina_List *l, *l_next; Reference *ref; if (fe->base.request) fe->base.request = NULL; EINA_LIST_FOREACH_SAFE(fe->base.references, l, l_next, ref) _font_entry_reference_del(ref->client, fe); } static Slave_Request_Funcs _font_load_funcs = { .msg_create = (Slave_Request_Msg_Create)_font_load_request_build, .msg_free = (Slave_Request_Msg_Free)_font_load_request_free, .response = (Slave_Request_Response)_font_load_request_response, .error = (Slave_Request_Error)_font_load_request_failed }; static Eina_Bool _glyphs_request_check(Glyphs_Request *req, Eina_Bool report_load) { unsigned int i, hint; Font_Entry *fe = req->fe; req->answer = malloc(sizeof(*req->answer) * req->nglyphs); req->nanswer = 0; hint = req->hint; if (hint > 2) { WRN("Invalid font hint requested. Defaulting to 0."); hint = 0; } for (i = req->current; i < req->nglyphs; i++) { Glyph_Entry *ge; ge = fash_gl_find(fe->glyph_entries[hint], req->glyphs[i]); if (ge) { req->answer[req->nanswer++] = ge; #ifdef DEBUG_LOAD_TIME // calculate average time saved when loading glyphs if (report_load) fe->gl_saved_time += (fe->gl_load_time / fe->nglyphs); #endif //ge->fc->inuse++; } else break; } req->current = i; // No glyphs need to be rendered. return (req->nanswer == req->nglyphs); } static Msg_Font_Glyphs_Loaded * _glyphs_loaded_msg_create(Glyphs_Request *req, int *resp_size) { Msg_Font_Glyphs_Loaded *msg; unsigned int size; const char *shmname; unsigned int shmname_size; unsigned int k; char *response, *buf; shmname = cserve2_shared_mempool_name_get(req->fe->mempool); shmname_size = strlen(shmname) + 1; size = sizeof(Msg_Font_Glyphs_Loaded); size += sizeof(int) * 2; size += shmname_size; size += req->nanswer * 10 * sizeof(int); response = malloc(size); if (!response) return NULL; msg = (Msg_Font_Glyphs_Loaded *) response; buf = response + sizeof(Msg_Font_Glyphs_Loaded); msg->base.type = CSERVE2_FONT_GLYPHS_LOADED; memcpy(buf, &shmname_size, sizeof(int)); buf += sizeof(int); memcpy(buf, shmname, shmname_size); buf += shmname_size; memcpy(buf, &req->nanswer, sizeof(int)); buf += sizeof(int); for (k = 0; k < req->nanswer; k++) { Glyph_Entry *ge; Glyph_Data *gldata; ge = req->answer[k]; gldata = _glyph_data_find(ge->fe->glyph_datas, ge->gldata_id); if (!gldata) { ERR("Glyph data not found for %d", ge->gldata_id); continue; } memcpy(buf, &gldata->index, sizeof(int)); buf += sizeof(int); memcpy(buf, &gldata->shm_id, sizeof(string_t)); buf += sizeof(string_t); memcpy(buf, &gldata->offset, sizeof(int)); buf += sizeof(int); memcpy(buf, &gldata->size, sizeof(int)); buf += sizeof(int); memcpy(buf, &gldata->rows, sizeof(int)); buf += sizeof(int); memcpy(buf, &gldata->width, sizeof(int)); buf += sizeof(int); memcpy(buf, &gldata->pitch, sizeof(int)); buf += sizeof(int); memcpy(buf, &gldata->num_grays, sizeof(int)); buf += sizeof(int); memcpy(buf, &gldata->pixel_mode, sizeof(int)); buf += sizeof(int); memcpy(buf, &gldata->hint, sizeof(int)); buf += sizeof(int); } *resp_size = size; return msg; } static void _glyphs_loaded_send(Glyphs_Request *req, unsigned int rid) { Msg_Font_Glyphs_Loaded *resp; int size; resp = _glyphs_loaded_msg_create(req, &size); resp->base.rid = rid; cserve2_client_send(req->client, &size, sizeof(size)); cserve2_client_send(req->client, resp, size); free(resp); } /* * taken from evas_path.c. It would be good to clean up those utils to * have cserve link against them easily without dragging unneeded dependencies */ #ifdef _WIN32 # define EVAS_PATH_SEPARATOR "\\" #else # define EVAS_PATH_SEPARATOR "/" #endif static char * _file_path_join(const char *path, const char *end) { char *res = NULL; size_t len; if ((!path) && (!end)) return NULL; if (!path) return strdup(end); if (!end) return strdup(path); len = strlen(path); len += strlen(end); len += strlen(EVAS_PATH_SEPARATOR); res = malloc(len + 1); if (!res) return NULL; strcpy(res, path); strcat(res, EVAS_PATH_SEPARATOR); strcat(res, end); return res; } static Glyphs_Request * _glyphs_request_create(Client *client, const char *source, const char *name, unsigned int hint, unsigned int rend_flags, unsigned int size, unsigned int dpi, unsigned int *glyphs, unsigned int nglyphs) { char *fullname; Glyphs_Request *req = calloc(1, sizeof(*req)); if (source && !*source) source = NULL; if (name && !*name) name = NULL; fullname = _file_path_join(source, name); req->fe = _cserve2_font_entry_find(fullname, size, rend_flags, dpi); free(fullname); if (!req->fe) { ERR("No font entry found: source %s, name %s, rendflags: %d, hint: %d," " size: %d, dpi: %d", source, name, rend_flags, hint, size, dpi); free(req); return NULL; } req->client = client; req->nglyphs = nglyphs; req->current = 0; req->glyphs = glyphs; req->hint = hint; return req; } static void _glyphs_request_free(Glyphs_Request *req) { free(req->glyphs); free(req->render); free(req->answer); free(req); } /* add glyphs that are already in cache to the "answers" array, and the ones * that are not cached to the "render" array. */ static void _glyphs_load_request_prepare(Glyphs_Request *req) { unsigned int i, max, hint; req->nrender = 0; Font_Entry *fe = req->fe; if (!fe) { ERR("No font entry for this request."); return; } hint = req->hint; if (hint > 2) { WRN("Invalid font hint requested. Defaulting to 0."); hint = 0; } // Won't render more than this number of glyphs max = req->nglyphs - req->nanswer; req->render = malloc(sizeof(*req->render) * max); for (i = req->current; i < req->nglyphs; i++) { Glyph_Entry *ge; ge = fash_gl_find(fe->glyph_entries[hint], req->glyphs[i]); if (ge) { req->answer[req->nanswer++] = ge; #ifdef DEBUG_LOAD_TIME // calculate average time saved when loading glyphs fe->gl_saved_time += (fe->gl_load_time / fe->nglyphs); #endif //ge->fc->inuse++; } else req->render[req->nrender++] = req->glyphs[i]; } } static void * _glyphs_load_request_build(void *data, int *size EINA_UNUSED) { Glyphs_Request *req = data; Slave_Msg_Font_Glyphs_Load *msg = NULL; Font_Entry *fe = req->fe; #ifdef DEBUG_LOAD_TIME gettimeofday(&fe->rstart, NULL); _glyphs_load_request_prepare(req); #endif msg = calloc(1, sizeof(*msg)); msg->font.ftdata1 = fe->src->ft; msg->font.ftdata2 = fe->ft; msg->font.hint = req->hint; msg->font.rend_flags = fe->rend_flags; msg->glyphs.nglyphs = req->nrender; msg->glyphs.glyphs = req->render; msg->cache.mempool = fe->mempool; return msg; } static void _glyphs_load_request_free(void *msg, void *data) { _glyphs_request_free(data); free(msg); } static Msg_Font_Glyphs_Loaded * _glyphs_load_request_response(Glyphs_Request *req, Slave_Msg_Font_Glyphs_Loaded *msg, int *size) { Font_Entry *fe = req->fe; Shared_Mempool *mempool = msg->mempool; unsigned int j, hint; string_t shm_id = 0; Font_Data *fd; if (!msg->nglyphs) return _glyphs_loaded_msg_create(req, size); hint = req->hint; if (hint > 2) { WRN("Invalid font hint requested. Defaulting to 0."); hint = 0; } fd = _font_data_find(fe->font_data_id); DBG("Font memory usage [begin]: %d / %d", font_mem_usage, max_font_usage); if (!mempool) { mempool = cserve2_shared_mempool_new(GLYPH_DATA_ARRAY_TAG, _generation_id, 0); font_mem_usage += cserve2_shared_mempool_size_get(mempool); } else cserve2_shared_mempool_generation_id_set(mempool, _generation_id); if (!fe->glyph_datas) { fe->glyph_datas = cserve2_shared_array_new(GLYPH_INDEX_ARRAY_TAG, _generation_id, sizeof(Glyph_Data), 0); font_mem_usage += cserve2_shared_array_map_size_get(fe->glyph_datas); fd->glyph_index_shm = cserve2_shared_string_add( cserve2_shared_array_name_get(fe->glyph_datas)); } shm_id = cserve2_shared_string_add(cserve2_shared_mempool_name_get(mempool)); for (j = 0; j < msg->nglyphs; j++) { Glyph_Entry *gl; gl = fash_gl_find(fe->glyph_entries[hint], msg->glyphs[j].index); if (!gl) { int glyph_id, orig_mapsize, new_mapsize; Glyph_Data *gldata; orig_mapsize = cserve2_shared_array_map_size_get(fe->glyph_datas); glyph_id = cserve2_shared_array_item_new(fe->glyph_datas); gldata = cserve2_shared_array_item_data_get(fe->glyph_datas, glyph_id); if (!gldata) { ERR("Could not create new Glyph_Data!"); // TODO: Return error? continue; } gl = calloc(1, sizeof(*gl)); gl->fe = fe; gl->gldata_id = ++_glyph_id; gldata->refcount = 1; gldata->id = gl->gldata_id; gldata->index = msg->glyphs[j].index; gldata->shm_id = cserve2_shared_string_ref(shm_id); gldata->buffer_id = msg->glyphs[j].buffer_id; gldata->offset = msg->glyphs[j].offset; // TODO: Remove? gldata->size = msg->glyphs[j].size; gldata->rows = msg->glyphs[j].rows; gldata->width = msg->glyphs[j].width; gldata->pitch = msg->glyphs[j].pitch; gldata->num_grays = msg->glyphs[j].num_grays; gldata->pixel_mode = msg->glyphs[j].pixel_mode; gldata->hint = hint; fe->nglyphs++; fash_gl_add(fe->glyph_entries[hint], gldata->index, gl); new_mapsize = cserve2_shared_array_map_size_get(fe->glyph_datas); font_mem_usage += new_mapsize - orig_mapsize; font_mem_usage += sizeof(*gl); } req->answer[req->nanswer++] = gl; } cserve2_shared_string_del(shm_id); #ifdef DEBUG_LOAD_TIME gettimeofday(&fe->rfinish, NULL); fe->gl_request_time += _timeval_sub(&fe->rfinish, &fe->rstart); fe->gl_load_time += msg->gl_load_time; fe->gl_render_time += msg->gl_render_time; fe->gl_slave_time += msg->gl_slave_time; #endif fe->mempool = mempool; if (!fd->mempool_shm) fd->mempool_shm = cserve2_shared_string_add( cserve2_shared_mempool_name_get(mempool)); DBG("Font memory usage [end]: %d / %d", font_mem_usage, max_font_usage); _font_lru_flush(); return _glyphs_loaded_msg_create(req, size); } static void _glyphs_load_request_failed(void *data EINA_UNUSED, Error_Type error EINA_UNUSED) { } static Slave_Request_Funcs _glyphs_load_funcs = { .msg_create = (Slave_Request_Msg_Create)_glyphs_load_request_build, .msg_free = (Slave_Request_Msg_Free)_glyphs_load_request_free, .response = (Slave_Request_Response)_glyphs_load_request_response, .error = (Slave_Request_Error)_glyphs_load_request_failed }; static Eina_Bool _font_requested_size_cb(const Eina_Hash *hash EINA_UNUSED, Glyph_Data *gldata, Msg_Stats *msg) { if (gldata->refcount) msg->fonts.requested_size += gldata->size; return EINA_TRUE; } static Eina_Bool _font_entry_stats_cb(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata) { Font_Entry *fe = data; Msg_Stats *msg = fdata; unsigned int shmsize; msg->fonts.fonts_loaded++; if (fe->unused) msg->fonts.fonts_unused++; // accounting size shmsize = cserve2_shared_mempool_size_get(fe->mempool); msg->fonts.real_size += shmsize; if (fe->unused) msg->fonts.unused_size += shmsize; cserve2_shared_array_foreach(fe->glyph_datas, EINA_EACH_CB(_font_requested_size_cb), msg); #ifdef DEBUG_LOAD_TIME // accounting fonts load time msg->fonts.fonts_load_time += fe->base.load_time; if (fe->mempool) { msg->fonts.fonts_used_load_time += fe->base.load_time; msg->fonts.fonts_used_saved_time += fe->base.saved_time; } // accounting glyphs load time msg->fonts.glyphs_load_time += fe->gl_load_time; msg->fonts.glyphs_render_time += fe->gl_render_time; msg->fonts.glyphs_saved_time += fe->gl_saved_time; msg->fonts.glyphs_request_time += fe->gl_request_time; msg->fonts.glyphs_slave_time += fe->gl_slave_time; #endif return EINA_TRUE; } static Eina_Bool _image_file_entry_stats_cb(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata) { Msg_Stats *msg = fdata; File_Entry *fentry = data; // accounting numbers msg->images.files_loaded++; // accounting size msg->images.files_size += sizeof(File_Data) + eina_list_count(fentry->images) * sizeof(Eina_List *) + eina_list_count(ASENTRY(fentry)->references) * (sizeof(Slave_Request *) + sizeof(Eina_List *)); #ifdef DEBUG_LOAD_TIME // accounting file entries load time msg->images.files_load_time += ASENTRY(fentry)->load_time; msg->images.files_saved_time += ASENTRY(fentry)->saved_time; #endif return EINA_TRUE; } static Eina_Bool _image_data_entry_stats_cb(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata) { Msg_Stats *msg = fdata; Image_Entry *ientry = data; Image_Data *idata; File_Data *fd; idata = _image_data_find(ENTRYID(ientry)); if (!idata) return EINA_TRUE; // accounting numbers msg->images.images_loaded++; if (idata->unused) msg->images.images_unused++; // accounting size msg->images.images_size += _image_entry_size_get(ientry) * 1024; if (idata->unused) msg->images.unused_size += _image_entry_size_get(ientry) * 1024; fd = _file_data_find(idata->file_id); if (fd) { unsigned int image_size = fd->w * fd->h * 4; msg->images.requested_size += (image_size * eina_list_count(ASENTRY(ientry)->references)); } #ifdef DEBUG_LOAD_TIME // accounting image entries load time msg->images.images_load_time += ASENTRY(ientry)->load_time; msg->images.images_saved_time += ASENTRY(ientry)->saved_time; #endif return EINA_TRUE; } static void _cserve2_cache_image_stats_get(Msg_Stats *msg) { eina_hash_foreach(file_entries, _image_file_entry_stats_cb, msg); eina_hash_foreach(image_entries, _image_data_entry_stats_cb, msg); } static void _cserve2_cache_font_stats_get(Msg_Stats *msg) { eina_hash_foreach(font_entries, _font_entry_stats_cb, msg); } struct _debug_info { unsigned int size; unsigned int nfonts; }; static Eina_Bool _font_entry_debug_size_cb(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata) { struct _debug_info *di = fdata; Font_Entry *fe = data; const char *str; // filelen di->size+= sizeof(int); // file if (fe->src->file) { str = cserve2_shared_string_get(fe->src->file); di->size+= strlen(str) + 1; } // namelen di->size+= sizeof(int); // name if (fe->src->name) { str = cserve2_shared_string_get(fe->src->file); di->size+= strlen(str) + 1; } // rend_flags, size, dpi, unused di->size+= 4 * sizeof(int); // glyph_data_shm and glyph_mempool_shm (short strings) di->size+= 2 * 64; // nglyphs di->size+= sizeof(int); // glyphs di->size+= fe->nglyphs * sizeof(Glyph_Data); di->nfonts++; return EINA_TRUE; } static Eina_Bool _font_entry_debug_cb(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata) { char **pos = fdata; char *buf = *pos; Font_Entry *fe = data; unsigned int len, k, nglyphs; const char *str; char *nglyphs_pos; // file str = cserve2_shared_string_get(fe->src->file); len = str ? (strlen(str) + 1) : 0; memcpy(buf, &len, sizeof(int)); buf += sizeof(int); if (len) { memcpy(buf, str, len); buf += len; } // name str = cserve2_shared_string_get(fe->src->name); len = str ? (strlen(str) + 1) : 0; memcpy(buf, &len, sizeof(int)); buf += sizeof(int); if (len) { memcpy(buf, str, len); buf += len; } // rend_flags, size, dpi, unused memcpy(buf, &fe->rend_flags, sizeof(int)); buf += sizeof(int); memcpy(buf, &fe->size, sizeof(int)); buf += sizeof(int); memcpy(buf, &fe->dpi, sizeof(int)); buf += sizeof(int); len = fe->unused; memcpy(buf, &len, sizeof(int)); buf += sizeof(int); // glyph shared index and mempool if (fe->glyph_datas) eina_strlcpy(buf, cserve2_shared_array_name_get(fe->glyph_datas), 64); else memset(buf, 0, 64); buf += 64; if (fe->mempool) eina_strlcpy(buf, cserve2_shared_mempool_name_get(fe->mempool), 64); else memset(buf, 0, 64); buf += 64; // skip nglyphs for now... nglyphs_pos = buf; buf += sizeof(int); nglyphs = 0; for (k = 0; k < fe->nglyphs; k++) { Glyph_Data *gd = cserve2_shared_array_item_data_get(fe->glyph_datas, k); if (!gd || !gd->id) break; nglyphs++; memcpy(buf, gd, sizeof(*gd)); buf += sizeof(*gd); } // write real value of nglyphs memcpy(nglyphs_pos, &nglyphs, sizeof(int)); if (nglyphs != fe->nglyphs) { WRN("Found %u valid glyphs when the font advertised %u entries", nglyphs, fe->nglyphs); } *pos = buf; return EINA_TRUE; } static void * _cserve2_cache_font_debug(unsigned int rid, unsigned int *size) { Msg_Font_Debug msg; char *buf, *pos; struct _debug_info di; di.size = sizeof(msg); di.nfonts = 0; memset(&msg, 0, sizeof(msg)); msg.base.type = CSERVE2_FONT_DEBUG; msg.base.rid = rid; eina_strlcpy(msg.fonts_index_path, cserve2_shared_array_name_get(_font_data_array), sizeof(msg.fonts_index_path)); // First calculate how much size is needed for this message eina_hash_foreach(font_entries, _font_entry_debug_size_cb, &di); // Now really create the message buf = malloc(di.size); pos = buf; // nfonts msg.nfonts = di.nfonts; // msg base memcpy(buf, &msg, sizeof(msg)); pos += sizeof(msg); eina_hash_foreach(font_entries, _font_entry_debug_cb, &pos); *size = di.size; return buf; } int cserve2_cache_file_open(Client *client, unsigned int client_file_id, const char *path, const char *key, unsigned int rid, Evas_Image_Load_Opts *lo) { unsigned int file_id; File_Data *fd; Reference *ref; File_Watch *fw; char buf[4906]; File_Entry *fentry; int idx; #if 0 // look for this file on client references ref = eina_hash_find(client->files.referencing, &client_file_id); if (ref) { _entry_load_reused(ref->entry); fd = _file_data_find(ref->entry->id); if (!fd || fd->invalid) { cserve2_client_error_send(client, rid, CSERVE2_FILE_CHANGED); return -1; } DBG("found client file id: %d", client_file_id); ref->count++; // File already being loaded, just add the request to be replied if (ref->entry->request) cserve2_request_waiter_add(ref->entry->request, rid, client); else _image_opened_send(client, fd, rid); return 0; } #endif // search whether the file is already opened by another client _file_hkey_get(buf, sizeof(buf), path, key, lo); file_id = (unsigned int)(uintptr_t)eina_hash_find(file_ids, buf); if (file_id) { DBG("found file_id %u for client file id %d", file_id, client_file_id); fentry = _file_entry_find(file_id); if (!fentry) { ERR("file \"%s\" is in file_ids hash but not in entries hash.", buf); cserve2_client_error_send(client, rid, CSERVE2_INVALID_CACHE); // FIXME: Maybe we should remove the entry from file_ids then? return -1; } ref = _entry_reference_add(ASENTRY(fentry), client, client_file_id); _entry_load_reused(ref->entry); eina_hash_add(client->files.referencing, &client_file_id, ref); if (ref->entry->request) cserve2_request_waiter_add(ref->entry->request, rid, client); else // File already loaded, otherwise there would be a request { fd = _file_data_find(file_id); _image_opened_send(client, fd, rid); } return 0; } file_id = ++_entry_id; while (eina_hash_find(file_entries, &file_id)) file_id = ++_entry_id; idx = cserve2_shared_array_item_new(_file_data_array); fd = cserve2_shared_array_item_data_get(_file_data_array, idx); DBG("Creating new entry with file_id: %u for file \"%s:%s\". Index %d at %p", file_id, path, key, idx, fd); if (!fd) { ERR("Could not create new file entry!"); return -1; } fd->valid = EINA_FALSE; fd->path = cserve2_shared_string_add(path); fd->key = cserve2_shared_string_add(key); if (!lo) lo = (Evas_Image_Load_Opts *) &empty_lo; fd->lo.region.x = lo->region.x; fd->lo.region.y = lo->region.y; fd->lo.region.w = lo->region.w; fd->lo.region.h = lo->region.h; fd->lo.dpi = lo->dpi; fd->lo.w = lo->w; fd->lo.h = lo->h; fd->lo.scale_down_by = lo->scale_down_by; fd->lo.orientation = lo->orientation; fd->refcount = 1; fd->id = file_id; fentry = calloc(1, sizeof(File_Entry)); ASENTRY(fentry)->type = CSERVE2_IMAGE_FILE; ASENTRY(fentry)->id = file_id; eina_hash_add(file_entries, &file_id, fentry); eina_hash_add(file_ids, buf, (void*)(intptr_t)file_id); ref = _entry_reference_add(ASENTRY(fentry), client, client_file_id); eina_hash_add(client->files.referencing, &client_file_id, ref); fw = eina_hash_find(file_watch, cserve2_shared_string_get(fd->path)); if (!fw) { fw = calloc(1, sizeof(File_Watch)); fw->path = eina_stringshare_add(cserve2_shared_string_get(fd->path)); cserve2_file_change_watch_add(fw->path, _file_changed_cb, fw); eina_hash_direct_add(file_watch, fw->path, fw); } fw->entries = eina_list_append(fw->entries, fentry); fentry->watcher = fw; ASENTRY(fentry)->request = cserve2_request_add(CSERVE2_REQ_IMAGE_OPEN, rid, client, NULL, &_open_funcs, fentry); return 0; } void cserve2_cache_file_close(Client *client, unsigned int client_file_id) { Reference *ref = eina_hash_find(client->files.referencing, &client_file_id); if (!ref) { ERR("Couldn't find file %d in client hash.", client_file_id); return; } ref->count--; if (ref->count <= 0) // will call _entry_free_cb() for this entry eina_hash_del_by_key(client->files.referencing, &client_file_id); } static int _cserve2_cache_fast_scaling_check(Client *client, Image_Entry *ientry) { Eina_Iterator *iter; Image_Entry *i; Image_Entry *orig_entry = NULL; Image_Data *orig_data = NULL; Evas_Image_Load_Opts unscaled; char buf[4096]; unsigned int image_id; int scaled_count = 0; int dst_w, dst_h; Eina_Bool first_attempt = EINA_TRUE; File_Entry *fentry; Image_Data *idata; if (!ientry) return -1; idata = _image_data_find(ENTRYID(ientry)); if (!idata) return -1; dst_w = idata->opts.scale_load.dst_w; dst_h = idata->opts.scale_load.dst_h; // Copy opts w/o scaling memset(&unscaled, 0, sizeof(unscaled)); unscaled.dpi = idata->opts.dpi; //unscaled.w = idata->opts.w; //unscaled.h = idata->opts.h; //unscaled.scale_down_by = idata->opts.scale_down_by; //unscaled.region.x = idata->opts.region.x; //unscaled.region.y = idata->opts.region.y; //unscaled.region.w = idata->opts.region.w; //unscaled.region.h = idata->opts.region.h; unscaled.scale_load.scale_hint = 0; unscaled.degree = idata->opts.degree; unscaled.orientation = idata->opts.orientation; unscaled.scale_load.smooth = idata->opts.scale_load.smooth; try_again: image_id = _image_opts_id_get(idata->file_id, &unscaled, buf, sizeof(buf)); if (image_id) { orig_data = _image_data_find(image_id); orig_entry = _image_entry_find(image_id); if (!orig_data || !orig_entry) return -1; DBG("Found original image in hash: %d,%d:%dx%d -> %dx%d shm %p", orig_data->opts.scale_load.src_x, orig_data->opts.scale_load.src_y, orig_data->opts.scale_load.src_w, orig_data->opts.scale_load.src_h, orig_data->opts.scale_load.dst_w, orig_data->opts.scale_load.dst_h, orig_entry->shm); goto do_scaling; } if (first_attempt) { first_attempt = EINA_FALSE; memset(&unscaled, 0, sizeof(unscaled)); goto try_again; } fentry = _file_entry_find(idata->file_id); iter = eina_list_iterator_new(fentry->images); EINA_ITERATOR_FOREACH(iter, i) { Image_Data *id; if (i == ientry) continue; id = _image_data_find(ENTRYID(i)); if (!id) continue; if (id->opts.w && id->opts.h && (!id->opts.scale_load.dst_w && !id->opts.scale_load.dst_h)) { DBG("Found image in list: %d,%d:%dx%d -> %dx%d shm %p", id->opts.scale_load.src_x, id->opts.scale_load.src_y, id->opts.scale_load.src_w, id->opts.scale_load.src_h, id->opts.scale_load.dst_w, id->opts.scale_load.dst_h, i->shm); if (i->base.request || !i->shm) continue; // Not loaded yet orig_entry = i; break; } scaled_count++; } eina_iterator_free(iter); if (!orig_entry) { DBG("Found %d scaled images for image %u but none matches", scaled_count, ENTRYID(ientry)); // FIXME: The value 4 is completely arbitrary. No benchmarks done yet. if (scaled_count >= 4) { DBG("Forcing load of original image now!"); orig_entry = _image_entry_new(client, 0, idata->file_id, 0, &unscaled, buf, sizeof(buf)); if (!orig_entry) return -1; _entry_unused_push(orig_entry); orig_data = _image_data_find(ENTRYID(orig_entry)); fentry = _file_entry_find(orig_data->file_id); fentry->images = eina_list_append(fentry->images, orig_entry); } else return -1; } do_scaling: if (!orig_entry || !orig_data) return -1; if (!orig_entry->shm && !orig_entry->base.request) { if (orig_entry->base.id != image_id) { CRIT("Entry IDs mismatch"); return -1; } orig_entry->base.request = cserve2_request_add( CSERVE2_REQ_IMAGE_LOAD, 0, NULL, 0, &_load_funcs, orig_entry); } if (orig_entry->base.request || !orig_entry->shm || !orig_data->valid) return -1; // Not loaded yet if (ientry->shm) cserve2_shm_unref(ientry->shm); ientry->shm = cserve2_shm_request("img", dst_w * dst_h * 4); if (!ientry->shm) return -1; if (_scaling_do(ientry->shm, idata, orig_entry) != 0) { cserve2_shm_unref(ientry->shm); ientry->shm = NULL; return -1; } if (orig_data->unused) { image_entries_lru = eina_list_remove(image_entries_lru, orig_entry); image_entries_lru = eina_list_prepend(image_entries_lru, orig_entry); } return 0; } int cserve2_cache_image_entry_create(Client *client, int rid, unsigned int client_file_id, unsigned int client_image_id, Evas_Image_Load_Opts *opts) { Image_Data *idata; Image_Entry *ientry; Reference *ref; File_Entry *fentry; unsigned int image_id = 0; char buf[4096]; // search whether the image is already loaded by another client ref = eina_hash_find(client->files.referencing, &client_file_id); if (ref && opts) image_id = _image_opts_id_get(ref->entry->id, opts, buf, sizeof(buf)); if (image_id) { // Just update the references Reference *oldref; DBG("found image_id %d for client image id %d", image_id, client_image_id); ientry = _image_entry_find(image_id); idata = _image_data_find(image_id); if (!ientry || !idata) { ERR("image entry %d (client id: %d) corrupted: entry %p data %p", image_id, client_image_id, ientry, idata); cserve2_client_error_send(client, rid, CSERVE2_INVALID_CACHE); return -1; } // FIXME: This might be broken (wrt. refcounting) if (idata->unused) { DBG("Re-using old image entry (id: %d) from the LRU list.", ientry->base.id); idata->unused = EINA_FALSE; idata->refcount++; image_entries_lru = eina_list_remove(image_entries_lru, ientry); unused_mem_usage -= _image_entry_size_get(ientry); } _entry_load_reused(&ientry->base); oldref = eina_hash_find(client->images.referencing, &client_image_id); if (oldref && (oldref->entry->id == image_id)) return 0; ref = _entry_reference_add(ASENTRY(ientry), client, client_image_id); if (oldref) eina_hash_del_by_key(client->images.referencing, &client_image_id); eina_hash_add(client->images.referencing, &client_image_id, ref); return 0; } ientry = _image_entry_new(client, rid, client_file_id, client_image_id, opts, buf, sizeof(buf)); if (!ientry) return -1; fentry = _file_entry_find(ref->entry->id); fentry->images = eina_list_append(fentry->images, ientry); if (opts && opts->scale_load.dst_w && opts->scale_load.dst_h) { if (!_cserve2_cache_fast_scaling_check(client, ientry)) return 0; } ASENTRY(ientry)->request = cserve2_request_add( CSERVE2_REQ_IMAGE_SPEC_LOAD, 0, NULL, ASENTRY(fentry)->request, &_load_funcs, ientry); return 0; } void cserve2_cache_image_load(Client *client, unsigned int client_image_id, unsigned int rid) { Image_Entry *ientry; Image_Data *idata; Reference *ref; File_Data *fd; ref = eina_hash_find(client->images.referencing, &client_image_id); if (!ref) { ERR("Can't load: client %d has no image id %d", client->id, client_image_id); return; } ientry = (Image_Entry *) ref->entry; idata = _image_data_find(ENTRYID(ientry)); if (!idata) return; fd = _file_data_find(idata->file_id); if (!fd || fd->invalid) { cserve2_client_error_send(client, rid, CSERVE2_FILE_CHANGED); return; } DBG("Loading image id: %d", ref->entry->id); // File already being loaded, just add the request to be replied if (ASENTRY(ientry)->request) { cserve2_request_waiter_add(ASENTRY(ientry)->request, rid, client); if (!idata->doload) { cserve2_request_type_set(ASENTRY(ientry)->request, CSERVE2_REQ_IMAGE_LOAD); } } else if (ientry->shm) _image_loaded_send(client, ientry, idata, rid); else { File_Entry *fentry = _file_entry_find(idata->file_id); ASENTRY(ientry)->request = cserve2_request_add(CSERVE2_REQ_IMAGE_LOAD, rid, client, ASENTRY(fentry)->request, &_load_funcs, ientry); } idata->doload = EINA_TRUE; } void cserve2_cache_image_preload(Client *client, unsigned int client_image_id, unsigned int rid) { // Same as normal load (for now) return cserve2_cache_image_load(client, client_image_id, rid); } void cserve2_cache_image_unload(Client *client, unsigned int client_image_id) { Reference *ref = eina_hash_find(client->images.referencing, &client_image_id); if (!ref) { ERR("Couldn't find file %d in client hash.", client_image_id); return; } ref->count--; if (ref->count <= 0) // will call _entry_free_cb() for this entry eina_hash_del_by_key(client->images.referencing, &client_image_id); } int cserve2_cache_font_load(Client *client, const char *source, const char *name, unsigned int rend_flags, unsigned int size, unsigned int dpi, unsigned int rid) { Reference *ref; Font_Source *fs; Font_Data *fd; Font_Entry *fe; char *fullname; int fd_index; int k; if (source && !*source) source = NULL; if (name && !*name) name = NULL; fullname = _file_path_join(source, name); fe = _cserve2_font_entry_find(fullname, size, rend_flags, dpi); if (fe) { DBG("found font entry %s, rendflags: %d, size: %d, dpi: %d", name, rend_flags, size, dpi); ref = _entry_reference_add((Entry *)fe, client, 0); client->fonts.referencing = eina_list_append( client->fonts.referencing, ref); _entry_load_reused(&fe->base); fe->unused = EINA_FALSE; _font_lru_update(fe); if (fe->base.request) cserve2_request_waiter_add(fe->base.request, rid, client); else _font_loaded_send(client, rid); free(fullname); return 0; } fe = calloc(1, sizeof(*fe)); fe->rend_flags = rend_flags; fe->size = size; fe->dpi = dpi; fe->base.type = CSERVE2_FONT_ENTRY; for (k = 0; k < 3; k++) fe->glyph_entries[k] = fash_gl_new(_glyph_free_cb); ref = _entry_reference_add((Entry *)fe, client, 0); client->fonts.referencing = eina_list_append(client->fonts.referencing, ref); fe->unused = EINA_FALSE; _font_lru_update(fe); fs = _cserve2_font_source_find(fullname); if (!fs) { const char *key; fs = calloc(1, sizeof(*fs)); if (source) { fs->key = cserve2_shared_string_add(fullname); fs->name = cserve2_shared_string_add(name); fs->file = cserve2_shared_string_add(source); key = fullname; } else { fs->file = cserve2_shared_string_add(name); fs->name = 0; fs->key = cserve2_shared_string_ref(fs->file); key = name; } eina_hash_add(font_sources, key, fs); } // Copy descriptor to Shared Array fd_index = cserve2_shared_array_item_new(_font_data_array); fd = cserve2_shared_array_item_data_get(_font_data_array, fd_index); fd->size = fe->size; fd->rend_flags = fe->rend_flags; fd->dpi = fe->dpi; fd->id = ++_font_data_id; fd->refcount = 1; fd->name = cserve2_shared_string_ref(fs->name); fd->file = cserve2_shared_string_ref(fs->file); fd->glyph_index_shm = 0; fe->src = fs; fe->font_data_id = fd->id; fs->refcount++; DBG("adding FONT_LOAD '%s' request.", name); fe->base.request = cserve2_request_add(CSERVE2_REQ_FONT_LOAD, rid, client, NULL, &_font_load_funcs, fe); eina_hash_direct_add(font_entries, fe, fe); free(fullname); return 0; } int cserve2_cache_font_unload(Client *client, const char *source, const char *name, unsigned int rend_flags, unsigned int size, unsigned int dpi, unsigned int rid EINA_UNUSED) { Font_Entry *fe; char *fullname; if (source && !*source) source = NULL; if (name && !*name) name = NULL; fullname = _file_path_join(source, name); fe = _cserve2_font_entry_find(fullname, size, rend_flags, dpi); free(fullname); if (!fe) { ERR("Unreferencing font not found: '%s:%s'.", source, name); return -1; } _font_entry_reference_del(client, fe); return 0; } int cserve2_cache_font_glyphs_load(Client *client, const char *source, const char *name, unsigned int hint, unsigned int rend_flags, unsigned int size, unsigned int dpi, unsigned int *glyphs, unsigned int nglyphs, unsigned int rid) { Glyphs_Request *req; req = _glyphs_request_create(client, source, name, hint, rend_flags, size, dpi, glyphs, nglyphs); if (!req) { free(glyphs); return -1; } if (_glyphs_request_check(req, EINA_TRUE)) { INF("Glyphs already loaded. Sending answer."); _glyphs_loaded_send(req, rid); _glyphs_request_free(req); } else { cserve2_request_add(CSERVE2_REQ_FONT_GLYPHS_LOAD, rid, client, req->fe->base.request, &_glyphs_load_funcs, req); } return 0; } int cserve2_cache_font_glyphs_used(Client *client, const char *source, const char *name, unsigned int hint, unsigned int rend_flags, unsigned int size, unsigned int dpi, unsigned int *glyphs, unsigned int nglyphs, unsigned int rid EINA_UNUSED) { Glyphs_Request *req; DBG("Received report of used glyphs from client %d", client->id); req = _glyphs_request_create(client, source, name, hint, rend_flags, size, dpi, glyphs, nglyphs); if (!req) { free(glyphs); return 0; } // TODO: We could finetune based on which glyphs exactly are used. req->fe->unused = EINA_FALSE; _font_lru_update(req->fe); _glyphs_request_free(req); return 0; } void cserve2_cache_stats_get(Client *client, unsigned int rid) { Msg_Stats msg; int size; memset(&msg, 0, sizeof(msg)); msg.base.type = CSERVE2_STATS; msg.base.rid = rid; _cserve2_cache_image_stats_get(&msg); _cserve2_cache_font_stats_get(&msg); size = sizeof(msg); cserve2_client_send(client, &size, sizeof(size)); cserve2_client_send(client, &msg, size); } void cserve2_cache_font_debug(Client *client, unsigned int rid) { void *msg; unsigned int size; msg = _cserve2_cache_font_debug(rid, &size); cserve2_client_send(client, &size, sizeof(size)); cserve2_client_send(client, msg, size); free(msg); }