Skip to content

Commit

Permalink
Merge pull request #24 from svencc/feature/more-fps
Browse files Browse the repository at this point in the history
Feature/more fps
  • Loading branch information
svencc authored Feb 3, 2024
2 parents 54e09e1 + cb8f88a commit 5fe5cf1
Show file tree
Hide file tree
Showing 14 changed files with 162 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,10 @@ public class SwappableCanvasBuffer {

private IntBuffer intBuffer = null;
private PixelFormat<IntBuffer> pixelFormat = null;
private PixelBuffer<IntBuffer> pixelBuffer = null;
private PixelBuffer<IntBuffer> imagePixelBuffer = null;
private WritableImage img = null;


public int currentBackBufferIndex = 0;
public int lastBackBufferIndex = -1;
public int currentShownBackBufferIndex = -1;


public SwappableCanvasBuffer(
Expand All @@ -39,20 +37,21 @@ public SwappableCanvasBuffer(

intBuffer = IntBuffer.allocate(rendererProperties.getWidth() * rendererProperties.getHeight());
pixelFormat = PixelFormat.getIntArgbPreInstance();
pixelBuffer = new PixelBuffer<>(rendererProperties.getWidth(), rendererProperties.getHeight(), intBuffer, pixelFormat);
img = new WritableImage(pixelBuffer);
imagePixelBuffer = new PixelBuffer<>(rendererProperties.getWidth(), rendererProperties.getHeight(), intBuffer, pixelFormat);
img = new WritableImage(imagePixelBuffer);
}

void swap() {
// swap the back buffer, and the front buffer
if (currentBackBufferIndex == lastBackBufferIndex) {
int previouslyFinishedBufferIndex = screenComposer.getPreviouslyFinishedBufferIndex();
if (currentShownBackBufferIndex == previouslyFinishedBufferIndex) {
// if the buffers are the same, do nothing
} else {
// else swap the buffers
lastBackBufferIndex = currentBackBufferIndex;
pixelBuffer.getBuffer().put(screenComposer.getPixelBuffer().directBufferAccess());
pixelBuffer.getBuffer().flip();
pixelBuffer.updateBuffer(__ -> null);
currentShownBackBufferIndex = previouslyFinishedBufferIndex;
imagePixelBuffer.getBuffer().put(screenComposer.getPreviouslyFinishedPixelBuffer().directBufferAccess());
imagePixelBuffer.getBuffer().flip();
imagePixelBuffer.updateBuffer(__ -> null);

final GraphicsContext graphicsContext2D = canvas.getGraphicsContext2D();
graphicsContext2D.drawImage(img, 0, 0, canvas.getWidth(), canvas.getHeight());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;

import java.time.Duration;

@Slf4j
public class TacViewer extends Canvas {

Expand All @@ -36,13 +38,16 @@ public class TacViewer extends Canvas {
private final GenericFXInputEventListener genericFXInputEventListener;
@NonNull
private final InputManager inputManager;
@NonNull
private final AnimationTimer animationTimerLoop;
@NonNull
private final Thread.UncaughtExceptionHandler globalExceptionHandler;


@NonNull
private final SwappableCanvasBuffer canvasBuffer;
@NonNull
private final AnimationTimer animationTimerLoop;

private Thread engineLoopRunnerThread;

@Setter
@Nullable
Expand All @@ -56,7 +61,8 @@ public TacViewer(
@NonNull final ScreenComposer screenComposer,
@NonNull final EngineModule engineModule,
@NonNull final GenericFXInputEventListener genericFXInputEventListener,
@NonNull final InputManager inputManager
@NonNull final InputManager inputManager,
@NonNull final Thread.UncaughtExceptionHandler globalExceptionHandler
) {
super(rendererProperties.getScaledWindowWidth(), rendererProperties.getScaledWindowHeight());
this.rendererProperties = rendererProperties;
Expand All @@ -65,6 +71,7 @@ public TacViewer(
this.engineModule = engineModule;
this.genericFXInputEventListener = genericFXInputEventListener;
this.inputManager = inputManager;
this.globalExceptionHandler = globalExceptionHandler;

this.canvasBuffer = new SwappableCanvasBuffer(this, rendererProperties, screenComposer);
this.profiler = new TacViewerProfiler(profilerProvider);
Expand All @@ -87,37 +94,58 @@ private AnimationTimer provideAnimationTimer() {
return new AnimationTimer() {
@Override
public void handle(final long now) {
engineLoop(tickProperties, rendererProperties);
canvasBuffer.swap();
profiler.getLoopCounter().countLoop();
}
};
}

private void runEngineLoop() {
engineLoopRunnerThread = new Thread(() -> {
Thread.currentThread().setUncaughtExceptionHandler(globalExceptionHandler);
Thread.setDefaultUncaughtExceptionHandler(globalExceptionHandler);

while (!Thread.currentThread().isInterrupted()) {
engineLoop(tickProperties, rendererProperties);
profiler.getLoopCounter().countLoop();
}
});

engineLoopRunnerThread.setDaemon(false);
engineLoopRunnerThread.start();
}


public synchronized void start() {
engineModule.run();
animationTimerLoop.start();
runEngineLoop();
}

public synchronized void stop() {
animationTimerLoop.stop();
engineLoopRunnerThread.interrupt();
try {
Thread.sleep(Duration.ofMillis(10));
} catch (final InterruptedException e) {
log.error("Interrupted while waiting for engine loop to stop");
}
}

private void engineLoop(
@NonNull final TickProperties tickProperties,
@NonNull final RendererProperties rendererProperties
) {
// HANDLE TIME
final long currentNanoTime = System.nanoTime();
final long elapsedEngineNanoTime = (currentNanoTime - profiler.previousTickNanoTime);
final long deltaTickNanoTime = currentNanoTime - profiler.previousTickNanoTime;
final long deltaFrameNanoTime = currentNanoTime - profiler.previousFrameNanoTime;
final long currentNanoTimeOnLoopStart = System.nanoTime();
final long elapsedEngineNanoTime = (currentNanoTimeOnLoopStart - profiler.previousTickNanoTime);
final long deltaTickNanoTime = currentNanoTimeOnLoopStart - profiler.previousTickNanoTime;
final long deltaFrameNanoTime = currentNanoTimeOnLoopStart - profiler.previousFrameNanoTime;
final long targetNanosOfLoops = Math.max(tickProperties.getTickThresholdNanoTime(), rendererProperties.getFrameThresholdNanoTime());

// HANDLE INPUT
final long inputHandlingStart = System.nanoTime();
inputManager.mapInputEventsToCommands();
engineModule.handleInputCommands(inputManager.popInputCommands());
inputManager.clearInputQueues();
profiler.inputHandlingNanoTime = System.nanoTime() - inputHandlingStart;

// HANDLE UPDATE
Expand All @@ -130,14 +158,26 @@ private void engineLoop(
// HANDLE RENDER
if (deltaFrameNanoTime >= rendererProperties.getFrameThresholdNanoTime()) {
profiler.previousFrameNanoTime = System.nanoTime();
canvasBuffer.currentBackBufferIndex = screenComposer.compose(engineModule.getEnvironment());
screenComposer.compose(engineModule.getEnvironment());
profiler.getFpsCounter().countFrame();
}

// HANDLE PROFILING
if (profileFPSStrategy != null && profiler.getLoopCounter().isOneSecondPassed()) {
profileFPSStrategy.execute(profiler.writeProfile());
}


// sleep until next loop
final long loopDuration = System.nanoTime() - currentNanoTimeOnLoopStart;
final long nanosToSleep = targetNanosOfLoops - loopDuration;
if (nanosToSleep >= nanosToSleep/10) {
try {
Thread.sleep(nanosToSleep / 1_000_000, (int) (nanosToSleep % 1_000_000));
} catch (final InterruptedException e) {
log.warn("Interrupted engineLoop while sleeping");
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@

public interface IsComposable extends HasPixelBuffer {

int compose(@NonNull final Environment environment);
void compose(@NonNull final Environment environment);

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,17 @@ public ScreenComposer(
}

@Override
public int compose(@NonNull final Environment environment) {
if (!environment.getRenderPipeline().isDirty()) {
return pixelRingBuffer.getCurrentBufferIndex();
} else {
pixelRingBuffer.next();
public void compose(@NonNull final Environment environment) {
if (environment.getRenderPipeline().isDirty()) {
pixelRingBuffer.getPixelBuffer().clearBuffer();
renderLayersInParallel(environment.getRenderPipeline());
environment.getRenderPipeline().getLayers().forEach(layer -> {
layer.mergeBufferWith(pixelRingBuffer.getPixelBuffer(), 0, 0);
layer.dispose();
});
pixelRingBuffer.next();

environment.getRenderPipeline().propagateCleanStateToChildren();

return pixelRingBuffer.getCurrentBufferIndex();
}
}

Expand Down Expand Up @@ -80,4 +76,14 @@ public PixelBuffer getPixelBuffer() {
return pixelRingBuffer.getPixelBuffer();
}

@NonNull
public PixelBuffer getPreviouslyFinishedPixelBuffer() {
return pixelRingBuffer.getPreviouslyFinishedPixelBuffer();
}

@NonNull
public int getPreviouslyFinishedBufferIndex() {
return pixelRingBuffer.getPreviouslyFinishedBufferIndex();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public class PixelRingBuffer implements HasPixelBuffer {
protected final PixelBuffer[] pixelBufferRing;
@Getter
private int currentBufferIndex;
@Getter
private int previouslyFinishedBufferIndex;


public PixelRingBuffer(
Expand All @@ -22,6 +24,7 @@ public PixelRingBuffer(
this.dimension = dimension;
this.pixelBufferRing = new PixelBuffer[capacity];
this.currentBufferIndex = 0;
this.previouslyFinishedBufferIndex = capacity - 1;

preInitialize(capacity);
}
Expand All @@ -31,9 +34,11 @@ private void preInitialize(@NonNull final int capacity) {
pixelBufferRing[i] = new PixelBuffer(dimension);
}
currentBufferIndex = 0;
this.previouslyFinishedBufferIndex = capacity - 1;
}

public void next() {
previouslyFinishedBufferIndex = currentBufferIndex;
currentBufferIndex = (currentBufferIndex + 1) % pixelBufferRing.length;
getPixelBuffer().setDirty(true);
}
Expand All @@ -44,6 +49,11 @@ public PixelBuffer getPixelBuffer() {
return pixelBufferRing[currentBufferIndex];
}

@NonNull
public PixelBuffer getPreviouslyFinishedPixelBuffer() {
return pixelBufferRing[previouslyFinishedBufferIndex];
}

// @TODO: >>>> to interface and further implement
public void updateDimension(@NonNull final PixelDimension dimension) {
this.dimension = dimension;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import org.springframework.stereotype.Service;

import java.util.LinkedList;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

@Slf4j
@Service
Expand All @@ -18,7 +20,9 @@ public class InputManager {

@Getter
@NonNull
private final LinkedList<NanoTimedEvent<? extends InputEvent>> inputEventQueue = new LinkedList<>();
private final BlockingQueue<NanoTimedEvent<? extends InputEvent>> inputEventQueue = new LinkedBlockingQueue<>();
@NonNull
final LinkedList<NanoTimedEvent<? extends InputEvent>> drainedInputEventQueue = new LinkedList<>();

@NonNull
private final LinkedList<IsInputCommandMapper<?>> registeredCommandsMappers = new LinkedList<>();
Expand All @@ -28,15 +32,13 @@ public class InputManager {


public void mapInputEventsToCommands() {
inputEventQueue.drainTo(drainedInputEventQueue);
for (final IsInputCommandMapper<?> mapper : registeredCommandsMappers) {
if (mapper.mapEvents(inputEventQueue.stream())) {
if (mapper.mapEvents(drainedInputEventQueue.stream())) {
createdInputCommands.addAll(mapper.popCreatedCommands());
}
}
}

public void clearInputQueues() {
inputEventQueue.clear();
drainedInputEventQueue.clear();
}

@NonNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,39 @@
import com.recom.tacview.engine.input.mapper.IsInputCommandMapper;
import javafx.scene.input.InputEvent;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import java.util.Comparator;
import java.util.LinkedList;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
public class JavaFxKeyboardCommandMapper implements IsInputCommandMapper<KeyboardCommand> {

@NonNull
private final LinkedList<NanoTimedEvent<KeyEvent>> unprocessedKeypressEvents = new LinkedList<>();
private final BlockingQueue<NanoTimedEvent<KeyEvent>> unprocessedKeypressEvents = new LinkedBlockingQueue<>();
@NonNull
private final KeyboardCommandGenerator keyboardCommandGenerator = new KeyboardCommandGenerator();


@Override
@SuppressWarnings("unchecked")
public boolean mapEvents(@NonNull final Stream<NanoTimedEvent<? extends InputEvent>> timedMouseEventStream) {
timedMouseEventStream
public boolean mapEvents(@NonNull final Stream<NanoTimedEvent<? extends InputEvent>> timedKeyboardEventStream) {
timedKeyboardEventStream
.filter(event -> event.getEvent() instanceof KeyEvent)
.map(event -> (NanoTimedEvent<KeyEvent>) event)
.filter(this::isObservedKeyEvent)
.forEach(unprocessedKeypressEvents::add);
.forEach(nanoTimedEvent -> {
try {
unprocessedKeypressEvents.put(nanoTimedEvent);
} catch (final InterruptedException e) {
log.error("Interrupted while adding keypress event to queue", e);
}
});

return runKeypresGenerator();
}
Expand Down
Loading

0 comments on commit 5fe5cf1

Please sign in to comment.