-
Notifications
You must be signed in to change notification settings - Fork 121
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Load beatmap background images in a new thread.
This eliminates the game-wide lag (100-200ms on my computer) when switching song nodes. Attempted to mask the loading time with a fade-in effect. Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
- Loading branch information
1 parent
e767800
commit 3214916
Showing
7 changed files
with
280 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
/* | ||
* opsu! - an open-source osu! client | ||
* Copyright (C) 2014, 2015 Jeffrey Han | ||
* | ||
* opsu! 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. | ||
* | ||
* opsu! 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 opsu!. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
package itdelatrisu.opsu.beatmap; | ||
|
||
import java.io.BufferedInputStream; | ||
import java.io.File; | ||
import java.io.FileInputStream; | ||
import java.io.IOException; | ||
import java.nio.ByteBuffer; | ||
|
||
import org.newdawn.slick.Image; | ||
import org.newdawn.slick.SlickException; | ||
import org.newdawn.slick.opengl.ImageData; | ||
import org.newdawn.slick.opengl.ImageDataFactory; | ||
import org.newdawn.slick.opengl.LoadableImageData; | ||
import org.newdawn.slick.util.Log; | ||
|
||
/** | ||
* Simple threaded image loader for a single image file. | ||
*/ | ||
public class ImageLoader { | ||
/** The image file. */ | ||
private final File file; | ||
|
||
/** The loaded image. */ | ||
private Image image; | ||
|
||
/** The image data. */ | ||
private LoadedImageData data; | ||
|
||
/** The image loader thread. */ | ||
private Thread loaderThread; | ||
|
||
/** ImageData wrapper, needed because {@code ImageIOImageData} doesn't implement {@code getImageBufferData()}. */ | ||
private class LoadedImageData implements ImageData { | ||
/** The image data implementation. */ | ||
private final ImageData imageData; | ||
|
||
/** The stored image. */ | ||
private final ByteBuffer buffer; | ||
|
||
/** | ||
* Constructor. | ||
* @param imageData the class holding the image properties | ||
* @param buffer the stored image | ||
*/ | ||
public LoadedImageData(ImageData imageData, ByteBuffer buffer) { | ||
this.imageData = imageData; | ||
this.buffer = buffer; | ||
} | ||
|
||
@Override public int getDepth() { return imageData.getDepth(); } | ||
@Override public int getWidth() { return imageData.getWidth(); } | ||
@Override public int getHeight() { return imageData.getHeight();} | ||
@Override public int getTexWidth() { return imageData.getTexWidth(); } | ||
@Override public int getTexHeight() { return imageData.getTexHeight(); } | ||
@Override public ByteBuffer getImageBufferData() { return buffer; } | ||
} | ||
|
||
/** Image loading thread. */ | ||
private class ImageLoaderThread extends Thread { | ||
/** The image file input stream. */ | ||
private BufferedInputStream in; | ||
|
||
@Override | ||
public void interrupt() { | ||
super.interrupt(); | ||
if (in != null) { | ||
try { | ||
in.close(); // interrupt I/O | ||
} catch (IOException e) {} | ||
} | ||
} | ||
|
||
@Override | ||
public void run() { | ||
// load image data into a ByteBuffer to use constructor Image(ImageData) | ||
LoadableImageData imageData = ImageDataFactory.getImageDataFor(file.getAbsolutePath()); | ||
try (BufferedInputStream in = this.in = new BufferedInputStream(new FileInputStream(file))) { | ||
ByteBuffer textureBuffer = imageData.loadImage(in, false, null); | ||
if (!isInterrupted()) | ||
data = new LoadedImageData(imageData, textureBuffer); | ||
} catch (IOException e) { | ||
if (!isInterrupted()) | ||
Log.warn(String.format("Failed to load background image '%s'.", file), e); | ||
} | ||
this.in = null; | ||
} | ||
} | ||
|
||
/** | ||
* Constructor. Call {@link ImageLoader#load(boolean)} to load the image. | ||
* @param file the image file | ||
*/ | ||
public ImageLoader(File file) { | ||
this.file = file; | ||
} | ||
|
||
/** | ||
* Loads the image. | ||
* @param threaded true to load the image data in a new thread | ||
*/ | ||
public void load(boolean threaded) { | ||
if (!file.isFile()) | ||
return; | ||
|
||
if (threaded) { | ||
if (loaderThread != null && loaderThread.isAlive()) | ||
loaderThread.interrupt(); | ||
loaderThread = new ImageLoaderThread(); | ||
loaderThread.start(); | ||
} else { | ||
try { | ||
image = new Image(file.getAbsolutePath()); | ||
} catch (SlickException e) { | ||
Log.warn(String.format("Failed to load background image '%s'.", file), e); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Returns the image. | ||
* @return the loaded image, or null if not loaded | ||
*/ | ||
public Image getImage() { | ||
if (image == null && data != null) { | ||
image = new Image(data); | ||
data = null; | ||
} | ||
return image; | ||
} | ||
|
||
/** | ||
* Returns whether an image is currently loading in another thread. | ||
* @return true if loading, false otherwise | ||
*/ | ||
public boolean isLoading() { return (loaderThread != null && loaderThread.isAlive()); } | ||
|
||
/** | ||
* Interrupts the image loader, if running. | ||
*/ | ||
public void interrupt() { | ||
if (isLoading()) | ||
loaderThread.interrupt(); | ||
} | ||
|
||
/** | ||
* Releases all resources. | ||
*/ | ||
public void destroy() { | ||
interrupt(); | ||
loaderThread = null; | ||
if (image != null && !image.isDestroyed()) { | ||
try { | ||
image.destroy(); | ||
} catch (SlickException e) { | ||
Log.warn(String.format("Failed to destroy image '%s'.", image.getResourceReference()), e); | ||
} | ||
image = null; | ||
} | ||
data = null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.