<'
# Copyright (c) 2011-2015, 2017, 2025-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 Mon Mar  5 2012
#

args components
lang c
'>
#include <sys/time.h>
#include <sys/wait.h>

#include <assert.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>

#include "component.h"
#include "typeops-c.h"


/* --- genom_daemonize ----------------------------------------------------- */

/* Daemonize and wait for child. Returns only in the child context, parent
 * exits when the child signals it's ready.
 */
int
genom_daemonize()
{
  sigset_t sset, oset;

  sigemptyset(&sset);
  sigaddset(&sset, SIGUSR1); /* proper startup */
  sigaddset(&sset, SIGCHLD); /* exited before startup */
  sigprocmask(SIG_BLOCK, &sset, &oset);

  switch(fork()) {
    case -1:
      perror("cannot fork");
      return 2;

    case 0: /* child */ {
      int fd;

      /* restore original mask */
      sigprocmask(SIG_SETMASK, &oset, NULL);

      if (setsid() == -1) { perror("setsid"); return 2; }
      if (chdir("/")) { /*nop*/; }

      fd = open("/dev/null", O_RDWR, 0);
      if (fd != -1) {
        (void)dup2(fd, STDIN_FILENO);
        if (fd > STDERR_FILENO)
          (void)close(fd);
      }

      /* tell child process to send back SIGUSR1 when ready */
      signal(SIGUSR1, SIG_IGN);
      break;
    }

    default: /* parent */ {
      int sig = 0;

      /* wait for child */
      do { sigwait(&sset, &sig); } while (sig == 0);
      assert(sig == SIGUSR1 || sig == SIGCHLD);

      if (sig == SIGUSR1) _exit(0);
      wait(&sig);
      _exit(WEXITSTATUS(sig));
    }
  }

  return 0;
}

/* --- genom_signal_parent ------------------------------------------------- */

/* Send SIGUSR1 to parent pid if it ignores it.
 * Either the parent really ignores it, so they won't care. Or the parent
 * actually waits for it after genom_daemonize() and this signals them that
 * they can leave.
 */
void
genom_signal_parent()
{
  struct sigaction sa;

  if (sigaction(SIGUSR1, NULL, &sa)) { perror("sigaction"); return; }

  if (sa.sa_handler == SIG_IGN)
    kill(getppid(), SIGUSR1);
}


/* --- genom_component_raise ----------------------------------------------- */

static genom_event
genom_component_raise(genom_event e, const void *detail, size_t size,
                      genom_context self)
{
  return genom_raise(e, detail, size, &self->data->ex);
}

static const void *
genom_component_raised(genom_event *e, genom_context self)
{
  return genom_raised(e, &self->data->ex);
}

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

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

/* <"[--- genom_tinit_${comp}_component ---------------------------------]"> */

/* Initialize component data structure */
void
genom_tinit_<"$comp">_component(struct genom_<"$comp">_component *self)
{
  *self = (struct genom_<"$comp">_component){
    .instance = "<"$comp">",
    .control = {
      .context = {
        .raise = genom_component_raise,
        .raised = genom_component_raised,
        .data = &self->control.context_data
      },
      .context_data.<"$comp">_ = self,
      .activity.any.status = ACT_VOID,
      .lock = PTHREAD_MUTEX_INITIALIZER,
      .sync = PTHREAD_COND_INITIALIZER
    },
    .resources = {
      .lock = PTHREAD_MUTEX_INITIALIZER,
      .sync = PTHREAD_COND_INITIALIZER
    }
  };

<'  if {![catch {$component ids} ids]} {'>
  genom_tinit_<"[$ids mangle]">(&self->ids);
<'  }'>

<'  foreach t [$c tasks] { '>
  self->tasks.<"[$t name]">_ = (struct genom_<"$comp">_exec_task){
    .context = {
      .raise = genom_component_raise,
      .raised = genom_component_raised,
      .data = &self->tasks.<"[$t name]">_.context_data
    },
    .context_data.<"$comp">_ = self,
    .name = "<"[$t name]">",
    .lock = PTHREAD_MUTEX_INITIALIZER,
    .sync = PTHREAD_COND_INITIALIZER
  };
  for(size_t id = 0; id < GENOM_MAX_ACTIVITIES; id++)
    self->tasks.<"[$t name]">_.activities.a[id].any.status = ACT_VOID;
<'  }'>
  gettimeofday(&self->control.wakeup, NULL);
}


/* <"[--- genom_tfini_${comp}_component ---------------------------------]"> */

/* Finalize component data structure */
void
genom_tfini_<"$comp">_component(struct genom_<"$comp">_component *self)
{
<'  if {![catch {$component ids} ids]} {'>
  genom_tfini_<"[$ids mangle]">(&self->ids);
<'  }'>
}


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

/* Process running activities in an execution task context, until shutdown
 * request. Apart from any specific initialization or termination, this is
 * the completement implementation of an execution task. */
static void
genom_<"$comp">_task_loop(
  struct genom_<"$comp">_exec_task *task,
  int periodic, const struct itimerspec period,
  enum genom_activity_status (*invoke)(
    union genom_<"$comp">_activity_any *, struct genom_<"$comp">_component *),
  struct genom_<"$comp">_component *self)
{
  genom_state_rusage rusage = { 0 };
  enum genom_activity_event e;
  struct timeval tvs, tve, tvp;
  struct timespec deadline;
  int ontick, missed;

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

  /* setup wake up deadline */
  gettimeofday(&tvs, NULL);
  if (period.it_interval.tv_sec > 1)
    deadline.tv_sec =
      tvs.tv_sec / period.it_interval.tv_sec * period.it_interval.tv_sec;
  else
    deadline.tv_sec = tvs.tv_sec;
  if (period.it_interval.tv_nsec)
    deadline.tv_nsec =
      tvs.tv_usec * 1000 /
      period.it_interval.tv_nsec * period.it_interval.tv_nsec;
  else
    deadline.tv_nsec = 0;

  deadline.tv_sec += period.it_value.tv_sec;
  deadline.tv_nsec += period.it_value.tv_nsec;
  if (deadline.tv_nsec >= 1000000000) {
    deadline.tv_sec++;
    deadline.tv_nsec -= 1000000000;
  }

  /* run */
  genom_log_info(self->instance, "spawned task %s", task->name);
  tve = tvp = tvs;
  e = 0;

  do {
    /* wait for next event */
    pthread_mutex_lock(&task->lock);
    task->rusage = rusage;
    ontick = periodic ? 0 : task->runnable;
    if (!(e & ACTEVT_MORE)) {
      /* next deadline, counting missed periods */
      for(missed = 0;
          tve.tv_sec > deadline.tv_sec || (
            tve.tv_sec == deadline.tv_sec &&
            tve.tv_usec * 1000 > deadline.tv_nsec); missed++) {
        deadline.tv_sec += period.it_interval.tv_sec;
        deadline.tv_nsec += period.it_interval.tv_nsec;
        if (deadline.tv_nsec >= 1000000000) {
          deadline.tv_sec++;
          deadline.tv_nsec -= 1000000000;
        }
      }
      if (missed > 1)
        genom_log_debug(
          self->instance, "%s missed %d periods", task->name, missed-1);

      while (periodic ? 1 : !task->runnable)
        if (pthread_cond_timedwait(&task->sync, &task->lock, &deadline))
          /* timeout */ break;

      ontick = 1;
      task->runnable = 0;
    }
    if (task->shutdown) {
      pthread_mutex_unlock(&task->lock);
      break;
    }
    pthread_mutex_unlock(&task->lock);

    if (ontick) {
      genom_log_debug(self->instance, "%s tick", task->name);
      gettimeofday(&tvs, NULL);
    }

    /* exec activities */
    e = genom_<"$comp">_activities_exec(task, ontick, invoke, self);

    /* update rusage - after a period for periodic tasks */
    if (!(periodic && (e & ACTEVT_MORE))) {
      double last, dt;

      tvp = tve;
      gettimeofday(&tve, NULL);
      last = tve.tv_sec - tvs.tv_sec + (tve.tv_usec - tvs.tv_usec)*1e-6;
      dt = tve.tv_sec - tvp.tv_sec + (tve.tv_usec - tvp.tv_usec)*1e-6;

      if (e & ACTEVT_EXEC) { /* if something was executed */
        rusage.cycles++;

        rusage.timings.last = last;
        rusage.load.last = 100. * last / dt;

        if (rusage.timings.max < rusage.timings.last)
          rusage.timings.max = rusage.timings.last;
        if (rusage.load.max < rusage.load.last)
          rusage.load.max = rusage.load.last;
      }

      rusage.timings.avg += (last - rusage.timings.avg) * dt / 10.;
      rusage.load.avg += (100. * last - dt * rusage.load.avg) / 10.;

      /* force genom_state update after 1s */
      pthread_mutex_lock(&self->control.lock);
      if (self->control.wakeup.tv_sec != tve.tv_sec)
        self->control.signalcb(self);
      pthread_mutex_unlock(&self->control.lock);
    }
  } while(1);

  genom_log_info(self->instance, "shutting down task %s", task->name);

  pthread_mutex_lock(&task->lock);
  task->spawned = 0;
  pthread_cond_broadcast(&task->sync);
  pthread_mutex_unlock(&task->lock);
}

<'  foreach t [$c tasks] {'>
/* <"[$t name]"> */
void *
genom_<"$comp">_task_<"[$t name]">_loop(void *data)
{
  struct genom_<"$comp">_component *self = data;
  struct genom_<"$comp">_exec_task * const task = &self->tasks.<"[$t name]">_;

<'    if {[catch {[$t period] value} period]} {'>
<'      set period 1.'>
  /* non-periodic, but wakes up every <"$period">s for rusage computation */
<'}'>
<'    if {[catch {[$t delay] value} delay]} { set delay 0. }'>
  const struct itimerspec period = {
    .it_interval = {
      .tv_sec = <"[expr {int($period)}]">,
      .tv_nsec = <"[expr {round(($period - int($period)) * 1000000000)}]">
    },
    .it_value = {
      .tv_sec = <"[expr {int($delay)}]">,
      .tv_nsec = <"[expr {round(($delay - int($delay)) * 1000000000)}]">
    }
  };

  genom_<"$comp">_task_loop(
    task, <"[expr {[catch {$t period}] ? 0 : 1}]">, period,
    genom_<"$comp">_<"[$t name]">_invoke, self);
  return NULL;
}

<'  }'>

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

/* Signal all tasks for shutdown in reverse order and wait for each of them.
 * This should be called when all activities have already been stopped and no
 * new activitiy can be spawned (e.g. when the control task is already
 * shutdown).
 */
void
genom_<"$comp">_task_shutdown(struct genom_<"$comp">_component *self)
{
<'  foreach t [lreverse [$c tasks]] {'>
  /* <"[$t name]"> */
  genom_log_debug(self->instance, "waiting task %s", "<"[$t name]">");
  pthread_mutex_lock(&self->tasks.<"[$t name]">_.lock);

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

  self->tasks.<"[$t name]">_.shutdown = 1;
  self->tasks.<"[$t name]">_.runnable = 1;
  pthread_cond_broadcast(&self->tasks.<"[$t name]">_.sync);
  while (self->tasks.<"[$t name]">_.spawned > 0)
    pthread_cond_wait(&self->tasks.<"[$t name]">_.sync,
                      &self->tasks.<"[$t name]">_.lock);
  pthread_mutex_unlock(&self->tasks.<"[$t name]">_.lock);

<'}'>
}

<'}'>
/* eof */
