efl/legacy/evas/src/lib/cache/evas_cache_image.c

1579 lines
34 KiB
C
Raw Normal View History

/*
* vim:ts=8:sw=3:sts=8:noexpandtab:cino=>5n-3f0^-2{2
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#ifdef HAVE_EVIL
# include <Evil.h>
#endif
#include "evas_common.h"
#include "evas_private.h"
shared cache server++ is it ok? 1. it can be --disabled in evas's configure, but i think it works WITHOUT disabling it (runtime) as it falls back to the old way of loading 2. it may cause build problems on some platforms - without it being enabled we won't find out, so enable. 3. it needs enabling runtime to make use of it so it should be safe for now until you enable it. what is it? it is a SHARED cache server - that means images loaded are loaded BY the cache server (not by the actual process using evas). images are shared via shared memory segments (shm_open + mmap). this means only 1 copy is in all ram at any time - no matter how many processes need it , and its only loaded once. also if another app has already loaded the same data - and its in the cache or active hash, then another process needing the same stuff will avoid the loads as it will just get instant replies from the cache of "image already there". as it runs in its own process it can also time-out images from the cache too. right now you enable it by doing 2 things 1. run evas_cserve (it has cmd-line options to configure cache etc. 2. export EVAS_CSERVE=1 (im the environment of apps that should use the cache server). it works (for me) without crashes or problems. except for the following: 1. preloading doesnt work so its disabled if cserve is enabled. thisis because the load threads interfere withthe unix comms socket causing problems. this need to really change and have the cserve know about/do preload and let the select() on the evas async events fd listen for the unsolicited reply "load done". but it's not broken - simple preloads are syncronous and forced if cserve is enabled (at build time). 2. if cserve is killed/crashes every app using it will have a bad day. baaad day. so dont do it. also cserve may be vulnerable to apps crashing on it - it may also exit with sigpipe. this needs fixing. 3. if the apps load using relative paths - this will break as it doesnt account for the CWD of the client currently. will be fixed. 4. no way to change cache config runtime (yet) 5. no way to get internal cache state (yet). 6. if cache server exist - it wont clean up the shmem file nodes in /dev/shm - it will clean on restart (remove the old junk). this needs fixing. if you fine other issues - let me know. things for the future: 1. now its a separate server.. the server could do async http etc. loads too 2. as a server it could monitor history of usage of files and images and auto-pre-load files it knows historically are loaded then whose data is immediately accessed. 3. the same infra could be used to share font loads (freetype and/or fontconfig data). 4. ultimately being able to share rendered font glyphs will help a lot too. 5. it could, on its own, monitor "free memory" and when free memory runs load, reduce cache size dynamically. (improving low memory situations). 6. it should get a gui to query cache state/contents and display visually. this would be awesome to have a list of thumbnails that show whats in the cache, how many referencesa they have, last active timestamps etc. blah blah. please let me know if the build is broken asap though as i will vanish offline for a bit in about 24hrs... SVN revision: 40478
2009-05-01 00:11:07 -07:00
#ifdef EVAS_CSERVE
// FIXME: cache server and threaded preload clash badly atm - disable
//#undef BUILD_ASYNC_PRELOAD
shared cache server++ is it ok? 1. it can be --disabled in evas's configure, but i think it works WITHOUT disabling it (runtime) as it falls back to the old way of loading 2. it may cause build problems on some platforms - without it being enabled we won't find out, so enable. 3. it needs enabling runtime to make use of it so it should be safe for now until you enable it. what is it? it is a SHARED cache server - that means images loaded are loaded BY the cache server (not by the actual process using evas). images are shared via shared memory segments (shm_open + mmap). this means only 1 copy is in all ram at any time - no matter how many processes need it , and its only loaded once. also if another app has already loaded the same data - and its in the cache or active hash, then another process needing the same stuff will avoid the loads as it will just get instant replies from the cache of "image already there". as it runs in its own process it can also time-out images from the cache too. right now you enable it by doing 2 things 1. run evas_cserve (it has cmd-line options to configure cache etc. 2. export EVAS_CSERVE=1 (im the environment of apps that should use the cache server). it works (for me) without crashes or problems. except for the following: 1. preloading doesnt work so its disabled if cserve is enabled. thisis because the load threads interfere withthe unix comms socket causing problems. this need to really change and have the cserve know about/do preload and let the select() on the evas async events fd listen for the unsolicited reply "load done". but it's not broken - simple preloads are syncronous and forced if cserve is enabled (at build time). 2. if cserve is killed/crashes every app using it will have a bad day. baaad day. so dont do it. also cserve may be vulnerable to apps crashing on it - it may also exit with sigpipe. this needs fixing. 3. if the apps load using relative paths - this will break as it doesnt account for the CWD of the client currently. will be fixed. 4. no way to change cache config runtime (yet) 5. no way to get internal cache state (yet). 6. if cache server exist - it wont clean up the shmem file nodes in /dev/shm - it will clean on restart (remove the old junk). this needs fixing. if you fine other issues - let me know. things for the future: 1. now its a separate server.. the server could do async http etc. loads too 2. as a server it could monitor history of usage of files and images and auto-pre-load files it knows historically are loaded then whose data is immediately accessed. 3. the same infra could be used to share font loads (freetype and/or fontconfig data). 4. ultimately being able to share rendered font glyphs will help a lot too. 5. it could, on its own, monitor "free memory" and when free memory runs load, reduce cache size dynamically. (improving low memory situations). 6. it should get a gui to query cache state/contents and display visually. this would be awesome to have a list of thumbnails that show whats in the cache, how many referencesa they have, last active timestamps etc. blah blah. please let me know if the build is broken asap though as i will vanish offline for a bit in about 24hrs... SVN revision: 40478
2009-05-01 00:11:07 -07:00
#endif
#ifdef BUILD_ASYNC_PRELOAD
typedef struct _Evas_Cache_Preload Evas_Cache_Preload;
struct _Evas_Cache_Preload
{
EINA_INLIST;
Image_Entry *ie;
};
static LK(engine_lock) = PTHREAD_MUTEX_INITIALIZER;
static LK(wakeup) = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond_wakeup = PTHREAD_COND_INITIALIZER;
static void _evas_cache_image_entry_preload_remove(Image_Entry *ie, const void *target);
#endif
#define FREESTRC(Var) \
if (Var) \
{ \
eina_stringshare_del(Var); \
Var = NULL; \
}
static void _evas_cache_image_entry_delete(Evas_Cache_Image *cache, Image_Entry *ie);
static void
_evas_cache_image_make_dirty(Evas_Cache_Image *cache,
Image_Entry *im)
{
im->flags.cached = 1;
im->flags.dirty = 1;
im->flags.activ = 0;
im->flags.lru_nodata = 0;
#ifdef EVAS_FRAME_QUEUING
LKL(cache->lock);
#endif
cache->dirty = eina_inlist_prepend(cache->dirty, EINA_INLIST_GET(im));
#ifdef EVAS_FRAME_QUEUING
LKU(cache->lock);
#endif
if (im->cache_key)
{
eina_stringshare_del(im->cache_key);
im->cache_key = NULL;
}
}
static void
_evas_cache_image_make_activ(Evas_Cache_Image *cache,
Image_Entry *im,
const char *key)
{
/* FIXME: Handle case when image is being processed anyway and don't do a double decode. */
im->cache_key = key;
if (key != NULL)
{
im->flags.cached = 1;
im->flags.activ = 1;
im->flags.lru_nodata = 0;
im->flags.dirty = 0;
#ifdef EVAS_FRAME_QUEUING
LKL(cache->lock);
#endif
eina_hash_direct_add(cache->activ, key, im);
#ifdef EVAS_FRAME_QUEUING
LKU(cache->lock);
#endif
}
else
{
_evas_cache_image_make_dirty(cache, im);
}
}
static void
_evas_cache_image_make_inactiv(Evas_Cache_Image *cache,
Image_Entry *im,
const char *key)
{
if (im->cache_key)
{
im->flags.activ = 0;
im->flags.dirty = 0;
im->flags.cached = 1;
#ifdef EVAS_FRAME_QUEUING
LKL(cache->lock);
#endif
eina_hash_direct_add(cache->inactiv, key, im);
cache->lru = eina_inlist_prepend(cache->lru, EINA_INLIST_GET(im));
cache->usage += cache->func.mem_size_get(im);
#ifdef EVAS_FRAME_QUEUING
LKU(cache->lock);
#endif
}
else
{
_evas_cache_image_entry_delete(cache, im);
}
}
static void
_evas_cache_image_remove_lru_nodata(Evas_Cache_Image *cache,
Image_Entry *im)
{
if (im->flags.lru_nodata)
{
im->flags.lru_nodata = 0;
#ifdef EVAS_FRAME_QUEUING
LKL(cache->lock);
#endif
cache->lru_nodata = eina_inlist_remove(cache->lru_nodata, EINA_INLIST_GET(im));
cache->usage -= cache->func.mem_size_get(im);
#ifdef EVAS_FRAME_QUEUING
LKU(cache->lock);
#endif
}
}
static void
_evas_cache_image_activ_lru_nodata(Evas_Cache_Image *cache,
Image_Entry *im)
{
im->flags.need_data = 0;
im->flags.lru_nodata = 1;
#ifdef EVAS_FRAME_QUEUING
LKL(cache->lock);
#endif
cache->lru_nodata = eina_inlist_prepend(cache->lru_nodata, EINA_INLIST_GET(im));
cache->usage += cache->func.mem_size_get(im);
#ifdef EVAS_FRAME_QUEUING
LKU(cache->lock);
#endif
}
static void
_evas_cache_image_remove_activ(Evas_Cache_Image *cache,
Image_Entry *ie)
{
if (ie->flags.cached)
{
if (ie->flags.activ)
{
#ifdef EVAS_FRAME_QUEUING
LKL(cache->lock);
#endif
eina_hash_del(cache->activ, ie->cache_key, ie);
#ifdef EVAS_FRAME_QUEUING
LKU(cache->lock);
#endif
_evas_cache_image_remove_lru_nodata(cache, ie);
}
else
{
#ifdef EVAS_FRAME_QUEUING
LKL(cache->lock);
#endif
if (ie->flags.dirty)
{
cache->dirty = eina_inlist_remove(cache->dirty, EINA_INLIST_GET(ie));
}
else
{
eina_hash_del(cache->inactiv, ie->cache_key, ie);
cache->lru = eina_inlist_remove(cache->lru, EINA_INLIST_GET(ie));
cache->usage -= cache->func.mem_size_get(ie);
}
#ifdef EVAS_FRAME_QUEUING
LKU(cache->lock);
#endif
}
ie->flags.cached = 0;
ie->flags.dirty = 0;
ie->flags.activ = 0;
}
}
static void
_evas_cache_image_entry_delete(Evas_Cache_Image *cache, Image_Entry *ie)
{
if (!ie)
return ;
if (cache->func.debug)
cache->func.debug("deleting", ie);
#ifdef BUILD_ASYNC_PRELOAD
if (ie->flags.delete_me == 1)
return ;
if (ie->preload)
{
ie->flags.delete_me = 1;
_evas_cache_image_entry_preload_remove(ie, NULL);
return ;
}
#endif
cache->func.destructor(ie);
_evas_cache_image_remove_activ(cache, ie);
if (ie->cache_key)
{
eina_stringshare_del(ie->cache_key);
ie->cache_key = NULL;
}
FREESTRC(ie->file);
FREESTRC(ie->key);
ie->cache = NULL;
cache->func.surface_delete(ie);
#ifdef BUILD_ASYNC_PRELOAD
LKD(ie->lock);
#endif
#ifdef EVAS_FRAME_QUEUING
LKD(ie->lock_references);
#endif
cache->func.dealloc(ie);
}
static Image_Entry *
_evas_cache_image_entry_new(Evas_Cache_Image *cache,
const char *hkey,
time_t timestamp,
const char *file,
const char *key,
RGBA_Image_Loadopts *lo,
int *error)
{
Image_Entry *ie;
const char *cache_key;
ie = cache->func.alloc();
if (!ie)
{
*error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
return NULL;
}
cache_key = hkey ? eina_stringshare_add(hkey) : NULL;
ie->flags.loaded = 0;
ie->flags.need_data = 1;
_evas_cache_image_make_activ(cache, ie, cache_key);
ie->space = EVAS_COLORSPACE_ARGB8888;
ie->w = -1;
ie->h = -1;
ie->allocated.w = 0;
ie->allocated.h = 0;
#ifdef EVAS_FRAME_QUEUING
LKI(ie->lock_references);
#endif
ie->references = 0;
ie->cache = cache;
ie->file = file ? eina_stringshare_add(file) : NULL;
ie->key = key ? eina_stringshare_add(key) : NULL;
ie->timestamp = timestamp;
ie->laststat = time(NULL);
ie->load_opts.scale_down_by = 0;
ie->load_opts.dpi = 0;
ie->load_opts.w = 0;
ie->load_opts.h = 0;
ie->load_opts.region.x = 0;
ie->load_opts.region.y = 0;
ie->load_opts.region.w = 0;
ie->load_opts.region.h = 0;
ie->scale = 1;
#ifdef BUILD_ASYNC_PRELOAD
LKI(ie->lock);
ie->targets = NULL;
ie->preload = NULL;
ie->flags.delete_me = 0;
#endif
if (lo)
ie->load_opts = *lo;
if (file)
{
*error = cache->func.constructor(ie);
if (*error != EVAS_LOAD_ERROR_NONE)
{
_evas_cache_image_entry_delete(cache, ie);
return NULL;
}
}
if (cache->func.debug)
cache->func.debug("build", ie);
return ie;
}
static void
_evas_cache_image_entry_surface_alloc__locked(Evas_Cache_Image *cache,
Image_Entry *ie,
int wmin,
int hmin)
{
if (ie->allocated.w == wmin && ie->allocated.h == hmin)
return ;
if (cache->func.surface_alloc(ie, wmin, hmin))
{
wmin = 0;
hmin = 0;
}
ie->w = wmin;
ie->h = hmin;
ie->allocated.w = wmin;
ie->allocated.h = hmin;
}
static void
_evas_cache_image_entry_surface_alloc(Evas_Cache_Image *cache,
Image_Entry *ie,
int w,
int h)
{
int wmin;
int hmin;
wmin = w > 0 ? w : 1;
hmin = h > 0 ? h : 1;
#ifdef BUILD_ASYNC_PRELOAD
LKL(engine_lock);
#endif
_evas_cache_image_entry_surface_alloc__locked(cache, ie, wmin, hmin);
#ifdef BUILD_ASYNC_PRELOAD
LKU(engine_lock);
#endif
}
#ifdef BUILD_ASYNC_PRELOAD
static void
_evas_cache_image_async_heavy(void *data)
{
Evas_Cache_Image *cache;
Image_Entry *current;
int error;
int pchannel;
current = data;
LKL(current->lock);
pchannel = current->channel;
current->channel++;
cache = current->cache;
if (!current->flags.loaded && ((Evas_Image_Load_Func*) current->info.module)->threadable)
{
error = cache->func.load(current);
if (cache->func.debug)
cache->func.debug("load", current);
if (error != EVAS_LOAD_ERROR_NONE)
{
current->flags.loaded = 0;
_evas_cache_image_entry_surface_alloc(cache, current,
current->w, current->h);
}
else
current->flags.loaded = 1;
}
current->channel = pchannel;
LKU(current->lock);
}
static void
_evas_cache_image_async_end(void *data)
{
Image_Entry *ie = (Image_Entry *) data;
Evas_Cache_Target *tmp;
ie->cache->preload = eina_list_remove(ie->cache->preload, ie);
ie->cache->pending = eina_list_remove(ie->cache->pending, ie);
ie->preload = NULL;
ie->flags.preload_done = ie->flags.loaded;
while (ie->targets)
{
tmp = ie->targets;
evas_object_inform_call_image_preloaded((Evas_Object*) tmp->target);
ie->targets = (Evas_Cache_Target*) eina_inlist_remove(EINA_INLIST_GET(ie->targets), EINA_INLIST_GET(ie->targets));
free(tmp);
}
}
static void
_evas_cache_image_async_cancel(void *data)
{
Image_Entry *ie = (Image_Entry *) data;
ie->preload = NULL;
ie->cache->pending = eina_list_remove(ie->cache->pending, ie);
if (ie->flags.delete_me || ie->flags.dirty)
{
ie->flags.delete_me = 0;
_evas_cache_image_entry_delete(ie->cache, ie);
return ;
}
if (ie->flags.loaded)
{
_evas_cache_image_async_end(ie);
}
#ifdef EVAS_FRAME_QUEUING
LKL(ie->lock_references);
#endif
if (ie->references == 0)
{
_evas_cache_image_remove_activ(ie->cache, ie);
_evas_cache_image_make_inactiv(ie->cache, ie, ie->cache_key);
evas_cache_image_flush(ie->cache);
}
#ifdef EVAS_FRAME_QUEUING
LKU(ie->lock_references);
#endif
}
static int
_evas_cache_image_entry_preload_add(Image_Entry *ie,
const void *target)
{
Evas_Cache_Target *tg;
if (ie->flags.preload_done) return 0;
tg = malloc(sizeof (Evas_Cache_Target));
if (!tg) return 0;
tg->target = target;
ie->targets = (Evas_Cache_Target*) eina_inlist_append(EINA_INLIST_GET(ie->targets), EINA_INLIST_GET(tg));
if (!ie->preload)
{
ie->cache->preload = eina_list_append(ie->cache->preload, ie);
ie->flags.pending = 0;
ie->preload = evas_preload_thread_run(_evas_cache_image_async_heavy,
_evas_cache_image_async_end,
_evas_cache_image_async_cancel,
ie);
}
return 1;
}
static void
_evas_cache_image_entry_preload_remove(Image_Entry *ie, const void *target)
{
if (target)
{
Evas_Cache_Target *tg;
EINA_INLIST_FOREACH(ie->targets, tg)
{
if (tg->target == target)
{
// FIXME: No callback when we cancel only for one target ?
ie->targets = (Evas_Cache_Target*) eina_inlist_remove(EINA_INLIST_GET(ie->targets), EINA_INLIST_GET(tg));
free(tg);
break;
}
}
}
else
{
Evas_Cache_Target *tg;
while (ie->targets)
{
tg = ie->targets;
ie->targets = (Evas_Cache_Target*) eina_inlist_remove(EINA_INLIST_GET(ie->targets), EINA_INLIST_GET(tg));
free(tg);
}
}
if (ie->targets == NULL && ie->preload && !ie->flags.pending)
{
ie->cache->preload = eina_list_remove(ie->cache->preload, ie);
ie->cache->pending = eina_list_append(ie->cache->pending, ie);
ie->flags.pending = 1;
evas_preload_thread_cancel(ie->preload);
}
}
#endif
EAPI int
evas_cache_image_usage_get(Evas_Cache_Image *cache)
{
assert(cache != NULL);
return cache->usage;
}
EAPI int
evas_cache_image_get(Evas_Cache_Image *cache)
{
assert(cache != NULL);
return cache->limit;
}
EAPI void
evas_cache_image_set(Evas_Cache_Image *cache, int limit)
{
assert(cache != NULL);
#ifdef EVAS_FRAME_QUEUING
LKL(cache->lock);
#endif
if (cache->limit == limit)
{
#ifdef EVAS_FRAME_QUEUING
LKU(cache->lock);
#endif
return;
}
cache->limit = limit;
evas_cache_image_flush(cache);
#ifdef EVAS_FRAME_QUEUING
LKU(cache->lock);
#endif
}
EAPI Evas_Cache_Image *
evas_cache_image_init(const Evas_Cache_Image_Func *cb)
{
Evas_Cache_Image *new;
new = malloc(sizeof (Evas_Cache_Image));
if (!new)
return NULL;
new->func = *cb;
new->limit = 0;
new->usage = 0;
new->dirty = NULL;
new->lru = NULL;
new->lru_nodata = NULL;
new->inactiv = eina_hash_string_superfast_new(NULL);
new->activ = eina_hash_string_superfast_new(NULL);
new->references = 1;
new->preload = NULL;
new->pending = NULL;
#ifdef EVAS_FRAME_QUEUING
LKI(new->lock);
#endif
return new;
}
static Eina_Bool
_evas_cache_image_free_cb(__UNUSED__ const Eina_Hash *hash, __UNUSED__ const void *key, void *data, void *fdata)
{
Eina_List **delete_list = fdata;
*delete_list = eina_list_prepend(*delete_list, data);
return EINA_TRUE;
}
EAPI void
evas_cache_image_shutdown(Evas_Cache_Image *cache)
{
Eina_List *delete_list;
Image_Entry *im;
assert(cache != NULL);
#ifdef EVAS_FRAME_QUEUING
LKL(cache->lock);
#endif
cache->references--;
if (cache->references > 0)
{
#ifdef EVAS_FRAME_QUEUING
LKU(cache->lock);
#endif
return ;
}
#ifdef BUILD_ASYNC_PRELOAD
EINA_LIST_FREE(cache->preload, im)
{
/* By doing that we are protecting us from destroying image when the cache is no longuer available. */
im->flags.delete_me = 1;
_evas_cache_image_entry_preload_remove(im, NULL);
}
evas_async_events_process();
#endif
while (cache->lru)
{
im = (Image_Entry *) cache->lru;
_evas_cache_image_entry_delete(cache, im);
}
while (cache->lru_nodata)
{
im = (Image_Entry *) cache->lru_nodata;
_evas_cache_image_entry_delete(cache, im);
}
/* This is mad, I am about to destroy image still alive, but we need to prevent leak. */
while (cache->dirty)
{
im = (Image_Entry *) cache->dirty;
_evas_cache_image_entry_delete(cache, im);
}
delete_list = NULL;
eina_hash_foreach(cache->activ, _evas_cache_image_free_cb, &delete_list);
while (delete_list)
{
_evas_cache_image_entry_delete(cache, eina_list_data_get(delete_list));
delete_list = eina_list_remove_list(delete_list, delete_list);
}
#ifdef BUILD_ASYNC_PRELOAD
/* Now wait for all pending image to die */
while (cache->pending)
{
evas_async_events_process();
LKL(wakeup);
if (cache->pending)
pthread_cond_wait(&cond_wakeup, &wakeup);
LKU(wakeup);
}
#endif
eina_hash_free(cache->activ);
eina_hash_free(cache->inactiv);
#ifdef EVAS_FRAME_QUEUING
LKU(cache->lock);
LKD(cache->lock);
#endif
free(cache);
}
#define STAT_GAP 2
EAPI Image_Entry *
evas_cache_image_request(Evas_Cache_Image *cache, const char *file, const char *key, RGBA_Image_Loadopts *lo, int *error)
{
const char *ckey = "(null)";
char *hkey;
Image_Entry *im;
Evas_Image_Load_Opts prevent = { 0, 0, 0, 0, { 0, 0, 0, 0 } };
size_t size;
int stat_done = 0;
size_t file_length;
size_t key_length;
struct stat st;
assert(cache != NULL);
if ((!file) || ((!file) && (!key)))
{
*error = EVAS_LOAD_ERROR_GENERIC;
return NULL;
}
file_length = strlen(file);
key_length = key ? strlen(key) : 6;
size = file_length + key_length + 128;
hkey = alloca(sizeof (char) * size);
memcpy(hkey, file, file_length);
size = file_length;
memcpy(hkey + size, "//://", 5);
size += 5;
if (key) ckey = key;
memcpy(hkey + size, ckey, key_length);
size += key_length;
if ((!lo) ||
(lo &&
(lo->scale_down_by == 0) &&
(lo->dpi == 0.0) &&
((lo->w == 0) || (lo->h == 0)) &&
((lo->region.w == 0) || (lo->region.w == 0))
))
{
lo = &prevent;
// if (key)
// format = "%s//://%s";
// else
// format = "%s//://%p";
}
else
{
memcpy(hkey + size, "//@/", 4);
size += 4;
size += eina_convert_xtoa(lo->scale_down_by, hkey + size);
hkey[size] = '/';
size += 1;
size += eina_convert_dtoa(lo->dpi, hkey + size);
hkey[size] = '/';
size += 1;
size += eina_convert_xtoa(lo->w, hkey + size);
hkey[size] = 'x';
size += 1;
size += eina_convert_xtoa(lo->h, hkey + size);
hkey[size] = '/';
size += 1;
size += eina_convert_xtoa(lo->region.x, hkey + size);
hkey[size] = '+';
size += 1;
size += eina_convert_xtoa(lo->region.y, hkey + size);
hkey[size] = '.';
size += 1;
size += eina_convert_xtoa(lo->region.w, hkey + size);
hkey[size] = 'x';
size += 1;
size += eina_convert_xtoa(lo->region.h, hkey + size);
}
hkey[size] = '\0';
#ifdef EVAS_FRAME_QUEUING
LKL(cache->lock);
#endif
im = eina_hash_find(cache->activ, hkey);
#ifdef EVAS_FRAME_QUEUING
LKU(cache->lock);
#endif
if (im)
{
time_t t;
int ok;
ok = 1;
t = time(NULL);
if ((t - im->laststat) > STAT_GAP)
{
stat_done = 1;
if (stat(file, &st) < 0) goto on_stat_error;
im->laststat = t;
if (st.st_mtime != im->timestamp) ok = 0;
}
if (ok) goto on_ok;
_evas_cache_image_remove_activ(cache, im);
_evas_cache_image_make_dirty(cache, im);
}
#ifdef EVAS_FRAME_QUEUING
LKL(cache->lock);
#endif
im = eina_hash_find(cache->inactiv, hkey);
#ifdef EVAS_FRAME_QUEUING
LKU(cache->lock);
#endif
if (im)
{
int ok;
ok = 1;
if (!stat_done)
{
time_t t;
t = time(NULL);
if ((t - im->laststat) > STAT_GAP)
{
stat_done = 1;
if (stat(file, &st) < 0) goto on_stat_error;
im->laststat = t;
if (st.st_mtime != im->timestamp) ok = 0;
}
}
else
if (st.st_mtime != im->timestamp) ok = 0;
if (ok)
{
_evas_cache_image_remove_activ(cache, im);
_evas_cache_image_make_activ(cache, im, im->cache_key);
goto on_ok;
}
_evas_cache_image_entry_delete(cache, im);
}
if (!stat_done)
{
if (stat(file, &st) < 0) goto on_stat_error;
}
im = _evas_cache_image_entry_new(cache, hkey, st.st_mtime, file, key, lo, error);
if (!im) return NULL;
if (cache->func.debug)
cache->func.debug("request", im);
on_ok:
*error = EVAS_LOAD_ERROR_NONE;
#ifdef EVAS_FRAME_QUEUING
LKL(im->lock_references);
#endif
im->references++;
if (im->references > 1 && im->flags.lru_nodata)
_evas_cache_image_remove_lru_nodata(cache, im);
#ifdef EVAS_FRAME_QUEUING
LKU(im->lock_references);
#endif
return im;
on_stat_error:
#ifndef _WIN32
if ((errno == ENOENT) || (errno == ENOTDIR) ||
(errno == ENAMETOOLONG) || (errno == ELOOP))
#else
if (errno == ENOENT)
#endif
*error = EVAS_LOAD_ERROR_DOES_NOT_EXIST;
#ifndef _WIN32
else if ((errno == ENOMEM) || (errno == EOVERFLOW))
#else
else if (errno == ENOMEM)
#endif
*error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
else if (errno == EACCES)
*error = EVAS_LOAD_ERROR_PERMISSION_DENIED;
else
*error = EVAS_LOAD_ERROR_GENERIC;
if (im) _evas_cache_image_entry_delete(cache, im);
return NULL;
}
EAPI void
evas_cache_image_drop(Image_Entry *im)
{
Evas_Cache_Image *cache;
int references;
assert(im);
assert(im->cache);
#ifdef EVAS_FRAME_QUEUING
LKL(im->lock_references);
#endif
im->references--;
references = im->references;
#ifdef EVAS_FRAME_QUEUING
LKU(im->lock_references);
#endif
cache = im->cache;
if (references == 0)
{
#ifdef EVAS_FRAME_QUEUING
LKL(((RGBA_Image *)im)->ref_fq_add);
LKL(((RGBA_Image *)im)->ref_fq_del);
if (((RGBA_Image *)im)->ref_fq[0] != ((RGBA_Image *)im)->ref_fq[1])
{
LKU(((RGBA_Image *)im)->ref_fq_add);
LKU(((RGBA_Image *)im)->ref_fq_del);
return;
}
LKU(((RGBA_Image *)im)->ref_fq_add);
LKU(((RGBA_Image *)im)->ref_fq_del);
#endif
#ifdef BUILD_ASYNC_PRELOAD
if (im->preload)
{
_evas_cache_image_entry_preload_remove(im, NULL);
return ;
}
#endif
if (im->flags.dirty)
{
_evas_cache_image_entry_delete(cache, im);
return;
}
_evas_cache_image_remove_activ(cache, im);
_evas_cache_image_make_inactiv(cache, im, im->cache_key);
evas_cache_image_flush(cache);
}
}
EAPI void
evas_cache_image_data_not_needed(Image_Entry *im)
{
Evas_Cache_Image *cache;
int references;
assert(im);
assert(im->cache);
cache = im->cache;
#ifdef EVAS_FRAME_QUEUING
LKL(im->lock_references);
#endif
references = im->references;
#ifdef EVAS_FRAME_QUEUING
LKU(im->lock_references);
#endif
if (references > 1) return ;
if (im->flags.dirty || !im->flags.need_data) return ;
_evas_cache_image_activ_lru_nodata(cache, im);
}
EAPI Image_Entry *
evas_cache_image_dirty(Image_Entry *im, int x, int y, int w, int h)
{
Image_Entry *im_dirty = im;
Evas_Cache_Image *cache;
int references;
assert(im);
assert(im->cache);
cache = im->cache;
if (!(im->flags.dirty))
{
#ifdef EVAS_FRAME_QUEUING
LKL(im->lock_references);
#endif
references = im->references;
#ifdef EVAS_FRAME_QUEUING
LKU(im->lock_references);
#endif
shared cache server++ is it ok? 1. it can be --disabled in evas's configure, but i think it works WITHOUT disabling it (runtime) as it falls back to the old way of loading 2. it may cause build problems on some platforms - without it being enabled we won't find out, so enable. 3. it needs enabling runtime to make use of it so it should be safe for now until you enable it. what is it? it is a SHARED cache server - that means images loaded are loaded BY the cache server (not by the actual process using evas). images are shared via shared memory segments (shm_open + mmap). this means only 1 copy is in all ram at any time - no matter how many processes need it , and its only loaded once. also if another app has already loaded the same data - and its in the cache or active hash, then another process needing the same stuff will avoid the loads as it will just get instant replies from the cache of "image already there". as it runs in its own process it can also time-out images from the cache too. right now you enable it by doing 2 things 1. run evas_cserve (it has cmd-line options to configure cache etc. 2. export EVAS_CSERVE=1 (im the environment of apps that should use the cache server). it works (for me) without crashes or problems. except for the following: 1. preloading doesnt work so its disabled if cserve is enabled. thisis because the load threads interfere withthe unix comms socket causing problems. this need to really change and have the cserve know about/do preload and let the select() on the evas async events fd listen for the unsolicited reply "load done". but it's not broken - simple preloads are syncronous and forced if cserve is enabled (at build time). 2. if cserve is killed/crashes every app using it will have a bad day. baaad day. so dont do it. also cserve may be vulnerable to apps crashing on it - it may also exit with sigpipe. this needs fixing. 3. if the apps load using relative paths - this will break as it doesnt account for the CWD of the client currently. will be fixed. 4. no way to change cache config runtime (yet) 5. no way to get internal cache state (yet). 6. if cache server exist - it wont clean up the shmem file nodes in /dev/shm - it will clean on restart (remove the old junk). this needs fixing. if you fine other issues - let me know. things for the future: 1. now its a separate server.. the server could do async http etc. loads too 2. as a server it could monitor history of usage of files and images and auto-pre-load files it knows historically are loaded then whose data is immediately accessed. 3. the same infra could be used to share font loads (freetype and/or fontconfig data). 4. ultimately being able to share rendered font glyphs will help a lot too. 5. it could, on its own, monitor "free memory" and when free memory runs load, reduce cache size dynamically. (improving low memory situations). 6. it should get a gui to query cache state/contents and display visually. this would be awesome to have a list of thumbnails that show whats in the cache, how many referencesa they have, last active timestamps etc. blah blah. please let me know if the build is broken asap though as i will vanish offline for a bit in about 24hrs... SVN revision: 40478
2009-05-01 00:11:07 -07:00
#ifndef EVAS_CSERVE
// if ref 1 also copy if using shared cache as its read-only
if (references == 1) im_dirty = im;
else
shared cache server++ is it ok? 1. it can be --disabled in evas's configure, but i think it works WITHOUT disabling it (runtime) as it falls back to the old way of loading 2. it may cause build problems on some platforms - without it being enabled we won't find out, so enable. 3. it needs enabling runtime to make use of it so it should be safe for now until you enable it. what is it? it is a SHARED cache server - that means images loaded are loaded BY the cache server (not by the actual process using evas). images are shared via shared memory segments (shm_open + mmap). this means only 1 copy is in all ram at any time - no matter how many processes need it , and its only loaded once. also if another app has already loaded the same data - and its in the cache or active hash, then another process needing the same stuff will avoid the loads as it will just get instant replies from the cache of "image already there". as it runs in its own process it can also time-out images from the cache too. right now you enable it by doing 2 things 1. run evas_cserve (it has cmd-line options to configure cache etc. 2. export EVAS_CSERVE=1 (im the environment of apps that should use the cache server). it works (for me) without crashes or problems. except for the following: 1. preloading doesnt work so its disabled if cserve is enabled. thisis because the load threads interfere withthe unix comms socket causing problems. this need to really change and have the cserve know about/do preload and let the select() on the evas async events fd listen for the unsolicited reply "load done". but it's not broken - simple preloads are syncronous and forced if cserve is enabled (at build time). 2. if cserve is killed/crashes every app using it will have a bad day. baaad day. so dont do it. also cserve may be vulnerable to apps crashing on it - it may also exit with sigpipe. this needs fixing. 3. if the apps load using relative paths - this will break as it doesnt account for the CWD of the client currently. will be fixed. 4. no way to change cache config runtime (yet) 5. no way to get internal cache state (yet). 6. if cache server exist - it wont clean up the shmem file nodes in /dev/shm - it will clean on restart (remove the old junk). this needs fixing. if you fine other issues - let me know. things for the future: 1. now its a separate server.. the server could do async http etc. loads too 2. as a server it could monitor history of usage of files and images and auto-pre-load files it knows historically are loaded then whose data is immediately accessed. 3. the same infra could be used to share font loads (freetype and/or fontconfig data). 4. ultimately being able to share rendered font glyphs will help a lot too. 5. it could, on its own, monitor "free memory" and when free memory runs load, reduce cache size dynamically. (improving low memory situations). 6. it should get a gui to query cache state/contents and display visually. this would be awesome to have a list of thumbnails that show whats in the cache, how many referencesa they have, last active timestamps etc. blah blah. please let me know if the build is broken asap though as i will vanish offline for a bit in about 24hrs... SVN revision: 40478
2009-05-01 00:11:07 -07:00
#endif
{
int error;
im_dirty = evas_cache_image_copied_data
(cache, im->w, im->h,
evas_cache_image_pixels(im),
im->flags.alpha,
im->space);
if (!im_dirty) goto on_error;
if (cache->func.debug)
cache->func.debug("dirty-src", im);
error = cache->func.dirty(im_dirty, im);
if (cache->func.debug)
cache->func.debug("dirty-out", im_dirty);
/*
im_dirty = _evas_cache_image_entry_new(cache, NULL, im->timestamp, im->file, im->key, &im->load_opts, &error);
if (!im_dirty) goto on_error;
if (cache->func.debug)
cache->func.debug("dirty-src", im);
error = cache->func.dirty(im_dirty, im);
if (cache->func.debug)
cache->func.debug("dirty-out", im_dirty);
if (error != 0) goto on_error;
*/
#ifdef EVAS_FRAME_QUEUING
LKL(im_dirty->lock_references);
#endif
im_dirty->references = 1;
#ifdef EVAS_FRAME_QUEUING
LKU(im_dirty->lock_references);
#endif
evas_cache_image_drop(im);
}
_evas_cache_image_remove_activ(cache, im_dirty);
_evas_cache_image_make_dirty(cache, im_dirty);
}
if (cache->func.debug)
cache->func.debug("dirty-region", im_dirty);
if (cache->func.dirty_region)
cache->func.dirty_region(im_dirty, x, y, w, h);
return im_dirty;
on_error:
if (im_dirty) _evas_cache_image_entry_delete(cache, im_dirty);
evas_cache_image_drop(im);
return NULL;
}
EAPI Image_Entry *
evas_cache_image_alone(Image_Entry *im)
{
Evas_Cache_Image *cache;
Image_Entry *im_dirty = im;
int references;
assert(im);
assert(im->cache);
cache = im->cache;
#ifdef EVAS_FRAME_QUEUING
LKL(im->lock_references);
#endif
references = im->references;
#ifdef EVAS_FRAME_QUEUING
LKU(im->lock_references);
#endif
if (references == 1)
{
if (!(im->flags.dirty))
{
_evas_cache_image_remove_activ(cache, im);
_evas_cache_image_make_dirty(cache, im);
}
}
else
{
int error;
im_dirty = evas_cache_image_copied_data
(cache, im->w, im->h,
evas_cache_image_pixels(im),
im->flags.alpha,
im->space);
if (!im_dirty) goto on_error;
if (cache->func.debug)
cache->func.debug("dirty-src", im);
error = cache->func.dirty(im_dirty, im);
if (cache->func.debug)
cache->func.debug("dirty-out", im_dirty);
/*
im_dirty = _evas_cache_image_entry_new(cache, NULL, im->timestamp, im->file, im->key, &im->load_opts, &error);
if (!im_dirty) goto on_error;
if (cache->func.debug)
cache->func.debug("dirty-src", im);
error = cache->func.dirty(im_dirty, im);
if (cache->func.debug)
cache->func.debug("dirty-out", im_dirty);
if (error != 0) goto on_error;
*/
#ifdef EVAS_FRAME_QUEUING
LKL(im_dirty->lock_references);
#endif
im_dirty->references = 1;
#ifdef EVAS_FRAME_QUEUING
LKU(im_dirty->lock_references);
#endif
evas_cache_image_drop(im);
}
return im_dirty;
on_error:
if (im_dirty) _evas_cache_image_entry_delete(cache, im_dirty);
evas_cache_image_drop(im);
return NULL;
}
EAPI Image_Entry *
evas_cache_image_copied_data(Evas_Cache_Image *cache, int w, int h, DATA32 *image_data, int alpha, int cspace)
{
Image_Entry *im;
assert(cache);
if ((cspace == EVAS_COLORSPACE_YCBCR422P601_PL) ||
(cspace == EVAS_COLORSPACE_YCBCR422P709_PL))
w &= ~0x1;
im = _evas_cache_image_entry_new(cache, NULL, 0, NULL, NULL, NULL, NULL);
if (!im) return NULL;
im->space = cspace;
im->flags.alpha = alpha;
_evas_cache_image_entry_surface_alloc(cache, im, w, h);
if (cache->func.copied_data(im, w, h, image_data, alpha, cspace) != 0)
{
_evas_cache_image_entry_delete(cache, im);
return NULL;
}
#ifdef EVAS_FRAME_QUEUING
LKL(im->lock_references);
#endif
im->references = 1;
#ifdef EVAS_FRAME_QUEUING
LKU(im->lock_references);
#endif
if (cache->func.debug)
cache->func.debug("copied-data", im);
return im;
}
EAPI Image_Entry *
evas_cache_image_data(Evas_Cache_Image *cache, int w, int h, DATA32 *image_data, int alpha, int cspace)
{
Image_Entry *im;
assert(cache);
if ((cspace == EVAS_COLORSPACE_YCBCR422P601_PL) ||
(cspace == EVAS_COLORSPACE_YCBCR422P709_PL))
w &= ~0x1;
im = _evas_cache_image_entry_new(cache, NULL, 0, NULL, NULL, NULL, NULL);
im->w = w;
im->h = h;
im->flags.alpha = alpha;
if (cache->func.data(im, w, h, image_data, alpha, cspace) != 0)
{
_evas_cache_image_entry_delete(cache, im);
return NULL;
}
#ifdef EVAS_FRAME_QUEUING
LKL(im->lock_references);
#endif
im->references = 1;
#ifdef EVAS_FRAME_QUEUING
LKU(im->lock_references);
#endif
if (cache->func.debug)
cache->func.debug("data", im);
return im;
}
EAPI void
evas_cache_image_surface_alloc(Image_Entry *im, int w, int h)
{
Evas_Cache_Image *cache;
assert(im);
assert(im->cache);
cache = im->cache;
if ((im->space == EVAS_COLORSPACE_YCBCR422P601_PL) ||
(im->space == EVAS_COLORSPACE_YCBCR422P709_PL))
w &= ~0x1;
_evas_cache_image_entry_surface_alloc(cache, im, w, h);
if (cache->func.debug)
cache->func.debug("surface-alloc", im);
}
EAPI Image_Entry *
evas_cache_image_size_set(Image_Entry *im, int w, int h)
{
Evas_Cache_Image *cache;
Image_Entry *new;
int error;
assert(im);
assert(im->cache);
#ifdef EVAS_FRAME_QUEUING
LKL(im->lock_references);
#endif
assert(im->references > 0);
#ifdef EVAS_FRAME_QUEUING
LKU(im->lock_references);
#endif
if ((im->space == EVAS_COLORSPACE_YCBCR422P601_PL) ||
(im->space == EVAS_COLORSPACE_YCBCR422P709_PL))
w &= ~0x1;
if ((im->w == w) && (im->h == h))
return im;
cache = im->cache;
new = _evas_cache_image_entry_new(cache, NULL, 0, NULL, NULL, NULL, &error);
if (!new) goto on_error;
new->flags.alpha = im->flags.alpha;
new->space = im->space;
new->load_opts = im->load_opts;
_evas_cache_image_entry_surface_alloc(cache, new, w, h);
error = cache->func.size_set(new, im, w, h);
if (error != 0) goto on_error;
#ifdef EVAS_FRAME_QUEUING
LKL(new->lock_references);
#endif
new->references = 1;
#ifdef EVAS_FRAME_QUEUING
LKU(new->lock_references);
#endif
evas_cache_image_drop(im);
if (cache->func.debug)
cache->func.debug("size_set", new);
return new;
on_error:
if (new) _evas_cache_image_entry_delete(cache, new);
evas_cache_image_drop(im);
return NULL;
}
EAPI void
evas_cache_image_load_data(Image_Entry *im)
{
#ifdef BUILD_ASYNC_PRELOAD
Eina_Bool preload = EINA_FALSE;
#endif
Evas_Cache_Image *cache;
int error;
assert(im);
assert(im->cache);
cache = im->cache;
if (im->flags.loaded)
{
return;
}
#ifdef BUILD_ASYNC_PRELOAD
if (im->preload)
{
preload = EINA_TRUE;
if (!im->flags.pending)
{
im->cache->preload = eina_list_remove(im->cache->preload, im);
im->cache->pending = eina_list_append(im->cache->pending, im);
im->flags.pending = 1;
evas_preload_thread_cancel(im->preload);
}
evas_async_events_process();
LKL(wakeup);
while (im->preload)
{
pthread_cond_wait(&cond_wakeup, &wakeup);
LKU(wakeup);
evas_async_events_process();
LKL(wakeup);
}
LKU(wakeup);
}
if (im->flags.loaded) return ;
LKL(im->lock);
#endif
error = cache->func.load(im);
#ifdef BUILD_ASYNC_PRELOAD
LKU(im->lock);
#endif
im->flags.loaded = 1;
if (cache->func.debug)
cache->func.debug("load", im);
if (error != EVAS_LOAD_ERROR_NONE)
{
_evas_cache_image_entry_surface_alloc(cache, im, im->w, im->h);
im->flags.loaded = 0;
}
#ifdef BUILD_ASYNC_PRELOAD
if (preload)
_evas_cache_image_async_end(im);
#endif
}
EAPI void
evas_cache_image_unload_data(Image_Entry *im)
{
Evas_Cache_Image *cache;
assert(im);
assert(im->cache);
cache = im->cache;
#ifdef BUILD_ASYNC_PRELOAD
LKL(im->lock);
#endif
if ((!im->flags.loaded) || (!im->file) ||
(!im->info.module) || (im->flags.dirty))
{
#ifdef BUILD_ASYNC_PRELOAD
LKU(im->lock);
#endif
return;
}
evas_common_rgba_image_scalecache_dirty(im);
cache->func.destructor(im);
#ifdef BUILD_ASYNC_PRELOAD
LKU(im->lock);
#endif
}
static Eina_Bool
_evas_cache_image_unload_cb(__UNUSED__ const Eina_Hash *hash, __UNUSED__ const void *key, void *data, __UNUSED__ void *fdata)
{
evas_cache_image_unload_data(data);
return EINA_TRUE;
}
EAPI void
evas_cache_image_unload_all(Evas_Cache_Image *cache)
{
Image_Entry *im;
EINA_INLIST_FOREACH(cache->lru, im) evas_cache_image_unload_data(im);
EINA_INLIST_FOREACH(cache->lru_nodata, im) evas_cache_image_unload_data(im);
eina_hash_foreach(cache->activ, _evas_cache_image_unload_cb, NULL);
eina_hash_foreach(cache->inactiv, _evas_cache_image_unload_cb, NULL);
}
EAPI Eina_Bool
evas_cache_image_is_loaded(Image_Entry *im)
{
assert(im);
if (im->flags.loaded) return EINA_TRUE;
return EINA_FALSE;
}
EAPI void
evas_cache_image_preload_data(Image_Entry *im, const void *target)
{
#ifdef BUILD_ASYNC_PRELOAD
Evas_Cache_Image *cache;
assert(im);
assert(im->cache);
if (im->flags.loaded)
{
evas_object_inform_call_image_preloaded((Evas_Object *)target);
return;
}
cache = im->cache;
if (!_evas_cache_image_entry_preload_add(im, target))
evas_object_inform_call_image_preloaded((Evas_Object *)target);
#else
evas_cache_image_load_data(im);
evas_object_inform_call_image_preloaded((Evas_Object *)target);
#endif
}
EAPI void
evas_cache_image_preload_cancel(Image_Entry *im, const void *target)
{
#ifdef BUILD_ASYNC_PRELOAD
Evas_Cache_Image *cache;
assert(im);
assert(im->cache);
cache = im->cache;
if (target == NULL) return ;
_evas_cache_image_entry_preload_remove(im, target);
#else
(void)im;
#endif
}
EAPI int
evas_cache_image_flush(Evas_Cache_Image *cache)
{
assert(cache);
if (cache->limit == -1) return -1;
while ((cache->lru) && (cache->limit < cache->usage))
{
Image_Entry *im;
im = (Image_Entry *)cache->lru->last;
_evas_cache_image_entry_delete(cache, im);
}
while ((cache->lru_nodata) && (cache->limit < cache->usage))
{
Image_Entry *im;
im = (Image_Entry *) cache->lru_nodata->last;
_evas_cache_image_remove_lru_nodata(cache, im);
cache->func.surface_delete(im);
im->flags.loaded = 0;
}
return cache->usage;
}
EAPI Image_Entry *
evas_cache_image_empty(Evas_Cache_Image *cache)
{
Image_Entry *im;
im = _evas_cache_image_entry_new(cache, NULL, 0, NULL, NULL, NULL, NULL);
if (!im) return NULL;
#ifdef EVAS_FRAME_QUEUING
LKL(im->lock_references);
#endif
im->references = 1;
#ifdef EVAS_FRAME_QUEUING
LKU(im->lock_references);
#endif
return im;
}
EAPI void
evas_cache_image_colorspace(Image_Entry *im, int cspace)
{
Evas_Cache_Image *cache;
assert(im);
assert(im->cache);
cache = im->cache;
if (im->space == cspace) return;
im->space = cspace;
cache->func.color_space(im, cspace);
}
EAPI void *
evas_cache_private_from_image_entry_get(Image_Entry *im)
{
Evas_Cache_Image *cache;
assert(im);
assert(im->cache);
cache = im->cache;
return (void*) cache->data;
}
EAPI void *
evas_cache_private_get(Evas_Cache_Image *cache)
{
assert(cache);
return cache->data;
}
EAPI void
evas_cache_private_set(Evas_Cache_Image *cache, const void *data)
{
assert(cache);
cache->data = (void *)data;
}
EAPI DATA32 *
evas_cache_image_pixels(Image_Entry *im)
{
Evas_Cache_Image *cache;
assert(im);
assert(im->cache);
cache = im->cache;
return cache->func.surface_pixels(im);
}
EAPI void
evas_cache_image_wakeup(void)
{
#ifdef BUILD_ASYNC_PRELOAD
pthread_cond_broadcast(&cond_wakeup);
#endif
}