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).

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




>> 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.

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.


>> > > 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 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.

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.



>> > > > 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 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.


>> > > 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:

 - 
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.


>> 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().

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

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 :-)

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).

;-)


>> > > 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.

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


>> 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.

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).


[...]
>> 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()...


>> 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.


>> 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.

[...]
>> > 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.

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.


>> 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, ...)


>> > > 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.


>> > > 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.


>> > > >> 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...

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.

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.

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

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 };
}


>> 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.


>> > > >> >> >> (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.


>> 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

------------------------------------------------------------------------------
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