Page MenuHomePhabricator

[Experiment] Function pointers in EO
Closed, ResolvedPublic

Description

From T5567 there is talk about having function pointers in EO.
See @felipealmeida comment there:

BTW, we might get away with factories in some places by using function pointers in Eolian. Our C# branch already has this implement in Eolian, I'm rebasing it over master upstream.
So, instead of getting a Eolian object with a create function in it, just pass a function pointer with whatever signature the method wants and that returns the element it will create. Like this:

-- pseudo code, not real eolian --
function factory_function {
  params {
    a: string;
    index: int;
  }
  return: efl.ui.Canvas;
};

class Toolbar {
  methods { @property item_factory { set {} params { factory: factory_function; } } }
}

And then, Toolbar just calls factory_function whenever it needs a new efl.ui.Canvas to show
as an element of the toolbar.

As this is pushed in different part of efl, I think I should comment here. I am not feeling confident with function pointer as there is a strong problem to be solved is the context lifecycle and there own lifecycle. It may be possible to force the context to be an Eo object and thus rely on efl_ref/unref to manage the context lifecycle. I am guessing bindings can hide the complexity of such a task, but it will be a pain to use in C.

Binding would have to enforce the creation of an Eo object as a context, watch for its deletion, set the function pointer and release their own refcount on the context. Now when the function get unused by the object, it will unref the context and the binding can detect the destruction of the context and properly cleanup things behind. This is a lot of additional work and overhead for something that I am not sure is very useful. And I am not even sure that I am not missing case here.

Making Eo override more usefull and base our new interfaces on inheritances seems to me a saner approach. I don't feel confident with this task at all.

I really have no idea what you are talking about with context. There's absolutely no need to make it a Eo object at all.

@felipealmeida void *data is what? how to automatically notify it's not used and can be destroyed, what's in it? etc.

In Python, it'll be a PyObject. In C it's a plain malloc() structure. In C++ it's likely a new allocated structure.

In C it's an usual practice to create a single structure (ie: your pd -- private data, or ctx -- application context) and use that, sharing among users. In Python we could use an object, be EFL or some temporary code, like:

obj.add(my_cb, (a, b, {'k': 'value'}, "str"))

Actually, for Python bindings the my_cb is a Python object on its own, the actual function pointer given to the C infrastructure would be a wrapper that is fixed, then it would get the user callback and arguments from a structure, and this structure lifecycle must be managed correctly (ie: if the object that holds the pointer dies, the structure must be released)

The function that receives the function pointer calls the free when it is no longer going to call it. It is not automatic for C, but in C++ you just pass a std::function<void(/*whatever signature*/)> with the following callable:

template <typename R, typename... Params>
struct call_c_function
{
  void* data;
  typedef R(*function_t)(Params...);
  function_t function;
  typedef void(*free_t)(void*);
  free_t free;
  
  call_c_function(call_c_function const&) = delete;
  call_c_function(call_c_function&& other) : data(other.data), function(other.function), free(other.free)
  { free = 0; data = 0; function = 0; }
  ~call_c_function() { if(free) free(data); }
  
  R operator()(Params ... params) const
  {
    return function(params...);
  }
};

For JavaScript the same thing, but you would use a Persistent to actually free the object. For C# a IDisposable would free the void* with the free function etc.

This is how we do for Eina_List*, and any other object that must free data. There's nothing special about functions at all. The only thing is that the free
function is necessary to be passed too. The free function will be passed by bindings to unpin state from GC (allowing GC to free it) or actually free data directly.

(From another thread, message in reply to @cedric)

You really are repeating yourself. But that can only be because you are not understanding the lifetime is simply fixed. And for bindings it is very easy to implement it.

A function becomes three parameters: The function pointer itself, the void* private data and another function pointer that frees the void* private data. When the method that received the function pointer no longer is going to call the function, it simply calls the function that frees the void* with the private data as parameter and then the context is freed.

The lifetime of the function is the lifetime of until it is still useful and can be called. The binding will not refcount the function pointer nor will it attach to the object that receives it. The free function is generated automatically by bindings.

How do you do eo_override for a specific parameter type? For elm_index that might not be a huge problem, because you will only use Eo objects for comparison, but that is not true for most places where sorting is needed.

The difference in syntax for languages is quite obvious too:

foo([] (auto lhs, auto rhs) { return lhs < rhs; });

vs:

struct foo_comparator : inherit< ;:efl::ui::elm_index_comparator>
{
  bool compare(object lhs, objet rhs)
  {
     X x_lhs = efl::downcast<X>(lhs);
     X x_rhs = efl::downcast<X>(rhs);
     return x_lhs < x_rhs;
  }
};

foo_comparator comparator;
foo(comparator);
q66 added a comment.Jul 19 2017, 6:45 AM

Yeah, if the function becomes three parameters, it's technically possible to implement. I just fear that this is 1) not really necessary 2) rather ugly API-wise... Also, the way Eolian currently works doesn't assume multiple things can be generated from one, so that'd have to be updated as well...

So I have been thinking more about this, and I do think now that it will solve quite a few case. The example for sorting is quite convincing in my opinion. @q66 if you do have better proposal for this kind of use case, that would be an alternative, otherwise we should go with that.

jpeg added a comment.Aug 21 2017, 9:30 PM

There are many places in the (eo) API where a function pointer would work better than any other solution (override, delegate + override or event).

The current implementation in GIT expects the documentation at an unusual place. While I'd expect this to work:

function factory_function {
  [[ DOCS HERE ]]
  params {
    a: string;
  }
};

I need to add it afterwards:

function factory_function {
  params {
    a: string;
  }
};   [[ DOCS HERE ]]
q66 added a comment.EditedAug 28 2017, 8:34 AM

this is what happens when people push into eolian without letting me review first... i didn't feel like calling out the responsible person at first because it was a change that needed doing anyway but i guess i will have to:

for @lauromoura and potentially others - do not push into eolian without pinging me first and at least let me review - unless you 1) want to waste my time 2) want your code rewritten :)

i fixed that doc issue for now @barbieri... next step is to go through this code and rethink what needs changing...

edit: okay, turns out @felipealmeida is the one who needs spanking for pushing a dev branch to master :) but what's done is done...

iscaro added a subscriber: iscaro.Aug 29 2017, 7:12 AM

Hello,

I'm using this new feature in a new code, however there's a feature missing. I would like to declare a function as @extern (since the function is already defined in a header file elsewhere).

I'm trying to do something like this:

function @extern My.Func {
  return: int;
}

If possible, could you please add this feature?

Thank you.

In T5583#96929, @q66 wrote:

Thanks @q66, works like a charm

jpeg closed this task as Resolved.Sep 19 2017, 2:47 AM