Create new orchestrator

pull/992/head
Mattia Iavarone 5 years ago
parent adffec766d
commit e3c586171a
  1. 8
      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. 247
      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)
setTargetSdkVersion(property("targetSdkVersion") as Int)
versionCode = 1
versionName = "2.6.4"
versionName = "2.6.4-zoom3"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArgument("filter", "" +
"com.otaliastudios.cameraview.tools.SdkExcludeFilter," +
@ -51,15 +51,15 @@ publisher {
project.group = "com.otaliastudios"
project.url = "https://github.com/natario1/CameraView"
project.addLicense(License.APACHE_2_0)
release.setSources(Release.SOURCES_AUTO)
release.setDocs(Release.DOCS_AUTO)
bintray {
release.setSources(Release.SOURCES_AUTO)
release.setDocs(Release.DOCS_AUTO)
auth.user = "BINTRAY_USER"
auth.key = "BINTRAY_KEY"
auth.repo = "BINTRAY_REPO"
}
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
// to be. So we remove the old runnable if still present and post a new one.
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() {
@Override
public void run() {

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

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

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

Loading…
Cancel
Save