efl/src/bin/evas/evas_cserve2_cache.c

3217 lines
87 KiB
C

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <string.h>
#include <sys/mman.h>
#ifdef DEBUG_LOAD_TIME
#include <sys/time.h>
#endif
#include "evas_cserve2.h"
#include "evas_cs2_utils.h"
#include <Evas_Loader.h>
#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
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 _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)
#define ARRAY_REPACK_TRIGGER_PERCENT 25 // repack when array conains 25% holes
#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_Mempool *sm, unsigned int glyph_id)
{
Glyph_Data *gldata;
Shared_Array *sa;
sa = cserve2_shared_mempool_index_get(sm);
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 >= ARRAY_REPACK_TRIGGER_PERCENT)))
{
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 >= ARRAY_REPACK_TRIGGER_PERCENT)))
{
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 >= ARRAY_REPACK_TRIGGER_PERCENT)))
{
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;
msg->alpha = idata->alpha;
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;
Image_Data *orig_idata;
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);
idata->alpha = orig_idata->alpha;
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,
idata->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 = resp->alpha;
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)
{
if (!opts)
opts = &empty_lo;
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, const 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)
{
CRI("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);
if (fentry)
{
fentry->images = eina_list_remove(fentry->images, ientry);
if (!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);
}
}
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 = sizeof(Font_Entry);
if (!fe) return 0;
if (!fe->mempool)
return size;
size += cserve2_shared_mempool_size_get(fe->mempool);
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_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->mempool, gl->gldata_id);
if (gldata)
{
cserve2_shared_string_del(gldata->mempool_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,
const 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 for client image id: %d", client_file_id);
cserve2_client_error_send(client, rid, CSERVE2_INVALID_CACHE);
return NULL;
}
fd = _file_data_find(ref->entry->id);
if (!fd || fd->invalid)
{
ERR("Can't find file data %d (rid %d)", ref->entry->id, rid);
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_Entry *fentry;
Eina_List *l, *l_next;
EINA_LIST_FOREACH_SAFE(fw->entries, l, l_next, fentry)
{
Eina_List *ll, *ll_next;
Image_Entry *ie;
File_Data *fd;
int fentry_id = ENTRYID(fentry);
fentry->watcher = NULL;
EINA_LIST_FOREACH_SAFE(fentry->images, ll, ll_next, ie)
{
Image_Data *idata;
idata = _image_data_find(ENTRYID(ie));
if (ASENTRY(ie)->request /*&& !ie->base.request->processing*/)
cserve2_request_cancel_all(ASENTRY(ie)->request,
CSERVE2_FILE_CHANGED);
ASENTRY(ie)->request = NULL;
eina_hash_set(image_entries, &ENTRYID(ie), NULL);
if (idata)
{
_image_id_free(idata);
if (idata->unused)
_image_entry_free(ie);
}
}
// from this point, fentry might have died already
// clean up if it's still alive somewhere
fd = _file_data_find(fentry_id);
if (fd)
{
fd->invalid = EINA_TRUE;
_file_id_free(fd);
eina_hash_set(file_entries, &fd->id, NULL);
}
fentry = (File_Entry *) eina_hash_find(file_entries, &fentry_id);
if (fentry)
{
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;
Shared_Array *sa;
unsigned int size;
const char *shmname, *idxname;
unsigned int shmname_size, idxname_size;
unsigned int k;
char *response, *buf;
shmname = cserve2_shared_mempool_name_get(req->fe->mempool);
if (!shmname) return NULL;
shmname_size = strlen(shmname) + 1;
sa = cserve2_shared_mempool_index_get(req->fe->mempool);
idxname = cserve2_shared_array_name_get(sa);
if (!idxname) return NULL;
idxname_size = strlen(idxname) + 1;
size = sizeof(Msg_Font_Glyphs_Loaded);
size += sizeof(int) * 3;
size += shmname_size + idxname_size;
size += req->nanswer * 10 * sizeof(int);
response = calloc(1, 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, &idxname_size, sizeof(int));
buf += sizeof(int);
memcpy(buf, idxname, idxname_size);
buf += idxname_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->mempool, 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->mempool_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->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 = calloc(1, 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 = calloc(max, sizeof(*req->render));
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;
Shared_Array *index;
unsigned int j, hint;
string_t shm_id;
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);
cserve2_shared_mempool_generation_id_set(mempool, _generation_id);
index = cserve2_shared_mempool_index_get(mempool);
if (!fd->glyph_index_shm)
{
fd->glyph_index_shm = cserve2_shared_string_add
(cserve2_shared_array_name_get(index));
}
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)
{
Glyph_Data *gldata;
gldata = _glyph_data_find(mempool, msg->glyphs[j].buffer_id);
if (!gldata)
{
ERR("Could not find Glyph_Data %d", msg->glyphs[j].buffer_id);
// TODO: Return error?
continue;
}
gl = calloc(1, sizeof(*gl));
gl->fe = fe;
gl->gldata_id = gldata->id;
gldata->mempool_id = cserve2_shared_string_ref(shm_id);
gldata->index = msg->glyphs[j].index;
gldata->offset = msg->glyphs[j].offset;
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->hint = hint;
fe->nglyphs++;
fash_gl_add(fe->glyph_entries[hint], gldata->index, gl);
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;
Shared_Array *sa;
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;
sa = cserve2_shared_mempool_index_get(fe->mempool);
if (sa)
{
cserve2_shared_array_foreach
(sa, 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->name);
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;
Shared_Array *index = NULL;
// 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->mempool)
{
index = cserve2_shared_mempool_index_get(fe->mempool);
eina_strlcpy(buf, cserve2_shared_mempool_name_get(fe->mempool), 64);
buf += 64;
eina_strlcpy(buf, cserve2_shared_array_name_get(index), 64);
buf += 64;
}
else
{
memset(buf, 0, 128);
buf += 128;
}
// skip nglyphs for now...
nglyphs_pos = buf;
buf += sizeof(int);
nglyphs = 0;
if (index)
{
for (k = 0; k < fe->nglyphs; k++)
{
Glyph_Data *gd = cserve2_shared_array_item_data_get(index, 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,
int client_file_id)
{
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 && unscaled.scale_load.smooth)
{
first_attempt = EINA_FALSE;
unscaled.scale_load.smooth = 0;
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 && (unused_mem_usage < max_unused_mem_usage))
{
DBG("Forcing load of original image now!");
orig_entry = _image_entry_new(client, 0, client_file_id,
0, &unscaled, buf, sizeof(buf));
if (!orig_entry) return -1;
orig_data = _image_data_find(ENTRYID(orig_entry));
if (!orig_data) return -1;
image_id = ENTRYID(orig_entry);
orig_data->unused = EINA_TRUE;
fentry = _file_entry_find(orig_data->file_id);
fentry->images = eina_list_append(fentry->images, orig_entry);
// TODO: Check validity of this call. Leak VS immediate delete?
//_entry_unused_push(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)
{
CRI("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,
const Evas_Image_Load_Opts *opts)
{
Image_Data *idata;
Image_Entry *ientry;
Reference *ref;
File_Entry *fentry;
unsigned int image_id = 0;
char buf[4096];
if (!opts)
opts = &empty_lo;
// search whether the image is already loaded by another client
ref = eina_hash_find(client->files.referencing, &client_file_id);
if (ref)
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, client_file_id))
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 (rid %d)",
client->id, client_image_id, rid);
cserve2_client_error_send(client, rid, CSERVE2_NOT_LOADED);
return;
}
ientry = (Image_Entry *) ref->entry;
idata = _image_data_find(ENTRYID(ientry));
if (!idata)
{
ERR("Can't load image %d for rid %d: data not found", ENTRYID(ientry), rid);
cserve2_client_error_send(client, rid, CSERVE2_INVALID_CACHE);
return;
}
fd = _file_data_find(idata->file_id);
if (!fd || fd->invalid)
{
ERR("Can't load image %d for rid %d%s", idata->file_id, rid,
fd->invalid ? ": 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);
cserve2_client_error_send(client, rid, CSERVE2_NOT_LOADED);
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);
cserve2_client_error_send(client, rid, CSERVE2_NOT_LOADED);
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);
}