Create new orchestrator

pull/992/head
Mattia Iavarone 5 years ago
parent adffec766d
commit e3c586171a
  1. 6
      cameraview/build.gradle.kts
  2. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java
  3. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java
  4. 221
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/orchestrator/CameraOrchestrator.java
  5. 10
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/orchestrator/CameraStateOrchestrator.java

@ -14,7 +14,7 @@ android {
setMinSdkVersion(property("minSdkVersion") as Int) setMinSdkVersion(property("minSdkVersion") as Int)
setTargetSdkVersion(property("targetSdkVersion") as Int) setTargetSdkVersion(property("targetSdkVersion") as Int)
versionCode = 1 versionCode = 1
versionName = "2.6.4" versionName = "2.6.4-zoom3"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArgument("filter", "" + testInstrumentationRunnerArgument("filter", "" +
"com.otaliastudios.cameraview.tools.SdkExcludeFilter," + "com.otaliastudios.cameraview.tools.SdkExcludeFilter," +
@ -51,15 +51,15 @@ publisher {
project.group = "com.otaliastudios" project.group = "com.otaliastudios"
project.url = "https://github.com/natario1/CameraView" project.url = "https://github.com/natario1/CameraView"
project.addLicense(License.APACHE_2_0) project.addLicense(License.APACHE_2_0)
bintray {
release.setSources(Release.SOURCES_AUTO) release.setSources(Release.SOURCES_AUTO)
release.setDocs(Release.DOCS_AUTO) release.setDocs(Release.DOCS_AUTO)
bintray {
auth.user = "BINTRAY_USER" auth.user = "BINTRAY_USER"
auth.key = "BINTRAY_KEY" auth.key = "BINTRAY_KEY"
auth.repo = "BINTRAY_REPO" auth.repo = "BINTRAY_REPO"
} }
directory { directory {
directory = "build/local" directory = file(repositories.mavenLocal().url).absolutePath
} }
} }

@ -888,7 +888,7 @@ public class Camera1Engine extends CameraBaseEngine implements
// The auto focus callback is not guaranteed to be called, but we really want it // The auto focus callback is not guaranteed to be called, but we really want it
// to be. So we remove the old runnable if still present and post a new one. // to be. So we remove the old runnable if still present and post a new one.
getOrchestrator().remove(JOB_FOCUS_END); getOrchestrator().remove(JOB_FOCUS_END);
getOrchestrator().scheduleDelayed(JOB_FOCUS_END, AUTOFOCUS_END_DELAY_MILLIS, getOrchestrator().scheduleDelayed(JOB_FOCUS_END, true, AUTOFOCUS_END_DELAY_MILLIS,
new Runnable() { new Runnable() {
@Override @Override
public void run() { public void run() {

@ -50,8 +50,8 @@ import java.util.List;
*/ */
public abstract class CameraBaseEngine extends CameraEngine { public abstract class CameraBaseEngine extends CameraEngine {
protected final static int ALLOWED_ZOOM_OPS = 10; protected final static int ALLOWED_ZOOM_OPS = Integer.MAX_VALUE;
protected final static int ALLOWED_EV_OPS = 10; protected final static int ALLOWED_EV_OPS = Integer.MAX_VALUE;
@SuppressWarnings("WeakerAccess") protected CameraPreview mPreview; @SuppressWarnings("WeakerAccess") protected CameraPreview mPreview;
@SuppressWarnings("WeakerAccess") protected CameraOptions mCameraOptions; @SuppressWarnings("WeakerAccess") protected CameraOptions mCameraOptions;

@ -1,7 +1,7 @@
package com.otaliastudios.cameraview.engine.orchestrator; package com.otaliastudios.cameraview.engine.orchestrator;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.Task;
@ -12,9 +12,10 @@ import com.otaliastudios.cameraview.internal.WorkerHandler;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Set;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
@ -40,36 +41,43 @@ public class CameraOrchestrator {
void handleJobException(@NonNull String job, @NonNull Exception exception); void handleJobException(@NonNull String job, @NonNull Exception exception);
} }
protected static class Token { protected static class Job<T> {
public final String name; public final String name;
public final Task<?> task; public final TaskCompletionSource<T> source = new TaskCompletionSource<>();
public final Callable<Task<T>> scheduler;
public final boolean dispatchExceptions;
public final long startTime;
private Token(@NonNull String name, @NonNull Task<?> task) { private Job(@NonNull String name, @NonNull Callable<Task<T>> scheduler, boolean dispatchExceptions, long startTime) {
this.name = name; this.name = name;
this.task = task; this.scheduler = scheduler;
} this.dispatchExceptions = dispatchExceptions;
this.startTime = startTime;
@Override
public boolean equals(@Nullable Object obj) {
return obj instanceof Token && ((Token) obj).name.equals(name);
} }
} }
protected final Callback mCallback; protected final Callback mCallback;
protected final ArrayDeque<Token> mJobs = new ArrayDeque<>(); protected final ArrayDeque<Job<?>> mJobs = new ArrayDeque<>();
protected final Object mLock = new Object(); protected boolean mJobRunning = false;
private final Map<String, Runnable> mDelayedJobs = new HashMap<>(); protected final Object mJobsLock = new Object();
public CameraOrchestrator(@NonNull Callback callback) { public CameraOrchestrator(@NonNull Callback callback) {
mCallback = callback; mCallback = callback;
ensureToken();
} }
@NonNull @NonNull
public Task<Void> schedule(@NonNull String name, public Task<Void> schedule(@NonNull String name,
boolean dispatchExceptions, boolean dispatchExceptions,
@NonNull Runnable job) {
return scheduleDelayed(name, dispatchExceptions, 0L, job);
}
@NonNull
public Task<Void> scheduleDelayed(@NonNull String name,
boolean dispatchExceptions,
long minDelay,
@NonNull final Runnable job) { @NonNull final Runnable job) {
return schedule(name, dispatchExceptions, new Callable<Task<Void>>() { return scheduleInternal(name, dispatchExceptions, minDelay, new Callable<Task<Void>>() {
@Override @Override
public Task<Void> call() { public Task<Void> call() {
job.run(); job.run();
@ -78,75 +86,110 @@ public class CameraOrchestrator {
}); });
} }
@SuppressWarnings("unchecked")
@NonNull @NonNull
public <T> Task<T> schedule(@NonNull final String name, public <T> Task<T> schedule(@NonNull String name,
final boolean dispatchExceptions, boolean dispatchExceptions,
@NonNull final Callable<Task<T>> job) { @NonNull Callable<Task<T>> scheduler) {
return scheduleInternal(name, dispatchExceptions, 0L, scheduler);
}
@NonNull
private <T> Task<T> scheduleInternal(@NonNull String name,
boolean dispatchExceptions,
long minDelay,
@NonNull Callable<Task<T>> scheduler) {
LOG.i(name.toUpperCase(), "- Scheduling."); LOG.i(name.toUpperCase(), "- Scheduling.");
final TaskCompletionSource<T> source = new TaskCompletionSource<>(); Job<T> job = new Job<>(name, scheduler, dispatchExceptions,
final WorkerHandler handler = mCallback.getJobWorker(name); System.currentTimeMillis() + minDelay);
synchronized (mLock) { synchronized (mJobsLock) {
applyCompletionListener(mJobs.getLast().task, handler, mJobs.addLast(job);
new OnCompleteListener() { sync(minDelay);
}
return job.source.getTask();
}
private void sync(long after) {
// Jumping on the message handler even if after = 0L should avoid StackOverflow errors.
mCallback.getJobWorker("_sync").post(after, new Runnable() {
@SuppressWarnings("StatementWithEmptyBody")
@Override @Override
public void onComplete(@NonNull Task task) { public void run() {
synchronized (mLock) { synchronized (mJobsLock) {
mJobs.removeFirst(); if (!mJobRunning) {
ensureToken(); long now = System.currentTimeMillis();
Job<?> job = null;
for (Job<?> candidate : mJobs) {
if (candidate.startTime <= now) {
job = candidate;
break;
}
}
if (job != null) execute(job);
} else {
// Do nothing, job will be picked in executed().
}
} }
}
});
}
@GuardedBy("mJobsLock")
private <T> void execute(@NonNull final Job<T> job) {
if (mJobRunning) {
throw new IllegalStateException("mJobRunning is already true! job=" + job.name);
}
mJobRunning = true;
final WorkerHandler worker = mCallback.getJobWorker(job.name);
worker.run(new Runnable() {
@Override
public void run() {
try { try {
LOG.i(name.toUpperCase(), "- Executing."); LOG.i(job.name.toUpperCase(), "- Executing.");
Task<T> inner = job.call(); Task<T> task = job.scheduler.call();
applyCompletionListener(inner, handler, new OnCompleteListener<T>() { onComplete(task, worker, new OnCompleteListener<T>() {
@Override @Override
public void onComplete(@NonNull Task<T> task) { public void onComplete(@NonNull Task<T> task) {
Exception e = task.getException(); Exception e = task.getException();
if (e != null) { if (e != null) {
LOG.w(name.toUpperCase(), "- Finished with ERROR.", e); LOG.w(job.name.toUpperCase(), "- Finished with ERROR.", e);
if (dispatchExceptions) { if (job.dispatchExceptions) {
mCallback.handleJobException(name, e); mCallback.handleJobException(job.name, e);
} }
source.trySetException(e); job.source.trySetException(e);
} else if (task.isCanceled()) { } else if (task.isCanceled()) {
LOG.i(name.toUpperCase(), "- Finished because ABORTED."); LOG.i(job.name.toUpperCase(), "- Finished because ABORTED.");
source.trySetException(new CancellationException()); job.source.trySetException(new CancellationException());
} else { } else {
LOG.i(name.toUpperCase(), "- Finished."); LOG.i(job.name.toUpperCase(), "- Finished.");
source.trySetResult(task.getResult()); job.source.trySetResult(task.getResult());
}
synchronized (mJobsLock) {
executed(job);
} }
} }
}); });
} catch (Exception e) { } catch (Exception e) {
LOG.i(name.toUpperCase(), "- Finished.", e); LOG.i(job.name.toUpperCase(), "- Finished with ERROR.", e);
if (dispatchExceptions) mCallback.handleJobException(name, e); if (job.dispatchExceptions) {
source.trySetException(e); mCallback.handleJobException(job.name, e);
} }
job.source.trySetException(e);
synchronized (mJobsLock) {
executed(job);
} }
});
mJobs.addLast(new Token(name, source.getTask()));
}
return source.getTask();
}
public void scheduleDelayed(@NonNull final String name,
long minDelay,
@NonNull final Runnable runnable) {
Runnable wrapper = new Runnable() {
@Override
public void run() {
schedule(name, true, runnable);
synchronized (mLock) {
if (mDelayedJobs.containsValue(this)) {
mDelayedJobs.remove(name);
} }
} }
});
} }
};
synchronized (mLock) { @GuardedBy("mJobsLock")
mDelayedJobs.put(name, wrapper); private <T> void executed(Job<T> job) {
mCallback.getJobWorker(name).post(minDelay, wrapper); if (!mJobRunning) {
throw new IllegalStateException("mJobRunning was not true after completing job=" + job.name);
} }
mJobRunning = false;
mJobs.remove(job);
sync(0L);
} }
public void remove(@NonNull String name) { public void remove(@NonNull String name) {
@ -154,35 +197,31 @@ public class CameraOrchestrator {
} }
public void trim(@NonNull String name, int allowed) { public void trim(@NonNull String name, int allowed) {
synchronized (mLock) { synchronized (mJobsLock) {
Token token = new Token(name, Tasks.forResult(null)); List<Job<?>> scheduled = new ArrayList<>();
int scheduled = 0; for (Job<?> job : mJobs) {
for (Token job : mJobs) { if (job.name.equals(name)) {
if (job == token) scheduled++; scheduled.add(job);
} }
boolean delayed = mDelayedJobs.containsKey(name);
if (delayed) scheduled++;
int existing = Math.max(scheduled - allowed, 0);
while (existing > 0 && mJobs.removeLastOccurrence(token)) {
existing--;
} }
if (existing > 0 && delayed) { LOG.v("trim: name=", name, "scheduled=", scheduled.size(), "allowed=", allowed);
//noinspection ConstantConditions int existing = Math.max(scheduled.size() - allowed, 0);
mCallback.getJobWorker(name).remove(mDelayedJobs.get(name)); // To remove the oldest ones first, we must reverse the list.
mDelayedJobs.remove(name); // Note that we will potentially remove a job that is being executed: we don't
existing--; // have a mechanism to cancel the ongoing execution, but it shouldn't be a problem.
Collections.reverse(scheduled);
scheduled = scheduled.subList(0, existing);
for (Job<?> job : scheduled) {
mJobs.remove(job);
} }
ensureToken();
} }
} }
public void reset() { public void reset() {
synchronized (mLock) { synchronized (mJobsLock) {
List<String> all = new ArrayList<>(); Set<String> all = new HashSet<>();
//noinspection CollectionAddAllCanBeReplacedWithConstructor for (Job<?> job : mJobs) {
all.addAll(mDelayedJobs.keySet()); all.add(job.name);
for (Token token : mJobs) {
all.add(token.name);
} }
for (String job : all) { for (String job : all) {
remove(job); remove(job);
@ -190,15 +229,7 @@ public class CameraOrchestrator {
} }
} }
private void ensureToken() { private static <T> void onComplete(@NonNull final Task<T> task,
synchronized (mLock) {
if (mJobs.isEmpty()) {
mJobs.add(new Token("BASE", Tasks.forResult(null)));
}
}
}
private static <T> void applyCompletionListener(@NonNull final Task<T> task,
@NonNull WorkerHandler handler, @NonNull WorkerHandler handler,
@NonNull final OnCompleteListener<T> listener) { @NonNull final OnCompleteListener<T> listener) {
if (task.isComplete()) { if (task.isComplete()) {

@ -35,10 +35,10 @@ public class CameraStateOrchestrator extends CameraOrchestrator {
} }
public boolean hasPendingStateChange() { public boolean hasPendingStateChange() {
synchronized (mLock) { synchronized (mJobsLock) {
for (Token token : mJobs) { for (Job<?> job : mJobs) {
if ((token.name.contains(" >> ") || token.name.contains(" << ")) if ((job.name.contains(" >> ") || job.name.contains(" << "))
&& !token.task.isComplete()) { && !job.source.getTask().isComplete()) {
return true; return true;
} }
} }
@ -107,7 +107,7 @@ public class CameraStateOrchestrator extends CameraOrchestrator {
@NonNull final CameraState atLeast, @NonNull final CameraState atLeast,
long delay, long delay,
@NonNull final Runnable job) { @NonNull final Runnable job) {
scheduleDelayed(name, delay, new Runnable() { scheduleDelayed(name, true, delay, new Runnable() {
@Override @Override
public void run() { public void run() {
if (getCurrentState().isAtLeast(atLeast)) { if (getCurrentState().isAtLeast(atLeast)) {

Loading…
Cancel
Save