Page MenuHomePhabricator

RFC Efl.Ui.Item_Position_Manager
Open, TODOPublic

Description

interface Efl.Ui.Item_Position_Manager extends Efl.Gfx.Arrangement, Efl.Ui.Layout_Orientable
{
   methods {
      @property data_access {
         [[set at construction time, general interface for accessing data fast]]
         set {

         }
         values {
           access : accessor<Efl.Gfx.Entity>;
           size : int;
         }
      }
      @property viewport_size {
         [[called when the viewport is resized]]
         set {

         }
         values {
           size : Eina.Size2D;
         }
      }
      item_added {
        params {
          added_index : int;
          subobj : Efl.Gfx.Entity;
        }
      }
      item_removed {
        params {
          removed_index : int;
          subobj : Efl.Gfx.Entity;
        }
      }
      @property scroll_positon {
         [[called when the position of the items has changed]]
         set {

         }
         values {
           x : double; [[X position of the scroller]]
           y : double; [[Y position of the scroller]]
         }
      }
   }
   events {
     content_size,changed : Eina.Size2D; [[called when the absolut size of all contents has changed, this can be used to resize the pan object]]
   }
}

First idea of what the positon_manager could do.

bu5hm4n created this task.Jun 28 2019, 2:28 AM
bu5hm4n triaged this task as High priority.

@cedric do you think this could work ?

bu5hm4n updated the task description. (Show Details)Jun 28 2019, 7:50 AM
bu5hm4n updated the task description. (Show Details)
bu5hm4n updated the task description. (Show Details)Jun 28 2019, 7:55 AM

Hum, I was going for a very different approach actually. I was thinking of having a function to call on a object with something like :

interface @beta Efl.Ui.Items_Position_Manager
{
   methods {
      layout_items {
         [[Function in charge of placing widget after they have been realized.
           This function will be called again with more items or if any of the realized
           item get there size changed.
         ]]
         params {
            @in viewport: Eina.Rect; [[Define the current space that need to be filled by the items]]
            @in items: iterator<Efl.Ui.Widget>; [[All the items that need to fit inside the viewport]]
         }
         return: bool; [[Return $true if the viewport was filled, $false if it needs more items to fill it.]]
      }
   }
}

The part that I don't know how to expose is the total space which is calculated by the Model in the MVVM case, but would have to be calculated somehow by the List/Grid in the non MVVM case.

So we will be going with @bu5hm4n solution. He has already build more code around it and I don't see anything wrong with it.

Their is a missing bit: a cache interface for the current information about the total size of all the object and a function to update that size when an item or a set of item get their size changed. The MVVM part will bind that cache to the Model information and that is how homogeneous and friends will be implemented.

We haven't talked about how special item like group header would be handled by the layout function, but I think they can just carry a specific interface that the layout function would recognize and properly place.

On the bit I would change in the current proposal, I would remove the item_add/item_del and just have a way to reset the accessor with a different offset. Additionally I do think we need an additional event for asking more items or dropping some.

@cedric you can find my current implementation in https://git.enlightenment.org/core/efl.git/log/?h=devs/bu5hm4n/work_container

the cache

After working more on the implementations, it turned out that a good cache heavily depends on the way of displaying, so the cache implemented in the list_item_position_manager is not helpfull for the grid. In order to work around that, i have dragged the cache from the item_container back into the position_manager. However, the whole thing has an access to the sizes through a accessor.

Group headers

I am not sure how we are going to identify them. But from a code POV:

  • It does not matter to the item_container if this is a header item or not
  • The position will just position it different, and change the viewport accordingly, should be easy i think

item_add / item_del / item_changed

I have done that because this turned out in the view_manager of active_view to be super helpfull. If you need to perform operations on each and every object element that gets added, you can simply do it there, without loops or so. It also takes away the burdon of searching for the changed item

Future work

Right now the implementation of the list is rather poor, we can do way more smart things with the caches, we are currently invalidating them *way* too often. Additionally, there are a lot of bugs arround, the version up there is just a early draft. However, the basic feature set is more or less done and implemented.

cedric added a subscriber: q66.Jul 26 2019, 2:00 PM

After a bit of work for CollectionView, I am pretty convinced that Accessor are a bad solution here.

I see two main reason for that

  • the get_at is a fairly complex function call that is called very frequently
  • there is no context information and I have to infer what the "Viewport" is and how much widget/information to keep around.

It would be neat if the API was more batch oriented to solve both problem. I think an Eina_Slice might be the correct answer here. The idea would be for the position manager to batch request object and size information into a buffer (It also start to make sense to now describe that into a single structure) and keep that buffer around for some time. With this there would be a very limited amount of function call back and force, and it would be easier to assume that the current Viewport is where the last request was.

It would be neat to then have slice<Type> as an eolian type (@q66 what do you think?)

The use of slice over other type allow for saving memory and reduce memory allocation between the two object. Alternative like Eina_Array kind of provide that for pointer, but not structure or simple type. Eina_Inarray is kind of similar to slice and would do also.

Right now we have m calls to the accessor, which are direct calls, with a batch oriented system we would have 1 call, but we iterate 2 times over the m items, first we copy everything into the buffer, and later on iterate again over the buffer to read it out.
In case of the object placement in list, i am also not sure if it would only be one call to request a buffer, since its not clear when we are stopping to place items, so it also might be multiple calls.
A few more things :

  1. I see one issue with your idea of getting the context from the accessing scheme, a position manager can request whatever items it likes to, and you might need to request items that are not even visible. IMO the logic to realize this tracking is way more complex than the current _get_at function.
  2. The logic for "keeing the buffer a little bit arround" makes me a little bit nervous, heavy scrolling or some animation that does scrolling might request a lot of batches, wouldn't that mean we explode memory wise for that time ?
  3. The reason the _get_at function in collection is looking how it looks, is to avoid eina_list_nth calls, because they are terrible slow. Even if you have the batch requesting, you will still want to have some caching mechanism like we have it right now in the _get_at function, which would bring that complexity from _get_at also into the batching, so this is not a real win (win over your first reason).
  4. I do not understand why you are saying that a slice is saving memory and reduces the memory allocation, we currently do 0 memory allocations when placing items, but with the batching we would need to allocate at least 1 buffer?

Another question for me is: what is the issue with a lot of calls to the accessor ? We also call for the size of every single item, a call to the accessor is not really bringing any weight IMO.

Alternative Solution

What do you think of another event which promotes something like a "visible range" which containes a start_id and a end_id ? collection and collection_view can use this to make items visible, or realize them.?

The basics of the Group feature are now available (Revisions linked to T8115).
The problems in there are that the accessing of the group is quite rough and tiresome, i am not happy myself with it. But i am thinking that its important to have at least a state, so we can look into performance and *feeling* as well.

@SanghyeonLee @Jaehyun_Cho @cedric I am planning to keep this interface and the corresponding method in collection beta for now, and give this setup one more release time to major, and more time for us to collect experience. I do not really feel comfortable with stabelizing this interface as we have no experience with something like this in EFL.

What do you think of:

  • Efl.Ui.Collection / Efl.Ui.List / Efl.Ui.Grid beeing stable in this release
  • Efl.Ui.Position_Manager.* beta for now ?
zmike lowered the priority of this task from High to TODO.

We have released a first iteration of position manager. Now there are some more iteration that could be done for improving speeds. Maybe we should close this ticket and open independent one for it?