/* egg-frame-source.c * * Copyright (C) 2010-2016 Christian Hergert * * 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 . */ #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; }