GT-3604 - Threaded Tables - update tables to buffer more when in a

flurry of updates
This commit is contained in:
dragonmacher 2020-03-18 13:54:33 -04:00
parent 01539bedd6
commit 3734d77bcd

View file

@ -15,14 +15,12 @@
*/
package docking.widgets.table.threaded;
import java.util.ArrayList;
import java.util.List;
import javax.swing.SwingUtilities;
import java.util.*;
import docking.widgets.table.AddRemoveListItem;
import docking.widgets.table.TableSortingContext;
import generic.concurrent.ConcurrentListenerSet;
import ghidra.util.Swing;
import ghidra.util.SystemUtilities;
import ghidra.util.task.*;
@ -35,9 +33,11 @@ import ghidra.util.task.*;
* @param <T> the row type
*/
class ThreadedTableModelUpdateMgr<T> {
static final int TOO_MANY_ADD_REMOVES = 3000;
public final static int DELAY = 1000;
// make the delay large enough that a flurry of events will stay buffered
public final static int DELAY = 5000;
public final static int MAX_DELAY = 1000 * 60 * 20;
static final int TOO_MANY_ADD_REMOVES = 3000;
private ThreadedTableModel<T, ?> model;
private SwingUpdateManager addRemoveUpdater;
@ -115,49 +115,11 @@ class ThreadedTableModelUpdateMgr<T> {
return addRemoveUpdater;
}
/**
* Processes the accumulated list of add/remove items.
* Called when the swing update manager decides its time to run.
*/
private void processAddRemoveItems() {
synchronized (addRemoveUpdater) {
if (addRemoveWaitList.isEmpty()) {
return;
}
// too many requests for add/remove triggers a full reload for efficiency
if (addRemoveWaitList.size() > maxAddRemoveCount) {
reload();
return;
}
pendingJob = new AddRemoveJob<>(model, addRemoveWaitList, monitor);
addRemoveWaitList = new ArrayList<>();
runJob();
}
}
/**
* Called when there is work to be done. It creates a thread if none is running to do the
* work that is built into the pending job.
*/
private void runJob() {
synchronized (addRemoveUpdater) {
if (thread != null) {
return; // if thread exists, it will handle any pending job
}
if (pendingJob == null) {
return; // nothing to do!
}
// Ok, we have to create a thread to process the pending job
thread = new Thread(threadRunnable,
"Threaded Table Model Update Manager: " + model.getName());
thread.start();
SwingUtilities.invokeLater(notifyUpdating);
}
private int getMaxAddRemoveCount() {
// have the max count grow with the size of the table to prevent too many calls to reload()
int n = model.getRowCount();
int cuttoff = (int) (n * .1);
return Math.max(maxAddRemoveCount, cuttoff);
}
/**
@ -184,8 +146,7 @@ class ThreadedTableModelUpdateMgr<T> {
void reload() {
synchronized (addRemoveUpdater) {
cancelAllJobs();
pendingJob = new LoadJob<>(model, monitor);
runJob();
runJob(new LoadJob<>(model, monitor));
}
}
@ -193,8 +154,7 @@ class ThreadedTableModelUpdateMgr<T> {
synchronized (addRemoveUpdater) {
cancelAllJobs();
TableData<T> tableData = TableData.createFullDataset(data);
pendingJob = new LoadSpecificDataJob<>(model, monitor, tableData);
runJob();
runJob(new LoadSpecificDataJob<>(model, monitor, tableData));
}
}
@ -225,8 +185,7 @@ class ThreadedTableModelUpdateMgr<T> {
return;
}
pendingJob = new SortJob<>(model, monitor, sortingContext, forceSort);
runJob();
runJob(new SortJob<>(model, monitor, sortingContext, forceSort));
}
}
@ -253,8 +212,7 @@ class ThreadedTableModelUpdateMgr<T> {
return;
}
pendingJob = new FilterJob<>(model, monitor);
runJob();
runJob(new FilterJob<>(model, monitor));
}
}
@ -270,17 +228,15 @@ class ThreadedTableModelUpdateMgr<T> {
void addRemove(AddRemoveListItem<T> item) {
synchronized (addRemoveUpdater) {
if (pendingJob != null) {
pendingJob.addRemove(item, maxAddRemoveCount);
return;
}
pendingJob.addRemove(item, getMaxAddRemoveCount());
if (addRemoveWaitList.size() > maxAddRemoveCount) {
reload();
// push the updater back to avoid constant reloading
addRemoveUpdater.updateLater();
return;
}
if (addRemoveWaitList.isEmpty() && thread == null) {
SwingUtilities.invokeLater(notifyPending);
Swing.runLater(notifyPending);
}
addRemoveWaitList.add(item);
@ -288,6 +244,28 @@ class ThreadedTableModelUpdateMgr<T> {
}
}
/**
* Processes the accumulated list of add/remove items.
* Called when the swing update manager decides its time to run.
*/
private void processAddRemoveItems() {
synchronized (addRemoveUpdater) {
if (addRemoveWaitList.isEmpty()) {
return;
}
// too many requests for add/remove triggers a full reload for efficiency
if (addRemoveWaitList.size() > getMaxAddRemoveCount()) {
reload();
return;
}
runJob(new AddRemoveJob<>(model, addRemoveWaitList, monitor));
addRemoveWaitList = new ArrayList<>();
}
}
/**
* Returns true if there is any scheduled work that has not been completed, including any
* deferred add/removes.
@ -356,6 +334,27 @@ class ThreadedTableModelUpdateMgr<T> {
addRemoveUpdater.updateNow();
}
/**
* Called when there is work to be done. It creates a thread if none is running to do the
* work that is built into the pending job.
*/
private void runJob(TableUpdateJob<T> job) {
synchronized (addRemoveUpdater) {
// Create a thread to process the pending job
pendingJob = Objects.requireNonNull(job);
if (thread != null) {
return; // if thread exists, it will handle any pending job
}
thread = new Thread(threadRunnable,
"Threaded Table Model Update Manager: " + model.getName());
thread.start();
Swing.runLater(notifyUpdating);
}
}
/**
* Transitions the pending job to the current job is a thread safe way.
* @return the new current job.
@ -380,22 +379,22 @@ class ThreadedTableModelUpdateMgr<T> {
*/
private void jobDone() {
// This synchronized is not need, as this method is only called from within a
// This synchronized is not needed, as this method is only called from within a
// synchronized block. But, it feels better to be explicit here to signal that
// synchronization is needed.
synchronized (addRemoveUpdater) {
boolean isCancelled = monitor.isCancelled();
if (isCancelled) {
SwingUtilities.invokeLater(notifyCancelled);
Swing.runLater(notifyCancelled);
}
else {
SwingUtilities.invokeLater(notifyDone);
Swing.runLater(notifyDone);
}
thread = null;
if (!addRemoveWaitList.isEmpty()) {
SwingUtilities.invokeLater(notifyPending);
Swing.runLater(notifyPending);
}
}
}