Skip to content

Commit

Permalink
Add docs on timing signals.
Browse files Browse the repository at this point in the history
  • Loading branch information
tmadden committed Jun 23, 2020
1 parent 4193260 commit c9bf2fd
Show file tree
Hide file tree
Showing 8 changed files with 306 additions and 3 deletions.
2 changes: 1 addition & 1 deletion docs/assorted-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ signals:
</div>

You can smooth anything that provides the basic arithmetic operators. Here's a
smoothed view of a color:
smoothed view of a color with a custom transition:

[source](timing.cpp ':include :fragment=color-smoothing')

Expand Down
1 change: 1 addition & 0 deletions docs/contents.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [Adaptors](signal-adaptors.md)
- [Consuming Signals](consuming-signals.md)
- [Custom Signals](custom-signals.md)
- [Time as a Signal](time-as-a-signal.md)

- Control Flow

Expand Down
130 changes: 130 additions & 0 deletions docs/time-as-a-signal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
Time as a Signal
================

<script>
init_alia_demos(['time-signal', 'simple-animation',
'number-smoothing', 'color-smoothing',
'flickering-demo', 'deflickering-demo']);
</script>

Although all signals in alia are conceptually time-varying values, most of them
only care about the present (e.g., `a + b` is just whatever `a` is right now
plus whatever `b` is). However, sometimes we want our signals to explicitly vary
with time (for example, for animation purposes). This section discusses some of
the utilities that alia provides for that purpose.

The Time Signal
---------------

The most basic way to work with time is to access alia's time signal. This is
just a simple millisecond counter:

[source](timing.cpp ':include :fragment=time-signal')

<div class="demo-panel">
<div id="time-signal"></div>
</div>

Calling `get_animation_tick_count` will cause alia to request that the
underlying system issue a new refresh event (after a small animation-friendly
delay) and will also ensure that any component calling it gets refreshed as part
of that event. This means that you can very easily create animations using
the animation tick count:

[source](timing.cpp ':include :fragment=simple-animation')

<div class="demo-panel">
<div id="simple-animation"></div>
</div>

Note that we use `get_raw_animation_tick_count` here. This returns the tick
count as a raw integer value. The benefits of working with signals are less
applicable when working with the animation tick count because a) the tick count
is always available and b) it's always changing, so there's little point in
trying to cache results and detect changes.

Of course, since accessing the animation tick count causes your UI to
continuously refresh, you should be careful when using it to develop web pages
or desktop apps that don't normally do this. Some of the other time-related
utilities in alia are provided specifically because they minimize unnecessary
refreshes.

Smoothing
---------

A common case where you want to make explicit use of time is to provide a
smoothed view of a signal as it changes. alia provides the `smooth` function for
this purpose:

<dl>

<dt>smooth(ctx, x, transition = default_transition)</dt><dd>

`smooth(ctx, x, transition)`, where `x` is a signal, yields another signal that
carries a smoothed view of the value in `x`. When `x` changes abruptly, the
smoothed view will change smoothly according to `transition`.

</dd>

</dl>

See `alia/timing/smoothing.hpp` for details on specifying custom transitions, abruptly changing a smoothed signal, and the interfaces required of a value that needs to be smoothed.

Here's an example of basic number smoothing:

[source](timing.cpp ':include :fragment=number-smoothing')

<div class="demo-panel">
<div id="number-smoothing"></div>
</div>

One option for making your value compatible with smoothing is to simply provide
the basic arithmetic operators. Here's a smoothed view of a color (with a custom
transition thrown in as well):

[source](timing.cpp ':include :fragment=color-smoothing')

<div class="demo-panel">
<div id="color-smoothing"></div>
</div>

Deflickering
------------

Sometimes your signals will suffer from flickering. This is common on signals
that are calculated (or retrieved) in the background, especially when the signal
is updated based on some input from the user. For signals like this, the default
behavior is to appear empty while the value is being calculated, and when the
calculation is nearly instantaneous, the user will perceive this as flickering.

alia provides a utility for deflickering a signal:

<dl>

<dt>deflicker(ctx, x, delay = default_deflicker_delay)</dt><dd>

`deflicker(ctx, x, delay)`, where `x` is a signal, yields another signal that
carries a deflickered view of the value in `x`. If `x` loses its value for less
than `delay` milliseconds, the deflickered view will *maintain the last value
that `x` carried.* (If `x` doesn't take on a new value before the delay elapses,
then the deflickered view will also lose its value.)

</dd>

</dl>

To see `deflicker()` in action, let's first create a signal that flickers. We can do this artificially using `alia::async` and a simple delayed callback...

[source](timing.cpp ':include :fragment=flickering')

<div class="demo-panel">
<div id="flickering-demo"></div>
</div>

Now let's add a call to `deflicker`:

[source](timing.cpp ':include :fragment=deflickering')

<div class="demo-panel">
<div id="deflickering-demo"></div>
</div>
3 changes: 2 additions & 1 deletion examples/asm-dom/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ string(APPEND CMAKE_CXX_FLAGS " -s FETCH=1")
string(APPEND CMAKE_CXX_FLAGS " -s DISABLE_EXCEPTION_CATCHING=0")

# Create a library of common demo functionality.
add_library(demolib STATIC color.cpp dom.cpp)
add_library(demolib STATIC color.cpp dom.cpp emscripten.cpp)
set_property(TARGET demolib PROPERTY CXX_STANDARD 17)
target_link_libraries(demolib PRIVATE asm-dom)

Expand All @@ -34,6 +34,7 @@ string(APPEND CMAKE_CXX_FLAGS
" -s 'EXPORT_NAME=\"AliaDemos\"'")
string(APPEND CMAKE_CXX_FLAGS
" -s ENVIRONMENT=web")

set_property(TARGET demos PROPERTY CXX_STANDARD 17)
target_link_libraries(demos PRIVATE asm-dom demolib)

Expand Down
115 changes: 114 additions & 1 deletion examples/asm-dom/demos/timing.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#include "demo.hpp"
#include "emscripten.hpp"

#include <cmath>
#include <future>

namespace time_signal {

Expand Down Expand Up @@ -29,6 +33,37 @@ static demo the_demo("time-signal", init_demo);

} // namespace time_signal

namespace simple_animation {

void
do_ui(dom::context ctx)
{
// clang-format off
/// [simple-animation]
dom::do_colored_box(
ctx,
interpolate(
rgb8(255, 255, 255),
rgb8(0, 118, 255),
(1 + std::sin(get_raw_animation_tick_count(ctx) / 600.)) / 2));
/// [simple-animation]
// clang-format on
}

void
init_demo(std::string dom_id)
{
static alia::system the_system;
static dom::system the_dom;

initialize(
the_dom, the_system, dom_id, [](dom::context ctx) { do_ui(ctx); });
}

static demo the_demo("simple-animation", init_demo);

} // namespace simple_animation

namespace number_smoothing {

void
Expand Down Expand Up @@ -69,7 +104,8 @@ do_ui(dom::context ctx, duplex<rgb8> color)
{
// clang-format off
/// [color-smoothing]
dom::do_colored_box(ctx, smooth(ctx, color));
dom::do_colored_box(
ctx, smooth(ctx, color, animated_transition{ease_in_out_curve, 200}));
dom::do_button(ctx, "Go Light", color <<= rgb8(210, 210, 220));
dom::do_button(ctx, "Go Dark", color <<= rgb8(50, 50, 55));
/// [color-smoothing]
Expand All @@ -90,3 +126,80 @@ init_demo(std::string dom_id)
static demo the_demo("color-smoothing", init_demo);

} // namespace color_smoothing

namespace flickering {

void
do_ui(dom::context ctx, duplex<int> n)
{
// clang-format off
/// [flickering]
dom::do_text(ctx, "Enter N:");
dom::do_input(ctx, n);
dom::do_button(ctx, "++", ++n);
dom::do_text(ctx, "Here's a flickering signal carrying N * 2:");
dom::do_text(ctx,
alia::async<int>(ctx,
[](auto ctx, auto report_result, auto n) {
web::async_call([=]() {
report_result(n * 2);
}, 200);
},
n));
/// [flickering]
// clang-format on
}

void
init_demo(std::string dom_id)
{
static alia::system the_system;
static dom::system the_dom;

initialize(the_dom, the_system, dom_id, [](dom::context ctx) {
do_ui(ctx, get_state(ctx, 1));
});
}

static demo the_demo("flickering-demo", init_demo);

} // namespace flickering

namespace deflickering {

void
do_ui(dom::context ctx, duplex<int> n)
{
// clang-format off
/// [deflickering]
dom::do_text(ctx, "Enter N:");
dom::do_input(ctx, n);
dom::do_button(ctx, "++", ++n);
dom::do_text(ctx, "Here's the same signal from above with deflickering:");
dom::do_text(ctx,
deflicker(ctx,
alia::async<int>(ctx,
[](auto ctx, auto report_result, auto n) {
web::async_call([=]() {
report_result(n * 2);
}, 200);
},
n)));
/// [deflickering]
// clang-format on
}

void
init_demo(std::string dom_id)
{
static alia::system the_system;
static dom::system the_dom;

initialize(the_dom, the_system, dom_id, [](dom::context ctx) {
do_ui(ctx, get_state(ctx, 1));
});
}

static demo the_demo("deflickering-demo", init_demo);

} // namespace deflickering
6 changes: 6 additions & 0 deletions examples/asm-dom/dom.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ do_button(dom::context ctx, Text text, action<> on_click)
void
do_colored_box(dom::context ctx, readable<rgb8> color);

inline void
do_colored_box(dom::context ctx, rgb8 const& color)
{
do_colored_box(ctx, value(color));
}

void
do_hr(dom::context ctx);

Expand Down
29 changes: 29 additions & 0 deletions examples/asm-dom/emscripten.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include "emscripten.hpp"

#include <emscripten/emscripten.h>

namespace alia {
namespace web {

struct async_call_data
{
std::function<void()> func;
};

static void
async_call_callback(void* data_arg)
{
std::unique_ptr<async_call_data> data(
reinterpret_cast<async_call_data*>(data_arg));
data->func();
}

void
async_call(std::function<void()> func, int millis)
{
auto* data = new async_call_data{std::move(func)};
emscripten_async_call(async_call_callback, data, millis);
}

} // namespace web
} // namespace alia
23 changes: 23 additions & 0 deletions examples/asm-dom/emscripten.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef ALIA_WEB_EMSCRIPTEN_HPP
#define ALIA_WEB_EMSCRIPTEN_HPP

#include <functional>

namespace alia {
namespace web {

// Call a function object asynchronously after a delay.
//
// This is a simple wrapper around emscripten_async_call and has identical
// behavior with respect to 'millis'.
//
// See
// https://emscripten.org/docs/api_reference/emscripten.h.html#c.emscripten_async_call
//
void
async_call(std::function<void()> func, int millis);

} // namespace web
} // namespace alia

#endif

0 comments on commit c9bf2fd

Please sign in to comment.