diff --git a/src/Makefile_Eina.am b/src/Makefile_Eina.am
index 9586e2bf6d..eca0bfe429 100644
--- a/src/Makefile_Eina.am
+++ b/src/Makefile_Eina.am
@@ -77,7 +77,8 @@ lib/eina/eina_value.h \
lib/eina/eina_inline_value.x \
lib/eina/eina_inline_lock_barrier.x \
lib/eina/eina_tmpstr.h \
-lib/eina/eina_alloca.h
+lib/eina/eina_alloca.h \
+lib/eina/eina_cow.h
# Will be back for developper after 1.2.
# lib/eina/eina_model.h
@@ -140,7 +141,8 @@ lib/eina/eina_xattr.c \
lib/eina/eina_share_common.h \
lib/eina/eina_private.h \
lib/eina/eina_strbuf_common.h \
-lib/eina/eina_tmpstr.c
+lib/eina/eina_tmpstr.c \
+lib/eina/eina_cow.c
# Will be back for developper after 1.2
# lib/eina/eina_model.c \
@@ -273,7 +275,8 @@ tests/eina/eina_test_strbuf.c \
tests/eina/eina_test_str.c \
tests/eina/eina_test_quadtree.c \
tests/eina/eina_test_simple_xml_parser.c \
-tests/eina/eina_test_value.c
+tests/eina/eina_test_value.c \
+tests/eina/eina_test_cow.c
# tests/eina/eina_test_model.c
tests_eina_eina_suite_CPPFLAGS = \
diff --git a/src/lib/eina/Eina.h b/src/lib/eina/Eina.h
index d7d1b86960..962701388f 100644
--- a/src/lib/eina/Eina.h
+++ b/src/lib/eina/Eina.h
@@ -262,6 +262,7 @@ extern "C" {
#include "eina_mmap.h"
#include "eina_xattr.h"
#include "eina_value.h"
+#include "eina_cow.h"
#ifdef __cplusplus
}
diff --git a/src/lib/eina/eina_cow.c b/src/lib/eina/eina_cow.c
new file mode 100644
index 0000000000..e891b5c6c2
--- /dev/null
+++ b/src/lib/eina/eina_cow.c
@@ -0,0 +1,412 @@
+/* EINA - EFL data type library
+ * Copyright (C) 2013 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 "eina_config.h"
+#include "eina_mempool.h"
+#include "eina_types.h"
+#include "eina_safety_checks.h"
+#include "eina_list.h"
+#include "eina_hash.h"
+
+#include "eina_cow.h"
+
+#define EINA_COW_MAGIC 0xDEADBEEF
+
+typedef struct _Eina_Cow_Ptr Eina_Cow_Ptr;
+typedef struct _Eina_Cow_GC Eina_Cow_GC;
+
+struct _Eina_Cow_Ptr
+{
+ int refcount;
+
+ Eina_Bool hashed;
+ Eina_Bool togc;
+};
+
+struct _Eina_Cow_GC
+{
+ Eina_List *togc;
+ Eina_Cow_Ptr *ref;
+ const void * const *dst;
+};
+
+struct _Eina_Cow
+{
+ EINA_MAGIC;
+
+ Eina_List *togc;
+ Eina_Hash *match;
+
+ Eina_Mempool *pool;
+ const void *default_value;
+
+ unsigned int struct_size;
+};
+
+typedef int (*Eina_Cow_Hash)(const void *, int);
+
+#define EINA_COW_MAGIC_CHECK(d) \
+ do { \
+ if (!EINA_MAGIC_CHECK((d), EINA_COW_MAGIC)) \
+ EINA_MAGIC_FAIL((d), EINA_COW_MAGIC); \
+ } while (0);
+
+static Eina_Mempool *gc_pool = NULL;
+
+static inline int
+_eina_cow_hash_gen(const void *key, int key_length,
+ Eina_Cow_Hash hash,
+ int size)
+{
+ const unsigned char *walk = key;
+ int r = 0xDEADBEEF;
+
+ while (key_length > 0)
+ {
+ r ^= hash(walk, size);
+
+ walk += size;
+ key_length -= size;
+ }
+
+ return r;
+}
+
+#ifdef __LP64__
+static int
+_eina_cow_hash64(const void *key, int key_length)
+{
+ return _eina_cow_hash_gen(key, key_length,
+ (Eina_Cow_Hash) eina_hash_int64, sizeof (unsigned long int));
+}
+#else
+static int
+_eina_cow_hash32(const void *key, int key_length)
+{
+ return _eina_cow_hash_gen(key, key_length,
+ (Eina_Cow_Hash) eina_hash_int32, sizeof (int));
+}
+#endif
+
+static int current_cow_size = 0;
+
+static unsigned int
+_eina_cow_length(const void *key EINA_UNUSED)
+{
+ /* nasty hack, has only gc need to access the hash, he will be in charge
+ of that global. access to the hash should be considered global. so a
+ lock will be needed to make multiple gc run at the same safely.
+ */
+ return current_cow_size;
+}
+
+static int
+_eina_cow_cmp(const void *key1, int key1_length,
+ const void *key2, int key2_length EINA_UNUSED)
+{
+ return memcmp(key1, key2, key1_length);
+}
+
+static void
+_eina_cow_hash_del(Eina_Cow *cow,
+ const void *data,
+ Eina_Cow_Ptr *ref)
+{
+ /* if eina_cow_gc is supposed to be thread safe, lock the cow here */
+ if (ref->hashed)
+ {
+ current_cow_size = cow->struct_size;
+ eina_hash_del(cow->match, data, ref);
+ ref->hashed = EINA_FALSE;
+ }
+}
+
+static void
+_eina_cow_togc_del(Eina_Cow *cow, Eina_Cow_Ptr *ref)
+{
+ /* if eina_cow_gc is supposed to be thread safe, lock the cow here */
+ if (ref->togc)
+ {
+ Eina_Cow_GC *gc;
+ Eina_List *l;
+
+ EINA_LIST_FOREACH(cow->togc, l, gc)
+ if (gc->ref == ref)
+ {
+ cow->togc = eina_list_remove_list(cow->togc, l);
+ break;
+ }
+ ref->togc = EINA_FALSE;
+ }
+}
+
+Eina_Bool
+eina_cow_init(void)
+{
+ const char *choice, *tmp;
+
+#ifdef EINA_DEFAULT_MEMPOOL
+ choice = "pass_through";
+#else
+ choice = "chained_mempool";
+#endif
+ tmp = getenv("EINA_MEMPOOL");
+ if (tmp && tmp[0])
+ choice = tmp;
+
+ gc_pool = eina_mempool_add(choice, "gc", NULL, sizeof (Eina_Cow_GC), 32);
+ if (!gc_pool)
+ {
+ /* ERR("ERROR: Mempool for cow '%s' cannot be allocated in eina_cow_new.", name); */
+ return EINA_FALSE;
+ }
+ return EINA_TRUE;
+}
+
+Eina_Bool
+eina_cow_shutdown(void)
+{
+ eina_mempool_del(gc_pool);
+ return EINA_TRUE;
+}
+
+EAPI Eina_Cow *
+eina_cow_add(const char *name, unsigned int struct_size, unsigned int step, const void *default_value)
+{
+ const char *choice, *tmp;
+ Eina_Cow *cow;
+
+ EINA_SAFETY_ON_NULL_RETURN_VAL(default_value, NULL);
+ EINA_SAFETY_ON_FALSE_RETURN_VAL(struct_size, NULL);
+ EINA_SAFETY_ON_FALSE_RETURN_VAL(step, NULL);
+
+ cow = malloc(sizeof (Eina_Cow));
+ if (!cow) return NULL;
+
+#ifdef EINA_DEFAULT_MEMPOOL
+ choice = "pass_through";
+#else
+ choice = "chained_mempool";
+#endif
+ tmp = getenv("EINA_MEMPOOL");
+ if (tmp && tmp[0])
+ choice = tmp;
+
+ cow->pool = eina_mempool_add(choice, name,
+ NULL,
+ struct_size + sizeof (Eina_Cow_Ptr), step);
+ if (!cow->pool)
+ {
+ /* ERR("ERROR: Mempool for cow '%s' cannot be allocated in eina_cow_new.", name); */
+ goto on_error;
+ }
+
+#ifdef __LP64__
+ cow->match = eina_hash_new(_eina_cow_length,
+ _eina_cow_cmp,
+ _eina_cow_hash64,
+ NULL,
+ 6);
+#else
+ cow->match = eina_hash_new(_eina_cow_length,
+ _eina_cow_cmp,
+ _eina_cow_hash32,
+ NULL,
+ 6);
+#endif
+
+ cow->togc = NULL;
+ cow->default_value = default_value;
+ cow->struct_size = struct_size;
+
+ EINA_MAGIC_SET(cow, EINA_COW_MAGIC);
+
+ return cow;
+
+ on_error:
+ free(cow);
+ return NULL;
+}
+
+EAPI void
+eina_cow_del(Eina_Cow *cow)
+{
+ Eina_Cow_GC *gc;
+
+ EINA_COW_MAGIC_CHECK(cow);
+
+ eina_mempool_del(cow->pool);
+
+ EINA_LIST_FREE(cow->togc, gc)
+ eina_mempool_free(gc_pool, gc);
+ free(cow);
+}
+
+EAPI const void *
+eina_cow_alloc(Eina_Cow *cow)
+{
+ EINA_COW_MAGIC_CHECK(cow);
+
+ return cow->default_value;
+}
+
+EAPI void
+eina_cow_free(Eina_Cow *cow, const void *data)
+{
+ Eina_Cow_Ptr *ref;
+
+ EINA_COW_MAGIC_CHECK(cow);
+
+ if (!data) return ;
+ if (cow->default_value == data) return ;
+
+ ref = (Eina_Cow_Ptr*)(((unsigned char*) data) + cow->struct_size);
+ ref->refcount--;
+
+ if (ref->refcount > 0) return ;
+
+ _eina_cow_hash_del(cow, data, ref);
+ _eina_cow_togc_del(cow, ref);
+ eina_mempool_free(cow->pool, (void*) data);
+}
+
+EAPI void *
+eina_cow_write(Eina_Cow *cow, const void * const *data)
+{
+ Eina_Cow_Ptr *ref;
+ const void *src;
+ void *r;
+
+ EINA_COW_MAGIC_CHECK(cow);
+
+ if (!*data) return NULL; /* cow pointer is always != NULL */
+ if (*data == cow->default_value)
+ {
+ src = cow->default_value;
+ goto allocate;
+ }
+
+ ref = (Eina_Cow_Ptr*)(((unsigned char*) *data) + cow->struct_size);
+
+ if (ref->refcount == 1)
+ {
+ _eina_cow_hash_del(cow, *data, ref);
+ return (void *) *data;
+ }
+
+ src = *data;
+
+ allocate:
+ r = eina_mempool_malloc(cow->pool,
+ cow->struct_size + sizeof (Eina_Cow_Ptr));
+ memcpy(r, src, cow->struct_size);
+
+ ref = (Eina_Cow_Ptr*)(((unsigned char*) r) + cow->struct_size);
+ ref->refcount = 1;
+ ref->hashed = EINA_FALSE;
+ ref->togc = EINA_FALSE;
+
+ *((void**) data) = r;
+
+ return r;
+}
+
+EAPI void
+eina_cow_commit(Eina_Cow *cow, const void * const * dst, const void *data)
+{
+ Eina_Cow_Ptr *ref;
+
+ EINA_COW_MAGIC_CHECK(cow);
+
+ ref = (Eina_Cow_Ptr*)(((unsigned char*) data) + cow->struct_size);
+
+ /* needed if we want to make cow gc safe */
+ if (!ref->togc)
+ {
+ Eina_Cow_GC *gc;
+
+ gc = eina_mempool_malloc(gc_pool, sizeof (Eina_Cow_GC));
+ if (!gc) return ; /* That one will not get gced this time */
+
+ gc->ref = ref;
+ gc->dst = dst;
+ cow->togc = gc->togc = eina_list_prepend(cow->togc, gc);
+ ref->togc = EINA_TRUE;
+ }
+}
+
+EAPI void
+eina_cow_memcpy(Eina_Cow *cow, const void * const *dst, const void *src)
+{
+ Eina_Cow_Ptr *ref;
+
+ EINA_COW_MAGIC_CHECK(cow);
+
+ eina_cow_free(cow, *dst);
+
+ ref = (Eina_Cow_Ptr*)(((unsigned char*) src) + cow->struct_size);
+ ref->refcount++;
+
+ *((const void**)dst) = src;
+}
+
+EAPI Eina_Bool
+eina_cow_gc(Eina_Cow *cow)
+{
+ Eina_Cow_Ptr *ref;
+ Eina_Cow_GC *gc;
+ void *data;
+ void *match;
+
+ EINA_COW_MAGIC_CHECK(cow);
+
+ if (!cow->togc) return EINA_FALSE; /* Nothing more to do */
+
+ /* Do handle hash and all funky merge think here */
+ gc = eina_list_data_get(eina_list_last(cow->togc));
+
+ data = ((unsigned char*) gc->ref) - cow->struct_size;
+
+ gc->ref->togc = EINA_FALSE;
+ cow->togc = eina_list_remove_list(cow->togc, eina_list_last(cow->togc));
+
+ current_cow_size = cow->struct_size;
+ match = eina_hash_find(cow->match, data);
+ if (match)
+ {
+ eina_cow_free(cow, data);
+
+ ref = (Eina_Cow_Ptr*)(((unsigned char*) match) + cow->struct_size);
+ *((void**)gc->dst) = match;
+ ref->refcount++;
+ }
+ else
+ {
+ eina_hash_direct_add(cow->match, data, data);
+ gc->ref->hashed = EINA_TRUE;
+ }
+
+ eina_mempool_free(gc_pool, gc);
+
+ return EINA_TRUE;
+}
+
diff --git a/src/lib/eina/eina_cow.h b/src/lib/eina/eina_cow.h
new file mode 100644
index 0000000000..03d3c5c0c8
--- /dev/null
+++ b/src/lib/eina/eina_cow.h
@@ -0,0 +1,36 @@
+/* EINA - EFL data type library
+ * Copyright (C) 2013 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 .
+ */
+
+#ifndef EINA_COW_H_
+#define EINA_COW_H_
+
+typedef struct _Eina_Cow Eina_Cow;
+
+EAPI Eina_Cow *eina_cow_add(const char *name, unsigned int struct_size, unsigned int step, const void *default_value);
+EAPI void eina_cow_del(Eina_Cow *cow);
+
+EAPI const void *eina_cow_alloc(Eina_Cow *cow);
+EAPI void eina_cow_free(Eina_Cow *cow, const void *data);
+
+EAPI void *eina_cow_write(Eina_Cow *cow, const void * const *src);
+EAPI void eina_cow_commit(Eina_Cow *cow, const void * const *dst, const void *data);
+EAPI void eina_cow_memcpy(Eina_Cow *cow, const void * const *dst, const void *src);
+
+EAPI Eina_Bool eina_cow_gc(Eina_Cow *cow);
+
+#endif
diff --git a/src/lib/eina/eina_main.c b/src/lib/eina/eina_main.c
index fb2bb0df73..893ca8e5d4 100644
--- a/src/lib/eina/eina_main.c
+++ b/src/lib/eina/eina_main.c
@@ -158,6 +158,7 @@ EAPI Eina_Inlist *_eina_tracking = NULL;
S(value);
S(tmpstr);
S(thread);
+ S(cow);
/* no model for now
S(model);
*/
@@ -199,7 +200,8 @@ static const struct eina_desc_setup _eina_desc_setup[] = {
S(prefix),
S(value),
S(tmpstr),
- S(thread)
+ S(thread),
+ S(cow)
/* no model for now
S(model)
*/
diff --git a/src/tests/eina/eina_suite.c b/src/tests/eina/eina_suite.c
index 866db2780a..cceda023ae 100644
--- a/src/tests/eina/eina_suite.c
+++ b/src/tests/eina/eina_suite.c
@@ -68,6 +68,7 @@ static const Eina_Test_Case etc[] = {
{ "Sched", eina_test_sched },
{ "Simple Xml Parser", eina_test_simple_xml_parser},
{ "Value", eina_test_value },
+ { "COW", eina_test_cow },
// Disabling Eina_Model test
// { "Model", eina_test_model },
{ NULL, NULL }
diff --git a/src/tests/eina/eina_suite.h b/src/tests/eina/eina_suite.h
index d399298128..b85dad2de3 100644
--- a/src/tests/eina/eina_suite.h
+++ b/src/tests/eina/eina_suite.h
@@ -57,5 +57,6 @@ void eina_test_sched(TCase *tc);
void eina_test_simple_xml_parser(TCase *tc);
void eina_test_value(TCase *tc);
void eina_test_model(TCase *tc);
+void eina_test_cow(TCase *tc);
#endif /* EINA_SUITE_H_ */
diff --git a/src/tests/eina/eina_test_cow.c b/src/tests/eina/eina_test_cow.c
new file mode 100644
index 0000000000..4d4d8e8f8a
--- /dev/null
+++ b/src/tests/eina/eina_test_cow.c
@@ -0,0 +1,85 @@
+/* EINA - EFL data type library
+ * Copyright (C) 2012 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 "eina_suite.h"
+#include "Eina.h"
+
+typedef struct _Eina_Cow_Test Eina_Cow_Test;
+struct _Eina_Cow_Test
+{
+ unsigned char c;
+ int i;
+ void *d;
+};
+
+START_TEST(eina_cow)
+{
+ const Eina_Cow_Test *prev;
+ const Eina_Cow_Test *cur;
+ Eina_Cow_Test *write;
+ Eina_Cow *cow;
+ Eina_Cow_Test default_value = { 42, 0, NULL };
+
+ cow = eina_cow_add("COW Test", sizeof (Eina_Cow_Test), 16, &default_value);
+ fail_if(cow == NULL);
+
+ prev = eina_cow_alloc(cow);
+ cur = eina_cow_alloc(cow);
+ fail_if(prev == NULL || cur == NULL);
+
+ write = eina_cow_write(cow, &cur);
+ fail_if(write == NULL || write == &default_value);
+
+ write->i = 7;
+ eina_cow_commit(cow, &cur, write);
+ fail_if(cur->i != 7 || prev->i != 0);
+
+ eina_cow_memcpy(cow, &prev, cur);
+ fail_if(cur->i != 7 || prev->i != 7);
+ fail_if(default_value.i != 0);
+
+ write = eina_cow_write(cow, &cur);
+ fail_if(write == NULL || write == &default_value);
+
+ write->i = 42; write->c = 5;
+ eina_cow_commit(cow, &cur, write);
+ fail_if(cur->i != 42 || cur->c != 5 ||
+ prev->i != 7 || prev->c != 42 ||
+ default_value.c != 42 || default_value.i != 0);
+
+ fail_if(eina_cow_gc(cow) == EINA_FALSE);
+ fail_if(eina_cow_gc(cow) == EINA_FALSE);
+
+ write = eina_cow_write(cow, &cur);
+ write->i = 7; write->c = 42;
+ eina_cow_commit(cow, &cur, write);
+
+ fail_if(eina_cow_gc(cow) == EINA_FALSE);
+ fail_if(cur != prev);
+}
+END_TEST
+
+void
+eina_test_cow(TCase *tc)
+{
+ tcase_add_test(tc, eina_cow);
+}