Since I wasn't proposing to get rid of subscriptions and since this
functionality is covered through subscriptions, my proposal arguably
doesn't cause a problem. But that's just getting through the loophole of
this specific example to dodge this particular case, so let me see if I can
get straight what the special property is that we need to preserve if we
were still trying to implement a tick command in the same manner of the
0.16 tick effect. (It's worth noting as a side note that in 0.16, all
effects other than tick were tasks, so obviously tasks are close to
covering the role of commands — née effects — but tick may point to extra
wrinkles.) I'm not going to assume a constraint by the current task
implementation, but any revised implementation has to have a clear behavior
in conjunction with the existing task APIs since its those APIs that make
tasks an attractive alternative to commands.

The special behavior for tick wasn't particularly documented for the 0.16
Effects module and isn't really documented for the 0.17+ AnimationFrame
module. Am I correct that the concern is that when the animation frame
"arrives":


   1. We run all of the update functions before actually calling the view
   function; and
   2. Any tick effects that result from those updates will be batched
   together for the next animation frame

Was there anything else that I'm missing?

The first point means that any implementation is going to have to be built
in conjunction with the Elm runtime since coordinating with the call of the
view function requires knowledge of when and how the view function gets
run. But since the whole point of this is to coordinate with the view
function that shouldn't be surprising nor is it particularly odd. It does
mean that my simple example using port tasks wouldn't work because it
wouldn't enjoy that coordination, but extra requirements add extra
implementation details.

So, what we want is that we can divide time up with a series of animation
frame events and at each animation frame event, we want to start a new
epoch for collecting tick effects, run the updates driven by all effects
collected for the previous epoch, and then do the view rendering. A naive
implementation will just keep listening for all animation frames. A more
sophisticated implementation will take steps to shutdown that observation
when it is no longer necessary.

The vague handwaving in the above is seemingly around what it means to say
"run the updates driven by all effects collected for the previous epoch".
In particular, how does this interact with task chains built using andThen,
etc.? I am going to make a distinction between two ways to specify
successor computation on a task. In one case with map the results or the
error coming from the task to produce a new result or error. In the other,
we map the results or the error coming from the task to produce a new task.
I would argue that the first category is what leads to actual messages for
the model. The second can be viewed as making no changes to the model but
queueing new task executions.

So, how could this work in a task-based scenario? I'm going to speak of
tasks initiating and resolving to distinguish between when they have code
invoked v when they deliver values.

When a tick task initiates, it adds itself to the set of tick task
executions for the current tick epoch.

When we receive an animation frame call, we do the following:

   1. We grab the contents of the tick epoch set and reset the set to empty
   2. For each of the tick executions in the tick epoch set grabbed in (1),
   we resolve the execution using the current time. This means running down
   the network of tasks chained onto the executions. For cases where we map
   the result or error to a new result or error, we do so and keep working
   down the chain. For cases where we map the result or error to a new task,
   we queue that task for execution.
   3. We execute the view function.

In this way, we deliver all of the updates that are simply dependent on the
tick event or a mapping thereof while initiating any further computations
that were waiting for the tick event.

The other piece of concern is how task queueing works and when tasks get to
execute. I would probably go for a structure on the task queue in which it
is also divided into epochs. We process all of the tasks within an epoch.
Any tasks produced during this processing as a result of andThen, etc. go
in the next epoch. All tasks produced during an update go in the current
epoch which we process at the end of the update. This design keeps chains
of tasks from blocking other update processing. We could go further and
pull tasks generated by tasks off of a queue that executes even more
incrementally. The key point is that all of the tasks from an update get
initiated immediately following the update and tasks initiated from other
tasks happen as we have time to process them.

In the case of ticks, this means that a tick task that initiates a chain
will execute immediately following the update but a tick task that is
initiated from some other task might get run at an arbitrary later time.
(How arbitrary determines whether we can do things like write Task.tick |>
Task.andThen (always Task.tick) in order to wait two ticks as opposed to at
least two ticks.)

To make animation frame updates as efficient as possible, we probably also
want to avoid draining the task-initiated task queue until after the view
function is run. That's not critical to the semantics but it could matter
for performance.

Now, maybe I've missed some other detail that matters, but as I've said the
documentation for both Effects.tick and AnimationFrame is relatively thin
on detailed semantics and requirements. But if I've gotten the concerns
right, then I believe the above shows how a task-based system could do what
is called for here. Am I missing something? I know I'm handwaving through
some of the execution machinery, but my intuition from writing lots of
promise systems in Lua says that this sort of structure would work.

Mark

On Sat, Dec 10, 2016 at 10:20 PM, Janis Voigtländer <
[email protected]> wrote:

> In the case of ticks, what I gather from a read through of the code that
> it does is guarantee that all of the tick requests placed between animation
> frame strobes will all be delivered at the next animation frame strobe. Is
> that a correct read?
>
> Yes, that is a correct read. But no, I don’t think that your post shows
> the same can be done with tasks instead of commands. The reason is, as
> previously mentioned, the existence of andThen for tasks. And your “P.S.”
> does not address this, because you are not there considering what the real
> complication with andThen is. The complication is not whether a tick task
> involved in two andThen chains is run once or twice, the complication is
> how to deal with the “continuations” in those two chains. Let’s look at
> this with some example:
>
> In the Effects/Cmd world, using ticks would be like this:
>
>    1. At some point, a command tick tagger1 reaches the runtime system,
>    where tagger1 : Time -> Msg for Msg being the program’s message type.
>    The runtime system will not do anything at that point, except for
>    registering in some internal state that a tick request was issued, and that
>    the tagger to use for it is tagger1. So essentially, the runtime
>    system (specifically, the effect manager) at that point stores the function
>    tagger1 in some list.
>    2. At some point after that, but before the next animation frame
>    happens, a command tick tagger2 reaches the runtime system. At that
>    point, the effect manager adds the function tagger2 to said internal
>    list.
>    3. Some time later, the next animation frame is due. So the runtime
>    system looks at its internal list, sees that there are [tagger1,
>    tagger2], takes the current time stamp t, evaluates msg1 = tagger1 t
>    and msg2 = tagger2 t, and passes msg1 and msg2 to the program’s update
>    function one after the other *but without any intermediate view
>    rendering*. If the update function creates additional
>    effects/commands, the ones created from the calls of update with msg1
>    and msg2 are batched (so that actually the grouping together of
>    updates will propagate to the future).
>
> What about in your hypothetical world in which no Cmd abstraction exists,
> but instead tick has type Task Never Time? Now instead of just using tick
> with functions of type Time -> Msg, tick can be used in task chains, like tick
> |> andThen cont with cont : Time -> Task Never Msg. Let’s look at such a
> scenario:
>
>    1. At some point, a task tick |> andThen cont1 reaches the runtime
>    system, where cont1 : Time -> Task Never Msg. As above, the runtime
>    system shouldn’t do anything at that point, except for registering in its
>    internal state that there is this tick request and what to do when it
>    eventually becomes active. The difference to above is that now instead of
>    storing a function Time -> Msg for later use, the system has to store
>    a function Time -> Task Never Msg. Fine enough.
>    2. At some point after that, but before the next animation frame
>    happens, a task tick |> andThen cont2 reaches the runtime system,
>    where cont2 : Time -> Task Never Msg. Again, the effect manager should
>    simply add that to the internal list of open tick requests.
>    3. Some time later, the next animation frame is due. Now what? The
>    runtime system knows that two tick requests are open. So of course it takes
>    the current time stamp t and passes it to the two functions stored in
>    its internal list. But instead of getting some msg1 : Msg and msg2 :
>    Msg as above, it now gets some task1 = cont1 t : Task Never Msg and task2
>    = cont2 t : Task Never Msg. So *unlike above*, the strategy now cannot
>    be to pass the relevant two messages to the program’s update function
>    while ensuring that no view rendering happens in between (which was the
>    whole point of tick batching in the animation scenario). Because those
>    messages are simply not available right now. What is available are two
>    tasks that when run will eventually return with messages (maybe after some
>    http requests or whatever). So the runtime system can now fire off
>    task1 and task2, but there is no way to tell when they will return,
>    and certainly no assumption can be made that they will return still before
>    the view rendering that is supposed to happen right now because we have an
>    animation frame just due. In consequence, the whole point of tick batching
>    is lost. We don’t get to ensure that groups of update calls get
>    performed “atomically” without intermediate view rendering.
>
> What do you think about the above? To me, it means that in the “just
> tasks” world tick batching as in the “separate tasks and commands” world is
> not possible (with the same quality).
> ​
>
> --
> You received this message because you are subscribed to the Google Groups
> "Elm Discuss" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> For more options, visit https://groups.google.com/d/optout.
>

-- 
You received this message because you are subscribed to the Google Groups "Elm 
Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to