All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alee.managers.animation.pipeline.TimedAnimationPipeline Maven / Gradle / Ivy

/*
 * This file is part of WebLookAndFeel library.
 *
 * WebLookAndFeel library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * WebLookAndFeel library 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WebLookAndFeel library.  If not, see .
 */

package com.alee.managers.animation.pipeline;

import com.alee.managers.animation.transition.Transition;
import com.alee.utils.ReflectUtils;
import com.alee.utils.TimeUtils;
import org.slf4j.LoggerFactory;

import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;

/**
 * {@link com.alee.managers.animation.pipeline.AnimationPipeline} implementation that performs animation frames at irregular time intervals
 * based on the delays returned by transitions.
 *
 * A few important notes:
 * 1. Single instance of this pipeline can process any amount of transitions with different frame rate starting at different times
 * 2. This pipeline does not guarantee that each transition frame will be executed exactly in time, but it tries to do so
 * 3. This pipeline do guarantee that each transition frame will be executed sooner or later depending on queue load and other factors
 * 4. Nanoseconds are used as unit of measurement as a more precise way to determine frames position on the timeline
 *
 * @author Mikle Garin
 * @see How to use AnimationManager
 * @see com.alee.managers.animation.AnimationManager
 */
public final class TimedAnimationPipeline extends AbstractAnimationPipeline implements Runnable
{
    /**
     * Currently active transition.
     */
    private final Set transitions;

    /**
     * Active transitions animator.
     */
    private final Thread animator;

    /**
     * Constructs new {@link TimedAnimationPipeline}.
     */
    public TimedAnimationPipeline ()
    {
        // Concurrent transitions set
        transitions = new ConcurrentSkipListSet ();

        // Active transitions animator
        animator = new Thread ( TimedAnimationPipeline.this );
        animator.setName ( ReflectUtils.getClassName ( TimedAnimationPipeline.this.getClass () ) );
        animator.setDaemon ( true );
        animator.start ();
    }

    @Override
    public void run ()
    {
        try
        {
            // Variable used to pass previous cycle start nano time
            // For the first cycle it is always {@code -1}
            long previousFrame = -1;

            // Continue running until pipeline is terminated
            while ( !isTerminated () )
            {
                // Saving current iteration start time
                // It is done first in cycle and outside of synchronization to be more precise
                final long currentFrame = System.nanoTime ();

                // Delay until next closest frame from the current frame time
                // In case no active transitions are encountered delay will be left undefined
                long delay = -1;

                // Synchronized by the pipeline instance to keep next transitions integrity and notify thread
                synchronized ( TimedAnimationPipeline.this )
                {
                    // First frame is always ignored
                    if ( previousFrame > 0 )
                    {
                        // Calculating new list of transitions that should be processed next
                        for ( final Transition transition : transitions )
                        {
                            // Performing next transition step
                            // It is up to specific transition implementation to proceed
                            final long untilNextFrame = transition.proceed ( previousFrame, currentFrame );

                            // Checking time until next transition frame
                            if ( untilNextFrame <= 0 )
                            {
                                // Removing finished or aborted transition from pipeline
                                transitions.remove ( transition );
                            }
                            else if ( delay <= 0 || untilNextFrame < delay )
                            {
                                // Updating next delay based on current transition if it is still running
                                delay = untilNextFrame;
                            }
                        }
                    }

                    // Updating previous time with current cycle start time
                    // This is necessary to avoid skipping any frames at any point
                    // We always check the timeline between previous cycle and current cycle for possible frames
                    previousFrame = currentFrame;

                    // Wait until next animation tick
                    // We will either wait until someone wakes up animator or delay ends
                    if ( delay > 0 )
                    {
                        // We need to limit delay to 1 millisecond minimum to avoid zero delay here
                        // No frames will be skipped in any case so we shouldn't worry about waiting extra time here
                        final long nextDelayMs = Math.round ( delay / TimeUtils.nsInMillisecond );
                        TimedAnimationPipeline.this.wait ( Math.max ( 1L, nextDelayMs ) );
                    }
                    else
                    {
                        // No specific delay, just wait until something wakes us up
                        TimedAnimationPipeline.this.wait ();
                    }
                }
            }
        }
        catch ( final InterruptedException ignored )
        {
            // Interrupted on shutdown
        }
    }

    @Override
    public void play ( final Transition transition )
    {
        try
        {
            // Informing transition about start
            transition.start ( System.nanoTime () );

            // Adding transition into active set
            transitions.add ( transition );

            // Reset animator
            reset ();
        }
        catch ( final Exception e )
        {
            final String msg = "Unable to play transition: %s";
            LoggerFactory.getLogger ( TimedAnimationPipeline.class ).error ( String.format ( msg, transition ), e );
        }
    }

    @Override
    public void stop ( final Transition transition )
    {
        try
        {
            // Aborting transition
            transition.abort ();

            // Removing transition from active set
            transitions.remove ( transition );

            // Reset animator
            reset ();
        }
        catch ( final Exception e )
        {
            final String msg = "Unable to stop transition: %s";
            LoggerFactory.getLogger ( TimedAnimationPipeline.class ).error ( String.format ( msg, transition ), e );
        }
    }

    @Override
    public void shutdown ()
    {
        try
        {
            // Shutdown tasks executor
            super.shutdown ();

            // Cleaning up resources
            synchronized ( TimedAnimationPipeline.this )
            {
                transitions.clear ();
                animator.interrupt ();
            }
        }
        catch ( final Exception e )
        {
            final String msg = "Unable to shutdown pipeline";
            LoggerFactory.getLogger ( TimedAnimationPipeline.class ).error ( msg, e );
        }
    }

    /**
     * Resets animator thread.
     * This will update next tick time and also reset transitions which were supposed to be procesed next.
     */
    private void reset ()
    {
        // Synchronized by the pipeline instance to keep next transitions integrity and notify thread
        synchronized ( TimedAnimationPipeline.this )
        {
            // Notifying animator to update next tick time
            TimedAnimationPipeline.this.notifyAll ();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy