elementary: add a factory that handle caching for you.
authorCedric BAIL <cedric.bail@free.fr>
Fri, 7 Dec 2018 00:38:47 +0000 (16:38 -0800)
committerJiyoun Park <jy0703.park@samsung.com>
Wed, 16 Jan 2019 02:13:22 +0000 (11:13 +0900)
This factory handle caching of one type of object and automatically empty the cache
when the application goes into pause.

Creating object is costly and time consuming, keeping a few on hands for when you next will need them help a lot.
This is what this factory caching infrastructure provide. It will create the object from the class defined on it,
set the parent and the model as needed for all items a Factory create. The View has to release the Item using the
release function of the Factory interface for all of this to work properly.

This is copying what Elm_Genlist was doing for you in the background and bring Efl interface to parity.

Reviewed-by: Felipe Magno de Almeida <felipe@expertisesolutions.com.br>
Differential Revision: https://phab.enlightenment.org/D7443

src/Makefile_Efl.am
src/Makefile_Elementary.am
src/lib/efl/Efl.h
src/lib/efl/interfaces/efl_cached_item.eo [new file with mode: 0644]
src/lib/efl/interfaces/efl_interfaces_main.c
src/lib/efl/interfaces/meson.build
src/lib/elementary/Efl_Ui.h
src/lib/elementary/Elementary.h
src/lib/elementary/efl_ui_caching_factory.c [new file with mode: 0644]
src/lib/elementary/efl_ui_caching_factory.eo [new file with mode: 0644]
src/lib/elementary/meson.build

index 6fe217a..b69f3b6 100644 (file)
@@ -78,6 +78,7 @@ efl_eolian_files = \
       lib/efl/interfaces/efl_gfx_text_class.eo \
       lib/efl/interfaces/efl_gfx_size_class.eo \
       lib/efl/interfaces/efl_interpolator.eo \
+      lib/efl/interfaces/efl_cached_item.eo \
       $(efl_eolian_legacy_files) \
       $(NULL)
 
index 0d2889d..cc86e58 100644 (file)
@@ -134,6 +134,7 @@ elm_public_eolian_files = \
        lib/elementary/efl_ui_tab_page_part_tab.eo \
        lib/elementary/efl_ui_widget_focus_manager.eo \
        lib/elementary/efl_ui_text_part.eo \
+       lib/elementary/efl_ui_caching_factory.eo \
        $(NULL)
 
 # More public files -- FIXME
@@ -908,6 +909,7 @@ lib_elementary_libelementary_la_SOURCES = \
        lib/elementary/efl_ui_tab_bar.c \
        lib/elementary/efl_ui_tab_page.c \
        lib/elementary/efl_ui_widget_focus_manager.c \
+       lib/elementary/efl_ui_caching_factory.c \
        $(NULL)
 
 
index 7da806c..af6154b 100644 (file)
@@ -149,6 +149,7 @@ typedef Efl_Gfx_Path_Command_Type Efl_Gfx_Path_Command;
 #include "interfaces/efl_ui_model_connect.eo.h"
 #include "interfaces/efl_ui_factory.eo.h"
 #include "interfaces/efl_ui_format.eo.h"
+#include "interfaces/efl_cached_item.eo.h"
 
 /* Observable interface */
 #include "interfaces/efl_observer.eo.h"
diff --git a/src/lib/efl/interfaces/efl_cached_item.eo b/src/lib/efl/interfaces/efl_cached_item.eo
new file mode 100644 (file)
index 0000000..ec1cd0c
--- /dev/null
@@ -0,0 +1,14 @@
+interface Efl.Cached.Item
+{
+   [[Efl Cached Item interface]]
+   methods {
+      @property memory_size {
+         get {
+            [[Get the memory size associated with an object.]]
+         }
+         values {
+            consumed: uint; [[Size of memory consumed by this object.]]
+         }
+      }
+   }
+}
index 5133def..d1f9b80 100644 (file)
@@ -81,6 +81,8 @@
 #include "interfaces/efl_ui_multi_selectable.eo.c"
 #include "interfaces/efl_ui_zoom.eo.c"
 
+#include "interfaces/efl_cached_item.eo.c"
+
 static void
 _noref_death(void *data EINA_UNUSED, const Efl_Event *event)
 {
index a78cf4a..13f71e4 100644 (file)
@@ -104,6 +104,7 @@ pub_eo_files = [
   'efl_gfx_color_class.eo',
   'efl_gfx_text_class.eo',
   'efl_gfx_size_class.eo',
+  'efl_cached_item.eo',
 ]
 
 foreach eo_file : pub_eo_files
index b4cb217..9ffaf80 100644 (file)
@@ -209,6 +209,8 @@ typedef Eo Efl_Ui_Focus_Manager;
 # include <efl_ui_calendar.h>
 # include <efl_ui_button_eo.h>
 
+# include "efl_ui_caching_factory.eo.h"
+
 /* FIXME: Multibuttonentry must not use elm_widget_item */
 # warning Efl.Ui.Multibutton is not available yet without Elementary.h
 # if 0
index 92c98e6..dc42b25 100644 (file)
@@ -358,6 +358,7 @@ typedef Eo Efl_Ui_Focus_Manager;
 # include <efl_ui_list_view_relayout.eo.h>
 # include <efl_ui_list_view.eo.h>
 # include <efl_ui_list_view_pan.eo.h>
+# include <efl_ui_caching_factory.eo.h>
 # include <efl_ui_pan.eo.h>
 # include <efl_ui_scroll_manager.eo.h>
 # include <efl_ui_scroller.eo.h>
diff --git a/src/lib/elementary/efl_ui_caching_factory.c b/src/lib/elementary/efl_ui_caching_factory.c
new file mode 100644 (file)
index 0000000..fe69e42
--- /dev/null
@@ -0,0 +1,218 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <Elementary.h>
+#include "elm_priv.h"
+
+typedef struct _Efl_Ui_Caching_Factory_Data Efl_Ui_Caching_Factory_Data;
+struct _Efl_Ui_Caching_Factory_Data
+{
+   const Efl_Class *klass;
+
+   // Simple list of ready-to-use objects. They are all equal so it does not matter from which
+   // end of the list objects are added and removed.
+   Eina_List *cache;
+
+   struct {
+      unsigned int memory;
+      unsigned int items;
+   } limit, current;
+};
+
+// Clear the cache until it meet the constraint
+static void
+_efl_ui_caching_factory_remove(Efl_Ui_Caching_Factory_Data *pd, Eina_List *l, Efl_Gfx_Entity *entity)
+{
+   pd->cache = eina_list_remove_list(pd->cache, l);
+   pd->current.items--;
+
+   pd->current.memory -= efl_class_memory_size_get(entity);
+   if (efl_isa(entity, EFL_CACHED_ITEM_INTERFACE))
+     pd->current.memory -= efl_cached_item_memory_size_get(entity);
+}
+
+static void
+_efl_ui_caching_factory_flush(Efl_Ui_Caching_Factory_Data *pd)
+{
+   while (pd->limit.items != 0 &&
+          pd->current.items > pd->limit.items)
+     {
+        Efl_Gfx_Entity *entity;
+
+        entity = eina_list_data_get(eina_list_last(pd->cache));
+
+        _efl_ui_caching_factory_remove(pd, eina_list_last(pd->cache), entity);
+
+        efl_del(entity);
+     }
+
+   while (pd->limit.memory != 0 &&
+          pd->current.memory > pd->limit.memory)
+     {
+        Efl_Gfx_Entity *entity;
+
+        entity = eina_list_data_get(eina_list_last(pd->cache));
+
+        _efl_ui_caching_factory_remove(pd, eina_list_last(pd->cache), entity);
+
+        efl_del(entity);
+     }
+}
+
+static Eina_Future *
+_efl_ui_caching_factory_efl_ui_factory_create(Eo *obj,
+                                              Efl_Ui_Caching_Factory_Data *pd,
+                                              Efl_Model *model, Efl_Gfx_Entity *parent)
+{
+   Efl_Gfx_Entity *r;
+
+   if (pd->cache)
+     {
+        r = eina_list_data_get(pd->cache);
+
+        _efl_ui_caching_factory_remove(pd, pd->cache, r);
+
+        efl_parent_set(r, parent);
+     }
+   else
+     {
+        r = efl_add(pd->klass, parent);
+     }
+
+   efl_ui_view_model_set(r, model);
+
+   return efl_loop_future_resolved(obj, eina_value_object_init(r));
+}
+
+static void
+_efl_ui_caching_factory_item_class_set(Eo *obj,
+                                       Efl_Ui_Caching_Factory_Data *pd,
+                                       const Efl_Object *klass)
+{
+   if (!efl_isa(klass, EFL_GFX_ENTITY_INTERFACE) ||
+       !efl_isa(klass, EFL_UI_VIEW_INTERFACE))
+     {
+        ERR("Provided class '%s' for factory '%s' doesn't implement '%s' and '%s' interfaces.",
+            efl_class_name_get(klass),
+            efl_class_name_get(obj),
+            efl_class_name_get(EFL_GFX_ENTITY_INTERFACE),
+            efl_class_name_get(EFL_UI_VIEW_INTERFACE));
+        return ;
+     }
+   pd->klass = klass;
+}
+
+static const Efl_Object *
+_efl_ui_caching_factory_item_class_get(const Eo *obj,
+                                       Efl_Ui_Caching_Factory_Data *pd)
+{
+   return pd->klass;
+}
+
+static void
+_efl_ui_caching_factory_memory_limit_set(Eo *obj,
+                                         Efl_Ui_Caching_Factory_Data *pd,
+                                         unsigned int limit)
+{
+   pd->limit.memory = limit;
+
+   _efl_ui_caching_factory_flush(pd);
+}
+
+static unsigned int
+_efl_ui_caching_factory_memory_limit_get(const Eo *obj,
+                                         Efl_Ui_Caching_Factory_Data *pd)
+{
+   return pd->limit.memory;
+}
+
+static void
+_efl_ui_caching_factory_items_limit_set(Eo *obj,
+                                        Efl_Ui_Caching_Factory_Data *pd,
+                                        unsigned int limit)
+{
+   pd->limit.items = limit;
+
+   _efl_ui_caching_factory_flush(pd);
+}
+
+static unsigned int
+_efl_ui_caching_factory_items_limit_get(const Eo *obj,
+                                        Efl_Ui_Caching_Factory_Data *pd)
+{
+   return pd->limit.items;
+}
+
+static void
+_efl_ui_caching_factory_efl_ui_factory_release(Eo *obj,
+                                               Efl_Ui_Caching_Factory_Data *pd,
+                                               Efl_Gfx_Entity *ui_view)
+{
+   // Change parent, disconnect the object and make it invisible
+   efl_parent_set(ui_view, obj);
+   efl_gfx_entity_visible_set(ui_view, EINA_FALSE);
+   efl_ui_view_model_set(ui_view, NULL);
+
+   // Add to the cache
+   pd->cache = eina_list_prepend(pd->cache, ui_view);
+   pd->current.items++;
+   pd->current.memory += efl_class_memory_size_get(ui_view);
+   if (efl_isa(ui_view, EFL_CACHED_ITEM_INTERFACE))
+     pd->current.memory += efl_cached_item_memory_size_get(ui_view);
+
+   // And check if the cache need some triming
+   _efl_ui_caching_factory_flush(pd);
+}
+
+static void
+_efl_ui_caching_factory_efl_object_invalidate(Eo *obj,
+                                              Efl_Ui_Caching_Factory_Data *pd)
+{
+   // As all the objects in the cache have the factory as parent, there's no need to unparent them
+   pd->cache = eina_list_free(pd->cache);
+}
+
+static Efl_App *
+_efl_ui_caching_factory_app_get(Eo *obj)
+{
+   Efl_Object *p;
+
+   p = efl_parent_get(obj);
+   if (!p) return NULL;
+
+   // It is acceptable to just have a loop as parent and not an app
+   return efl_provider_find(obj, EFL_APP_CLASS);
+}
+
+static void
+_efl_ui_caching_factory_pause(void *data, const Efl_Event *event)
+{
+   Efl_Ui_Caching_Factory_Data *pd = data;
+   Efl_Gfx_Entity *entity;
+
+   // Application is going into background, let's free ressource
+   // Possible improvement would be to delay that by a few second.
+   EINA_LIST_FREE(pd->cache, entity)
+     efl_del(entity);
+
+   pd->current.items = 0;
+   pd->current.memory = 0;
+}
+
+static void
+_efl_ui_caching_factory_efl_object_parent_set(Eo *obj, Efl_Ui_Caching_Factory_Data *pd, Efl_Object *parent)
+{
+   Efl_App *a;
+
+   a = _efl_ui_caching_factory_app_get(obj);
+   if (a) efl_event_callback_del(a, EFL_APP_EVENT_PAUSE, _efl_ui_caching_factory_pause, pd);
+
+   efl_parent_set(efl_super(obj, EFL_UI_CACHING_FACTORY_CLASS), parent);
+
+   // We are fetching the parent again, just in case the update was denied
+   a = _efl_ui_caching_factory_app_get(obj);
+   if (a) efl_event_callback_add(a, EFL_APP_EVENT_PAUSE, _efl_ui_caching_factory_pause, pd);
+}
+
+#include "efl_ui_caching_factory.eo.c"
diff --git a/src/lib/elementary/efl_ui_caching_factory.eo b/src/lib/elementary/efl_ui_caching_factory.eo
new file mode 100644 (file)
index 0000000..c894e2c
--- /dev/null
@@ -0,0 +1,48 @@
+class Efl.Ui.Caching_Factory (Efl.Loop_Consumer, Efl.Ui.Factory)
+{
+   [[Efl Ui Factory that provides object caching.
+
+     This factory handles caching of one type of object and automatically empties the cache
+     when the application goes into pause.
+
+     Creating objects is costly and time consuming, keeping a few on hand for when you next will need them helps a lot.
+     This is what this factory caching infrastructure provides. It will create the object from the class defined on it and
+     set the parent and the model as needed for all created items. The View has to release the Item using the
+     release function of the Factory interface for all of this to work properly.
+
+     The cache might decide to flush itself when the application event pause is triggered.
+   ]]
+   methods {
+      @property item_class {
+         [[Define the class of the item returned by this factory.]]
+         get {}
+         set {}
+         values {
+            klass: const(Efl.Class); [[The class identifier to create item from.]]
+         }
+      }
+      @property memory_limit {
+         [[Define the maxium size in Bytes that all the object waiting on standby in the cache take. They must provide the @Efl.Cached.Item interface for an accurate accounting.]]
+         get {}
+         set {}
+         values {
+            limit: uint; [[When set to zero, there is no limit on the amount of memory the cache will use.]]
+         }
+      }
+      @property items_limit {
+         [[Define how many maximum number of items are waiting on standby in the cache.]]
+         get {}
+         set {}
+         values {
+            limit: uint; [[When set to zero, there is no limit to the amount of items stored in the cache.]]
+         }
+      }
+   }
+
+   implements {
+      Efl.Ui.Factory.create;
+      Efl.Ui.Factory.release;
+      Efl.Object.invalidate;
+      Efl.Object.parent { set; }
+   }
+}
index b05c838..cfc4155 100644 (file)
@@ -281,7 +281,8 @@ pub_eo_files = [
   'elm_view_form.eo',
   'elm_web.eo',
   'elm_widget_item.eo',
-  'efl_ui_text_part.eo'
+  'efl_ui_text_part.eo',
+  'efl_ui_caching_factory.eo',
 ]
 
 foreach eo_file : pub_eo_files
@@ -908,7 +909,8 @@ elementary_src = [
   'efl_ui_tab_pager.c',
   'efl_ui_tab_bar.c',
   'efl_ui_tab_page.c',
-  'efl_ui_widget_focus_manager.c'
+  'efl_ui_widget_focus_manager.c',
+  'efl_ui_caching_factory.c'
 ]
 
 elementary_deps = [emile, eo, efl, edje, ethumb, ethumb_client, emotion, ecore_imf, ecore_con, eldbus, efreet, efreet_mime, efreet_trash, eio, atspi, dl, intl]