On Tue, 13 Mar 2018 10:35:15 -0300 Gustavo Sverzut Barbieri
<barbi...@gmail.com> said:

> On Tue, Mar 13, 2018 at 1:17 AM, Carsten Haitzler <ras...@rasterman.com>
> wrote: [...]
> >>
> >> > > >> for instance, with events rarely we carry payload info, usually one
> >> > > >> queries from the object... like text changed on widgets.
> >> > > >>
> >> > > >> however for promises, with the chaining idea, rarely you should be
> >> > > >> getting the owner object... in the rare cases you need it, use "the
> >> > > >> closure" (void *data), which BTW is specific for each future in the
> >> > > >> chain, then you can chain multiple and "pass thru" the value if you
> >> > > >> need multiple objects (ie: say you need to get a value and show it
> >> > > >> to 2 label objects, you can connect 2 cb in a chain, each with a
> >> > > >> label object.
> >> > > >
> >> > > > actually it's going to be incredibly common. example:
> >> > > >
> >> > > > file_set on an image object... what do you think people want to do?
> >> > > > they want to then SHOW the object or emit a signal to it or
> >> > > > something. they want to initiate some response.
> >> > > >
> >> > > > for exe's and threads the object will not be magically deleted if the
> >> > > > exe exits or thread exits. the object has to exist firstly to collect
> >> > > > the results (i/o, exit code results etc.) - it has fd's it has to
> >> > > > listen on etc. etc. ...
> >> > >
> >> > > not sure if you checked the efl_future (EFL, not EINA) version I told
> >> > > you... the object goes there for you... no need for you, the promise
> >> > > creator, to do that...
> >> > >
> >> > > the promise create should only care about resolving a value or
> >> > > rejecting it.
> >> >
> >> > in this case it'd be resolving the result of an action of an object -
> >> > same as file_set. the object is the key thing here, not the value.
> >>
> >> here, since the loaded data is tied to the object... sure... but the
> >> promise should return none, like "job" or "timeout"... you get a
> >> notification the action completed, not the object that originated it
> >> (ie: job is the mainloop).
> >
> > i agree with you here as there is no action required on the loop object once
> > the promise is complete. because threads and exe's require action (deletion
> > of the object otherwise you'll leak) i think it's key to pass the object
> > here, not the exit code. :)
> >
> > sure. you can pass it with data... but then the user of the api no longer
> > can pass anything else of their own as data as they used up that one value
> > for the object which is going to need addressing as above. the exit code is
> > going to be directly accessible from the passed object so no need to jump
> > through hoops to get it... :)
> >
> > at least in THIS case i think passing exit code is worse than passing
> > object.
> >
> > to boot your "i wand void pointers for threads" argument now has void ptr's
> > for in and out values for threads... and it's likely you don't want the
> > exit code but this value. i can't pass everything... so i have to choose
> > something to p[ass that is the most useful, and if i pass the object, you
> > can get the out pointer that is the "return data" from the thread. thus i
> > think this favours object being the value even more.
> 
> you can pass void* as EINA_VALUE_TYPE_BLOB, the only thing is not much
> can be done about it, just release (free).

that's far less useful than the EO obj handle... :) this can't be done for the
task class because it doesn't know about the thread superclass offering void
ptrs.

> and my position still remains, even for threads... getting the actual
> thread results.

so when a future is bound to an object - which actually most of ours seem to
be (file_set, if exe/thread get in it task class there too, jobs, timeouts),
why hide the bound object? by forcing it to be manually passed by the void
*data we use up the only user-supplied data into the result. it can't be used
by the user for their own data.

exe's produce int exit codes only. threads produce an int exit code AND can
deliver a void ptr as an output value as well (like ecore_thread). how do i
know which the user will want/need? the best way is to p.ass the object and
have the user fetch what they want from it. this can't be done with passing a
value because the value would be defined in the task class, not the thread
class, thus i can only pass int and nothing else. the superclass (thread) cant
go changing the type passed as the value for the future.

> >> take my word, the object is better passed elsewhere. You may not get
> >> it now, but using it a little bit you'll.
> >>
> >> what I'd agree to change is Efl_Future_Cb_Desc. Currently it carries
> >> the obj, but no extra data (as you could hook the data in obj, using
> >> keys...). But I'd not oppose to offer an extra "void *data" there.
> >>
> >> https://git.enlightenment.org/core/efl.git/tree/src/lib/eo/Eo.h#n367
> >
> > for Eo i think this would be incredibly valuable. i think it actually
> > should be the object that originated the promise (if there is an object,
> > otherwise NULL), passed there. not a void *data but actually Eo *... that'd
> > make me a happy clam and then we'd be in agreement. :)
> 
> here is a clarification: usually there is no need to know the "origin"
> of something, particularly since it can be transformed in the next
> future.

i disagree. at least for thread, exe and file_set the origin is key. that's my
point. the fact that the promise is bound to an object and that object affects
the result of the promise (success, failure) and the value (load success or
failure from file_set or return code or return data ptr from an exe or
thread)... makes it for a very different typ-e of promise than you are thinking
promises ONLY are a value.

> what efl_future_..._then(obj, prev_future, cb...) does is bind the
> future to the object, so you get it when called and once it dies, your
> promise is cancelled.
> 
> note that the signature is cb(obj, value), so this can be given a
> method (that accepts eina_value, returns eina_value)...
> 
> the extra void *data can be added, there's no work other than adding
> the pointer to the struct and callbacks... just need to see if it's
> useful or not.
> 
> maybe yes, it was not put to full practice/use yet.

and that is when you find the errors. when it gets used. that's kind of my
point here. the design is basically missing KEY data to the result of the
promise (the object), OR i have to pass obj as the value, OR we force the user
to pass obj with the void *data thus being unable to pass something for
themselves.

> >> > > However, as I wrote before, more than just doing that, allowing the
> >> > > user to control the behavior would be nice, given the very rare cases
> >> > > detach is expected could do 1 prop-set more to get desired results.
> >> >
> >> > right now "you just aren't allowed to do this unless you no longer care
> >> > about the exe". deletion == you don't care anymore.
> >>
> >> here I'm not sure we're talking the same.
> >>
> >> if I delete an exe *without* calling "end()": does it auto-end to you?
> >
> > no. it doesn't. i was mentioning that before as a policy decision/question.
> > right now it doesn't end().
> >
> > alternatives are:
> >
> >   a) it does end() and then just continue destruction
> > or
> >   b) it does end() and wait for the child to exit/finish and then continue
> > destruction
> >
> > right now i opted for the current behavior. you delete... it detaches. for
> > threads this is currently a problem as there is nothing left to join the
> > thread. so "don't do this". i need to figure out a solution to this, but
> > that depends on if policy should change. for threads you can't join without
> > waiting.
> 
> in the destructor if we join/wait, even if it blocks, it would make
> lives simpler, particularly for shutdown-cleanup cases (even if it's
> not the *process shutdown*, just some "close this window and bring
> everything with it").

it would, but at the cost of a block - possibly for a loooong time.

> It will block, but there is an alternative for those who want to avoid
> it: efl_task_end() BEFORE and wait the promise to be resolved... then
> delete from there.

should the "your loop will pause - possibly for multiple seconds or even
minutes or indefinitely" be the default? it's asking for pretty serious
side-effects. if the thread or exe doesn't exit at all (it's hung), especially
with the exe as it's external, this smells of being a bit severe to me.

> you can write this in the class description as well as end method
> documentation.
> 
> 
> >> It's similar to my Efl.Io.Closer.close_on_destructor. That's the kind
> >> of flag I'm talking about... 99% of the cases, "close_on_destructor"
> >> is what you want, whenever you  efl_del()/unref-the-last-ref, it
> >> behaves just like you called efl_io_closer_close(obj).
> >>
> >> For tasks it's the same. 99% (and .9999....) of the cases you want the
> >> task to be ended when you delete the object... ie: once the mainloop
> >> ends, deletes all chidren tasks, which kills all processes... It's
> >> analogous to when you fork() but don't detach. To "fork-a-daemon" you
> >> need to explicitly detach to make it happen.
> >
> > well that's the question. so you say switch to option a) from the current
> > code. it's not unreasonable. I would need to then have the option to not do
> > this like you say. this would be necessary for enlightenment when it spawns
> > apps so when it restarts it doesn't kill off apps it started. :) but either
> > way buried in this thread is this important question and it's the kind of
> > stuff i had hoped to discuss (instead of argv stuff).
> 
> well, that's because you did a very ugly design on the argv thing...
> also, instead of a constructor-only "array<string>" setter and a
> getter you replicated all array manipulation methods in the object.
> Insane :-( instead of a simple "get/set" pair written as 1 property
> you did like 5-6, that catches attention to the wrong prob.

the array is worse IMHO. i did the array first but then when figuring out who
owns the strings and what happens if you get, modify then set... i went for the
above instead as it just was far simpler on the user of the api. less code and
complexity for sure. think about it.

have to create an array. then have to stringshare_add a new string, then append
that, per item, then pass in as array.

  array = eina_array_new(1);
  eina_array_append(array, eina_stringshare_add("arg"));
  efl_task_args_set(task, array);

vs.

  efl_task_args_append(task, "arg");

likewise with modifying args members, clearing the args out, setting them
etc. ...

the path i chose is simpler and less work in C and TBH that is our main
language and target.  

> >> > > > but... once the thread or exe has exited... the object SHOULD then be
> >> > > > deleted. otherwise it hangs around forever consuming memory for no
> >> > > > reason other than to store an exit code and any other data the user
> >> > > > may have attached to it.
> >> > >
> >> > > here you're mixing things. It traces back to the other discussion
> >> > > about invalidate x reference... the object should be invalidated,
> >> > > sure... not deleted as refcounts shouldn't be messed up.
> >> >
> >> > i can't control if someone dels or unrefs the object to 0. i can only
> >> > control what is done about it. i'm talking about that case where the
> >> > object will be destroyed etc.
> >>
> >> as per above. I'd add a end_on_destructor = true (default).
> >
> > indeed i'm thinking this over. the general idea is sane. should we also wait
> > though? that is CLEAN in that it then gets you a return value neatly... BUT
> > it leads to side-effects of loops pausing possibly for indefinite periods. i
> > dislike the block and wait... unless we can guarantee the block will be very
> > short lived.
> 
> as per above, on destructor I'd wait... safer. For those not wanting
> to block, just end() and wait the promise to resolve.

if they have to wait for the promise to resolve that complicates their code if
they don't want to block and just "get out of there". they can do this end +
wait anyway to be clean, but if they are deleting the object... i think they
don't care.

threads + outdata ptrs are a problem though because if we don't cleanly collect
the outdata it won't get freed (there is a race where it gets sent before the
request to end but then doesn't get picked up as a reply).

> >> > > If you ask me, for processes, rejecting on status != 0 is bad since it
> >> > > will be harder to pass the value (there is no standard for you to
> >> > > convert that to an Eina_Error, or "Exception" or "Error" in other
> >> > > languages). And some people do use return status as "0" (all right)
> >> > > but others to indicate the kind of failure (ie: 1 = invalid params, 2
> >> > > = failed resources, ...). These won't be able to use your promise.
> >> >
> >> > but != 0 is that a process failed. "file not found" or "io error during
> >> > cp" etc. etc. - the shell considers it a failure when you do
> >> >
> >> > cmd1 && cmd2 && cmd3
> >> >
> >> > which is analogous to
> >> >
> >> > cmd1.run().then(cmd2.run().then(cmd3.run()))
> >> >
> >> > cmd2 will never run if cmd1 returns non-0...
> >> >
> >> > indeed the return value if not 0 indicates the kind of error. there
> >> > isn't a standard to convert that indeed other than "that's an error"
> >> > which is about all I can do. say it's an error and offer the code.
> >>
> >> okay, you're right but shell also offers:
> >>
> >> cmd
> >> if [ $? -eq 1 ]; then...
> >>
> >> which some people use.
> >
> > that is true... but i think the && is more common - at least for shorthand.
> > that's why the value as an object offers everything. you can get the code if
> > you need to know exactly what it was. :)
> 
> ok, but that's why I'm saying we should output the status(int) and
> then offer a simple future_cb that compares values, converting
> everything else to error...
>
> then you just move this decision bit further, keep your promise api
> simple, users can add a single line to check for non-zero returns.

well isn't that the idea of having automatic decisions on failure and success
so the race/chain stuff just works?

> >> > > that's the reason why the http/promise people use that, so 50x is
> >> > > handled on way, 40x is another, etc. very similar proble.
> >> >
> >> > the problem with exe's is non-zero could be "command not found" too.
> >> > it's an error tat tells you the desired result of the execution failed
> >> > somehow... it's pretty coarse. :(
> >>
> >> here it's something interesting. "command not found" is not triggered
> >> *after* the command runs, OTOH: exec() fails... this is one of the
> >> cases where you're reject the promise. The other is when fork() fails.
> >
> > indeed there are various stages. if fork fails that's another error point
> > that we can express as something other than an exit code. but if exec fails
> > - our "return a value" path, (unless we use a special side-channel like
> > another pipe) is the exit code, so that has to express "exec failed" too. :(
> 
> bit painful, sure... :-/
> 
> 
> >> So basically, if we follow the promise/http convention:
> >>
> >>   - fork or exec failed: reject
> >>   - process exit (0 or not): resolve
> >
> > exec may succeed but then runtime linker may fail. (missing libs/symbols).
> > init of process may fail (missing data files) etc....
> >
> > i think it's hard to have a clear dividing line here like http. it's pretty
> > much all "something failed" except fork which we can do differently (but
> > fork failing is going to be so rare... it's barely worth differentiating,
> > but we do right now as run() would return false, or a NULL future ... and a
> > question - is returning NULL as a future ok?)
> 
> if you return NULL as future everything in the chain is cancelled
> automatically. So:
> 
>   eina_future_chain(NULL, cb1, cb2...);
> 
> will call cb1, cb2... with EINVAL:

i was assuming null return was ok for "we can't do this". :) it is, so that's
good.

>  -
> https://git.enlightenment.org/core/efl.git/tree/src/lib/eina/eina_promise.c#n935
>  -
> https://git.enlightenment.org/core/efl.git/tree/src/lib/eina/eina_promise.c#n929
> 
> Of course these will be called in the current context/stack as there
> is no future so you can't get a scheduler to dispatch from the next
> mainloop iteration.

interesting gotcha there. so you can't guarantee the promise is called "only
with platform code below it". :(

> >> Since we lack easy to use lambda, what we can use is a future comparison:
> >>
> >>     eina_future_cb_compare_to(other_value, enum eina_compare_result);
> >>     enum eina_compare_result { less, less_equal, equal, greater_equal,
> >> greater, different };
> >>
> >> Usage:
> >>
> >>    eina_future_chain(efl_task_run(t),
> >>       eina_future_cb_compare_to(eina_value_init_init(0),
> >> eina_compare_equal), my_actual_future);
> >>
> >> with that, if task ends != 0, it's failed.
> >>
> >>
> >>    eina_future_chain(efl_task_run(t),
> >>       eina_future_cb_compare_to(eina_value_init_init(0),
> >> eina_compare_different),
> >>       my_actual_future);
> >>
> >> with that, if task ends == 0, it's failed.
> >
> > callbacks will do i think. but the problem is the practicality of knowing
> > exactly the error when our pipeline for error data is just a number that is
> > the same value as the real process running may use. we just have a
> > convention of 0 == all ok. :(
> 
> ok, but what about moving this further? This
> eina_future_cb_compare_to() is very simple to write and very easy to
> use.
> 
> You can then just write in the documentation:
> 
>    This method returns the status/exit-code of the process. Usually 0
> is success, everything else is failure. To convert non-zero results
> into rejection, use:
> 
>     Eina_Future *f = eina_future_then(efl_task_run(exe),
> eina_future_cb_compare_to(eina_value_init_init(0),
> eina_compare_equal));
> 
> While in high-level languages people can bind this, very likely they
> will not do it, as this is simpler (ie: Python):
> 
>    exe.run().then(lambda status: Exception('non-zero exit') if status
> != 0 else 0)
> 
> for C you can even add a helper (ie: macro/inline-function) that does
> that for you, ie:
> 
> static inline Eina_Future *
> efl_task_run_clean_exit(Eo *task)
> {
>    return eina_future_then(efl_task_run(task),
>      eina_future_cb_compare_to(eina_value_init_init(0), eina_compare_equal));
> }
> 
> 
> >> > > 2) there is no need to keep exit_called
> >> > > 3) there is no need to efl_ref
> >> >
> >> > since it's a job - there is. the exe obj may be deleted and thus the job
> >> > called with no exe obj around. well.. there is nothing that tells me if
> >> > the future will or will not be magically cancelled if obj (the data ptr)
> >> > is deleted. as its a generic void * i'm not making that assumption.
> >>
> >> ahhhhh... but You're saying "single line for events"... however this
> >> is not needed for promises! ;-)
> >>
> >> (and if you listen to me, not even the "ref" as it's managed outside).
> >
> > but the loop job is a future... created by the loop obj. not the exe. so if
> > the exe obj is deleted .. the loop job future has no idea... unless you're
> > telling me somehow it does and it assumes that void *data is actually the
> > parent owning obj of the job future... because it returns a future, not an
> > eo obj when u create  loop job. so the owning object would be the loop, not
> > the exe./ if loop was deleted - then sure, but it's the exe here that is
> > the relevant object, not loop. :)
> 
> you'll remove the job, use the future scheduler instead via
> eina_promise_resolve().

so i have to store the job future obj and basically cancel it... right? so
that's storing + cancelling on destructor (and set to null once resolved) or...
just ref + unref in resolve :) 2 ways of dealing with it. but one way or the
other i have to deal with it.

> Upon eina_future_cancel(), scheduled entries are recalled/cancelled.

yup, but have to cancel/resolve on destruction myself because i cant have the
future be a "Child object" of the eo obj and be auto deleted on parent object
destruction. :) one of those reasons why i still dislike promises not being eo
objects.

> remember efl_future_..._then() (EFL not EINA) binds future to the
> object, so once the object dies, pending futures are cancelled, thus
> scheduled entries are recalled... all clean, all easy :-)

you mean efl_future_Eina_FutureXXX_then(obj, job) right? (that is a horrible
function name btw! :) )

> the scheduler doesn't use "job" internally, otherwise it would cause
> cyclic dependency (job is a future, which needs a scheduler, which
> would be using a job). Right now we're still using ecore_event... but
> IMO the future dispatch should be first-class citizen in new efl_loop
> core. with events done on top, not the other way around, since future
> is more common and leaner to implement.
> 
> Ok, but assume there was a job/future, they are chained so:
> 
>    - job(): future1 bound to loop
>    - then(): future2 bound to exe
>    - then(): future3 bound to app
> 
> if any of loop, exe or app dies... the chain is gone:
> 
>   - if loop dies, it calls eina_future_cancel(future1), cancels the whole
> chain;
>   - if exe dies, it calls eina_future_cancel(future2), cancels the whole
> chain;
>   - if app dies, it calls eina_future_cancel(future3), cancels the whole
> chain;
> 
> and note that once one element of the chain is gone, it's
> automatically unlinked... so the "whole chain" there is what is left
> to dispatch. If the promise wasn't resolved, it includes calling
> promise_cancel_cb... if it was resolved, that is not called (of
> course), only the remaining futures will be rejected with ECANCELLED.
> 
> 
> 
> >> > > 4) there is no need to efl_job, promises are already resolved in the
> >> > > next mainloop iteration (clean context)
> >> >
> >> > they don't resolve in-line there and then like callback_call() ?
> >>
> >> exactly: promise delivery requires a clean context. Always... that's
> >> why we need the scheduler... so we know how to do it.
> >>
> >> See https://promisesaplus.com/#point-34
> >>
> >>    onFulfilled or onRejected must not be called until the execution
> >> context stack contains only platform code
> >>
> >> in EFL implementation the only difference is error handling *while
> >> creating the future or promise itself*. That is:
> >
> > ok. cool. so no job needed to delay it. docs and examples would be
> > incredibly valuable here.
> 
> https://git.enlightenment.org/core/efl.git/tree/src/lib/eina/eina_promise.h#n631
> 
>  * Resolves a promise.
>  *
>  * This function schedules a resolve event in a
>  * safe context (main loop or some platform defined safe context),
>  * whenever possible the future callbacks will be dispatched.
>  *
>  * @param p A promise to resolve.
>  * @param value The value to be delivered. Note that the value
> contents must survive this function scope,
>  * that is, do @b not use stack allocated blobs, arrays, structures or
> types that
>  * keep references to memory you give. Values will be automatically cleaned up
>  * using @c eina_value_flush() once they are unused (no more future or futures
>  * returned a new value).
> 
> ;-)

i was more thinking examples on producer side with "resolve here. note this
schedules a job in the loop to resolve it later on during the event cycle". or
something like that. i don't look at eina to know about loop scheduling stuff
normally. i look to ecore for that. i wasn't expecting eina promise to even
know about scheduling of the cb.

> >> > > it would help immensely if you give it a try for real... before you
> >> > > implement the promise side (provider), give it a try as an user... get
> >> > > some js stuff, they are full of promise, then you use to implement
> >> > > something, then you come back with your experience...
> >> > >
> >> > > ie: https://github.com/axios/axios (http) + https://redux-saga.js.org
> >> > > (state management) ?
> >> >
> >> > i've seen the code before. well code full of then's etc. - i know the
> >> > syntax sugar. i'm not talking about the "consumer" side. i'm talking
> >> > about the "producer" side inside efl.
> >>
> >> producer side is a PITA in JS as well, not as much since they don't
> >> offer cancel AND they use "closure" to give you the context, so you
> >> don't need "Eina_Promise", instead they give you 2 functions
> >> (reject/resolve) bound to the promise context... they call it
> >> "Promise", but it's actually our "Eina_Future" since their
> >> Eina_Promise equivalent is hidden.
> >
> > and you now agree with me on the PITA of the producer side even over
> > there... :) i'm glad we finally agree on the original point. :) making it
> > less of a PITA and closer to the ease of events would be really important
> > IMHO.
> 
> I never said it was not a PITA, just not agreed it is as big as you
> think. Now that you're more aware of it, I bet you can implement it
> and see what I'm talking about.

there need to be more examples. or clear ones on the producer side. as i said -
i was looking at ecore for that and those were long and painful.

> OTOH as an user... it's much, much simper!

i think it's much of a muchness from the user side. i'm not so hot ans sweaty
for them on that end. but as i said.. this is about the producer side of
things. :)

> >> but point is: do it where the most expert developers are (ie: efl core
> >> developers), to aid non-expert developers to use it correctly.
> >>
> >>
> >> > in C the code difference for the vast majority of cases is the same - you
> >> > have to provide a callback. in the past i have rarely chained events in
> >> > C. in edje - sometimes. maybe 10-15% of events chain multiple anims. in
> >> > C ... hardly ever. that's my point. it has some syntax value for SOME
> >> > use cases. they are not the majority in my experience. if i had to chain
> >> > things, i did. i didn't avoid it "because it was hard". it wasn't. in
> >> > the callback just have 1 line to do the next step
> >>
> >> You're overlooking 'chaining' here... in EFL we do chain *a lot*, but
> >> it's not explicitly done as multiple simple functions. Rather we
> >> create big callbacks that does it all:
> >>
> >>
> >> bla_cb(void *data, ...) {
> >>     My_Ctx *ctx = data;
> >>     const xpto = a_thing(ctx->a);
> >>     b_thing(ctx->b, xpto);
> >>     ...
> >> }
> >
> > i don't think we do much of this. most is just "call func x that eg. emits a
> > signal or shows an object, etc." at the end of 1 event. e.g. aysnc preload
> > of an img or when a signal comes in from an edje object etc. (these are not
> > actually even promises as they are actually rely events and can happen
> > multiple times, but i'm going to include them in the example here for the
> > purposes of discussing chaining or not). my point is your example above is
> > more thab a_thing is not another promise creating thing. at least not in efl
> > commonly from my recollection.
> 
> we don't even notice, first because we're used to write as one single
> callback and second because not used to futures...
> 
> if futures were there for long time and and we used it, likely your
> view would be different.
> 
> as I said, we can even help people to design UI connections more easily.
> 
> and once you're used to it, you'd want some. "event to future
> creator". See an old email where I propose that, something like:
> 
> efl_future_factory(obj, EFL_X_EVENT_TRIGGER,
> EFL_X_EVENT_MEANS_SUCCESS, EFL_X_EVENT_SAYS_FAILURE, future_handler,
> data);
> 
> void
> future_handler(void *data, Eina_Future *future, Eo *source, Efl_Event
> *success_event, Efl_Event *failure_event) {
>     eina_future_chain(future,
>       download_file_cb_future("http://bla.com";, ...), // output string
>       json_parse_cb_future(), // outputs Efl_Model
>       {my_obj_use_json_cb_future, data},
> }
> 
> things like "download file_cb_future" and "json_parse_cb_future" will
> start to appear once models and future are more widely used. Then all
> that an app has to do is to use them and implement the actual work,
> knowing it will get called with actual data or error, etc
> 
> in efl_future_factory() above it takes 3 events: one that means "it's
> resolved" and the other "it's rejected", and one to trigger the
> promise + future creation.
> 
> trigger creates promise + future pair, register success/failure event
> callback providing promise to be resolved and call future_handler.
> 
> if success event: resolve, unregister event callbacks;
> if failure event: reject, unregister event callbacks;
> 
> what's missing? a way to convert event->info to Eina_Value...
> currently event->info carries *NO* runtime information on its
> contents... you're left with "void *" and the developer must check in
> the docs what's in there.

the eo files know what the info is. so there is runtime info in libeolian
actually :)

> my proposal was to store an Eina_Value_Type and the likes in
> Efl_Event, so one can convert "void *info" to Eina_Value... It
> wouldn't impact users, as this would be optional and stored only once
> for Efl_Event (static-const, generated by eolian). Then
> efl_future_factory() event callbacks would use that to convert to
> Eina_Value and call resolve.
> 
> The idea is to wrap existing events, like efl_io_copier done...
> (copier was done before promises, maybe a more correct approach would
> be to rewrite to return a future, but I don't think we're going to
> rewrite most of EFL that is meant to be a future to that... due legacy
> and all).

my take is that it's still much of a muchness, but adding a whole bunch of
more code, and infra needs to be thought about before adding it, if the value
is really there.

i know we have 2 camps on promises/futures. there's your cam of "yay! promises!
awesome" and "meh. whatever. events work fine". both camps have multiple
members.

i fight things that i really don't like, but i've given promises a lot of room
to prove themselves or not. i'm in the latter camp. i'm willing to deal with
them, until they become a barrier/pain/problem.

in this case the "way to use promises as a producer" is basically nigh
impossible to find or figure out. docs are needed. samples at a minimum with
some explanations of what it's doing and why. otherwise people are going to run
into a brick wall of pain and rebel.

> [...]
> >> EVAS_CALLBACK_MOVE: payload NONE, where to get the position? "obj"
> >
> > errr.. the payload was the event coords in the struct. the coords were
> > absolute canvas coords like all other events. also the payload was the
> > target object the event happened on as well... ?
> 
> was it? I recall having to use evas_object_geometry_get()...

that's if you want the geometry of the target object which is needed to
translate absolute coords into object relative coords. this is needed if you
want coords relative to THAT object. if it's relative to another object then
you have to translate relative to that one. sometimes relative coords are
needed, sometimes not. for things like dnd you absolutely want abs coords. for
relative - it depends which obj u want them relative to.

but coords are absolutely delivered in the event info.

> >> EVAS_CALLBACK_MOVE: payload {x,y}... no use for "obj".
> >
> > umm... you are in fact incorrect here. events have 2 main payloads. the
> > event the object is for and the event data. both of these were passed.
> >
> > sample: efl_selection_manager.c:4405
> >
> > see:    Evas_Event_Mouse_Move *ev = event_info;
> >
> > and:
> >
> >         dx = ev->cur.canvas.x - dc->down.x;
> >         dy = ev->cur.canvas.y - dc->down.y;
> >
> > ummm... so not sure what you are remembering but values were passed. the
> > target object too. AND custom void *data ptr as well provided by the
> > callback user to store context of their own. you'll also find they all want
> > the object because that is where they store context of the event... e.g.
> > the original mouse down coords so they can detect if the drag has moved
> > more than N pixels from the down coord to indicate a drag has started... :)
> >
> > promises should pass the same.
> >
> > actually...
> >
> >    Eina_Value (*success)(Eo *o, const Eina_Value value);
> >
> > the Eo *o - is that means to be the target object there? the problem is
> > this Eo *o isn't passed to the eina promise cb... :( it's lost.
> 
> this "o" is not the one that originated "value", rather the one given
> at efl_future_..._then() (can be the source object or can be another).
> 
> in our tests it was enough to store *data in *o (ie: key), but
> thinking about multiple future of the same kind, there is no way to
> distinguish between them... so likely missing the void *data.

so o there is the obj that also will cancel the future on deletion. in my case
at least they are the same obj here. originating obj. i don't know if there
will be a need to have different ones. there may be.

> >> also, unless using very high level components that dispatch to
> >> multiple children widgets, it's not always the case you're using the
> >> same object down in the pipeline.
> >
> > indeed. with event propagation this can be an issue. i remember bringing
> > this up before that perhaps we should not just have obj for where the event
> > happened but an originating obj where it originally happened so you can
> > know it was propagated and even where it propagated from... :)
> >
> >> click button -> navigate to other screen: different objects.
> >> click button -> close window: different objects.
> >> type text -> enable "proceed" button: different objects.
> >
> > sure. this is where you may use data ptr to pass in "Delete this obj on
> > close" etc. but for the exit stuff fro threads and exe's i think the obj is
> > key. see above - is the Eo *o there the object the future was for?
> 
> no, it's not the source... you can provide the source *if you want*.
> 
> the efl_future variant is more like: bind this future to this object,
> and call methods on this object.

i think the futures are missing a source obj. that i think is the key here.

> [...]
> >> > the exited flag i guess is superfluous as
> >> > pd->fd.exited_read being -1 says the same thing, though this has nothing
> >> > to do with promises or not. it's part of the race fixup above between
> >> > exit signal and the stdout from the child being all read and flushed.
> >> >
> >> > > if you ask me, the real PITA with promises in C is lack of lambda.
> >> > > This is the real pain, having to declare the callbacks OUTSIDE of your
> >> > > context, not being able to capture...
> >> >
> >> > that's actually why i think syntax-wise they don't come out ahead in C.
> >> >
> >> > > I had proposed to implement c++ lambdas in C as a pre-processor... but
> >> > > nobody wanted to pay.
> >> >
> >> > there already is a pre-processor that does this. it's just a matter of
> >> > using it. lambdapp instead of gcc or clang or cc ... :) the problem is
> >> > more that some people have major objections to having a preprocessor.
> >> > "it's not C anymore if you need a different compile command than your
> >> > normal c compiler" is the summary.
> >>
> >> it's not, there is no capture... which is a major usability drawback.
> >
> > you mean closures? a lambda at a minimum would be good. it'd save footwork
> > of moving small callbacks elsewhere in code and jumping around to follow
> > them.
> 
> yes, multiple names... the captures is what's inherited from the
> outside scope and ends as "context" for your lambda.

yup. the name made total sense to me for defining the precise scope vars to
"capture".

> you can have copy (value) or reference (pointers)... so say an "int",
> if passed by copy/value, changes won't be reflected outside.
> If by reference/pointers,once the callback finishes the last value
> will be reset and visible outside.

yup. that leads to the complexity of copy or reference. if copied - how do u
copy an Eo *? if references, how do u keep the life of an int *x alive?. :)

> >> Like with c++, my proposal would capture values by value or reference
> >> (pointers) depending on your declared mode, they would be set in the
> >> callback and once the callback finishes, if "by reference", they would
> >> be set to their pointers so they're reflected outside.
> >>
> >> I'd also add explicit Eo* support, so these are auto-ref/unref.
> >
> > that'd be a far more expansive thing than a lambda IMHO. that's more a
> > closure with selective context capture. that would even better indeed, but
> > i'd be happy as a pig in mud just with lambdapp. :)
> 
> this is what c++ and all other lambda frameworks offer.
> 
> lambdapp is crap, since you're adding a pre-processor, just make it
> specific to EFL and you can get lots of benefits... like you can
> implement captures and handle our types (stringshare, eo, array, ...)

a pre-processor that just adds lambdas is a good START. once you have it you can
add more features to it later... :) getting it into the pipeline to begin with
is a huge step. :)

> >> > > and that's why I'm insisting so much on the value thing... if you do
> >> > > have a proper value for exe, then you can get this. With that you can
> >> > > even do some UI editor or save this as as part fo eet (ie: Gstreamer
> >> > > offers "gst-launch" to test stuff, can load pipelines from xml...)
> >> >
> >> > since the actual value of return is don't generically interpretable
> >> > except 0 == success, anything else == failure... i don't see a lot of
> >> > value in the "exit code" value. the object is of far more use than the
> >> > exit code.
> >>
> >> okay, this is more like a convention... HOWEVER I'm pointing similar
> >> cases that after some thought moved to what I said.
> >>
> >> if you look at the
> >> https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
> >>
> >> The fetch specification differs from jQuery.ajax() in two main ways:
> >>
> >>   * The Promise returned from fetch() won’t reject on HTTP error
> >> status even if the response is an HTTP 404 or 500. Instead, it will
> >> resolve normally (with ok status set to false), and it will only
> >> reject on network failure or if anything prevented the request from
> >> completing.
> >> ...
> >>
> >> jquery is the "first experience", it improved usability over
> >> traditional XMLHttpRequest a lot (which uses events/callbacks)...
> >> first version was exactly what you propose, but after some usage they
> >> noticed it was being a pain and the "standard" became to pass the
> >> status code as resolved (not rejected).
> >
> > there is a big difference. http error codes are clearly defined with
> > meaning... at least exit codes of tasks are only defined as 0 == success,
> > anything else, failure. :( you still could get the exit code in the error
> > case and choose what to do... if the obj was passed - get get it and
> > decide, but for promise chains or races you have to decide at what point is
> > something a failure or success so the race/chain does the right thing. for
> > that case i think 0 == success, anything else failure is correct (cmd1 &&
> > cmd2 && cmd3 ... chain like thing). if you want to explicitly decide based
> > on specific error codes you can chain etc. by hand.
> >
> > one option could be to provide an interpreter table. by default 0 ==
> > success is always there. but you could add more error code values that
> > could be interpreted as success. then the task class will map those
> > specific values to success for you so the chains/races then do what you
> > want. you can always do it by hand like above too. i don't think we can by
> > default interpret like http because there is no meaning other than success
> > of failure by convention and failure reasons are undefined other than
> > "failure" in general (unlike http).
> 
> see my "compare-to" above, 1 line and let the user do that.

well they have to provide some converter func callback and then the contents
of that func. which most likely will be:

if (code == x) || (code == y) ... etc then return success; else return fail;

> >> > > anyway, you can explicitly add the future to do that OUTSIDE, which
> >> > > would enable things like this:
> >> > >
> >> > >   eina_future_chain(some_future_creator(x),
> >> > >       efl_future_cb_unref(obj1),
> >> > >       efl_future_cb_unref(obj2),
> >> > >       efl_future_cb_unref(obj3)
> >> > > );
> >> > >
> >> > > this would unref all 3 objects once the future dispatches... note
> >> > > efl_future_cb_unref() doesn't exist, it need to be created like the
> >> > > other helpers that returns Eina_Future_Desc...
> >> >
> >> > then you can only pass in obj1/2/3 via data ptr and then you cant use the
> >> > data ptr for "your own data". :(
> >>
> >> yep, in such case. But for most real world usages there is a "ctx" (or
> >> globals)... So either you create a ctx, use what likely exists, etc.
> >
> > globals are a horrible answer to this IMHO. context is better but that only
> > applies yto js/lua like langs where you get a full closure. it doesn't
> > apply to c and c++ where you have to explicitly pass etc.
> 
> in c++ lambda provides capture.
> 
> in C you can use blocks from GCC/Clang... not sure about Microsoft.

i didn't think gcc supported blocks, just nested functions?

> >> > > >> cancelling the task = sending term/kill signals or i/o commands.
> >> > > >
> >> > > > which may eventually result in an exit from the process which then
> >> > > > will result in the current exit event (or the promise success/cancel
> >> > > > callbacks if those were used).
> >> > >
> >> > > and isn't it the desired? Just note that since the promise is already
> >> > > rejected with ECANCELLED, it won't be rejected anymore...
> >> >
> >> > i'm not sure sending an int signal == cancel. it does say stop to the
> >> > process. interrupt. but ... it may not mean the process failed. it may
> >> > have done its job and was waiting around for more messages...
> >> >
> >> > i think the success or failure of the promise in this case would be
> >> > based on the exit code alone.
> >>
> >> not really. If you "cancel the future"... you cancelled the future,
> >> that's what the function is called :-D
> >
> > indeed cancelling is another error path. i think that the promise callback
> > data/signature etc. passed is actually too limited. that;s the problem. i
> > have to make too much of a compromise in choice of what is delivered. if
> > it's an error i only have specific error defined types to use.
> 
> you can define your own eina_error...

it's more a problem that an error code from an exe could be any value pretty
much so some values will overlap with other eina error values thus be ambiguous.

> the only issue is that we went with posix-like errno approach: no
> payload... like when you open('...') you end with a number, not
> details... you can get some reason, like permission, but no info where
> the permission failed.

that's about all we have with exit codes too.

> worked for posix... likely to work for us... I'm a Python guy and
> exceptions do bring more data there. In JavaScript doesn't bring much
> (usually). But in Python the major use is just for pretty reporting,
> at the end you "deal with error" and that's it, eventually error
> type... not much payload.
> 
> 
> > if it's success the
> > value is always 0 anyway really (except see above with mapping tables). it's
> > far too limited and if the object was passed then those limitations go away
> > and all that data is available. i don't want to make a "sophie's choice" as
> > to which data gets killed/dropped or harder to hunt down... the object being
> > passed is my way out, but only on success. on error the eina value has to
> > be an error type thus no obj... argh. so even if the eina value was an int
> > in success, it'd be pointless for error/failure, which means everything has
> > to succeed even if "command not found" because that is also just an error
> > code (not distinguishable from linker errors or init errors or a clean
> > error exit code).
> 
> Again i request you to use it a little bit... the information is
> passed elsewhere and it's what you're missing.

i have to use the void *data to pass the obj then and i cannot use it to pass
something else of mine. i don't have to use promises to know about this choice.
i've had to make it often enough with events where i can only pass one pointer
to one thing, and i wanted to pass more.

> you are correct, the obj maybe useful in some cases. But stop thinking
> as "single shot big callback", this is the problem. Think "can I
> achieve this with smaller, single purpose futures?" The answer is yes.
> 
> As I wrote before, your main case is "I need to unref the exe once it
> finishes executing":
> 
> eina_future_chain(efl_task_run(exe),
>    your_real_work_here,
>    efl_future_cb_unref(exe)); // <-- this future_cb doesn't exist yet,
> we should create to make it single-line

hmmm. that could work. if you rely on RAII in c++ and gc's in js/lua etc.

> with that you can do more, like unref *OTHER* objects, not just the
> exe. Also note that if you return the results of "eina_future_chain()"
> here, you STILL get the exe unreferenced:
> 
>   eina_future_then(previous_chain, one_more_future);
> 
> It will look like:
> 
>    your_real_work_here -> efl_future_unref -> one_more_future_here
> 
> Code is actually simple:
> 
> // Eo.h:
> 
> Eina_Value
> efl_future_unref(void *data, const Eina_Value value)
> {
>    Eo *o = data;
>    efl_unref(o);
>    return value;
> }
> 
> static inline Eina_Future_Desc
> efl_future_cb_unref(Eo *o)
> {
>    return (Eina_Future_Desc){ .cb = efl_future_unref, data = o,
> .storage = NULL };
> }

that would indeed  work.

> >> and this not related to processes or what is resolving...  it's a
> >> convention and most people can just throw away everything once
> >> ECANCELED.
> >>
> >> What you mean is different: if you explicitly efl_task_end(obj), then
> >> SURE, signal is sent to the process, which finishes and that is
> >> resolved/rejected as the future.
> >>
> >> different semantics even if the eina_future_cancel() happens to use
> >> task_end in your case. That's a impl detail that shouldn't leak.
> >
> > if i was using a future i'd have to add handling of cancel for sure then to
> > do the end... :)
> 
> why? it's just the reject path... same you'd handle for fork() errors, etc.

yes. i have to add a function that eina_future_cancel calls that implements
the cancelling... that is what i mean by "add handling". there needs to be
code to go request the exe or thread to exit if the user cancels the future.

> >> > > >> >> >> (and of course you are not describing it in the .eo file). I
> >> > > >> >> >> don't even see how you can compare this two lines ? And we are
> >> > > >> >> >> not even looking at the user of the API here which is what
> >> > > >> >> >> matter even more. How do you make sure that the event is
> >> > > >> >> >> always delivered properly ? How often do you generate the
> >> > > >> >> >> event (Every time someone register a callback) ? There is a
> >> > > >> >> >> lot of open question with this kind of API that you are just
> >> > > >> >> >> disregarding here.
> >> > > >> >> >
> >> > > >> >> > it's called when the parent process knows the child has exited
> >> > > >> >> > and what the exit code is. there is no guarantee this happens
> >> > > >> >> > during the life of the object at all. the process could become
> >> > > >> >> > hung on a kernel syscall and never exit, ever. that's what the
> >> > > >> >> > exit event is. when this happens. it's guaranteed to be called
> >> > > >> >> > when this event occurs.
> >> > > >> >> >
> >> > > >> >> > my point is the event is 1 line. futures is a massive blob of
> >> > > >> >> > code to achieve the same goal. i find them basically unusable
> >> > > >> >> > to the point that i'm just avoiding them now. if they were "1
> >> > > >> >> > liners" too... then great. but they are not. (well they will
> >> > > >> >> > be a line to create and store, a line in a destructor to
> >> > > >> >> > cancel on destruction if still there and another line in the
> >> > > >> >> > "where the event happens" to either call success or failure
> >> > > >> >> > with the future data... but it needs to be far far far simpler
> >> > > >> >> > than it is.
> >> > > >> >>
> >> > > >> >> you just realize that the code is always the same, right? there
> >> > > >> >> is no magic, we're just doing something on behalf of users, in a
> >> > > >> >> clear and standard way.
> >> > > >> >
> >> > > >> > ummm it's not the same. its far longer. even if its half of what i
> >> > > >> > described it's still far longer than an event.
> >> > > >>
> >> > > >> I'll not even bother to explain it's doing more on user's behalf...
> >> > > >> I tried, Cedric tried, seems you don't want to see.
> >> > > >
> >> > > > i know that.. i'm not talking about the outside api to users. i'm
> >> > > > talking about the inside api to efl to drive them.
> >> > >
> >> > > let's start with fixing the code? Once it's correct we can think about
> >> > > adding some helpers. the one efl_promise_new(), that creates a promise
> >> > > using an efl_loop_provider object as argument looks like a good one to
> >> > > start with.
> >> >
> >> > so what code is needed instead of calling the exit event and the end of
> >> > run() to create the promise? what minimal code is needed?
> >> >
> >> > in run()
> >>
> >> _run(...) {
> >>    if (pd->promise) return NULL; // do not run twice
> >>    pd->promise = efl_loop_user_promise_new(obj, _run_cancel_cb, obj);
> >
> > you mean efl_loop_promise_new() right?
> 
> yep, sorry.
> 
> 
> >> // efl_loop_user_promise_new() must be created, only exposed in C
> >>    f = eina_future_new(pd->promise);
> >>    // your code to actually run the task, if fails:
> >> eina_future_cancel(f), return NULL;
> >>    return efl_future_Eina_Future_XXX_then(obj, f); // will be done
> >> automatically by eolian in the future
> >> }
> >
> > oh... so if it's efl_loop_promise_new() - there's no samples of that being
> > used in ecore. i had to look. found some in efl_io_manager.c ... thus my
> > point about docs... i was looking at the other promise code in ecore that
> > returned promises (like jobs/timeouts). and that was not pretty.
> 
> I didn't add that one, it was someone else that added that helper, maybe
> cedric.

that's what i was looking at as it was the nearest example of something
producing a future/promise as a return and driving it etc.

> >> well, some time I dont touch efl, but we wrote the efl.io/net examples
> >> using timeout futures... and was working.
> >
> > you only had them on a single thread and a single loop. :) i already noticed
> > promises were written to cancel ALL promises when a loop is destroyed, not
> > just the ones bound to that loop... :) i fixed that. i bet there are other
> > nasties somewhere...
> 
> because it was done pre-multi loop... it was done on top of events,
> events were global...
> 
> as I said the schedule_entry used by future dispatch should be handled
> as first-class citizen... it's simpler than events, as it's
> non-recurring and there isn't much you can do about it, so no need to
> lots of bookkeeping (no "is deleted", etc... it's only called once,
> not renewing, not able to cancel itself from the callback).
> 
> events could be done on top.
> 
> -- 
> Gustavo Sverzut Barbieri
> --------------------------------------
> Mobile: +55 (16) 99354-9890
> 


-- 
------------- Codito, ergo sum - "I code, therefore I am" --------------
Carsten Haitzler - ras...@rasterman.com


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
enlightenment-devel mailing list
enlightenment-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/enlightenment-devel

Reply via email to