Page MenuHomePhabricator

Support easy object instantiation with Edje External
Open, WishlistPublic

Description

General UI frameworks usually support convenient object instantiation with script or mark-up language. By combining edje external with eolian, object instantiation and property initailization could be supported in EDC.

For example,

collections {
  group { "somegroup";
    parts {
      external { "OK_button";
        source: "elm/button"; // source: "Efl.Ui.Button"?
        desc {
          params {
            label: "OK";
            autorepeat: true;
            autorepeat_gap: 1.0;
          }
        }
      }
    }
  }
}
conr2d created this task.Jun 14 2017, 5:57 AM

This exists.
What is missing is a way to easily bind those "parameters" to the properties on the widget. Either using some kind of meta property model (with a property name as a string and a value as an Eina_Value), or a generated binding between EO and edje.

indeed, I believe when he said "eolian" is already the generation of the edje-external modules from each object.

however, better than generating lots of small .so that would impact EFL's compile time, I wonder if we have enough runtime introspection to translate from edje_external_params directly to eo calls... this would also mean all objects would be usable, independent of an external module... extra cool if that works for bindings such as objects defined in lua, javascript and python (with some restrictions, at least in Python one could use decorators to flag methods and signatures...)

I am not a fine of runtime introspection as you pay the cost of compilation time... at run time. I believe it should be possible to generate one .so file for all our elementary widget directly from our .eo file and have all the properties of a widget exposed as edje external property. Basically an eolian edje external compiler.

One more.

About this task, we also need to decide what params would be supported for this edje-external.
All properties ? or Some of them ?

  1. If all properties will be supported, then we need to think about some conflictions between this edje_external property and edje's automated property setting.

For example, visiblity of the object,

  1. If some of them will be supported, we need to think about the consistent way to define the scope of properties for all widget classes.

For about the above discussion - I also agree with cedric's opinion in that "paying the cost of compliation time at run time".
BUT, if the cost could be proved as cheaper than we expect, I think it deserves to think about that seriously.

barbieri added a subscriber: q66.EditedJun 15 2017, 7:06 AM
In T5577#88932, @cedric wrote:

I am not a fine of runtime introspection as you pay the cost of compilation time... at run time.

This is not true, since the generated code is simply a proxy, then moving it to runtime is not adding cost, rather removing cost. Put simply the way we wrote edje_external is just a metainformation, providing way to decode parameters from text and calling functions. If we use Eo instrospection with ffi we can achieve the same, without the module... so it's less cost since there is no extra dlopen(), no extra linking, etc.

what I wonder (actually I should know, but I never remember) and maybe @q66 can clarify is if we have that information available in runtime and how/where? If that's not available, then we add. If it's not available in a nice format, we still have time to change. For instance we could generate a efl_eo read-only section in the ELF binaries and set load on demand to not pollute memory space, then each eo class would have a description pointer to a binary structure that ressembles the .eo file, for generated binaries, the description would reside in efl_eo section... for dynamically generated classes (ie: Python) one could allocate that at runtime -- everything else would still work. As for basic types, such as the structs and enums we declare, we simply keep references to them, never to access them directly (then in Python they would be in RAM/malloc, while for eolian generated they would be in the efl_eo section).

I believe it should be possible to generate one .so file for all our elementary widget directly from our .eo file and have all the properties of a widget exposed as edje external property. Basically an eolian edje external compiler.

-1 on that, it's just extra cost... if fact we can deprecate edje_external modules and query them *first*, so we keep the existing for legacy, while all new classes only go thru dynamic Eo instrospection.

One more.

About this task, we also need to decide what params would be supported for this edje-external.
All properties ? or Some of them ?

when we created edje_external (I was the one pushing for that as we used it a lot), the intention was to support *all* properties... however there was no way to generate or even figure out what was a property, then we may have missed or some could lack updates.

The idea was to do exactly what eo is doing, offer introspection... so we could even expose those in Lua/Embryo later -- something we never worked on.

  1. If all properties will be supported, then we need to think about some conflictions between this edje_external property and edje's automated property setting. For example, visiblity of the object,

well, just define these parameters get executed *before* edje's native properties and you're good to go. You may blacklist some properties that edje manage and cause edje_cc compilation errors if you wish, the list is not that big and is fixed (color, geometry, visibility...).

  1. If some of them will be supported, we need to think about the consistent way to define the scope of properties for all widget classes.

I don't like this one, it's yet-another-thing for eo developers to specify.

For about the above discussion - I also agree with cedric's opinion in that "paying the cost of compliation time at run time".
BUT, if the cost could be proved as cheaper than we expect, I think it deserves to think about that seriously.

Last but not least, with this infrastructure in place we could implement not only edje_external, but also Glade-like "widget tree description" (for those that don't recall glade, it was a XML that declares GTK widgets).

These days I was talking to @raster at IRC about a revamped version of that done by Facebook for their web platforms, it's called React and also have a version that uses native widgets (React Native). Basically you declare these trees and they will instantiate widgets or HTML elements with the given properties (XML attributes). At this point it's exactly like Glade (minus XML namespace craps). You may have these widget trees per component (ie: part of your app) and combine them together (ie: a root component that includes others).

The key differences that makes Facebook's React very usable to developers are:

    • on every component update you return your whole widget tree, the core will diff it for you, creating/deleting widgets and changing properties accordingly. The tree is small, only widgets (we're not talking about every Evas_Object neither low level groups such as Edje_Object, so not much work on the core, but it boosts usability as the developer simply return a string. This is important since the developer only needs to know what a programmer-101 knows "printf", no pointers, widget lifetime, etc... Events are also automatically connected/disconnected respecting the lifetime.
  • they use a custom "transpiled" language called JSX to allow embedding XML and code transparently. Some hate, some love, you're not obligated to use that, but must people do as saves lots of typing... similar to what Java's JSP, PHP, etc... This allows you to save on string concatenation by embedding some <Widget param={code here}>{or other code here}</Widget>.

While the second part (JSX) is harder for us to match within Edje, in my understanding we could use this work to match the first key difference within Edje, which is also *much easier to write* than XML :-P With that I mean we could extend part { type: EXTERNAL } to allow trees and just like we allow parameters to be changed using part descriptions, trees could be modified as such! This would make Edje External much more usable for containers, as currently that's the last level you can do in Edje, then you must use C/Lua/Python to populate that, even if it's a fixed set...

For Edje External reference, see my original announcement 7 years ago: http://marc.info/?l=enlightenment-devel&m=128565419327454&w=2

q66 added a comment.Jun 15 2017, 7:51 AM

what I wonder (actually I should know, but I never remember) and maybe @q66 can clarify is if we have that information available in runtime and how/where?

We don't have any structural information about eo objects available directly via runtime api. What you *can* do however is check the class of an object, get its name, parse the respective .eo file, and retrieve the information via libeolian if that's what you mean. But obviously that involves on-demand parsing.

In T5577#89003, @q66 wrote:

what I wonder (actually I should know, but I never remember) and maybe @q66 can clarify is if we have that information available in runtime and how/where?

We don't have any structural information about eo objects available directly via runtime api. What you *can* do however is check the class of an object, get its name, parse the respective .eo file, and retrieve the information via libeolian if that's what you mean. But obviously that involves on-demand parsing.

Great, but could we add those? My proposal above should have minimum impact on runtime:

  • Efl_Class->description would point to a binary structure of the parsed Eo object
  • generated code (eolian) puts the pointed structure in a separate read-only ELF section that is not automatically loaded. I believe other systems have similar primitives, otherwise we simply put at the end of the .text sections by declaring as static const. If disk size is a problem for tiny spaces and there is no need for introspection, we can introduce a --no-introspection that would set the pointer to NULL and do not emit the structure;
  • dynamic code (Python, JS...) would malloc the structure and populate in RAM, waste is minimal compared to everything else in these languages anyway...

Positive side effects that I foresee:

  • dynamic bindings like for edje externals, but also Python and others... this solves the usage of Eo object from Python/Lua as *embedded scripting languages*, that is, a C application uses a dynamic language and may expose some existing objects, not the traditional pattern that a dynamic language imports efl and then uses that to create wrapped objects. BTW this exists even today if you call edje_object_part_object_get() on an object that was created by Edje itself...
  • able to regenerate .eo for any given object, like for debug

Negative side effects that I foresee:

  • bigger binaries
  • more code to compile
  • adds complexity to eolian

however all of these should be very small.

q66 added a comment.Jun 15 2017, 8:29 AM

I guess it's possible in theory, though it's more work than you think and I'm not sure if I like the idea yet. Also it doesn't properly handle types and other dependent things (which are shared between classes and multiple translation units) and I could probably think up a billion other potential issues that could get in the way, so the extra complexity is by no means small. Having to care about where things are put in the binary sounds super nasty and I hate it, too. So this is definitely not something to think about for this release cycle, if at all.

My point is that you have all the information and the binary format in ram, it's the Eolian database. It's just a matter of dumping it in the generated code.

One solution to the shared symbols is to use the linker, declare as a public and visible symbol, then only generate users to that symbol, such as _efl_description_Symbol_Name. Then problem turns to be "okay, but when it's declared?", this is better solved explicitly, currently we already ask eolian to generate .eot.h, we'd also generate .eot.c with such. All the linking and everything will be handled automatically as the libraries will be related by the build system already (it's very rare to only use the type definition from a library, most of the times we use it in both CFLAGS and LDFLAGS/LIBADD).

As for the early optimization, if you declare as static const it will have very small impact... and that is simple... damn... I believe I've been contaminated by raster :-D

Other than that, share complexities you foresee. To me it look plain simple as it's one extra structure to dump at the end of .eo.c

As I said there is extra cost to do it at runtime, even when you are done binary compiling all of it. At the end, it is pretty much the same as what you would have generated at compile time. And the extra unecessary complexity to just reduce the need of one .so, which we could easily solve by having edje support static external and elementary register them directly. The added code would I bet be not far off the size of the added inline database without the extra complexity. I really think you are trying to over engineer something here with no clear win.

Cedric, the extra cost I dare to bet it's not extra, it's less as it's central and basically walk an array doing strcpy() x explicitly declared chain of strcpy(). Everything else is the same, you must parse the arguments and convert them to the data type, then call. Also, read it right, currently what I'm proposing is already being done at edje-external layer... which is edje-specific, just move that out of edje and make it automatically (and dynamically) generated.

It's not just "reduce the need of one .so", it's actually make it more accessible. Say your application defines some widgets, or you did some in Python and want to use them in External... then the ".so" is not that easy anymore. You could force Python/JS devs to write ".eo" manually, then call eolian_${LANG}, compile and get one... but then it does start to show it's not that "simpler", right? :-)

having edje support static external and elementary register them directly

this works right now, you register yourself with edje, not the other way around... but currently if it "faults" then it will dlopen based on the external source name (ie: elm/button will fault, then it will dlopen /usr/lib/edje/modules/elm/v-1.18/module.so and call its init, that will register the widgets)

As a last reference, what I'm proposing is what we did in Soletta's FBP. There we have BOTH, you define in JSON your node-type (analogous to define your object in .eo), then you have an option to generate code (as done by eolian_gen) with #ifdef`-able metadata/introspection. You can then generate fully static code (ie: for microcontrollers or tiny embedded systems) OR use the runtime interpreter:

raster added a comment.EditedJun 15 2017, 11:21 PM

edje files are meant to be "safe". that means you can download a random file from the internet and it wont be able to run ARBITRARY code. if we let the edje file define what we would use for ffi ... then it will become by definition unsafe. then they can call any eo methods on any object... not good. i dislike this. the only way to do this is have a string list of properties/methods that can be set on an object... again using eolian to look ANYTHING up and just ffi jump to it etc. is asking for trouble. big fat trouble. we'd need a restricted list of what can be called and passed in to fix this up. that list has to be determined at compile time of efl or runtime by the app, and sure we could tag properties/methods...

so just in the interests of security/safety etc. ... we pretty much can't go using introspection... unless we overload eolian and eo files to have more and more metadata for specific higher level use case like this. i do not like this slippery slope atm. put more and more high level features in a core oo system. hmmm. a bit messy.

my proposal would be something simpler:

have some metadata files SEPARATE to eo files. they define a very simple mapping for an object (and can inherit like eo files, so define a base class with properties and mappings and you can inherit that etc.). from this file we code-generate the property handling/parsing for the edj file externals data. this way the file you edit can be VERY compact. no one needs to manually write C code like the externals modules have. we auto-generate it then it gets hooked into edje at runtime. cool thing - apps can use the same generator to write their own externals and property handling and add that to their app build and register it at runtime to have custom externals.

we'd essentially cover all the points:

  1. it's EASY to write (if you have to tag properties in eo files, how is this much different from listing the property in another file? a little more work but now it's separated from eo/eolian, and imho overall simpler and cleaner, though maintaining that separate file with mappings does mean a bit more typing etc.).
  2. it doesn't involve lots of text parsing at runtime - you do need to strcmp and so on properties and probably parse their string values (as you don't know type at edje_cc compile time - unless we force tagging of type too).
  3. it keeps restrictions in place and doesn't allow blanket access.
q66 added a comment.Jun 16 2017, 2:07 AM

That sounds potentially sensible to me. EDD is coming up soon so we can also discuss the details more there.

Raster, your security concerns are really valid and worth enforcing.

However my suggestion is not less secure than that, it's not the input EDC that's resulting in random dlopen() and FFI. Similar to current EXTERNAL implementation, we can restrict where modules are looked up, if looked up at all (we may just ask currently loaded libraries to register exposed widgets and that's all that's allowed, no dlopen at all). Then for each source: "name" and params (properties), you use the provided information, nothing else is allowed. Actually if we do it in binary we just write the actual function name and let the linker fill it, you just call that pointer, no FFI involved.

Separate files are cumbersome, they're not tied to the object, then it's MORE work, not less... for everyone, packagers, buildsystem, and runtime.

To be clear, what I'm talking about is eolian_gen to write at the end of each .eo.c something like:


static const Efl_Class_Description Efl_Class_Description_My_Class = {
    .doc = "... whatever user described in .eo ...",
    .methods = [
       (const Efl_Class_Method_Description){
         .name = "method_name",
         .fn = method_name, // C symbol that was previously generated and exposed in .eo.h
         .return = {
           .type = Efl_Type_Description_Eina_Error, // previously emitted...
           .doc = "... what was documented ...",
         },
         .params = [
            (const Efl_Class_Param_Description){
               .name = "str",
               .type = Efl_Type_Description_String, // previously emitted...
               .doc = "... docs here ...",
               .optional = EINA_TRUE,
            },
            { } // null terminator
         ]
       },
       { } // null terminator
    ],
};

IOW, that's exactly the same information eolian stores in its database and what user types in .eo, then in runtime things such as edje external would do:


const Efl_Description *d = efl_description_get(obj); // basically: obj->description
const Efl_Method_Description *m;
for (m = d->methods; m->fn != NULL; m++) {
   if (m->is_property) {
      if (strcmp(m->name, pname) == 0) {
          parse_and_call(obj, m, pvalue); // pvalue is the string from edje external params...
      }
   }
}

We can make things such as .doc members #ifdef-able so we shrink binary sizes if that's of worry... and of course we should make some functions to deal with those structures so we can change the layout a bit when that's needed without breaking users (API version in the structure, etc).

parse_and_call tho will need ffi. properties can have arbitrary parameters for the prop... it depends in the signature in the .eo... and if you allow all methods or properties to be defined in edje files... then it's as i said above... a security issue. as you would allow parent class props as well to be access. not everything is a property. it could just be a regular method too.. you still nee a whitelist.

cedric lowered the priority of this task from TODO to Wishlist.Jul 10 2017, 3:43 PM
zmike edited projects, added Restricted Project; removed efl.Jun 11 2018, 6:57 AM
bu5hm4n edited projects, added efl: layout engine; removed Restricted Project.Jun 11 2018, 8:24 AM