Blob Blame History Raw
/* egg-frame-source.c
 *
 * Copyright (C) 2010-2016 Christian Hergert <christian@hergert.me>
 *
 * This file is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This file is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "animation/egg-frame-source.h"

typedef struct
{
   GSource parent;
   guint   fps;
   guint   frame_count;
   gint64  start_time;
} EggFrameSource;

static gboolean
egg_frame_source_prepare (GSource *source,
                          gint    *timeout_)
{
   EggFrameSource *fsource = (EggFrameSource *)(gpointer)source;
   gint64 current_time;
   guint elapsed_time;
   guint new_frame_num;
   guint frame_time;

   current_time = g_source_get_time(source) / 1000;
   elapsed_time = current_time - fsource->start_time;
   new_frame_num = elapsed_time * fsource->fps / 1000;

   /* If time has gone backwards or the time since the last frame is
    * greater than the two frames worth then reset the time and do a
    * frame now */
   if (new_frame_num < fsource->frame_count ||
       new_frame_num - fsource->frame_count > 2) {
      /* Get the frame time rounded up to the nearest ms */
      frame_time = (1000 + fsource->fps - 1) / fsource->fps;

      /* Reset the start time */
      fsource->start_time = current_time;

      /* Move the start time as if one whole frame has elapsed */
      fsource->start_time -= frame_time;
      fsource->frame_count = 0;
      *timeout_ = 0;
      return TRUE;
   } else if (new_frame_num > fsource->frame_count) {
      *timeout_ = 0;
      return TRUE;
   } else {
      *timeout_ = (fsource->frame_count + 1) * 1000 / fsource->fps - elapsed_time;
      return FALSE;
   }
}

static gboolean
egg_frame_source_check (GSource *source)
{
   gint timeout_;
   return egg_frame_source_prepare(source, &timeout_);
}

static gboolean
egg_frame_source_dispatch (GSource     *source,
                           GSourceFunc  source_func,
                           gpointer     user_data)
{
   EggFrameSource *fsource = (EggFrameSource *)(gpointer)source;
   gboolean ret;

   if ((ret = source_func(user_data)))
      fsource->frame_count++;
   return ret;
}

static GSourceFuncs source_funcs = {
   egg_frame_source_prepare,
   egg_frame_source_check,
   egg_frame_source_dispatch,
};

/**
 * egg_frame_source_add:
 * @frames_per_sec: (in): Target frames per second.
 * @callback: (in) (scope notified): A #GSourceFunc to execute.
 * @user_data: (in): User data for @callback.
 *
 * Creates a new frame source that will execute when the timeout interval
 * for the source has elapsed. The timing will try to synchronize based
 * on the end time of the animation.
 *
 * Returns: A source id that can be removed with g_source_remove().
 */
guint
egg_frame_source_add (guint       frames_per_sec,
                      GSourceFunc callback,
                      gpointer    user_data)
{
   EggFrameSource *fsource;
   GSource *source;
   guint ret;

   g_return_val_if_fail (frames_per_sec > 0, 0);
   g_return_val_if_fail (frames_per_sec <= 120, 0);

   source = g_source_new(&source_funcs, sizeof(EggFrameSource));
   fsource = (EggFrameSource *)(gpointer)source;
   fsource->fps = frames_per_sec;
   fsource->frame_count = 0;
   fsource->start_time = g_get_monotonic_time() / 1000;
   g_source_set_callback(source, callback, user_data, NULL);
   g_source_set_name(source, "EggFrameSource");

   ret = g_source_attach(source, NULL);
   g_source_unref(source);

   return ret;
}