Update replay frames in a new thread.
This drops less frames, but is still pretty bad. See #42. - Changed some LinkedList classes to LinkedBlockingDeques and added some synchronized methods. - Slight modifications to OpenALStreamPlayer (may or may not be slightly more accurate). Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
parent
39caf30770
commit
1f8c150e6c
|
@ -26,6 +26,7 @@ import itdelatrisu.opsu.audio.SoundEffect;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.concurrent.LinkedBlockingDeque;
|
||||||
|
|
||||||
import org.newdawn.slick.Animation;
|
import org.newdawn.slick.Animation;
|
||||||
import org.newdawn.slick.Color;
|
import org.newdawn.slick.Color;
|
||||||
|
@ -165,7 +166,7 @@ public class GameData {
|
||||||
private int[] hitResultOffset;
|
private int[] hitResultOffset;
|
||||||
|
|
||||||
/** List of hit result objects associated with hit objects. */
|
/** List of hit result objects associated with hit objects. */
|
||||||
private LinkedList<OsuHitObjectResult> hitResultList;
|
private LinkedBlockingDeque<OsuHitObjectResult> hitResultList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to store hit error information.
|
* Class to store hit error information.
|
||||||
|
@ -198,7 +199,7 @@ public class GameData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** List containing recent hit error information. */
|
/** List containing recent hit error information. */
|
||||||
private LinkedList<HitErrorInfo> hitErrorList;
|
private LinkedBlockingDeque<HitErrorInfo> hitErrorList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hit result helper class.
|
* Hit result helper class.
|
||||||
|
@ -332,8 +333,8 @@ public class GameData {
|
||||||
health = 100f;
|
health = 100f;
|
||||||
healthDisplay = 100f;
|
healthDisplay = 100f;
|
||||||
hitResultCount = new int[HIT_MAX];
|
hitResultCount = new int[HIT_MAX];
|
||||||
hitResultList = new LinkedList<OsuHitObjectResult>();
|
hitResultList = new LinkedBlockingDeque<OsuHitObjectResult>();
|
||||||
hitErrorList = new LinkedList<HitErrorInfo>();
|
hitErrorList = new LinkedBlockingDeque<HitErrorInfo>();
|
||||||
fullObjectCount = 0;
|
fullObjectCount = 0;
|
||||||
combo = 0;
|
combo = 0;
|
||||||
comboMax = 0;
|
comboMax = 0;
|
||||||
|
|
|
@ -148,6 +148,9 @@ public class Game extends BasicGameState {
|
||||||
/** Number of retries. */
|
/** Number of retries. */
|
||||||
private int retries = 0;
|
private int retries = 0;
|
||||||
|
|
||||||
|
/** Whether or not this game is a replay. */
|
||||||
|
private boolean isReplay = false;
|
||||||
|
|
||||||
/** The replay, if any. */
|
/** The replay, if any. */
|
||||||
private Replay replay;
|
private Replay replay;
|
||||||
|
|
||||||
|
@ -163,6 +166,12 @@ public class Game extends BasicGameState {
|
||||||
/** The replay skip time, or -1 if none. */
|
/** The replay skip time, or -1 if none. */
|
||||||
private int replaySkipTime = -1;
|
private int replaySkipTime = -1;
|
||||||
|
|
||||||
|
/** The thread updating the replay frames. */
|
||||||
|
private Thread replayThread;
|
||||||
|
|
||||||
|
/** Whether or not the replay thread should continue running. */
|
||||||
|
private boolean replayThreadRunning;
|
||||||
|
|
||||||
/** The previous game mod state (before the replay). */
|
/** The previous game mod state (before the replay). */
|
||||||
private int previousMods = 0;
|
private int previousMods = 0;
|
||||||
|
|
||||||
|
@ -385,7 +394,7 @@ public class Game extends BasicGameState {
|
||||||
cursorCirclePulse.drawCentered(pausedMouseX, pausedMouseY);
|
cursorCirclePulse.drawCentered(pausedMouseX, pausedMouseY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (replay == null)
|
if (!isReplay)
|
||||||
UI.draw(g);
|
UI.draw(g);
|
||||||
else
|
else
|
||||||
UI.draw(g, replayX, replayY, replayKeyPressed);
|
UI.draw(g, replayX, replayY, replayKeyPressed);
|
||||||
|
@ -396,7 +405,7 @@ public class Game extends BasicGameState {
|
||||||
throws SlickException {
|
throws SlickException {
|
||||||
UI.update(delta);
|
UI.update(delta);
|
||||||
int mouseX, mouseY;
|
int mouseX, mouseY;
|
||||||
if (replay == null) {
|
if (!isReplay) {
|
||||||
mouseX = input.getMouseX();
|
mouseX = input.getMouseX();
|
||||||
mouseY = input.getMouseY();
|
mouseY = input.getMouseY();
|
||||||
} else {
|
} else {
|
||||||
|
@ -460,7 +469,7 @@ public class Game extends BasicGameState {
|
||||||
else { // go to ranking screen
|
else { // go to ranking screen
|
||||||
((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data);
|
((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data);
|
||||||
ScoreData score = data.getScoreData(osu);
|
ScoreData score = data.getScoreData(osu);
|
||||||
if (!GameMod.AUTO.isActive() && !GameMod.RELAX.isActive() && !GameMod.AUTOPILOT.isActive() && replay == null)
|
if (!GameMod.AUTO.isActive() && !GameMod.RELAX.isActive() && !GameMod.AUTOPILOT.isActive() && !isReplay)
|
||||||
ScoreDB.addScore(score);
|
ScoreDB.addScore(score);
|
||||||
game.enterState(Opsu.STATE_GAMERANKING, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
game.enterState(Opsu.STATE_GAMERANKING, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
||||||
}
|
}
|
||||||
|
@ -484,29 +493,12 @@ public class Game extends BasicGameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// replays
|
// replays
|
||||||
if (replay != null) {
|
if (isReplay) {
|
||||||
// skip intro
|
// skip intro
|
||||||
if (replaySkipTime > 0 && trackPosition > replaySkipTime) {
|
if (replaySkipTime > 0 && trackPosition > replaySkipTime) {
|
||||||
skipIntro();
|
skipIntro();
|
||||||
replaySkipTime = -1;
|
replaySkipTime = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// load next frame(s)
|
|
||||||
int replayKey = ReplayFrame.KEY_NONE;
|
|
||||||
while (replayIndex < replay.frames.length && trackPosition >= replay.frames[replayIndex].getTime()) {
|
|
||||||
ReplayFrame frame = replay.frames[replayIndex];
|
|
||||||
replayX = frame.getX();
|
|
||||||
replayY = frame.getY();
|
|
||||||
replayKeyPressed = frame.isKeyPressed();
|
|
||||||
if (replayKeyPressed)
|
|
||||||
replayKey = frame.getKeys();
|
|
||||||
replayIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// send a key press
|
|
||||||
if (replayKey != ReplayFrame.KEY_NONE)
|
|
||||||
gameKeyPressed(((replayKey & ReplayFrame.KEY_M1) > 0) ?
|
|
||||||
Input.MOUSE_LEFT_BUTTON : Input.MOUSE_RIGHT_BUTTON, replayX, replayY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// song beginning
|
// song beginning
|
||||||
|
@ -534,7 +526,7 @@ public class Game extends BasicGameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// pause game if focus lost
|
// pause game if focus lost
|
||||||
if (!container.hasFocus() && !GameMod.AUTO.isActive() && replay == null) {
|
if (!container.hasFocus() && !GameMod.AUTO.isActive() && !isReplay) {
|
||||||
if (pauseTime < 0) {
|
if (pauseTime < 0) {
|
||||||
pausedMouseX = mouseX;
|
pausedMouseX = mouseX;
|
||||||
pausedMouseY = mouseY;
|
pausedMouseY = mouseY;
|
||||||
|
@ -559,14 +551,14 @@ public class Game extends BasicGameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// game over, force a restart
|
// game over, force a restart
|
||||||
if (replay == null) {
|
if (!isReplay) {
|
||||||
restart = Restart.LOSE;
|
restart = Restart.LOSE;
|
||||||
game.enterState(Opsu.STATE_GAMEPAUSEMENU);
|
game.enterState(Opsu.STATE_GAMEPAUSEMENU);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update objects (loop in unlikely event of any skipped indexes)
|
// update objects (loop in unlikely event of any skipped indexes)
|
||||||
boolean keyPressed = ((replay != null && replayKeyPressed) || Utils.isGameKeyPressed());
|
boolean keyPressed = ((isReplay && replayKeyPressed) || Utils.isGameKeyPressed());
|
||||||
while (objectIndex < hitObjects.length && trackPosition > osu.objects[objectIndex].getTime()) {
|
while (objectIndex < hitObjects.length && trackPosition > osu.objects[objectIndex].getTime()) {
|
||||||
// check if we've already passed the next object's start time
|
// check if we've already passed the next object's start time
|
||||||
boolean overlap = (objectIndex + 1 < hitObjects.length &&
|
boolean overlap = (objectIndex + 1 < hitObjects.length &&
|
||||||
|
@ -588,7 +580,7 @@ public class Game extends BasicGameState {
|
||||||
int trackPosition = MusicController.getPosition();
|
int trackPosition = MusicController.getPosition();
|
||||||
|
|
||||||
// game keys
|
// game keys
|
||||||
if (!Keyboard.isRepeatEvent() && replay == null) {
|
if (!Keyboard.isRepeatEvent() && !isReplay) {
|
||||||
if (key == Options.getGameKeyLeft())
|
if (key == Options.getGameKeyLeft())
|
||||||
gameKeyPressed(Input.MOUSE_LEFT_BUTTON, input.getMouseX(), input.getMouseY());
|
gameKeyPressed(Input.MOUSE_LEFT_BUTTON, input.getMouseX(), input.getMouseY());
|
||||||
else if (key == Options.getGameKeyRight())
|
else if (key == Options.getGameKeyRight())
|
||||||
|
@ -598,7 +590,7 @@ public class Game extends BasicGameState {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case Input.KEY_ESCAPE:
|
case Input.KEY_ESCAPE:
|
||||||
// "auto" mod or watching replay: go back to song menu
|
// "auto" mod or watching replay: go back to song menu
|
||||||
if (GameMod.AUTO.isActive() || replay != null) {
|
if (GameMod.AUTO.isActive() || isReplay) {
|
||||||
game.closeRequested();
|
game.closeRequested();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -696,7 +688,7 @@ public class Game extends BasicGameState {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// watching replay
|
// watching replay
|
||||||
if (replay != null) {
|
if (isReplay) {
|
||||||
// only allow skip button
|
// only allow skip button
|
||||||
if (button != Input.MOUSE_MIDDLE_BUTTON && skipButton.contains(x, y))
|
if (button != Input.MOUSE_MIDDLE_BUTTON && skipButton.contains(x, y))
|
||||||
skipIntro();
|
skipIntro();
|
||||||
|
@ -837,7 +829,7 @@ public class Game extends BasicGameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// load replay frames
|
// load replay frames
|
||||||
if (replay != null) {
|
if (isReplay) {
|
||||||
// unhide cursor
|
// unhide cursor
|
||||||
UI.showCursor();
|
UI.showCursor();
|
||||||
|
|
||||||
|
@ -862,6 +854,44 @@ public class Game extends BasicGameState {
|
||||||
} else
|
} else
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// run frame updates in another thread
|
||||||
|
killReplayThread();
|
||||||
|
replayThread = new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (replayThreadRunning) {
|
||||||
|
// update frames
|
||||||
|
int trackPosition = MusicController.getPosition();
|
||||||
|
int keys = ReplayFrame.KEY_NONE;
|
||||||
|
while (replayIndex < replay.frames.length && trackPosition >= replay.frames[replayIndex].getTime()) {
|
||||||
|
ReplayFrame frame = replay.frames[replayIndex];
|
||||||
|
replayX = frame.getX();
|
||||||
|
replayY = frame.getY();
|
||||||
|
replayKeyPressed = frame.isKeyPressed();
|
||||||
|
if (replayKeyPressed)
|
||||||
|
keys = frame.getKeys();
|
||||||
|
replayIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a key press
|
||||||
|
if (replayKeyPressed && keys != ReplayFrame.KEY_NONE)
|
||||||
|
gameKeyPressed(((keys & ReplayFrame.KEY_M1) > 0) ?
|
||||||
|
Input.MOUSE_LEFT_BUTTON : Input.MOUSE_RIGHT_BUTTON, replayX, replayY);
|
||||||
|
|
||||||
|
// out of frames
|
||||||
|
if (replayIndex >= replay.frames.length)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// sleep execution
|
||||||
|
try {
|
||||||
|
Thread.sleep(0, 512000);
|
||||||
|
} catch (InterruptedException e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
replayThreadRunning = true;
|
||||||
|
replayThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
leadInTime = osu.audioLeadIn + approachTime;
|
leadInTime = osu.audioLeadIn + approachTime;
|
||||||
|
@ -876,10 +906,11 @@ public class Game extends BasicGameState {
|
||||||
throws SlickException {
|
throws SlickException {
|
||||||
// container.setMouseGrabbed(false);
|
// container.setMouseGrabbed(false);
|
||||||
|
|
||||||
// reset previous mod state and re-hide cursor
|
// replays
|
||||||
if (replay != null) {
|
if (isReplay) {
|
||||||
GameMod.loadModState(previousMods);
|
GameMod.loadModState(previousMods);
|
||||||
UI.hideCursor();
|
UI.hideCursor();
|
||||||
|
killReplayThread();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -914,7 +945,7 @@ public class Game extends BasicGameState {
|
||||||
* Skips the beginning of a track.
|
* Skips the beginning of a track.
|
||||||
* @return true if skipped, false otherwise
|
* @return true if skipped, false otherwise
|
||||||
*/
|
*/
|
||||||
private boolean skipIntro() {
|
private synchronized boolean skipIntro() {
|
||||||
int firstObjectTime = osu.objects[0].getTime();
|
int firstObjectTime = osu.objects[0].getTime();
|
||||||
int trackPosition = MusicController.getPosition();
|
int trackPosition = MusicController.getPosition();
|
||||||
if (objectIndex == 0 && trackPosition < firstObjectTime - SKIP_OFFSET) {
|
if (objectIndex == 0 && trackPosition < firstObjectTime - SKIP_OFFSET) {
|
||||||
|
@ -923,6 +954,8 @@ public class Game extends BasicGameState {
|
||||||
MusicController.resume();
|
MusicController.resume();
|
||||||
}
|
}
|
||||||
replaySkipTime = -1;
|
replaySkipTime = -1;
|
||||||
|
if (replayThread != null && replayThread.isAlive())
|
||||||
|
replayThread.interrupt();
|
||||||
MusicController.setPosition(firstObjectTime - SKIP_OFFSET);
|
MusicController.setPosition(firstObjectTime - SKIP_OFFSET);
|
||||||
SoundController.playSound(SoundEffect.MENUHIT);
|
SoundController.playSound(SoundEffect.MENUHIT);
|
||||||
return true;
|
return true;
|
||||||
|
@ -1052,9 +1085,23 @@ public class Game extends BasicGameState {
|
||||||
*/
|
*/
|
||||||
public float getTimingPointMultiplier() { return beatLength / beatLengthBase; }
|
public float getTimingPointMultiplier() { return beatLength / beatLengthBase; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kills the running replay updating thread, if any.
|
||||||
|
*/
|
||||||
|
private void killReplayThread() {
|
||||||
|
if (replayThread != null && replayThread.isAlive()) {
|
||||||
|
replayThreadRunning = false;
|
||||||
|
replayThread.interrupt();
|
||||||
|
}
|
||||||
|
replayThread = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a replay to view.
|
* Sets a replay to view.
|
||||||
* @param replay the replay
|
* @param replay the replay
|
||||||
*/
|
*/
|
||||||
public void setReplay(Replay replay) { this.replay = replay; }
|
public void setReplay(Replay replay) {
|
||||||
|
this.isReplay = true;
|
||||||
|
this.replay = replay;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,6 +239,8 @@ public class OpenALStreamPlayer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long playedPos_ = playedPos;
|
||||||
|
long lastUpdateTime_ = lastUpdateTime;
|
||||||
int processed = AL10.alGetSourcei(source, AL10.AL_BUFFERS_PROCESSED);
|
int processed = AL10.alGetSourcei(source, AL10.AL_BUFFERS_PROCESSED);
|
||||||
while (processed > 0) {
|
while (processed > 0) {
|
||||||
unqueued.clear();
|
unqueued.clear();
|
||||||
|
@ -248,11 +250,11 @@ public class OpenALStreamPlayer {
|
||||||
|
|
||||||
int bufferLength = AL10.alGetBufferi(bufferIndex, AL10.AL_SIZE);
|
int bufferLength = AL10.alGetBufferi(bufferIndex, AL10.AL_SIZE);
|
||||||
|
|
||||||
playedPos += bufferLength;
|
playedPos_ += bufferLength;
|
||||||
lastUpdateTime = getTime();
|
lastUpdateTime_ = getTime();
|
||||||
|
|
||||||
if (musicLength > 0 && playedPos > musicLength)
|
if (musicLength > 0 && playedPos_ > musicLength)
|
||||||
playedPos -= musicLength;
|
playedPos_ -= musicLength;
|
||||||
|
|
||||||
if (stream(bufferIndex)) {
|
if (stream(bufferIndex)) {
|
||||||
AL10.alSourceQueueBuffers(source, unqueued);
|
AL10.alSourceQueueBuffers(source, unqueued);
|
||||||
|
@ -264,6 +266,8 @@ public class OpenALStreamPlayer {
|
||||||
}
|
}
|
||||||
processed--;
|
processed--;
|
||||||
}
|
}
|
||||||
|
playedPos = playedPos_;
|
||||||
|
lastUpdateTime = lastUpdateTime_;
|
||||||
|
|
||||||
int state = AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE);
|
int state = AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE);
|
||||||
|
|
||||||
|
@ -382,7 +386,7 @@ public class OpenALStreamPlayer {
|
||||||
* @return The current position in seconds.
|
* @return The current position in seconds.
|
||||||
*/
|
*/
|
||||||
public float getPosition() {
|
public float getPosition() {
|
||||||
float playedTime = ((float) playedPos / (float) sampleSize) / sampleRate;
|
float playedTime = ((float) playedPos / sampleSize) / sampleRate;
|
||||||
float timePosition = playedTime + (getTime() - lastUpdateTime) / 1000f;
|
float timePosition = playedTime + (getTime() - lastUpdateTime) / 1000f;
|
||||||
// + AL10.alGetSourcef(source, AL11.AL_SEC_OFFSET);
|
// + AL10.alGetSourcef(source, AL11.AL_SEC_OFFSET);
|
||||||
return timePosition;
|
return timePosition;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user