All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.diffplug.common.swt.SwtExec Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2020-2022 DiffPlug
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.diffplug.common.swt;

import static java.util.Objects.requireNonNull;

import com.diffplug.common.base.Box.Nullable;
import com.diffplug.common.primitives.Ints;
import com.diffplug.common.rx.Chit;
import com.diffplug.common.rx.GuardedExecutor;
import com.diffplug.common.rx.Rx;
import com.diffplug.common.rx.RxExecutor;
import com.diffplug.common.rx.RxSubscriber;
import com.diffplug.common.util.concurrent.MoreExecutors;
import com.diffplug.common.util.concurrent.Runnables;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.reactivex.Scheduler;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.Disposables;
import io.reactivex.schedulers.Schedulers;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.Callable;
import java.util.concurrent.Delayed;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.Function;
import java.util.function.Supplier;
import kotlin.coroutines.CoroutineContext;
import kotlinx.coroutines.CoroutineDispatcher;
import kotlinx.coroutines.MainCoroutineDispatcher;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Widget;
import org.jetbrains.annotations.NotNull;

/**
 * {@link Executor Executors} which execute on the SWT UI thread.
 *
 * There are two primary kinds of `SwtExec`:
 *
 * - {@link #async()} -> performs actions using {@link Display#asyncExec(Runnable) Display.asyncExec}
 * - {@link #immediate()} -> performs actions immediately if called from a UI thread, otherwise delegates to `asyncExec`.
 *
 * In addition to the standard executor methods, each `SwtExec` also has a method {@link #guardOn(ControlWrapper) guardOn}, which
 * returns a {@link Guarded} instance - the cure for "Widget is disposed" errors.  `Guarded` is an {@link Executor} and {@link RxSubscriber} which
 * stops running tasks and cancels all subscriptions and futures when the guard widget is disposed.
 *
 * ```java
 * SwtExec.immediate().guardOn(myWidget).subscribe(someFuture, value -> myWidget.setContentsTo(value));
 * ```
 *
 * In the example above, if the widget is disposed before the future completes, that's fine!  No "widget is disposed" errors.
 *
 * {@link #blocking()} is similar to `async()` and `immediate()`, but it doesn't support `guard` - it's just a simple `Executor`.
 * It performs actions immediately if called from a UI thread, else delegates to the blocking {@link Display#syncExec}.  It also
 * has the {@link Blocking#get(Supplier)} method, which allows you to easily get a value using a function which must be called
 * on the SWT thread.
 *
 * In the rare scenario where you need higher performance, it is possible to get similar behavior as {@link #immediate()} but with
 * less overhead (and safety) in {@link #swtOnly()} and {@link SwtExec#sameThread()}.  It is very rarely worth this sacrifice.
 */
public class SwtExec extends AbstractExecutorService implements ScheduledExecutorService, RxExecutor.Has {
	/** Returns true iff called from the UI thread. */
	public static boolean isRunningOnUI() {
		initSwtThreads();
		return Thread.currentThread() == swtThread;
	}

	private static Display display;
	private static Thread swtThread;

	@SuppressFBWarnings(value = {"LI_LAZY_INIT_STATIC", "LI_LAZY_INIT_UPDATE_STATIC"}, justification = "This race condition is fine, see comment in SwtExec.blocking()")
	static void initSwtThreads() {
		if (display == null) {
			display = Display.getDefault();
			swtThread = display.getThread();
		}
	}

	/** Global executor for async. */
	private static SwtExec async;

	/**
	 * Returns an "async" SwtExecutor.
	 * 

* When `execute(Runnable)` is called, the `Runnable` will be passed to {@link Display#asyncExec Display.asyncExec}. */ @SuppressFBWarnings(value = "LI_LAZY_INIT_STATIC", justification = "This race condition is fine, see comment in SwtExec.blocking()") public static SwtExec async() { if (async == null) { async = new SwtExec(); } return async; } /** Global executor for immediate. */ private static SwtExec immediate; /** * Returns an "immediate" SwtExecutor. * * - When `execute(Runnable)` is called from the SWT thread, the `Runnable` will be executed immediately. * - Else, the `Runnable` will be passed to {@link Display#asyncExec(Runnable) Display.asyncExec}. * * In the rare case that `immediate()` only ever receives events on the SWT thread, there are faster options: * * - {@link #swtOnly} is about 3x faster, and will throw an error if you call it from somewhere besides an SWT thread. * - {@link #sameThread} is about 15x faster, and will not throw an error if you call it from somewhere besides an SWT thread (but your callback probably will). * * It is very rare that sacrificing the safety of `immediate()` is worth it. Here is the approximate throughput * of the three options on a Win 10, i7-2630QM machine. * * - `immediate()` - 2.9 million events per second * - `swtOnly()` - 8.3 million events per second * - `sameThread()` - 50 million events per second */ @SuppressFBWarnings(value = "LI_LAZY_INIT_STATIC", justification = "This race condition is fine, see comment in SwtExec.blocking()") public static SwtExec immediate() { if (immediate == null) { immediate = new SwtExec() { @Override public void execute(Runnable runnable) { if (Thread.currentThread() == swtThread) { runnable.run(); } else { requireNonNull(runnable); display.asyncExec(runnable); } } }; } return immediate; } /** Global executor for blocking. */ private static Blocking blocking; /** * Returns a "blocking" Executor for the SWT thread. *

    *
  • When `execute(Runnable)` is called from the SWT thread, the `Runnable` will be executed immediately.
  • *
  • Else, the `Runnable` will be passed to {@link Display#syncExec Display.syncExec}.
  • *
* This instance also has a blocking {@link Blocking#get get()} method for doing a get in the UI thread. */ @SuppressFBWarnings(value = "LI_LAZY_INIT_STATIC", justification = "This race condition is fine, see comment in SwtExec.blocking()") public static Blocking blocking() { // There is an acceptable race condition here - blocking might get set multiple times. // This would happen if multiple threads called blocking() at the same time // during initialization, and this is likely to actually happen in practice. // // It is important for this method to be fast, so it's better to accept // that blocking() might return different instances (which each have the // same behavior), rather than to incur the cost of some type of synchronization. if (blocking == null) { blocking = new Blocking(); } return blocking; } /** * An Executor (obtained via {@link SwtExec#blocking()}) which adds a blocking {@link Blocking#get get()} method. *
    *
  • When `execute(Runnable)` is called from the SWT thread, the `Runnable` will be executed immediately.
  • *
  • Else, the `Runnable` will be passed to {@link Display#syncExec Display.syncExec}.
  • *
* @see SwtExec#blocking */ public static class Blocking implements Executor { final Display display; final Thread swtThread; private Blocking() { display = Display.getDefault(); swtThread = display.getThread(); } /** Returns an executor which will only execute if the given guard hasn't been disposed. */ public Executor guardOn(Control guard) { Objects.requireNonNull(guard); return runnable -> { execute(() -> { if (!guard.isDisposed()) { runnable.run(); } }); }; } /** Returns an executor which will only execute if the given guard hasn't been disposed. */ public Executor guardOn(ControlWrapper guard) { return guardOn(guard.getRootControl()); } @Override public void execute(Runnable runnable) { if (Thread.currentThread() == swtThread) { runnable.run(); } else { requireNonNull(runnable); display.syncExec(runnable); } } /** * Performs a blocking get in the UI thread. * * @param supplier will be executed in the UI thread. * @return the value which was returned by supplier. */ public T get(Supplier supplier) { if (Thread.currentThread() == swtThread) { return supplier.get(); } else { Nullable holder = Nullable.ofVolatileNull(); display.syncExec(() -> holder.set(supplier.get())); return holder.get(); } } } /** Executes the given runnable in the UI thread after the given delay. */ public static void timerExec(int ms, Runnable runnable) { initSwtThreads(); display.timerExec(ms, runnable); } /** Returns an API for performing actions which are guarded on the given Widget. */ public Guarded guardOn(Chit chit) { return new Guarded(this, chit); } /** Returns an API for performing actions which are guarded on the given Widget. */ public Guarded guardOn(Widget widget) { return guardOn(SwtRx.chit(widget)); } /** Returns an API for performing actions which are guarded on the given ControlWrapper. */ public Guarded guardOn(ControlWrapper wrapper) { return guardOn(wrapper.getRootControl()); } /** * {@link Executor} and {@link com.diffplug.common.rx.Rx} for conducting actions which are guarded on an SWT widget. * Obtained via {@link SwtExec#guardOn(Widget) SwtExec.guardOn(Widget)} or {@link SwtExec#guardOn(ControlWrapper) SwtExec.guardOn(ControlWrapper)}. *

*

	 * SwtExec.Guarded guarded = SwtExec.immediate().guardOn(textBox);
	 * // guaranteed to not cause "Widget is disposed" errors
	 * guarded.subscribe(serverResponse, txt -> textBox.setText(text));
	 * 
* @see com.diffplug.common.rx.Rx */ public static class Guarded extends GuardedExecutor { private Guarded(SwtExec parent, Chit chit) { super(parent.rxExecutor, chit); } /** Runs the given runnable after the given delay iff the guard widget is not disposed. */ public void timerExec(int delayMs, Runnable runnable) { display.timerExec(delayMs, getGuard().guard(runnable)); } /** * Same as {@link SwtExec#schedule(Runnable, long, TimeUnit)} but automatically * cancels when the guard is disposed. Identical behavior for `immediate()`, * `async()`, etc. */ public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { return hook(SwtExec.async().schedule(getGuard().guard(command), delay, unit)); } /** * Same as {@link SwtExec#scheduleWithFixedDelay(Runnable, long, long, TimeUnit)} but automatically * cancels when the guard is disposed. Identical behavior for `immediate()`, * `async()`, etc. */ public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { return hook(SwtExec.async().scheduleWithFixedDelay(getGuard().guard(command), initialDelay, delay, unit)); } /** * Same as {@link SwtExec#scheduleAtFixedRate(Runnable, long, long, TimeUnit)} but automatically * cancels when the guard is disposed. Identical behavior for `immediate()`, * `async()`, etc. */ public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long delay, TimeUnit unit) { return hook(SwtExec.async().scheduleAtFixedRate(getGuard().guard(command), initialDelay, delay, unit)); } private ScheduledFuture hook(ScheduledFuture future) { getGuard().runWhenDisposed(() -> future.cancel(true)); return future; } } protected final RxExecutor rxExecutor; /** Returns an instance of {@link com.diffplug.common.rx.RxExecutor}. */ @Override public RxExecutor getRxExecutor() { return rxExecutor; } SwtExec() { this(exec -> Rx.callbackOn(exec, new SwtScheduler(exec), new SwtDispatcher(exec))); } SwtExec(Function rxExecutorCreator) { initSwtThreads(); this.rxExecutor = rxExecutorCreator.apply(this); } ////////////// // Executor // ////////////// /** * Executes the given command at some time in the future. The command * may execute in a new thread, in a pooled thread, or in the calling * thread, at the discretion of the Executor implementation. * * @param runnable * the runnable task * @throws RejectedExecutionException * if this task cannot be * accepted for execution. * @throws NullPointerException * if command is null */ @Override public void execute(Runnable runnable) { requireNonNull(runnable); display.asyncExec(runnable); } //////////////////////////////////////////////////////// // ExecutorService shutdown stuff (all unimplemented) // //////////////////////////////////////////////////////// /** * Initiates an orderly shutdown in which previously submitted * tasks are executed, but no new tasks will be accepted. * Invocation has no additional effect if already shut down. * * @throws SecurityException * if a security manager exists and * shutting down this ExecutorService may manipulate * threads that the caller is not permitted to modify * because it does not hold {@link java.lang.RuntimePermission}("modifyThread"), * or the security manager's checkAccess method * denies access. */ @Deprecated @Override public void shutdown() { throw new UnsupportedOperationException(); } /** * Attempts to stop all actively executing tasks, halts the * processing of waiting tasks, and returns a list of the tasks that were * awaiting execution. * *

* There are no guarantees beyond best-effort attempts to stop processing actively executing tasks. For example, typical implementations will cancel via {@link Thread#interrupt}, so any task that fails to respond to interrupts may never terminate. * * @return list of tasks that never commenced execution * @throws SecurityException * if a security manager exists and * shutting down this ExecutorService may manipulate * threads that the caller is not permitted to modify * because it does not hold {@link java.lang.RuntimePermission}("modifyThread"), * or the security manager's checkAccess method * denies access. */ @Deprecated @Override public List shutdownNow() { throw new UnsupportedOperationException(); } /** * Returns true if this executor has been shut down. * * @return true if this executor has been shut down */ @Deprecated @Override public boolean isShutdown() { throw new UnsupportedOperationException(); } /** * Returns true if all tasks have completed following shut down. * Note that isTerminated is never true unless * either shutdown or shutdownNow was called first. * * @return true if all tasks have completed following shut down */ @Deprecated @Override public boolean isTerminated() { throw new UnsupportedOperationException(); } /** * Blocks until all tasks have completed execution after a shutdown * request, or the timeout occurs, or the current thread is * interrupted, whichever happens first. * * @param timeout * the maximum time to wait * @param unit * the time unit of the timeout argument * @return true if this executor terminated and false if the timeout elapsed before termination * @throws InterruptedException * if interrupted while waiting */ @Deprecated @Override public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { throw new UnsupportedOperationException(); } ////////////////////////////// // ScheduledExecutorService // ////////////////////////////// /** * Creates and executes a one-shot action that becomes enabled * after the given delay. * * @param command * the task to execute * @param delay * the time from now to delay execution * @param unit * the time unit of the delay parameter * @return a ScheduledFuture representing pending completion of * the task and whose get() method will return null upon completion * @throws RejectedExecutionException * if the task cannot be * scheduled for execution * @throws NullPointerException * if command is null */ @Override public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { long delayMs = TimeUnit.MILLISECONDS.convert(delay, unit); return submitFuture(new RunnableScheduledFuture<>(newTaskFor(command, null), delayMs)); } /** * Creates and executes a ScheduledFuture that becomes enabled after the * given delay. * * @param callable * the function to execute * @param delay * the time from now to delay execution * @param unit * the time unit of the delay parameter * @return a ScheduledFuture that can be used to extract result or cancel * @throws RejectedExecutionException * if the task cannot be * scheduled for execution * @throws NullPointerException * if callable is null */ @Override public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { long delayMs = TimeUnit.MILLISECONDS.convert(delay, unit); return submitFuture(new RunnableScheduledFuture<>(newTaskFor(callable), delayMs)); } /** * Creates and executes a periodic action that becomes enabled first * after the given initial delay, and subsequently with the given * period; that is executions will commence after initialDelay then initialDelay+period, then initialDelay + 2 * period, and so on. * If any execution of the task * encounters an exception, subsequent executions are suppressed. * Otherwise, the task will only terminate via cancellation or * termination of the executor. If any execution of this task * takes longer than its period, then subsequent executions * may start late, but will not concurrently execute. * * @param command * the task to execute * @param initialDelay * the time to delay first execution * @param period * the period between successive executions * @param unit * the time unit of the initialDelay and period parameters * @return a ScheduledFuture representing pending completion of * the task, and whose get() method will throw an * exception upon cancellation * @throws RejectedExecutionException * if the task cannot be * scheduled for execution * @throws NullPointerException * if command is null * @throws IllegalArgumentException * if period less than or equal to zero */ @Override public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { long initialDelayMs = TimeUnit.MILLISECONDS.convert(initialDelay, unit); long periodMs = TimeUnit.MILLISECONDS.convert(period, unit); return submitFuture(new RunnableScheduledFuture<>(newTaskFor(Runnables.doNothing(), null), command, initialDelayMs, periodMs)); } /** * Creates and executes a periodic action that becomes enabled first * after the given initial delay, and subsequently with the * given delay between the termination of one execution and the * commencement of the next. If any execution of the task * encounters an exception, subsequent executions are suppressed. * Otherwise, the task will only terminate via cancellation or * termination of the executor. * * @param command * the task to execute * @param initialDelay * the time to delay first execution * @param delay * the delay between the termination of one * execution and the commencement of the next * @param unit * the time unit of the initialDelay and delay parameters * @return a ScheduledFuture representing pending completion of * the task, and whose get() method will throw an * exception upon cancellation * @throws RejectedExecutionException * if the task cannot be * scheduled for execution * @throws NullPointerException * if command is null * @throws IllegalArgumentException * if delay less than or equal to zero */ @Override public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { long initialDelayMs = TimeUnit.MILLISECONDS.convert(initialDelay, unit); long periodMs = -TimeUnit.MILLISECONDS.convert(delay, unit); return submitFuture(new RunnableScheduledFuture<>(newTaskFor(Runnables.doNothing(), null), command, initialDelayMs, periodMs)); } static ScheduledFuture submitFuture(RunnableScheduledFuture future) { if (Thread.currentThread() == swtThread) { display.timerExec(future.delayInt(), future); } else { display.asyncExec(() -> { int delay = future.delayInt(); if (delay <= 0) { future.run(); } else { display.timerExec(delay, future); } }); } return future; } /** Simple little mixin for making RunnableFutures schedulable. */ @SuppressFBWarnings(value = "EQ_COMPARETO_USE_OBJECT_EQUALS", justification = "changes as it runs") private static class RunnableScheduledFuture implements Runnable, ScheduledFuture { private RunnableFuture cancelDelegate; private Runnable toRun; private long time; /** * = 0 -> no period * > 0 -> fixedRate * < 0 -> fixedDelay */ private long periodMs; private RunnableScheduledFuture(RunnableFuture runnableFuture, long delayMs) { this.cancelDelegate = runnableFuture; this.toRun = runnableFuture; this.time = System.currentTimeMillis() + delayMs; this.periodMs = 0; } private RunnableScheduledFuture(RunnableFuture runnableFuture, Runnable toRun, long delayMs, long periodMs) { this.cancelDelegate = runnableFuture; this.toRun = toRun; this.time = System.currentTimeMillis() + delayMs; this.periodMs = periodMs; } int delayInt() { return Ints.saturatedCast(time - System.currentTimeMillis()); } // Runnable, overridden @Override public void run() { if (cancelDelegate.isCancelled()) { return; } if (periodMs > 0) { // fixedRate time += periodMs; } toRun.run(); if (periodMs < 0) { // fixedDelay time = System.currentTimeMillis() - periodMs; } if (periodMs != 0) { long now = System.currentTimeMillis(); // if it's periodic, we need to reschedule int delay = Ints.saturatedCast(time - now); if (delay < 0) { // if we blew a deadline, reset the schedule time = now; display.asyncExec(this); } else { display.timerExec(delay, this); } } } @Override public long getDelay(TimeUnit unit) { return unit.convert(time - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed other) { if (other instanceof RunnableScheduledFuture) { return Ints.saturatedCast(time - ((RunnableScheduledFuture) other).time); } else { int otherDelay = Ints.saturatedCast(other.getDelay(TimeUnit.MILLISECONDS)); return delayInt() - otherDelay; } } // ScheduledFuture, delegated @Override public boolean cancel(boolean mayInterruptIfRunning) { return cancelDelegate.cancel(mayInterruptIfRunning); } @Override public T get() throws InterruptedException, ExecutionException { return cancelDelegate.get(); } @Override public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return cancelDelegate.get(timeout, unit); } @Override public boolean isCancelled() { return cancelDelegate.isCancelled(); } @Override public boolean isDone() { return cancelDelegate.isDone(); } } static final class SwtDispatcher extends MainCoroutineDispatcher { private final SwtExec exec; public SwtDispatcher(SwtExec exec) { this.exec = exec; } public boolean isDispatchNeeded(CoroutineContext context) { if (exec == SwtExec.immediate && swtThread == Thread.currentThread()) { return false; } else { return true; } } @Override public void dispatch(@NotNull CoroutineContext coroutineContext, @NotNull Runnable runnable) { display.asyncExec(runnable); } @NotNull @Override public MainCoroutineDispatcher getImmediate() { if (exec == SwtExec.immediate()) { return this; } else { return (SwtDispatcher) SwtExec.immediate().rxExecutor.getDispatcher(); } } } /** Scheduler that runs tasks on Swt's event dispatch thread. */ static final class SwtScheduler extends Scheduler { final SwtExec exec; public SwtScheduler(SwtExec exec) { this.exec = exec; } @Override public Worker createWorker() { return new SwtWorker(exec); } static final class SwtWorker extends Scheduler.Worker { final SwtExec exec; volatile boolean unsubscribed; /** Set of active tasks, guarded by this. */ Set tasks; public SwtWorker(SwtExec exec) { this.exec = exec; this.tasks = new HashSet<>(); } @Override public void dispose() { if (unsubscribed) { return; } unsubscribed = true; Set set; synchronized (this) { set = tasks; tasks = null; } if (set != null) { for (SwtScheduledAction a : set) { a.cancelFuture(); } } } void remove(SwtScheduledAction a) { if (unsubscribed) { return; } synchronized (this) { if (unsubscribed) { return; } tasks.remove(a); } } @Override public boolean isDisposed() { return unsubscribed; } @Override public Disposable schedule(Runnable action) { if (unsubscribed) { return Disposables.disposed(); } SwtScheduledAction a = new SwtScheduledAction(action, this); synchronized (this) { if (unsubscribed) { return Disposables.disposed(); } tasks.add(a); } exec.execute(a); if (unsubscribed) { a.cancel(); return Disposables.disposed(); } return a; } @Override public Disposable schedule(Runnable action, long delayTime, TimeUnit unit) { if (unsubscribed) { return Disposables.disposed(); } SwtScheduledAction a = new SwtScheduledAction(action, this); synchronized (this) { if (unsubscribed) { return Disposables.disposed(); } tasks.add(a); } Future f = exec.schedule(a, delayTime, unit); if (unsubscribed) { a.cancel(); f.cancel(true); return Disposables.disposed(); } a.setFuture(f); return a; } /** * Represents a cancellable asynchronous Runnable that wraps an action * and manages the associated Worker lifecycle. */ static final class SwtScheduledAction implements Runnable, Disposable { final Runnable action; final SwtWorker parent; volatile Future future; @SuppressWarnings("rawtypes") static final AtomicReferenceFieldUpdater FUTURE = AtomicReferenceFieldUpdater.newUpdater(SwtScheduledAction.class, Future.class, "future"); static final Future CANCELLED = new FutureTask<>(() -> {}, null); static final Future FINISHED = new FutureTask<>(() -> {}, null); volatile int state; static final AtomicIntegerFieldUpdater STATE = AtomicIntegerFieldUpdater.newUpdater(SwtScheduledAction.class, "state"); static final int STATE_ACTIVE = 0; static final int STATE_FINISHED = 1; static final int STATE_CANCELLED = 2; public SwtScheduledAction(Runnable action, SwtWorker parent) { this.action = action; this.parent = parent; } @Override public void run() { if (!parent.unsubscribed && state == STATE_ACTIVE) { try { action.run(); } finally { FUTURE.lazySet(this, FINISHED); if (STATE.compareAndSet(this, STATE_ACTIVE, STATE_FINISHED)) { parent.remove(this); } } } } @Override public boolean isDisposed() { return state != STATE_ACTIVE; } @Override public void dispose() { if (STATE.compareAndSet(this, STATE_ACTIVE, STATE_CANCELLED)) { parent.remove(this); } cancelFuture(); } void setFuture(Future f) { if (FUTURE.compareAndSet(this, null, f)) { if (future != FINISHED) { f.cancel(true); } } } void cancelFuture() { Future f = future; if (f != CANCELLED && f != FINISHED) { f = FUTURE.getAndSet(this, CANCELLED); if (f != null && f != CANCELLED && f != FINISHED) { f.cancel(true); } } } void cancel() { state = STATE_CANCELLED; } } } } /** Global executor for actions which should only execute immediately on the SWT thread. */ private static SwtExec swtOnly; /** * UNLESS YOU HAVE PERFORMANCE PROBLEMS, USE {@link #immediate()} INSTEAD. * * Returns an SwtExecutor which can only be called from the SWT * thread, and runs actions immediately. Has the same behavior * as {@link #immediate()} for callbacks on the SWT * thread. For values not on the SWT thread, `immediate()` behaves * likes {@link #async()}, while `swtOnly()` throws an exception. */ @SuppressFBWarnings(value = "LI_LAZY_INIT_STATIC", justification = "This race condition is fine, see comment in SwtExec.blocking()") public static SwtExec swtOnly() { if (swtOnly == null) { swtOnly = new SwtExec(exec -> Rx.callbackOn(exec, new SwtOnlyScheduler(), new SwtOnlyDispatcher())) { @Override public void execute(Runnable runnable) { requireNonNull(runnable); if (Thread.currentThread() == swtThread) { runnable.run(); } else { SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS); } } }; } return swtOnly; } static class SwtOnlyDispatcher extends CoroutineDispatcher { @Override public boolean isDispatchNeeded(CoroutineContext context) { return false; } @Override public void dispatch(@NotNull CoroutineContext coroutineContext, @NotNull Runnable runnable) { if (Thread.currentThread() == swtThread) { runnable.run(); } else { SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS); } } } /** * Copied straight from rx.schedulers.ImmediateScheduler, * but checks for the SWT thread before running stuff, * and handles future-scheduling correctly. */ static final class SwtOnlyScheduler extends Scheduler { @Override public Worker createWorker() { return new InnerImmediateScheduler(); } private static final class InnerImmediateScheduler extends Scheduler.Worker { final Disposable innerSubscription = Disposables.empty(); @Override public Disposable schedule(Runnable action, long delayTime, TimeUnit unit) { CompositeDisposable sub = new CompositeDisposable(); Future future = SwtExec.async().schedule(() -> { if (!sub.isDisposed()) { action.run(); sub.dispose(); } }, delayTime, unit); sub.add(Disposables.fromFuture(future)); return sub; } @Override public Disposable schedule(Runnable action) { if (Thread.currentThread() == swtThread) { action.run(); } else { SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS); } return Disposables.disposed(); } @Override public void dispose() { innerSubscription.dispose(); } @Override public boolean isDisposed() { return innerSubscription.isDisposed(); } } } private static class SameThreadCoroutineDispatcher extends CoroutineDispatcher { @Override public void dispatch(@NotNull CoroutineContext coroutineContext, @NotNull Runnable runnable) { runnable.run(); } } private static final SwtExec sameThread = new SwtExec(exec -> Rx.callbackOn(MoreExecutors.directExecutor(), Schedulers.trampoline(), new SameThreadCoroutineDispatcher())) { @Override public void execute(Runnable runnable) { requireNonNull(runnable); runnable.run(); } }; /** * UNLESS YOU HAVE PERFORMANCE PROBLEMS, USE {@link #immediate()} INSTEAD. * * Returns an SwtExec which runs actions immediately, without checking * whether they were called from the SWT thread or not. */ public static SwtExec sameThread() { return sameThread; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy