Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix some stutters when dragging maps #4003

Merged
merged 4 commits into from
Apr 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 24 additions & 37 deletions src/main/java/net/rptools/maptool/client/DebounceExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
*/
package net.rptools.maptool.client;

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nonnull;

/**
* Manages throttling rapid successive attempts to execute a task.
Expand All @@ -42,36 +42,21 @@ public class DebounceExecutor {
.setNameFormat("debounce-executor-%d")
.build();

/** The time, in milliseconds, during which to throttle subsequent requests to run the task. */
private final long delay;

/**
* A {@link ScheduledExecutorService} that will be used to run the debounced task when the delay
* elapses.
*/
private final ScheduledExecutorService executor =
Executors.newSingleThreadScheduledExecutor(threadFactory);

/** A {@link ScheduledFuture} that represents the debounced task. */
private ScheduledFuture<?> future;

/**
* The synchronization lock used during the critical section for determining how to dispose of any
* single request.
*/
private final Object syncLock = new Object();
/** The time, in milliseconds, during which to throttle subsequent requests to run the task. */
private final long delay;

/** A {@link Runnable} representing the task to be managed. */
private final Runnable task;

/**
* A {@link long} indicating the time, in milliseconds, when the task last entered a pending
* state.
*/
private long taskPendingSince = -1;

/** A reference to the logging service. */
// private static final Logger log = LogManager.getLogger(DebounceExecutor.class);
/** A {@link long} indicating the time, in milliseconds, when the task was last scheduled for. */
private final AtomicLong taskScheduledTime = new AtomicLong(-1);

/**
* Initializes a new instance of the {@link DebounceExecutor} class.
Expand All @@ -85,32 +70,34 @@ public class DebounceExecutor {
* executing the <i>task</i> and throttle subsequent requests.
* @param task The task to be executed after the <i>delay</i> elapses.
*/
public DebounceExecutor(long delay, Runnable task) {
public DebounceExecutor(long delay, @Nonnull Runnable task) {
this.delay = delay;
this.task = task;
}

/** Dispatches a task to be executed by this {@link DebounceExecutor} instance. */
public void dispatch() {
if (this.task == null) {
// log.info("Exited debouncer because of a null task.");
return;
}
if (this.delay < 1) {
this.task.run();
return;
}
synchronized (syncLock) {
long now = (new Date()).getTime();
if (this.taskPendingSince == -1 || now - this.taskPendingSince >= this.delay) {
this.taskPendingSince = now;
this.future = this.executor.schedule(this.task, this.delay, TimeUnit.MILLISECONDS);
} /* else {
log.info(
String.format(
"Task execution was debounced. (now: %d; taskPendingSince: %d; delay: %d; now - taskPendingSince: %d)",
now, this.taskPendingSince, this.delay, now - this.taskPendingSince));
} */

/*
* There are three time windows we need to account for.
* 1. The scheduled time has not yet passed, so we consider the execution redundant.
* 2. The scheduled time has passed, but not by much. So we can run the task with a small delay.
* 3. The scheduled time has long passed, so we can run the task right away.
*/

final var taskScheduledTime = this.taskScheduledTime.get();
final var now = System.currentTimeMillis();
if (now >= taskScheduledTime) {
// This request is not redundant, so we need to schedule it.
final var nextTargetTime = Math.max(now, taskScheduledTime + this.delay);
// If this check fails, that means someone beat us to the punch and our task is now redundant.
if (this.taskScheduledTime.compareAndSet(taskScheduledTime, nextTargetTime)) {
this.executor.schedule(this.task, nextTargetTime - now, TimeUnit.MILLISECONDS);
}
}
}
}
15 changes: 3 additions & 12 deletions src/main/java/net/rptools/maptool/client/tool/DefaultTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,7 @@ public abstract class DefaultTool extends Tool
protected int mouseY;

// This is to manage overflowing of map move events (keep things snappy)
private long lastMoveRedraw;
private int mapDX, mapDY;
private static final int REDRAW_DELAY = 25; // millis

// TBD
private boolean isTouchScreen = false;
Expand Down Expand Up @@ -294,16 +292,9 @@ public void mouseDragged(MouseEvent e) {

setDragStart(mX, mY);

long now = System.currentTimeMillis();
if (now - lastMoveRedraw > REDRAW_DELAY) {
// TODO: does it matter to capture the last map move in the series ?
// TODO: This should probably be genericized and put into ZoneRenderer to prevent over
// zealous repainting
renderer.moveViewBy(mapDX, mapDY);
mapDX = 0;
mapDY = 0;
lastMoveRedraw = now;
}
renderer.moveViewBy(mapDX, mapDY);
mapDX = 0;
mapDY = 0;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
*/
package net.rptools.maptool.client.ui.zone;

import java.awt.AlphaComposite;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Transparency;
Expand Down Expand Up @@ -118,11 +117,6 @@ public Handle acquire() {
final var instance = available.removeLast();
checkedOut.add(instance);

final var g = instance.createGraphics();
g.setComposite(AlphaComposite.Clear);
g.fillRect(0, 0, instance.getWidth(), instance.getHeight());
g.dispose();

return new Handle(instance);
}

Expand Down

This file was deleted.