<'
# Copyright (c) 2012-2015, 2017, 2026 LAAS/CNRS
# All rights reserved.
#
# Redistribution and use  in source  and binary  forms,  with or without
# modification, are permitted provided that the following conditions are
# met:
#
#   1. Redistributions of  source  code must retain the  above copyright
#      notice and this list of conditions.
#   2. Redistributions in binary form must reproduce the above copyright
#      notice and  this list of  conditions in the  documentation and/or
#      other materials provided with the distribution.
#
#                                      Anthony Mallet on Wed Mar  7 2012
#

args components
lang c
'>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

#include "activity.h"
#include "component.h"

<'foreach c $components {'>
<'  set comp [$c name]'>

/* <"[--- $comp =========================================================]"> */


/* <"[--- genom_${comp}_activity_alloc ----------------------------------]"> */

/* Look for the first free activity, starting from the last allocated slot.
 * This should be called with the task lock held.
 */
int
genom_<"$comp">_activity_alloc(struct genom_<"$comp">_activities *activities)
{
  static int aid = 0;
  size_t id, n;

  for(id = aid, n = 0; n < GENOM_MAX_ACTIVITIES; id++, n++) {
    id %= GENOM_MAX_ACTIVITIES;
    if (activities->a[id].any.status == ACT_VOID) {
      activities->a[id].any.aid = ++aid;
      return id;
    }
  }

  return -1;
}


/* <"[--- genom_${comp}_activities_startup ------------------------------]"> */

/* Start the permanent activity of an execution task and wait for the start
 * codel to terminate. All tasks are started in the order of declaration.
 * The genom_state port is also initialized with static information and
 * published. */

/* per task helper */
static int
genom_<"$comp">_activities_startup_task(
  struct genom_<"$comp">_exec_task *task,
  struct genom_<"$comp">_component *self)
{
  struct genom_activity *a;
  size_t i;

  pthread_mutex_lock(&task->lock);
  assert(task->spawned > 0);
  assert(task->started <= 0);

  /* alloc permanent activity */
  assert(task->activities.permanent == NULL);
  i = genom_<"$comp">_activity_alloc(&task->activities);
  assert(i >= 0);
  a = task->activities.permanent = &task->activities.a[i].any;
  a->sid = -1;
  genom_tinit_activity(a);

  /* start */
  a->start = 1;
  while (task->started <= 0)
    pthread_cond_wait(&task->sync, &task->lock);

  if (a->status == ACT_ETHER && a->state != <"$comp">_ether) {
    genom_log_warnx(
      self->instance, "exec task %s failed with exception %s",
      task->name, a->state);
    pthread_mutex_unlock(&task->lock);
    return -1;
  }
  pthread_mutex_unlock(&task->lock);
  return 0;
}

int
genom_<"$comp">_activities_startup(struct genom_<"$comp">_component *self)
{
  genom_state_component *state_data =
    self->ports.genom_state_.data(&self->control.context);
  genom_state_task *task;
  assert(state_data);

  /* init genom_state port */
  if (genom_sequence_reserve(&state_data->task, <"$comp">_NTASK)) {
    genom_log_warn(self->instance, "genom_state port failed");
    return -1;
  }
  state_data->task._length = <"$comp">_NTASK;
<'  foreach t [$c tasks] {'>

  task = &state_data->task._buffer[<"${comp}_[$t name]_TASKID">];
  snprintf(task->name, sizeof(task->name), "%s", "<"[$t name]">");
  task->rusage = (genom_state_rusage){0};
  if (genom_sequence_reserve(&task->activity, GENOM_MAX_ACTIVITIES)) {
    genom_log_warn(self->instance, "genom_state port failed");
    return -1;
  }
<'}'>

  snprintf(state_data->digest, sizeof(state_data->digest),
           "%s",  "<"[$c digest]">");
  snprintf(state_data->date, sizeof(state_data->date),
           "%s", genom_build_date);
  snprintf(state_data->version, sizeof(state_data->version),
           "%s-%s", "<"[$c name]">", "<"[$c version]">");

  if (self->ports.genom_state_.write(&self->control.context)) {
    genom_log_warnx(self->instance, "genom_state port failed");
    return -1;
  }
  genom_log_debug(self->instance, "initialized genom_state port");

  /* start activities */
<'  foreach t [$c tasks] {'>
  if (genom_<"$comp">_activities_startup_task(
        &self->tasks.<"[$t name]">_, self)) return -1;
<'  }'>
  return 0;
}


/* <"[--- genom_${comp}_activities_ctrl ---------------------------------]"> */

/* Process activities in the control task: pending activities are started
 * and ether activities are replied to.
 * The genom_state port is also updated with current activities and published.
 * This is the main loop of a control task. */

/* per-task helper */
static int
genom_<"$comp">_activities_ctrl_task(
  struct genom_<"$comp">_exec_task *task, genom_state_task *state_task,
  struct genom_<"$comp">_component *self)
{
  struct genom_activity *a;
  size_t id, stateid;
  const char *s;

  int pending = 0;

  /* process pending activities */
  pthread_mutex_lock(&task->lock);
  for(id = stateid = 0; id < GENOM_MAX_ACTIVITIES; id++) {
    a = &task->activities.a[id].any;
    switch(a->status) {
      case ACT_VOID: case ACT_STOP: break;

      case ACT_INIT:
        if (!a->start) {
          int delay;
          pthread_mutex_unlock(&task->lock);
          delay = genom_<"$comp">_interrupt_other(a, self);
          pthread_mutex_lock(&task->lock);
          if (!delay) {
            a->start = 1;
            if (self->control.shutdown && a != task->activities.permanent) {
              /* also stop it right away when shutting down */
              a->interruptedby = "kill";
              a->stop = 1;
            }
            if (!task->runnable) {
              task->runnable = 1;
              pthread_cond_broadcast(&task->sync);
            }
          }
        }
        break;

      case ACT_RUN:
        if (self->control.shutdown && a != task->activities.permanent) {
          if (!a->stop) {
            a->interruptedby = "kill";
            a->stop = 1;
            if (!task->runnable) {
              task->runnable = 1;
              pthread_cond_broadcast(&task->sync);
            }
          }
        }
        break;

      case ACT_ETHER:
        if (a == task->activities.permanent) {
          task->activities.permanent = NULL;
          if (a->state != <"$comp">_ether) {
            /* shutdown component if a task's activity fails */
            self->control.shutdown = 1;
            genom_log_warnx(
              self->instance, "exec task %s failed with exception %s",
              task->name, a->state);
          }
          a->status = ACT_VOID;
        } else {
          pthread_mutex_unlock(&task->lock);
          genom_<"$comp">_activity_report(&task->activities.a[id], self);
          pthread_mutex_lock(&task->lock);
        }
        break;
    }

    if (a->status != ACT_VOID) {
      if (a != task->activities.permanent) pending++;

      /* update genom_state port with running activities */
      state_task->activity._buffer[stateid].id = a->aid;
      switch(a->sid) {
        case -1: s = "<task>"; break;
<'  foreach s [$c services] {'>
<'    if {[$s kind] ne "activity"} continue'>
        case <"$comp">_<"[$s name]">_SVCID: s = "<"[$s name]">"; break;
<'}'>
        default: assert(!"unknown service id"); s = "<unknown>"; break;
      }
      snprintf(state_task->activity._buffer[stateid].name,
               sizeof(state_task->activity._buffer[stateid].name), "%s", s);
      stateid++;
    }
  }

  state_task->rusage = task->rusage;
  state_task->activity._length = stateid;
  pthread_mutex_unlock(&task->lock);

  return pending;
}

int
genom_<"$comp">_activities_ctrl(struct genom_<"$comp">_component *self)
{
  genom_state_component *state_data =
    self->ports.genom_state_.data(&self->control.context);
  genom_event e;

  int pending = 0;
  assert(state_data);

<'  foreach t [$c tasks] {'>
  pending +=
    genom_<"$comp">_activities_ctrl_task(
      &self->tasks.<"[$t name]">_,
      &state_data->task._buffer[<"$comp">_<"[$t name]">_TASKID], self);
<'  }'>

  pthread_mutex_lock(&self->control.lock);
  gettimeofday(&self->control.wakeup, NULL);
  pthread_mutex_unlock(&self->control.lock);

  e = self->ports.genom_state_.write(&self->control.context);
  if (e)
    genom_log_warnx(self->instance, "cannot update state port: %s", e);

  return pending;
}


/* <"[--- genom_${comp}_activities_shutdown -----------------------------]"> */

/* Stop the permanent activity of an execution task and wait for the start
 * codel to terminate. All tasks are started in the reverse order of
 * declaration. */
void
genom_<"$comp">_activities_shutdown(struct genom_<"$comp">_component *self)
{
  /* interrupt permanent activities */
<'  foreach t [lreverse [$c tasks]] {'>
  if (self->tasks.<"[$t name]">_.started > 0) {
    struct genom_activity *a;

    genom_log_debug(self->instance, "interrupting task <"[$t name]">");
    pthread_mutex_lock(&self->tasks.<"[$t name]">_.lock);
    a = self->tasks.<"[$t name]">_.activities.permanent;

    assert(self->tasks.<"[$t name]">_.started > 0);
    for(size_t id = 0; id < GENOM_MAX_ACTIVITIES; id++)
      if (&self->tasks.<"[$t name]">_.activities.a[id].any != a)
        assert(
          self->tasks.<"[$t name]">_.activities.a[id].any.status == ACT_VOID);

    if (!a) {
      /* activity was ether, realloc one for the stop state */
      int i = genom_<"$comp">_activity_alloc(
        &self->tasks.<"[$t name]">_.activities);
      assert(i >= 0);
      a = &self->tasks.<"[$t name]">_.activities.a[i].any;
      genom_tinit_activity(a);
      a->sid = -1;
      self->tasks.<"[$t name]">_.activities.permanent = a;
    }
    if (a->status != ACT_STOP) {
      if (a->status == ACT_ETHER) a->status = ACT_INIT;
      a->stop = 1;
      a->interruptedby = "kill";
      self->tasks.<"[$t name]">_.runnable = 1;
      pthread_cond_broadcast(&self->tasks.<"[$t name]">_.sync);
    }
    while(a->status != ACT_ETHER)
      pthread_cond_wait(&self->tasks.<"[$t name]">_.sync,
                        &self->tasks.<"[$t name]">_.lock);

    genom_tfini_activity(a);
    self->tasks.<"[$t name]">_.started = 0;
    pthread_mutex_unlock(&self->tasks.<"[$t name]">_.lock);
  }
<'  }'>
}


/* <"[--- genom_${comp}_activities_exec ---------------------------------]"> */

/* Process activities in the exec task according to their state machine
 * definition. This is the main loop of an exec task. */
enum genom_activity_event
genom_<"$comp">_activities_exec(
  struct genom_<"$comp">_exec_task *task, int ontick,
  enum genom_activity_status (*invoke)(
    union genom_<"$comp">_activity_any *, struct genom_<"$comp">_component *),
  struct genom_<"$comp">_component *self)
{
  enum genom_activity_event events = 0;
  enum genom_activity_status e;
  struct genom_activity *a;
  size_t id;

  /* handle start/stop requests, wake up paused activities */
  pthread_mutex_lock(&task->lock);
  for(id = 0; id < GENOM_MAX_ACTIVITIES; id++) {
    a = &task->activities.a[id].any;
    switch(a->status) {
      case ACT_VOID: case ACT_ETHER: continue;

      case ACT_INIT:
        if (a->start && ontick) {
          /* start request, only on a tick in periodic tasks */
          a->status = ACT_RUN;
          a->state = <"${comp}">_start;
          a->start = 0;
          a->pause = 0;
        }
        if (a->stop) {
          /* stop before start: raise 'interrupted' exception, except for
           * the permanent activity for which we still go to stop */
          if (a == task->activities.permanent) {
            a->status = ACT_STOP;
            a->state = <"${comp}">_stop;
            a->stop = 0;
            a->pause = 0;
          } else {
            genom_interrupted_detail d;
            assert(a->interruptedby);
            strncpy(d.by, a->interruptedby, sizeof(d.by));
            d.by[sizeof(d.by)-1] = *"";
            genom_interrupted(&d, &task->context);

            a->status = ACT_ETHER;
            a->stop = 0;
            a->exdetail =
              (void *)task->context.raised(&a->state, &task->context);
            events |= ACTEVT_ETHER;
          }
        }
        break;

      case ACT_RUN:
        if (a->stop) {
          a->status = ACT_STOP;
          a->state = <"${comp}">_stop;
          a->stop = 0;
          a->pause = 0;
        }
        /*FALLTHROUGH*/
      case ACT_STOP:
        if (a->pause && ontick) {
          a->pause = 0;
        }
        break;
    }
  }

  /* run activities */
  for(id = 0; id < GENOM_MAX_ACTIVITIES; id++) {
    a = &task->activities.a[id].any;
    if (a->status != ACT_RUN && a->status != ACT_STOP) continue;
    if (a->pause) continue;

    pthread_mutex_unlock(&task->lock);
    e = (*invoke)(&task->activities.a[id], self);
    pthread_mutex_lock(&task->lock);
    events |= ACTEVT_EXEC;
    switch(e) {
      case ACT_RUN:
        if (!a->pause) events |= ACTEVT_MORE;
        break;

      case ACT_ETHER:
        a->status = ACT_ETHER;
        events |= ACTEVT_ETHER;
        break;

      case ACT_VOID: case ACT_INIT: case ACT_STOP:
        assert(!"not reached");
    }
    if (task->started <= 0 && a == task->activities.permanent)
      task->started = 1;
  }

  /* send signals */
  if (events & ACTEVT_MORE) task->runnable = 1;
  if (events & ACTEVT_EXEC) pthread_cond_broadcast(&task->sync);
  pthread_mutex_unlock(&task->lock);

  if (events & ACTEVT_ETHER) self->control.signalcb(self);
  if (events & (ACTEVT_MORE | ACTEVT_ETHER)) {
    /* wake up non-periodic tasks (non-paused transition) */
<'  foreach t [$c tasks] {'>
<'    if {[catch {$t period}]} {'>
    if (task != &self->tasks.<"[$t name]">_) {
      pthread_mutex_lock(&self->tasks.<"[$t name]">_.lock);
      if (!self->tasks.<"[$t name]">_.runnable) {
        self->tasks.<"[$t name]">_.runnable = 1;
        pthread_cond_broadcast(&self->tasks.<"[$t name]">_.sync);
      }
      pthread_mutex_unlock(&self->tasks.<"[$t name]">_.lock);
    }
<'    }'>
<'  }'>
  }

  return events;
}


/* <"[--- genom_${comp}_activity_report ---------------------------------]"> */

/* Invoke the done callback of an activity, if any. */
void
genom_<"$comp">_activity_report(
  union genom_<"$comp">_activity_any *a, struct genom_<"$comp">_component *self)
{
  assert(a->any.sid >= -1 && a->any.sid < <"$comp">_NSVC);

  if (a->any.state == <"$comp">_ether && a->any.sid >= 0)
    /* success: update after/before array (except permanent activity) */
    self->control.run_map[a->any.sid] = 1;

  switch(a->any.sid) {
    default: assert(!"code not reached");

    case -1: /* permanent activity reports nothing */
      genom_tfini_activity(&a->any);
      break;
<'  foreach s [$c services] {'>
    case <"$comp">_<"[$s name]">_SVCID:
      if (a-><"[$s name]">_.donecb)
        a-><"[$s name]">_.donecb(&a-><"[$s name]">_, self);
      genom_tfini_<"$comp">_activity_<"[$s name]">(&a-><"[$s name]">_);
      break;
<'  }'>
  }

  assert(a->any.status == ACT_VOID);
}


/* <"[--- genom_${comp}_interrupt_other ---------------------------------]"> */

/* Interrupt any running activity in the 'interrupts' list of the one given
 * in argument. Return zero if the new activity can be started and non-zero
 * if one has to wait for incompatible actitivies to stop. */

<'  foreach s [$c services] {'>
/* <"[$s name]"> */
static int
genom_<"$comp">_<"[$s name]">_interrupt_reqd(
  struct genom_activity *a, struct genom_<"$comp">_component *self)
{
  int delay = 0;
  (void)self; (void)a; /* fix -Wunused-parameter */

<'    foreach task [$c tasks] {'>
<'      set ilist [list]'>
<'      foreach i [$s interrupt] {'>
<'        if {[catch {$i task} t]} continue'>
<'        if {$t eq $task} { lappend ilist $i }'>
<'      }'>
<'      if {[llength $ilist] == 0} continue'>

  /* task <"[$task name]"> */
  pthread_mutex_lock(&self->tasks.<"[$task name]">_.lock);
  {
    struct genom_activity *c;
    size_t id;

    for(id = 0; id < GENOM_MAX_ACTIVITIES; id++) {
      c = &self->tasks.<"[$task name]">_.activities.a[id].any;
      if (c->status == ACT_VOID || c->status == ACT_ETHER) continue;
      if (c->aid == a->aid) continue;
      switch(c->sid) {
<'    foreach i $ilist {'>
        case <"$comp">_<"[$i name]">_SVCID:
<'    }'>
          switch(c->status) {
            case ACT_RUN:
              delay = 1;
              if (!self->tasks.<"[$task name]">_.runnable) {
                self->tasks.<"[$task name]">_.runnable = 1;
                pthread_cond_broadcast(&self->tasks.<"[$task name]">_.sync);
              }
              /*FALLTHROUGH*/
            case ACT_INIT:
              c->stop = 1;
              c->interruptedby = "<"[$s name]">";
              break;

            case ACT_STOP: delay = 1; break;

            default: assert(!"code not reached");
          }
      }
    }
  }
  pthread_mutex_unlock(&self->tasks.<"[$task name]">_.lock);
<'    }'>

  return delay;
}

<'  }'>

int
genom_<"$comp">_interrupt_other(
  struct genom_activity *a, struct genom_<"$comp">_component *self)
{
  /* interrupt incompatible activities */
  switch(a->sid) {
    case -1: return 0; /* permanent activity is not interruptible */

<'  foreach s [$c services] {'>
    case <"$comp">_<"[$s name]">_SVCID:
      return genom_<"$comp">_<"[$s name]">_interrupt_reqd(a, self);
<'  }'>
  }

  assert(!"code not reached");
  return 0;
}

<'}'>
/* eof */
