efl/src/lib/eina/eina_object.c

853 lines
20 KiB
C

/* EINA - EFL data type library
* Copyright (C) 2011 Cedric Bail
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library;
* if not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <string.h>
#include "eina_private.h"
#include "eina_inlist.h"
#include "eina_rbtree.h"
#include "eina_mempool.h"
#include "eina_trash.h"
#include "eina_log.h"
#include "eina_stringshare.h"
#include "eina_lock.h"
#include "eina_object.h"
/*============================================================================*
* Local *
*============================================================================*/
/**
* @cond LOCAL
*/
/* If we are on a 64bits computer user bigger generation and ID */
/* FIXME: make it GCC independent */
/* FIXME: maybe having 2^32 objects doesn't make sense and 2^24 are enough
so instead of increasing the object count, we could just add a magic
to first check if the pointer is valid at all (and maybe use a pointer
that will always trigger a segv if we try to use it directly).
*/
#ifdef _LP64
typedef unsigned long Eina_Object_ID;
typedef unsigned short Eina_Object_Generation;
# define EINA_GEN_OFFSET 48
# define EINA_ID_STR "%lu"
#elif _WIN64
typedef unsigned __int64 Eina_Object_ID;
typedef unsigned short Eina_Object_Generation;
# define EINA_GEN_OFFSET 48
# define EINA_ID_STR "%I64u"
#else
typedef unsigned int Eina_Object_ID;
typedef unsigned char Eina_Object_Generation;
# define EINA_GEN_OFFSET 24
# define EINA_ID_STR "%u"
#endif
typedef struct _Eina_Class_Range Eina_Class_Range;
typedef struct _Eina_Object_Item Eina_Object_Item;
typedef struct _Eina_Range Eina_Range;
typedef struct _Eina_Class_Top Eina_Class_Top;
struct _Eina_Range
{
EINA_INLIST;
unsigned int start;
unsigned int end;
};
struct _Eina_Object_Item
{
EINA_INLIST;
Eina_Class_Range *range;
Eina_Object_Item *parent;
Eina_Inlist *link;
unsigned int index;
};
struct _Eina_Class_Range
{
EINA_INLIST;
EINA_RBTREE;
unsigned int start;
unsigned int end;
unsigned int current;
unsigned int empty_count;
Eina_Class *type;
Eina_Trash *empty;
Eina_Object_Item **pointer_array;
Eina_Object_Generation generation_array[1];
};
struct _Eina_Class_Top
{
Eina_Class *top_parent;
Eina_Rbtree *range;
Eina_Inlist *available;
unsigned int upper_limit;
};
struct _Eina_Class
{
EINA_INLIST;
const char *name;
Eina_Class_Top *top;
Eina_Class *parent;
Eina_Inlist *childs;
Eina_Class_Callback constructor;
Eina_Class_Callback destructor;
void *data;
Eina_Inlist *allocated_range;
Eina_Mempool *mempool;
unsigned int class_size;
unsigned int object_size;
unsigned int pool_size;
Eina_Bool repack_needed : 1;
#ifdef EINA_HAVE_DEBUG_THREADS
pthread_t self;
#endif
Eina_Lock mutex;
EINA_MAGIC;
};
static const char EINA_MAGIC_CLASS_STR[] = "Eina Class";
static Eina_Mempool *_eina_class_mp = NULL;
static Eina_Mempool *_eina_top_mp = NULL;
static Eina_Mempool *_eina_range_mp = NULL;
static int _eina_object_log_dom = -1;
static unsigned int _eina_object_item_size = 0;
#ifdef ERR
#undef ERR
#endif
#define ERR(...) EINA_LOG_DOM_ERR(_eina_object_log_dom, __VA_ARGS__)
#ifdef DBG
#undef DBG
#endif
#define DBG(...) EINA_LOG_DOM_DBG(_eina_object_log_dom, __VA_ARGS__)
#define EINA_MAGIC_CHECK_CLASS(d, ...) \
do { \
if (!EINA_MAGIC_CHECK(d, EINA_MAGIC_CLASS)) \
{ \
EINA_MAGIC_FAIL(d, EINA_MAGIC_CLASS); \
return __VA_ARGS__; \
} \
} while(0)
static int
_eina_rbtree_cmp_range(const Eina_Rbtree *node, const void *key,
EINA_UNUSED int length, EINA_UNUSED void *data)
{
Eina_Class_Range *range;
Eina_Object_ID id;
range = EINA_RBTREE_CONTAINER_GET(node, Eina_Class_Range);
id = (Eina_Object_ID) key;
if (id < range->start) return -1;
else if (id >= range->end) return 1;
return 0;
}
static Eina_Rbtree_Direction
_eina_class_direction_range(const Eina_Rbtree *left,
const Eina_Rbtree *right,
EINA_UNUSED void *data)
{
Eina_Class_Range *rl;
Eina_Class_Range *rr;
rl = EINA_RBTREE_CONTAINER_GET(left, Eina_Class_Range);
rr = EINA_RBTREE_CONTAINER_GET(right, Eina_Class_Range);
if (rl->start < rr->start) return EINA_RBTREE_LEFT;
return EINA_RBTREE_RIGHT;
}
/* really destroying a range and handling that case is a complex
problem to solve. Not handling it right now, exposing myself to
DoS. */
static void
_eina_range_cleanup(Eina_Class_Range *range)
{
range->current = 0;
range->empty = NULL;
}
static void
_eina_object_constructor_call(Eina_Class *class, void *object)
{
if (class->parent) _eina_object_constructor_call(class->parent, object);
if (class->constructor) class->constructor(class, object, class->data);
}
static void
_eina_object_destructor_call(Eina_Class *class, void *object)
{
if (class->destructor) class->destructor(class, object, class->data);
if (class->parent) _eina_object_destructor_call(class->parent, object);
}
static Eina_Object*
_eina_object_get(Eina_Object_Item *item)
{
Eina_Object_Generation gen;
Eina_Object_ID id;
if (!item) return NULL;
gen = item->range->generation_array[item->index];
id = ((Eina_Object_ID) gen << EINA_GEN_OFFSET) + item->range->start + item->index;
return (Eina_Object *) id;
}
static Eina_Object_Item *
_eina_object_find_item(Eina_Class *class, Eina_Object *object)
{
Eina_Class_Range *matched;
Eina_Object_Item *item;
Eina_Class *search;
Eina_Rbtree *match;
Eina_Object_Generation generation;
Eina_Object_ID id;
Eina_Object_ID idx;
id = (Eina_Object_ID) object;
idx = id & (((Eina_Object_ID) 1 << EINA_GEN_OFFSET) - 1);
generation = id & !(((Eina_Object_ID) 1 << EINA_GEN_OFFSET) - 1);
/* Try to find the ID */
match = eina_rbtree_inline_lookup(class->top->range,
(void*) idx, sizeof (Eina_Object_ID),
_eina_rbtree_cmp_range, NULL);
/* ID not found, invalid pointer ! */
if (!match)
{
ERR("%p: ID ["EINA_ID_STR"] not found in class hiearchy of [%s].",
object, idx, class->name);
return NULL;
}
matched = EINA_RBTREE_CONTAINER_GET(match, Eina_Class_Range);
/* generation mismatch, invalid pointer ! */
if (generation != matched->generation_array[idx - matched->start])
{
ERR("%p: generation mismatch [%i] vs [%i].",
object, generation, matched->generation_array[idx - matched->start]);
return NULL;
}
/* does it belong to the right class ? */
for (search = matched->type; search && search != class; search = search->parent)
;
/* class match request or invalid pointer ! */
if (search != class)
{
ERR("%p: from class [%s] is not in the hierarchie of [%s].",
object, matched->type->name, class->name);
return NULL;
}
/* retrieve pointer */
item = matched->pointer_array[id - matched->start];
/* allocated or invalid pointer ? */
if (!item)
{
ERR("%p: ID is not allocated.", object);
return NULL;
}
/* be aware, that because we use eina_trash to store empty pointer after first use,
pointer could be != NULL and still be empty. Need another pool of data somewhere
to store the state of the allocation... Is it needed ?
*/
return item;
}
static void
_eina_object_item_del(Eina_Object_Item *item)
{
Eina_Class *class;
class = item->range->type;
_eina_object_destructor_call(class,
(unsigned char*) item + _eina_object_item_size);
eina_trash_push(&item->range->empty, item->range->pointer_array + item->index);
item->range->generation_array[item->index]++;
item->range->empty_count++;
if (item->range->empty_count == item->range->end - item->range->start)
_eina_range_cleanup(item->range);
item->range = NULL;
if (item->parent)
item->parent->link = eina_inlist_remove(item->parent->link, EINA_INLIST_GET(item));
item->parent = NULL;
while (item->link)
{
Eina_Object_Item *child;
child = EINA_INLIST_CONTAINER_GET(item->link, Eina_Object_Item);
_eina_object_item_del(child);
}
eina_mempool_free(class->mempool, item);
}
static Eina_Class_Range *
_eina_class_empty_range_get(Eina_Inlist *list)
{
while (list)
{
Eina_Class_Range *range;
range = EINA_INLIST_CONTAINER_GET(list, Eina_Class_Range);
if (range->empty_count > 0)
return range;
list = list->next;
}
return NULL;
}
static Eina_Class_Range *
_eina_class_range_add(Eina_Class *class)
{
Eina_Class_Range *range;
unsigned char *tmp;
Eina_Range *av = NULL;
range = malloc(sizeof (Eina_Class_Range)
+ sizeof (Eina_Object_Item*) * class->pool_size
+ sizeof (Eina_Object_Generation) * (class->pool_size - 1));
if (!range) return NULL;
tmp = (void*) (range + 1);
tmp += sizeof (Eina_Object_Generation) * (class->pool_size - 1);
range->pointer_array = (Eina_Object_Item**) tmp;
/* no need to fix generation to a specific value as random value should be just fine */
memset(range->pointer_array, 0, sizeof (Eina_Object_Item*) * class->pool_size);
range->current = 0;
range->type = class;
range->empty = NULL;
/* and now find an empty block */
EINA_INLIST_FOREACH(class->top->available, av)
if ((av->end - av->start) == class->pool_size)
break;
if (av)
{
range->start = av->start;
range->end = av->end;
class->top->available = eina_inlist_remove(class->top->available,
EINA_INLIST_GET(av));
eina_mempool_free(_eina_range_mp, av);
}
else
{
range->start = class->top->upper_limit;
range->end = range->start + class->pool_size;
class->top->upper_limit = range->end;
}
return range;
}
static void
_eina_class_range_del(Eina_Class_Range *range)
{
Eina_Class_Top *top;
Eina_Range *keep;
top = range->type->top;
keep = eina_mempool_malloc(_eina_range_mp, sizeof (Eina_Range));
if (!keep)
{
ERR("Not enougth memory to keep track of allocated range.");
goto delete_class_range;
}
keep->start = range->start;
keep->end = range->end;
top->available = eina_inlist_prepend(top->available,
EINA_INLIST_GET(keep));
delete_class_range:
top->range = eina_rbtree_inline_remove(top->range, EINA_RBTREE_GET(range),
_eina_class_direction_range, NULL);
range->type->allocated_range = eina_inlist_remove(range->type->allocated_range,
EINA_INLIST_GET(range));
free(range);
}
static void
_eina_class_range_repack(void *dst, void *src, void *data)
{
Eina_Object_Item *di = dst;
Eina_Object_Item *si = src;
Eina_Class *class = data;
(void) class;
si->range->pointer_array[si->index] = di;
/* FIXME: We could just lock the right Eina_Class_Range
here instead of locking all the Class */
}
/**
* @endcond
*/
Eina_Bool
eina_object_init(void)
{
_eina_object_log_dom = eina_log_domain_register("eina_object",
EINA_LOG_COLOR_DEFAULT);
if (_eina_object_log_dom < 0)
{
EINA_LOG_ERR("Could not register log domain: eina_list");
return EINA_FALSE;
}
_eina_class_mp = eina_mempool_add("chained_mempool", "class",
NULL, sizeof (Eina_Class), 16);
if (!_eina_class_mp)
{
ERR("ERROR: Mempool for Eina_Class cannot be allocated in object init.");
goto on_init_fail;
}
_eina_top_mp = eina_mempool_add("chained_mempool", "top",
NULL, sizeof (Eina_Class_Top), 16);
if (!_eina_top_mp)
{
ERR("ERROR: Mempool for Eina_Class_Top cannot be allocated in object init.");
goto on_init_fail;
}
_eina_range_mp = eina_mempool_add("chained_mempool", "range",
NULL, sizeof (Eina_Range), 16);
if (!_eina_range_mp)
{
ERR("ERROR: Mempool for Eina_Class_Top cannot be allocated in object init.");
goto on_init_fail;
}
#define EMS(n) eina_magic_string_static_set(n, n ## _STR)
EMS(EINA_MAGIC_CLASS);
#undef EMS
_eina_object_item_size = eina_mempool_alignof(sizeof (Eina_Object_Item));
return EINA_TRUE;
on_init_fail:
eina_log_domain_unregister(_eina_object_log_dom);
_eina_object_log_dom = -1;
if (_eina_top_mp)
{
eina_mempool_del(_eina_top_mp);
_eina_top_mp = NULL;
}
if (_eina_class_mp)
{
eina_mempool_del(_eina_class_mp);
_eina_class_mp = NULL;
}
return EINA_FALSE;
}
Eina_Bool
eina_object_shutdown(void)
{
eina_mempool_del(_eina_class_mp);
eina_mempool_del(_eina_top_mp);
eina_log_domain_unregister(_eina_object_log_dom);
_eina_object_log_dom = -1;
return EINA_TRUE;
}
Eina_Class *
eina_class_new(const char *name,
unsigned int class_size,
unsigned int pool_size,
Eina_Class_Callback constructor,
Eina_Class_Callback destructor,
Eina_Class *parent,
void *data)
{
Eina_Class *c;
unsigned int object_size = class_size;
if (parent) EINA_MAGIC_CHECK_CLASS(parent, NULL);
c = eina_mempool_malloc(_eina_class_mp, sizeof (Eina_Class));
if (!c) return NULL;
c->parent = parent;
if (parent)
{
parent->childs = eina_inlist_append(parent->childs,
EINA_INLIST_GET(c));
c->top = parent->top;
}
else
{
c->top = eina_mempool_malloc(_eina_top_mp, sizeof (Eina_Class_Top));
c->top->top_parent = c;
c->top->range = NULL;
c->top->available = NULL;
c->top->upper_limit = 0;
}
/* Build complete object size and find top parent */
if (parent) object_size += parent->object_size;
c->name = eina_stringshare_add(name);
c->class_size = class_size;
c->pool_size = pool_size;
c->object_size = object_size;
c->mempool = eina_mempool_add("chained_mempool", "range",
NULL,
_eina_object_item_size + object_size, pool_size);
c->constructor = constructor;
c->destructor = destructor;
c->data = data;
c->allocated_range = NULL;
c->childs = NULL;
#ifdef EINA_HAVE_DEBUG_THREADS
c->self = pthread_self();
#endif
eina_lock_new(&c->mutex);
EINA_MAGIC_SET(c, EINA_MAGIC_CLASS);
return c;
}
const char *
eina_class_name_get(Eina_Class *class)
{
EINA_MAGIC_CHECK_CLASS(class, NULL);
return class->name;
}
unsigned int
eina_class_size_get(Eina_Class *class)
{
EINA_MAGIC_CHECK_CLASS(class, 0);
return class->class_size;
}
unsigned int
eina_class_object_size_get(Eina_Class *class)
{
EINA_MAGIC_CHECK_CLASS(class, 0);
return class->object_size;
}
void
eina_class_del(Eina_Class *class)
{
EINA_MAGIC_CHECK_CLASS(class);
EINA_MAGIC_SET(class, 0);
while (class->allocated_range)
_eina_class_range_del(EINA_INLIST_CONTAINER_GET(class->allocated_range,
Eina_Class_Range));
while (class->childs)
{
Eina_Class *child;
child = EINA_INLIST_CONTAINER_GET(class->childs, Eina_Class);
eina_class_del(child);
}
if (class->parent)
{
class->parent->childs = eina_inlist_remove(class->parent->childs,
EINA_INLIST_GET(class));
}
else
{
while (class->top->available)
{
Eina_Range *range;
range = EINA_INLIST_CONTAINER_GET(class->top->available, Eina_Range);
class->top->available = eina_inlist_remove(class->top->available,
class->top->available);
eina_mempool_free(_eina_range_mp, range);
}
}
#ifdef EINA_HAVE_DEBUG_THREADS
assert(pthread_equal(class->self, pthread_self()));
#endif
eina_lock_free(&class->mutex);
eina_mempool_del(class->mempool);
eina_mempool_free(_eina_class_mp, class);
}
void
eina_class_repack(Eina_Class *class)
{
Eina_Class *child;
EINA_MAGIC_CHECK_CLASS(class);
if (!eina_lock_take(&class->mutex))
{
#ifdef EINA_HAVE_DEBUG_THREADS
assert(pthread_equal(class->self, pthread_self()));
#endif
}
eina_mempool_repack(class->mempool, _eina_class_range_repack, class);
eina_lock_release(&class->mutex);
EINA_INLIST_FOREACH(class->childs, child)
eina_class_repack(child);
}
Eina_Object *
eina_object_add(Eina_Class *class)
{
Eina_Class_Range *range;
Eina_Object_Item **object;
int localid;
EINA_MAGIC_CHECK_CLASS(class, NULL);
/* No need to lock the class as we don't access/modify the pointer inside
* this function. */
range = _eina_class_empty_range_get(class->allocated_range);
if (!range) range = _eina_class_range_add(class);
if (range->empty_count == 0)
{
ERR("The impossible happen, range is empty when it should not !");
return NULL;
}
range->empty_count--;
object = eina_trash_pop(&range->empty);
if (!object) object = range->pointer_array + range->current++;
localid = object - range->pointer_array;
*object = eina_mempool_malloc(class->mempool,
class->object_size + _eina_object_item_size);
(*object)->index = localid;
(*object)->link = NULL;
(*object)->range = range;
_eina_object_constructor_call(class,
((unsigned char*)(*object)) + _eina_object_item_size);
if (!(++range->generation_array[localid]))
++range->generation_array[localid];
return _eina_object_get(*object);
}
void *
eina_object_pointer_get(Eina_Class *class,
Eina_Object *object)
{
Eina_Object_Item *item;
unsigned char *mem = NULL;
if (!object) return NULL;
EINA_MAGIC_CHECK_CLASS(class, NULL);
if (!eina_lock_take(&class->mutex))
{
#ifdef EINA_HAVE_DEBUG_THREADS
assert(pthread_equal(class->self, pthread_self()));
#endif
}
item = _eina_object_find_item(class, object);
if (!item) goto on_error;
mem = (unsigned char*) item + _eina_object_item_size;
on_error:
eina_lock_release(&class->mutex);
return mem;
}
void
eina_object_del(Eina_Class *class,
Eina_Object *object)
{
Eina_Object_Item *item;
if (!object) return;
EINA_MAGIC_CHECK_CLASS(class);
if (!eina_lock_take(&class->mutex))
{
#ifdef EINA_HAVE_DEBUG_THREADS
assert(pthread_equal(class->self, pthread_self()));
#endif
}
item = _eina_object_find_item(class, object);
if (!item) goto on_error;
_eina_object_item_del(item);
on_error:
eina_lock_release(&class->mutex);
}
Eina_Bool
eina_object_parent_set(Eina_Class *parent_class, Eina_Object *parent,
Eina_Class *object_class, Eina_Object *object)
{
Eina_Object_Item *parent_item;
Eina_Object_Item *object_item;
if (!parent) return EINA_FALSE;
if (!object) return EINA_FALSE;
EINA_MAGIC_CHECK_CLASS(parent_class, EINA_FALSE);
EINA_MAGIC_CHECK_CLASS(object_class, EINA_FALSE);
if (!eina_lock_take(&parent_class->mutex))
{
#ifdef EINA_HAVE_DEBUG_THREADS
assert(pthread_equal(parent_class->self, pthread_self()));
#endif
}
if (!eina_lock_take(&object_class->mutex))
{
#ifdef EINA_HAVE_DEBUG_THREADS
assert(pthread_equal(object_class->self, pthread_self()));
#endif
}
parent_item = _eina_object_find_item(parent_class, parent);
if (!parent_item) return EINA_FALSE;
object_item = _eina_object_find_item(object_class, object);
if (!object_item) return EINA_FALSE;
if (object_item->parent)
object_item->parent->link = eina_inlist_remove(object_item->parent->link,
EINA_INLIST_GET(object_item));
object_item->parent = parent_item;
parent_item->link = eina_inlist_append(parent_item->link,
EINA_INLIST_GET(object_item));
eina_lock_release(&parent_class->mutex);
eina_lock_release(&object_class->mutex);
return EINA_TRUE;
}
Eina_Object *
eina_object_parent_get(Eina_Class *class, Eina_Object *object)
{
Eina_Object_Item *object_item;
Eina_Object *or = NULL;
if (!object) return EINA_FALSE;
EINA_MAGIC_CHECK_CLASS(class, EINA_FALSE);
if (!eina_lock_take(&class->mutex))
{
#ifdef EINA_HAVE_DEBUG_THREADS
assert(pthread_equal(class->self, pthread_self()));
#endif
}
object_item = _eina_object_find_item(class, object);
if (object_item)
or = _eina_object_get(object_item->parent);
eina_lock_release(&class->mutex);
return or;
}