/* 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 . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #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, __UNUSED__ int length, __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, __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), 64); 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), 64); 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), 64); 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; }