Mercurial > louis > mq > lightsd
changeset 489:b0d228d02f61
WIP effect, much better timer accuracy
author | Louis Opter <kalessin@kalessin.fr> |
---|---|
date | Sat, 16 Jul 2016 00:17:43 -0700 |
parents | 3dff3076c229 |
children | b5efb1c879c6 |
files | add_power_transition.patch |
diffstat | 1 files changed, 163 insertions(+), 106 deletions(-) [+] |
line wrap: on
line diff
--- a/add_power_transition.patch Sun Jul 10 23:19:31 2016 -0700 +++ b/add_power_transition.patch Sat Jul 16 00:17:43 2016 -0700 @@ -154,7 +154,7 @@ new file mode 100644 --- /dev/null +++ b/core/effect.c -@@ -0,0 +1,300 @@ +@@ -0,0 +1,345 @@ +// Copyright (c) 2015, Louis Opter <kalessin@kalessin.fr> +// +// This file is part of lighstd. @@ -199,6 +199,9 @@ + + lgtd_info("ending effect %s, id=%p", effect->name, effect); + LIST_REMOVE(effect, link); ++ if (effect->stop_cb) { ++ effect->stop_cb(effect); ++ } + if (effect->timer) { + lgtd_timer_stop(effect->timer); + } @@ -259,81 +262,113 @@ +static void +lgtd_effect_timer_callback(struct lgtd_timer *timer, union lgtd_timer_ctx ctx) +{ -+ (void)timer; -+ + struct lgtd_effect *effect = (struct lgtd_effect *)ctx.as_ptr; + -+ if (effect->apply_left < 0) { -+ lgtd_warnx( -+ "stopping runaway effect %s, id=%p, apply_left=%d", -+ effect->name, effect, effect->apply_left -+ ); -+ lgtd_effect_stop(effect); -+#ifndef NDEBUG -+ abort(); -+#endif -+ return; -+ } -+ -+ lgtd_time_mono_t now = lgtd_time_monotonic_msecs(); -+ lgtd_time_mono_t ends_at = effect->ends_at; ++ int recursions = 0; ++ do { ++ if (effect->apply_left < 0) { ++ lgtd_warnx( ++ "stopping runaway effect %s, id=%p, apply_left=%d", ++ effect->name, effect, effect->apply_left ++ ); ++ lgtd_effect_stop(effect); ++ return; ++ } + -+ int error_margin = LGTD_EFFECT_STALE_THRESHOLD_MS; -+ if (ends_at && lgtd_time_mono_cmp(now, ends_at, error_margin) > 0) { -+ // check if the effect is stale (e.g: the computer went to sleep) -+ // and stop it if it is the case: -+ lgtd_warnx( -+ "stopping stale periodic effect %s created %jums ago, " -+ "id=%p, duration=%jums", -+ effect->name, (uintmax_t)(now - effect->created_at), effect, -+ (uintmax_t)(effect->ends_at - effect->created_at) -+ ); -+ lgtd_effect_stop(effect); -+ return; -+ } ++ lgtd_time_mono_t now = lgtd_time_monotonic_msecs(); ++ lgtd_time_mono_t callback_started_at = now; ++ lgtd_time_mono_t ends_at = effect->ends_at; + -+ lgtd_info( -+ "calling apply callback for effect %s, id=%p, apply_left=%d", -+ effect->name, effect, effect->apply_left -+ ); -+ assert(effect->apply_cb); -+ effect->apply_cb(effect); -+ effect->apply_cnt++; -+ if (ends_at && effect->apply_left-- == 0) { -+ error_margin = LGTD_EFFECT_TIMER_RESCHEDULE_THRESHOLD_MS; -+ if (lgtd_time_mono_cmp(now, ends_at, error_margin) <= 0) { -+ // now <= ends_at and we aren't stale, call the duration callback rn -+ lgtd_info( -+ "calling duration callback for effect %s, id=%p", -+ effect->name, effect ++ int error_margin = LGTD_EFFECT_STALE_THRESHOLD_MS; ++ if (ends_at && lgtd_time_mono_cmp(now, ends_at, error_margin) > 0) { ++ // check if the effect is stale (e.g: the computer went to sleep) ++ // and stop it if it is the case: ++ lgtd_warnx( ++ "stopping stale periodic effect %s created %jums ago, " ++ "id=%p, duration=%jums", ++ effect->name, (uintmax_t)(now - effect->created_at), effect, ++ (uintmax_t)(effect->ends_at - effect->created_at) + ); -+ effect->duration_cb(effect); + lgtd_effect_stop(effect); + return; + } + -+ // now > ends_at, schedule the duration callback -+ lgtd_timer_stop(timer); -+ lgtd_time_mono_t wait = now - ends_at; -+ effect->timer = lgtd_timer_start( -+ LGTD_TIMER_DEFAULT_FLAGS, -+ wait, -+ lgtd_effect_duration_callback, -+ ctx ++ lgtd_info( ++ "calling apply callback for effect %s, id=%p, apply_left=%d", ++ effect->name, effect, effect->apply_left + ); -+ if (!effect->timer) { -+ lgtd_warn( -+ "can't schedule the duration callback for effect %s, id=%p", -+ effect->name, effect ++ assert(effect->apply_cb); ++ enum lgtd_effect_flags rv = effect->apply_cb(effect); ++ effect->apply_cnt++; ++ ++ error_margin = LGTD_EFFECT_TIMER_RESCHEDULE_THRESHOLD_MS; ++ if (ends_at && effect->apply_left-- == 0) { ++ if (lgtd_time_mono_cmp(now, ends_at, error_margin) <= 0) { ++ // now <= ends_at and we aren't stale, call the duration ++ // callback now and/or stop the effect. ++ if (effect->duration_cb) { ++ lgtd_info( ++ "calling duration callback for effect %s, id=%p", ++ effect->name, effect ++ ); ++ effect->duration_cb(effect); ++ } ++ lgtd_effect_stop(effect); ++ return; ++ } ++ ++ if (!effect->duration_cb) { ++ lgtd_effect_stop(effect); ++ return; ++ } ++ ++ // now > ends_at, schedule the duration callback ++ lgtd_timer_stop(timer); ++ now = lgtd_time_monotonic_msecs(); // refresh ++ lgtd_time_mono_t wait = now - ends_at; ++ effect->timer = lgtd_timer_start( ++ LGTD_TIMER_DEFAULT_FLAGS, ++ wait, ++ lgtd_effect_duration_callback, ++ ctx ++ ); ++ if (!effect->timer) { ++ lgtd_warn( ++ "can't schedule the duration callback for effect %s, id=%p", ++ effect->name, effect ++ ); ++ lgtd_effect_stop(effect); ++ return; ++ } ++ lgtd_info( ++ "scheduled duration callback for effect %s, id=%p, in %jums", ++ effect->name, effect, (uintmax_t)wait ++ ); ++ return; ++ } ++ ++ if (rv == LGTD_EFFECT_STOP) { ++ lgtd_effect_stop(effect); ++ return; ++ } ++ ++ // schedule the next call or recurse if we actually spent enough time ++ // here to reach next_tick_at: ++ lgtd_time_mono_t next_tick_at = callback_started_at + effect->timer_ms; ++ now = lgtd_time_monotonic_msecs(); // refresh ++ if (lgtd_time_mono_cmp(next_tick_at, now, error_margin) > 0) { ++ struct timeval tv = LGTD_MSECS_TO_TIMEVAL(next_tick_at - now); ++ lgtd_timer_reschedule(timer, &tv); ++ return; ++ } else if (++recursions == LGTD_EFFECT_MAX_APPLY_RECURSIONS) { ++ lgtd_warnx( ++ "stopping runaway effect after %d recursive calls %s, id=%p", ++ recursions, effect->name, effect + ); + lgtd_effect_stop(effect); + return; + } -+ lgtd_info( -+ "scheduled duration callback for effect %s, id=%p, in %jums", -+ effect->name, effect, (uintmax_t)wait -+ ); -+ } ++ } while (1); +} + +struct lgtd_effect * @@ -342,7 +377,9 @@ + void (*duration_cb)(const struct lgtd_effect *), + int timer_flags, + int timer_ms, -+ void (*apply_cb)(const struct lgtd_effect *), ++ enum lgtd_effect_flags (*apply_cb)( ++ const struct lgtd_effect *), ++ void (*stop_cb)(const struct lgtd_effect *), + union lgtd_effect_ctx ctx) +{ + assert(name); @@ -361,7 +398,9 @@ + effect->created_at = lgtd_time_monotonic_msecs(); + effect->ends_at = effect->created_at + duration; + effect->duration_cb = duration_cb; ++ effect->timer_ms = timer_ms; + effect->apply_cb = apply_cb; ++ effect->stop_cb = stop_cb; + effect->ctx = ctx; + LIST_INSERT_HEAD(&lgtd_effects, effect, link); + @@ -370,24 +409,30 @@ + assert(apply_cb); + if (!apply_cb) { + lgtd_warnx( -+ "cannot create periodic effect %s without a callback", name ++ "cannot create periodic or delayed effect %s without a " ++ "callback", name + ); + goto err; + } + assert(duration || (timer_flags & LGTD_TIMER_PERSISTENT)); -+ if (!duration && !(timer_flags & LGTD_TIMER_PERSISTENT)) { ++ if (timer_flags & LGTD_TIMER_PERSISTENT) { // finite or infinite periodic effect ++ // disable that and re-schedule the timer at every call to ++ // get execution time into account and therefore slightly ++ // more accurate timings: ++ timer_flags &= ~LGTD_TIMER_PERSISTENT; ++ if (duration) { // finite periodic effect ++ effect->apply_left = duration / timer_ms; ++ if (!(timer_flags & LGTD_TIMER_ACTIVATE_NOW)) { ++ effect->apply_left--; ++ } ++ } ++ } else if (!duration) { + lgtd_warnx( + "cannot create infinite effect %s without a persistent timer", + name + ); + goto err; + } -+ if (duration && (timer_flags & LGTD_TIMER_PERSISTENT)) { -+ effect->apply_left = duration / timer_ms; -+ } -+ if (!(timer_flags & LGTD_TIMER_ACTIVATE_NOW)) { -+ effect->apply_left--; -+ } + timer_cb = lgtd_effect_timer_callback; + if (duration) { + lgtd_info( @@ -459,7 +504,7 @@ new file mode 100644 --- /dev/null +++ b/core/effect.h -@@ -0,0 +1,64 @@ +@@ -0,0 +1,76 @@ +// Copyright (c) 2015, Louis Opter <kalessin@kalessin.fr> +// +// This file is part of lighstd. @@ -484,15 +529,23 @@ + void *as_ptr; +}; + ++enum lgtd_effect_flags { ++ LGTD_EFFECT_CONTINUE = 0, ++ LGTD_EFFECT_STOP = 1 ++}; ++ ++#pragma GCC diagnostic push ++#pragma GCC diagnostic warning "-Wpadded" +struct lgtd_effect { + LIST_ENTRY(lgtd_effect) link; -+ const char *name; + lgtd_time_mono_t created_at; + lgtd_time_mono_t ends_at; ++ const char *name; + struct lgtd_timer *timer; ++ void (*duration_cb)(const struct lgtd_effect *); ++ enum lgtd_effect_flags (*apply_cb)(const struct lgtd_effect *); ++ void (*stop_cb)(const struct lgtd_effect *); + int timer_ms; -+ void (*duration_cb)(const struct lgtd_effect *); -+ void (*apply_cb)(const struct lgtd_effect *); + // how many times apply_cb will be called again or 0 if your effect + // is infinite (duration == 0 and timer_ms > 0) or doesn't use a + // timer (timer_ms == 0): @@ -500,8 +553,11 @@ + int apply_cnt; + union lgtd_effect_ctx ctx; +}; ++#pragma GCC diagnostic pop +LIST_HEAD(lgtd_effect_list, lgtd_effect); + ++enum { LGTD_EFFECT_MAX_APPLY_RECURSIONS = 5 }; ++ +enum { LGTD_EFFECT_STALE_THRESHOLD_MS = 2000 }; +enum { LGTD_EFFECT_TIMER_RESCHEDULE_THRESHOLD_MS = 30 }; + @@ -520,7 +576,8 @@ + void (*)(const struct lgtd_effect *), // duration cb + int, // timer flags + int, // timer ms -+ void (*)(const struct lgtd_effect *), // apply cb ++ enum lgtd_effect_flags (*)(const struct lgtd_effect *), // apply cb ++ void (*)(const struct lgtd_effect *), // stop cb + union lgtd_effect_ctx); +void lgtd_effect_stop(struct lgtd_effect *); +void lgtd_effect_stop_all(void); @@ -1212,7 +1269,7 @@ new file mode 100644 --- /dev/null +++ b/effects/power_transition.c -@@ -0,0 +1,346 @@ +@@ -0,0 +1,343 @@ +// Copyright (c) 2015, Louis Opter <kalessin@kalessin.fr> +// +// This file is part of lighstd. @@ -1269,6 +1326,17 @@ + } +} + ++static void ++lgtd_effect_power_transition_stop_callback(const struct lgtd_effect *effect) ++{ ++ assert(effect); ++ ++ struct lgtd_effect_power_transition_ctx *ctx = effect->ctx.as_ptr; ++ lgtd_effect_power_transition_clear_target_list(&ctx->targets); ++ free(ctx); ++} ++ ++ +static struct lgtd_lifx_bulb * +lgtd_effect_power_transition_possibly_get_bulb(uint8_t *device_id) +{ @@ -1316,9 +1384,6 @@ + ); + } + } -+ -+ lgtd_effect_power_transition_clear_target_list(&ctx->targets); -+ free(ctx); +} + +static void @@ -1371,7 +1436,7 @@ + } +} + -+static void ++static enum lgtd_effect_flags +lgtd_effect_power_transition_off_apply_callback(const struct lgtd_effect *effect) +{ + assert(effect); @@ -1387,29 +1452,16 @@ + + if (effect->apply_cnt >= ctx->periods) { + lgtd_effect_power_transition_off_power_off(&ctx->targets); -+ return; ++ } else { ++ lgtd_effect_power_transition_off_set_brightness_to_zero( ++ &ctx->targets, ctx->ends_at - lgtd_time_monotonic_msecs() ++ ); + } -+ lgtd_effect_power_transition_off_set_brightness_to_zero( -+ &ctx->targets, ctx->ends_at - lgtd_time_monotonic_msecs() -+ ); ++ ++ return LGTD_EFFECT_CONTINUE; +} + -+static void -+lgtd_effect_power_transition_on_duration_callback(const struct lgtd_effect *effect) -+{ -+ assert(effect); -+ -+ lgtd_info( -+ "now = %ju, effect->ends_at=%ju", -+ (uintmax_t)lgtd_time_monotonic_msecs(), (uintmax_t)effect->ends_at -+ ); -+ -+ struct lgtd_effect_power_transition_ctx *ctx = effect->ctx.as_ptr; -+ lgtd_effect_power_transition_clear_target_list(&ctx->targets); -+ free(ctx); -+} -+ -+static void ++static enum lgtd_effect_flags +lgtd_effect_power_transition_on_apply_callback(const struct lgtd_effect *effect) +{ + assert(effect); @@ -1470,6 +1522,8 @@ + bulb, LGTD_LIFX_SET_LIGHT_COLOR, &pkt_fade_out + ); + } ++ ++ return LGTD_EFFECT_CONTINUE; +} + +const struct lgtd_effect * @@ -1513,7 +1567,7 @@ + // setup the effect: + const char *name = NULL; + void (*duration_cb)(const struct lgtd_effect *) = NULL; -+ void (*apply_cb)(const struct lgtd_effect *) = NULL; ++ enum lgtd_effect_flags (*apply_cb)(const struct lgtd_effect *) = NULL; + int timer_ms = LGTD_EFFECT_POWER_TRANSITION_TIMER_MS; + ctx->periods = duration / timer_ms; + // align the timer with the duration: @@ -1529,7 +1583,6 @@ + } else { + name = "power_transition[on]"; + apply_cb = lgtd_effect_power_transition_on_apply_callback; -+ duration_cb = lgtd_effect_power_transition_on_duration_callback; + } + union lgtd_effect_ctx effect_ctx = { .as_ptr = ctx }; + const struct lgtd_effect *effect = lgtd_effect_start( @@ -1539,6 +1592,7 @@ + LGTD_TIMER_ACTIVATE_NOW|LGTD_TIMER_PERSISTENT, + timer_ms, + apply_cb, ++ lgtd_effect_power_transition_stop_callback, + effect_ctx + ); + if (effect) { @@ -1563,7 +1617,7 @@ new file mode 100644 --- /dev/null +++ b/effects/power_transition.h -@@ -0,0 +1,55 @@ +@@ -0,0 +1,58 @@ +// Copyright (c) 2015, Louis Opter <kalessin@kalessin.fr> +// +// This file is part of lighstd. @@ -1607,14 +1661,17 @@ + lgtd_effect_power_transition_target +); + ++#pragma GCC diagnostic push ++#pragma GCC diagnostic warning "-Wpadded" +struct lgtd_effect_power_transition_ctx { + struct lgtd_effect_power_transition_target_list targets; -+ // how many times the apply_cb will be called -+ int periods; + // This doesn't include the delay to restore the light color after a + // power off: + lgtd_time_mono_t ends_at; ++ // how many times the apply_cb will be called ++ int periods; +}; ++#pragma GCC diagnostic pop + +const struct lgtd_effect *lgtd_effect_power_transition(const struct lgtd_proto_target_list *, + enum lgtd_effect_power_transition_type,