Package org.gbcpainter.view.draw

Source Code of org.gbcpainter.view.draw.ComposableRedrawJob

package org.gbcpainter.view.draw;

import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
import org.gbcpainter.game.levels.Level;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferStrategy;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* A redraw job that draws each view component separately
* <p/>
* This job uses a {@link java.util.concurrent.ScheduledThreadPoolExecutor} to execute the redraw at fixed ratio
*
* @author Lorenzo Pellegrini
*/
@ThreadSafe
public abstract class ComposableRedrawJob<T> implements RedrawJob, Runnable {

  private final static long MILLIS_FADEOUT = 1200;

  private final static float INITIAL_FADEOUT = 0.2f;
  @NotNull
  private final Level level;
  @NotNull
  private final java.util.List<T> components;

  @NotNull
  private final Canvas drawingCanvas;

  @NotNull
  @GuardedBy ( "readWriteLock" )
  private final ScheduledThreadPoolExecutor executors;

  @GuardedBy ( "readWriteLock" )
  private final Set<DrawListener> eventObservers = new HashSet<>( 1 );
  private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  private final Lock readLock = readWriteLock.readLock();
  private final Lock writeLock = readWriteLock.writeLock();
  @GuardedBy ( "readWriteLock" )
  private int millisTimeout = 0;
  @Nullable
  @GuardedBy ( "readWriteLock" )
  private Throwable executionError = null;
  @Nullable
  @GuardedBy ( "readWriteLock" )
  private ScheduledFuture<?> scheduled = null;
  @GuardedBy ( "readWriteLock" )
  private volatile boolean paused = true;
  @GuardedBy ( "readWriteLock" )
  private volatile boolean terminated = false;
  @GuardedBy ( "readWriteLock" )
  private long millisStartFadeout = 0;


  public ComposableRedrawJob( @NotNull Canvas whereTo, @NotNull Level level, int refreshPerSecond, @NotNull java.util.List<T> components ) {
    this.drawingCanvas = whereTo;
    this.millisTimeout = 1000 / refreshPerSecond;
    this.level = level;
    this.components = new LinkedList<>( components );
    this.executors = new ScheduledThreadPoolExecutor( 1 );
  }

  @Override
  public void pauseDraw( final boolean pause ) {
    this.writeLock.lock();
    try {
      this.paused = pause;
      if ( pause ) {
        if ( this.scheduled != null ) {
          this.executors.remove( this );
          this.scheduled.cancel( false );
          this.scheduled = null;
        }
      } else {
        if ( this.scheduled == null ) {

          this.scheduled = this.executors.scheduleWithFixedDelay( this, 0, this.millisTimeout, TimeUnit.MILLISECONDS );
        }
      }
    } finally {
      this.writeLock.unlock();
    }


  }

  @Override
  public boolean isPaused() {
    this.readLock.lock();
    try {
      return this.paused;
    } finally {
      this.readLock.unlock();
    }
  }

  @Override
  public void terminate() {
    this.writeLock.lock();
    try {
      if ( ! this.terminated ) {
        this.terminated = true;
        if ( this.scheduled != null ) {
          this.executors.remove( this );
          this.scheduled.cancel( false );
          this.scheduled = null;
        }
        this.executors.shutdown();
        this.executors.purge();
      }
    } finally {
      this.writeLock.unlock();
    }
  }

  @Override
  public boolean isTerminated() {
    this.readLock.lock();
    try {
      return this.terminated;
    } finally {
      this.readLock.unlock();
    }
  }

  @Override
  public int getRefreshPerSecond() {
    this.readLock.lock();
    try {
      return 1000 / this.millisTimeout;
    } finally {
      this.readLock.unlock();
    }
  }

  @Override
  public void setRefreshPerSecond( final int refresh ) {
    this.writeLock.lock();
    try {
      this.millisTimeout = 1000 / refresh;
      if ( this.scheduled != null ) {
        this.executors.remove( this );
        this.scheduled.cancel( false );

        this.scheduled = this.executors.scheduleWithFixedDelay( this, 0, this.millisTimeout, TimeUnit.MILLISECONDS );
      }
    } finally {
      this.writeLock.unlock();
    }
  }

  @Override
  public boolean hasFailed() {
    return this. getError() != null;
  }

  @Override
  public long startFadeOut() {
    this.writeLock.lock();
    try {
      this.millisStartFadeout = System.currentTimeMillis();
      return MILLIS_FADEOUT;
    } finally {
      this.writeLock.unlock();
    }
  }

  @Override
  @Nullable
  public Throwable getError() {
    this.readLock.lock();

    try {
      return this.executionError;
    } finally {
      this.readLock.unlock();
    }
  }

  protected void setError( @Nullable Throwable error ) {
    this.writeLock.lock();
    try {
      this.executionError = error;
      if ( error != null ) {
        this.pauseDraw( true );
      }
    } finally {
      this.writeLock.unlock();
    }
  }

  @Override
  public void addLevelStopListener( @NotNull final DrawListener callback ) {
    this.writeLock.lock();
    try {
      this.eventObservers.add( callback );
    } finally {
      this.writeLock.unlock();
    }
  }

  @Override
  public void removeLevelStopListener( @NotNull final DrawListener toBeRemoved ) {
    this.writeLock.lock();
    try {
      this.eventObservers.remove( toBeRemoved );
    } finally {
      this.writeLock.unlock();
    }
  }

  @Override
  public void run() {
    if ( this.isDrawable() ) {
      BufferStrategy bs;
      Graphics2D g2;
      boolean mustCreateBuffer;

      do {
        g2 = null;
        mustCreateBuffer = false;
        bs = this.getBufferStrategy();

        if ( bs == null ) {
          if ( ! this.isDrawable() ) {
            return;
          }
          mustCreateBuffer = true;
        } else {

          g2 = this.getStrategyGraphics( bs );
          if ( g2 == null ) {
            return;
          }
        }

        if ( mustCreateBuffer ) {
          if ( ! this.createBufferStrategy() ) {

            return;
          }
        }

        if ( bs == null ) {
          bs = this.getBufferStrategy();

        }

        try {

          if ( g2 == null ) {
            this.readLock.lock();
            try {
              g2 = (Graphics2D) ( ( bs == null ) ? this.drawingCanvas.getGraphics() : this.getStrategyGraphics( bs ) );
            } finally {
              this.readLock.unlock();
            }
          }
          if ( g2 != null ) {
            g2.setColor( this.drawingCanvas.getBackground() );
            g2.fillRect( 0, 0, this.drawingCanvas.getWidth(), this.drawingCanvas.getHeight() );
            g2.setColor( this.drawingCanvas.getForeground() );
            this.doDraw( g2 );
            this.applyFadeoutEffect( g2, this.getFadeoutStartTime() );
          }
        } catch ( Exception e ) {
          this.setError( e );
          this.doCallback( this.getObservers() );
          return;
        } finally {
          if ( g2 != null && bs != null ) {
            g2.dispose();
          }
        }

        if ( bs != null ) {
          bs.show();
        }

      } while ( bs != null && bs.contentsLost() && this.isDrawable() );

    }
  }

  private void doDraw( @NotNull Graphics2D g2d ) throws Exception {
    final Rectangle previousClip = g2d.getClipBounds();
    for (T element : this.components) {
      final Rectangle where = this.getComponentRect( element );
      if ( where != null ) {
        g2d.setTransform( new AffineTransform() );
        g2d.setClip( where.x, where.y, where.width, where.height );
        this.drawComponent( element, g2d );
      }
    }

    g2d.setTransform( new AffineTransform() );
    g2d.setClip( previousClip );
  }

  private long getFadeoutStartTime() {
    this.readLock.lock();
    try {
      return this.millisStartFadeout;
    } finally {
      this.readLock.unlock();
    }
  }

  protected void applyFadeoutEffect( @NotNull Graphics2D g2d, long millisStartTime ) {
    final long currentTime = System.currentTimeMillis();
    if ( currentTime < ( millisStartTime + MILLIS_FADEOUT ) ) {
      final float fadeAlpha = INITIAL_FADEOUT + ( 1.0f - INITIAL_FADEOUT ) * ( (float) ( currentTime - millisStartTime ) / MILLIS_FADEOUT );
      g2d.setColor( new Color( 0, 0, 0, fadeAlpha ) );
      Rectangle bounds = g2d.getClipBounds();
      if ( bounds != null ) {
        g2d.fillRect( bounds.x, bounds.y, bounds.width, bounds.height );
      } else {
        g2d.fillRect( 0, 0, this.drawingCanvas.getWidth() - 1, this.drawingCanvas.getHeight() - 1 );
      }


    }
  }

  /**
   * Draws the component on screen
   *
   * The Graphics object is clipped to the position specified from {@link #getComponentRect(Object)}
   *
   * @param component The component to be drawn
   * @param g2d       The grphics object
   *
   * @throws Exception If an error occurs
   */
  protected abstract void drawComponent( @NotNull T component, @NotNull Graphics2D g2d ) throws Exception;

  /**
   * Returns the clip of the component
   *
   * @param component The component whose size is required
   *
   * @return The size of the component or null if the element is hidden
   */
  @Nullable
  protected abstract Rectangle getComponentRect( @NotNull T component );

  @NotNull
  protected Level getLevel() {
    return this.level;
  }

  @NotNull
  protected Set<DrawListener> getObservers() {
    this.readLock.lock();
    try {
      return new HashSet<>( this.eventObservers );
    } finally {
      this.readLock.unlock();
    }
  }

  protected void doCallback( @NotNull Iterable<DrawListener> observers ) {
    for (DrawListener exec : observers) {
      try {
        exec.drawStopped( this, this.level );
      } catch ( Exception ignored ) {
      }
    }
  }

  protected boolean isDrawable() {
    this.readLock.lock();
    try {
      return this.drawingCanvas.isVisible() && ( ! this.isPaused() ) && ( ! this.isTerminated() );
    } finally {
      this.readLock.unlock();
    }
  }

  @Nullable
  protected BufferStrategy getBufferStrategy() {
    this.readLock.lock();
    try {
      if ( ! this.isDrawable() ) {
        return null;
      }
      return this.drawingCanvas.getBufferStrategy();
    } finally {
      this.readLock.unlock();
    }
  }

  protected boolean createBufferStrategy() {
    try {
      EventQueue.invokeAndWait( new Runnable() {
        @Override
        public void run() {
          ComposableRedrawJob.this.createBufferStrategyDirect();
        }
      } );
    } catch ( Exception e ) {
      return false;
    }

    return true;
  }

  private void createBufferStrategyDirect() {
    this.readLock.lock();
    try {
      if ( ! this.isDrawable() ) {
        throw new IllegalStateException();
      }
      this.drawingCanvas.createBufferStrategy( 2 );
    } catch ( IllegalStateException e ) {
      throw new IllegalStateException();
    } finally {
      this.readLock.unlock();
    }
  }

  @Nullable
  protected Graphics2D getStrategyGraphics( @NotNull final BufferStrategy strategy ) {

    final Graphics2D[] holder = { null };
    try {
      EventQueue.invokeAndWait( new Runnable() {
        @Override
        public void run() {
          ComposableRedrawJob.this.readLock.lock();
          try {
            if ( ! ComposableRedrawJob.this.isDrawable() ) {
              holder[0] = null;
            } else {
              holder[0] = (Graphics2D) strategy.getDrawGraphics();

            }

          } catch ( IllegalStateException e ) {

            ComposableRedrawJob.this.createBufferStrategyDirect();
            BufferStrategy strategyRec = ComposableRedrawJob.this.getBufferStrategy();
            if ( strategyRec != null ) {
              holder[0] = ComposableRedrawJob.this.getStrategyGraphics( strategyRec );
            } else {
              holder[0] = null;
            }
          } finally {
            ComposableRedrawJob.this.readLock.unlock();
          }
        }
      } );
    } catch ( Exception e ) {
      holder[0] = null;
    }

    return holder[0];
  }

  @Override
  @NonNls
  public String toString() {
    this.readLock.lock();
    try {


    return "ComposableRedrawJob{" +
           "level=" + this.level +
           ", components=" + this.components +
           ", drawingCanvas=" + this.drawingCanvas +
           ", executors=" + this.executors +
           ", eventObservers=" + this.eventObservers +
           ", millisTimeout=" + this.millisTimeout +
           ", executionError=" + this.executionError +
           ", scheduled=" + this.scheduled +
           ", paused=" + this.paused +
           ", terminated=" + this.terminated +
           ", millisStartFadeout=" + this.millisStartFadeout +
           ", readWriteLock=" + this.readWriteLock +
           ", readLock=" + this.readLock +
           ", writeLock=" + this.writeLock +
           '}';
    } finally {
      this.readLock.unlock();
    }
  }

  @Override
  public void finalize() throws Throwable {
    this.terminate();
    super.finalize();
  }
}
TOP

Related Classes of org.gbcpainter.view.draw.ComposableRedrawJob

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.