Skip to content

Commit a32ed0b

Browse files
committed
Block when loading image which is being saved asynchronously
1 parent f15316d commit a32ed0b

3 files changed

Lines changed: 118 additions & 21 deletions

File tree

core/src/processing/core/PApplet.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5243,6 +5243,13 @@ public PImage loadImage(String filename) {
52435243
* @param extension type of image to load, for example "png", "gif", "jpg"
52445244
*/
52455245
public PImage loadImage(String filename, String extension) { //, Object params) {
5246+
5247+
// await... has to run on the main thread, because P2D and P3D call GL functions
5248+
// If this runs on background, requestImage() already called await... on the main thread
5249+
if (!Thread.currentThread().getName().startsWith(ASYNC_IMAGE_LOADER_THREAD_PREFIX)) {
5250+
g.awaitAsyncSaveCompletion(filename);
5251+
}
5252+
52465253
if (extension == null) {
52475254
String lower = filename.toLowerCase();
52485255
int dot = filename.lastIndexOf('.');
@@ -5394,6 +5401,9 @@ public PImage requestImage(String filename) {
53945401
* @see PApplet#loadImage(String, String)
53955402
*/
53965403
public PImage requestImage(String filename, String extension) {
5404+
// Make sure saving to this file completes before trying to load it
5405+
// Has to be called on main thread, because P2D and P3D need GL functions
5406+
g.awaitAsyncSaveCompletion(filename);
53975407
PImage vessel = createImage(0, 0, ARGB);
53985408
AsyncImageLoader ail =
53995409
new AsyncImageLoader(filename, extension, vessel);
@@ -5426,12 +5436,17 @@ public PImage requestImage(String filename, String extension) {
54265436
public int requestImageMax = 4;
54275437
volatile int requestImageCount;
54285438

5439+
private static final String ASYNC_IMAGE_LOADER_THREAD_PREFIX = "ASYNC_IMAGE_LOADER";
5440+
54295441
class AsyncImageLoader extends Thread {
54305442
String filename;
54315443
String extension;
54325444
PImage vessel;
54335445

54345446
public AsyncImageLoader(String filename, String extension, PImage vessel) {
5447+
// Give these threads distinct name so we can check whether we are loading
5448+
// on the main/background thread; for now they are all named the same
5449+
super(ASYNC_IMAGE_LOADER_THREAD_PREFIX);
54355450
this.filename = filename;
54365451
this.extension = extension;
54375452
this.vessel = vessel;

core/src/processing/core/PGraphics.java

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@
3838
import java.util.WeakHashMap;
3939
import java.util.concurrent.ArrayBlockingQueue;
4040
import java.util.concurrent.BlockingQueue;
41+
import java.util.concurrent.ExecutionException;
4142
import java.util.concurrent.ExecutorService;
4243
import java.util.concurrent.Executors;
44+
import java.util.concurrent.Future;
4345
import java.util.concurrent.RejectedExecutionException;
4446
import java.util.concurrent.TimeUnit;
4547

@@ -8288,14 +8290,26 @@ public boolean save(String filename) { // ignore
82888290
if (target == null) return false;
82898291
int count = PApplet.min(pixels.length, target.pixels.length);
82908292
System.arraycopy(pixels, 0, target.pixels, 0, count);
8291-
asyncImageSaver.saveTargetAsync(this, target, filename);
8293+
asyncImageSaver.saveTargetAsync(this, target, parent.sketchPath(filename));
82928294

82938295
return true;
82948296
}
82958297

82968298
protected void processImageBeforeAsyncSave(PImage image) { }
82978299

82988300

8301+
/**
8302+
* If there is running async save task for this file, blocks until it completes.
8303+
* Has to be called on main thread because OpenGL overrides this and calls GL.
8304+
* @param filename
8305+
*/
8306+
protected void awaitAsyncSaveCompletion(String filename) {
8307+
if (asyncImageSaver != null) {
8308+
asyncImageSaver.awaitAsyncSaveCompletion(parent.sketchPath(filename));
8309+
}
8310+
}
8311+
8312+
82998313
protected static AsyncImageSaver asyncImageSaver;
83008314

83018315
protected static class AsyncImageSaver {
@@ -8308,6 +8322,9 @@ protected static class AsyncImageSaver {
83088322

83098323
int targetsCreated = 0;
83108324

8325+
Map<String, Future<?>> runningTasks = new HashMap<>();
8326+
final Object runningTasksLock = new Object();
8327+
83118328

83128329
static final int TIME_AVG_FACTOR = 32;
83138330

@@ -8367,7 +8384,7 @@ public void returnUnusedTarget(PImage target) { // ignore
83678384

83688385

83698386
public void saveTargetAsync(final PGraphics renderer, final PImage target, // ignore
8370-
final String filename) {
8387+
final String absFilename) {
83718388
target.parent = renderer.parent;
83728389

83738390
// if running every frame, smooth the framerate
@@ -8388,14 +8405,17 @@ public void saveTargetAsync(final PGraphics renderer, final PImage target, // ig
83888405
lastFrameCount = target.parent.frameCount;
83898406
lastTime = System.nanoTime();
83908407

8391-
try {
8392-
saveExecutor.submit(new Runnable() {
8393-
@Override
8394-
public void run() { // ignore
8408+
awaitAsyncSaveCompletion(absFilename);
8409+
8410+
// Explicit lock, because submitting a task and putting it into map
8411+
// has to be atomic (and happen before task tries to remove itself)
8412+
synchronized (runningTasksLock) {
8413+
try {
8414+
Future<?> task = saveExecutor.submit(() -> {
83958415
try {
83968416
long startTime = System.nanoTime();
83978417
renderer.processImageBeforeAsyncSave(target);
8398-
target.save(filename);
8418+
target.save(absFilename);
83998419
long saveNanos = System.nanoTime() - startTime;
84008420
synchronized (AsyncImageSaver.this) {
84018421
if (avgNanos == 0) {
@@ -8409,13 +8429,32 @@ public void run() { // ignore
84098429
}
84108430
} finally {
84118431
targetPool.offer(target);
8432+
synchronized (runningTasksLock) {
8433+
runningTasks.remove(absFilename);
8434+
}
84128435
}
8413-
}
8414-
});
8415-
} catch (RejectedExecutionException e) {
8416-
// the executor service was probably shut down, no more saving for us
8436+
});
8437+
runningTasks.put(absFilename, task);
8438+
} catch (RejectedExecutionException e) {
8439+
// the executor service was probably shut down, no more saving for us
8440+
}
8441+
}
8442+
}
8443+
8444+
8445+
public void awaitAsyncSaveCompletion(final String absFilename) { // ignore
8446+
Future<?> taskWithSameFilename;
8447+
synchronized (runningTasksLock) {
8448+
taskWithSameFilename = runningTasks.get(absFilename);
8449+
}
8450+
8451+
if (taskWithSameFilename != null) {
8452+
try {
8453+
taskWithSameFilename.get();
8454+
} catch (InterruptedException | ExecutionException e) { }
84178455
}
84188456
}
8457+
84198458
}
84208459

84218460
}

core/src/processing/opengl/PGraphicsOpenGL.java

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ public boolean saveImpl(String filename) {
730730
updatePixelSize();
731731

732732
// get the whole async package
733-
asyncPixelReader.readAndSaveAsync(filename);
733+
asyncPixelReader.readAndSaveAsync(parent.sketchPath(filename));
734734

735735
if (needEndDraw) endDraw();
736736
} else {
@@ -743,7 +743,7 @@ public boolean saveImpl(String filename) {
743743
if (target == null) return false;
744744
int count = PApplet.min(pixels.length, target.pixels.length);
745745
System.arraycopy(pixels, 0, target.pixels, 0, count);
746-
asyncImageSaver.saveTargetAsync(this, target, filename);
746+
asyncImageSaver.saveTargetAsync(this, target, parent.sketchPath(filename));
747747
}
748748

749749
return true;
@@ -5584,8 +5584,7 @@ protected void processImageBeforeAsyncSave(PImage image) {
55845584

55855585
protected static void completeFinishedPixelTransfers() {
55865586
ongoingPixelTransfersIterable.addAll(ongoingPixelTransfers);
5587-
for (PGraphicsOpenGL.AsyncPixelReader pixelReader :
5588-
ongoingPixelTransfersIterable) {
5587+
for (AsyncPixelReader pixelReader : ongoingPixelTransfersIterable) {
55895588
// if the getter was not called this frame,
55905589
// tell it to check for completed transfers now
55915590
if (!pixelReader.calledThisFrame) {
@@ -5598,14 +5597,27 @@ protected static void completeFinishedPixelTransfers() {
55985597

55995598
protected static void completeAllPixelTransfers() {
56005599
ongoingPixelTransfersIterable.addAll(ongoingPixelTransfers);
5601-
for (PGraphicsOpenGL.AsyncPixelReader pixelReader :
5602-
ongoingPixelTransfersIterable) {
5600+
for (AsyncPixelReader pixelReader : ongoingPixelTransfersIterable) {
56035601
pixelReader.completeAllTransfers();
56045602
}
56055603
ongoingPixelTransfersIterable.clear();
56065604
}
56075605

56085606

5607+
@Override
5608+
protected void awaitAsyncSaveCompletion(String filename) {
5609+
if (asyncPixelReader != null) {
5610+
ongoingPixelTransfersIterable.addAll(ongoingPixelTransfers);
5611+
String absFilename = parent.sketchPath(filename);
5612+
for (AsyncPixelReader pixelReader : ongoingPixelTransfersIterable) {
5613+
pixelReader.awaitTransferCompletion(absFilename);
5614+
}
5615+
ongoingPixelTransfersIterable.clear();
5616+
}
5617+
super.awaitAsyncSaveCompletion(filename);
5618+
}
5619+
5620+
56095621
protected class AsyncPixelReader {
56105622

56115623
// PImage formats used internally to offload
@@ -5678,15 +5690,15 @@ public void dispose() {
56785690
}
56795691

56805692

5681-
public void readAndSaveAsync(final String filename) {
5693+
public void readAndSaveAsync(final String absFilename) {
56825694
if (size > 0) {
56835695
boolean shouldRead = (size == BUFFER_COUNT);
56845696
if (!shouldRead) shouldRead = isLastTransferComplete();
56855697
if (shouldRead) endTransfer();
56865698
} else {
56875699
ongoingPixelTransfers.add(this);
56885700
}
5689-
beginTransfer(filename);
5701+
beginTransfer(absFilename);
56905702
calledThisFrame = true;
56915703
}
56925704

@@ -5715,25 +5727,56 @@ public void completeFinishedTransfers() {
57155727

57165728
protected void completeAllTransfers() {
57175729
if (size <= 0) return;
5730+
completeTransfers(size);
5731+
}
5732+
5733+
5734+
protected void completeTransfers(int count) {
5735+
if (size <= 0) return;
5736+
if (count <= 0) return;
57185737

57195738
boolean needEndDraw = false;
57205739
if (!drawing) {
57215740
beginDraw();
57225741
needEndDraw = true;
57235742
}
57245743

5725-
while (size > 0) {
5744+
while (size > 0 && count > 0) {
57265745
endTransfer();
5746+
count--;
57275747
}
57285748

57295749
// make sure to always unregister if there are no ongoing transfers
57305750
// so that PGraphics can be GC'd if needed
5731-
ongoingPixelTransfers.remove(this);
5751+
if (size <= 0) {
5752+
ongoingPixelTransfers.remove(this);
5753+
}
57325754

57335755
if (needEndDraw) endDraw();
57345756
}
57355757

57365758

5759+
protected void awaitTransferCompletion(String absFilename) {
5760+
if (size <= 0) return;
5761+
5762+
int i = tail; // tail -> head, wraps around (we have circular queue)
5763+
int j = 0; // 0 -> size, simple counter
5764+
int lastIndex = 0;
5765+
do {
5766+
if (absFilename.equals(filenames[i])) {
5767+
lastIndex = j; // no 'break' here, we need last index for this filename
5768+
}
5769+
i = (i + 1) % BUFFER_COUNT;
5770+
j++;
5771+
} while (i != head);
5772+
5773+
if (lastIndex <= 0) return;
5774+
5775+
// Saving this file is in progress, block until transfers complete
5776+
completeTransfers(lastIndex + 1);
5777+
}
5778+
5779+
57375780
/// TRANSFERS //////////////////////////////////////////////////////////////
57385781

57395782
public boolean isLastTransferComplete() {

0 commit comments

Comments
 (0)