Skip to content

Commit

Permalink
Merge pull request #23 from saalfeldlab/0.8.0
Browse files Browse the repository at this point in the history
0.8.0
  • Loading branch information
cmhulbert authored Sep 28, 2023
2 parents 8766d4c + 6ef266d commit 3ff79cd
Show file tree
Hide file tree
Showing 7 changed files with 332 additions and 141 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<groupId>org.janelia.saalfeldlab</groupId>
<artifactId>saalfx</artifactId>
<version>0.7.1-SNAPSHOT</version>
<version>0.8.0-SNAPSHOT</version>

<name>Saal FX</name>
<description>Saalfeld lab JavaFX tools and extensions</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@
*/
package org.janelia.saalfeldlab.control.mcu;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.Receiver;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Transmitter;
import javax.sound.midi.*;

/**
* @author Stephan Saalfeld &lt;[email protected]&gt;
Expand All @@ -18,11 +14,13 @@ public abstract class MCUControlPanel implements Receiver {
private static final int STATUS_KEY = 0x90;
private static final int STATUS_FADER = 0xe8;

private final MidiDevice device;
private final Transmitter trans;
private final Receiver rec;

public MCUControlPanel(final Transmitter trans, final Receiver rec) {
public MCUControlPanel(final MidiDevice device, final Transmitter trans, final Receiver rec) {

this.device = device;
this.trans = trans;
this.rec = rec;
trans.setReceiver(this);
Expand Down Expand Up @@ -97,5 +95,6 @@ public void close() {

trans.close();
rec.close();
device.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,9 @@ public class XTouchMiniMCUControlPanel extends MCUControlPanel {
}

private final MCUFaderControl fader = new MCUFaderControl();
public XTouchMiniMCUControlPanel(final MidiDevice device, final Transmitter trans, final Receiver rec) {

public XTouchMiniMCUControlPanel(final Transmitter trans, final Receiver rec) {

super(trans, rec);
super(device, trans, rec);

for (int i = 0; i < vpots.length; ++i)
vpots[i] = new MCUVPotControl(vpotLedIds[i], rec);
Expand Down Expand Up @@ -149,8 +148,9 @@ public static XTouchMiniMCUControlPanel build(final String deviceDescription) th
MidiDevice recDev = null;
Receiver rec = null;

MidiDevice device = null;
for (final Info info : MidiSystem.getMidiDeviceInfo()) {
final MidiDevice device = MidiSystem.getMidiDevice(info);
device = MidiSystem.getMidiDevice(info);
final String lowerDeviceDescription = deviceDescription.toLowerCase();
if (info.getDescription().toLowerCase().contains(lowerDeviceDescription) || info.getName().toLowerCase().contains(lowerDeviceDescription)) {
if (device.getMaxTransmitters() != 0) {
Expand All @@ -164,10 +164,10 @@ public static XTouchMiniMCUControlPanel build(final String deviceDescription) th
}
}

if (!(trans == null || rec == null)) {
if (!(device == null || trans == null || rec == null)) {
transDev.open();
recDev.open();
final XTouchMiniMCUControlPanel panel = new XTouchMiniMCUControlPanel(trans, rec);
final XTouchMiniMCUControlPanel panel = new XTouchMiniMCUControlPanel(device, trans, rec);
trans.setReceiver(panel);
panel.reset();
return panel;
Expand Down
145 changes: 112 additions & 33 deletions src/main/kotlin/org/janelia/saalfeldlab/fx/Tasks.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package org.janelia.saalfeldlab.fx

import com.google.common.util.concurrent.ThreadFactoryBuilder
import javafx.beans.value.ChangeListener
import javafx.concurrent.Task
import javafx.concurrent.Worker
import javafx.concurrent.Worker.State.*
import javafx.concurrent.WorkerStateEvent
import javafx.event.EventHandler
import org.janelia.saalfeldlab.fx.util.InvokeOnJavaFXApplicationThread
import org.slf4j.LoggerFactory
import java.util.concurrent.ExecutorService
Expand Down Expand Up @@ -41,7 +44,7 @@ private val THREAD_FACTORY: ThreadFactory = ThreadFactoryBuilder()
.setNameFormat("task-thread-%d")
.build()

private val TASK_SERVICE = Executors.newCachedThreadPool(THREAD_FACTORY)
private val TASK_SERVICE = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() - 1, THREAD_FACTORY)

/**
* Convenience wrapper class around [Task]
Expand All @@ -52,6 +55,7 @@ private val TASK_SERVICE = Executors.newCachedThreadPool(THREAD_FACTORY)
*/
class UtilityTask<V>(private val onCall: (UtilityTask<V>) -> V) : Task<V>() {

private var executorService : ExecutorService = TASK_SERVICE
private var onFailedSet = false

companion object {
Expand All @@ -61,11 +65,11 @@ class UtilityTask<V>(private val onCall: (UtilityTask<V>) -> V) : Task<V>() {
override fun call(): V? {
try {
/* If no `onEnd/onFail` has been set, then we should listen for thrown exceptions and throw them */
if (!this.onFailedSet) setDefaultExceptionHandler()
if (!onFailedSet) setDefaultOnFailed()
return onCall(this)
} catch (e: Exception) {
LOG.trace("Task Exception (cancelled=$isCancelled): ", e)
if (isCancelled) {
LOG.debug("Task was cancelled")
return null
}
throw e
Expand All @@ -76,7 +80,7 @@ class UtilityTask<V>(private val onCall: (UtilityTask<V>) -> V) : Task<V>() {
super.updateValue(value)
}

private fun setDefaultExceptionHandler() {
private fun setDefaultOnFailed() {
InvokeOnJavaFXApplicationThread {
this.onFailed { _, task -> LOG.error(task.exception.stackTraceToString()) }
}
Expand All @@ -85,101 +89,176 @@ class UtilityTask<V>(private val onCall: (UtilityTask<V>) -> V) : Task<V>() {
/**
* Builder-style function to set [SUCCEEDED] callback.
*
* @param append flag to determine behavior if an existing `onSuccess` callback is present:
* - if `true`, the current callback will be called prior to this `consumer` being called
* - if `false`, the prior callback will be removed and never called.
* - if `null`, this will throw a runtime exception if an existing callback is present.
* - This is meant to help unintended overrides of existing callbacks when `append` is not explicitly specified
* @param consumer to be called when [SUCCEEDED]
* @return this
*/
@JvmSynthetic
fun onSuccess(consumer: (WorkerStateEvent, UtilityTask<V>) -> Unit): UtilityTask<V> {
this.setOnSucceeded { event -> consumer(event, this) }
fun onSuccess(append: Boolean? = null, consumer: (WorkerStateEvent, UtilityTask<V>) -> Unit): UtilityTask<V> {
val appendCallbacks = onSucceeded?.appendCallbacks(append, consumer)
val consumerEvent = EventHandler<WorkerStateEvent> { event -> consumer(event, this) }
setOnSucceeded(appendCallbacks ?: consumerEvent)
return this
}

/**
* Builder-style function to set [CANCELLED] callback.
*
* @param append flag to determine behavior if an existing `onCancelled` callback is present:
* - if `true`, the current callback will be called prior to this `consumer` being called
* - if `false`, the prior callback will be removed and never called.
* - if `null`, this will throw a runtime exception if an existing callback is present.
* - This is meant to help unintended overrides of existing callbacks when `append` is not explicitly specified
* @param consumer to be called when [CANCELLED]
* @return this
*/
@JvmSynthetic
fun onCancelled(consumer: (WorkerStateEvent, UtilityTask<V>) -> Unit): UtilityTask<V> {
this.setOnCancelled { event -> consumer(event, this) }
fun onCancelled(append: Boolean? = null, consumer: (WorkerStateEvent, UtilityTask<V>) -> Unit): UtilityTask<V> {
val appendCallbacks = onCancelled?.appendCallbacks(append, consumer)
val consumerEvent = EventHandler<WorkerStateEvent> { event -> consumer(event, this) }
setOnCancelled(appendCallbacks ?: consumerEvent)
return this
}

/**
* Builder-style function to set [FAILED] callback.
*
* @param append flag to determine behavior if an existing `onFailed` callback is present:
* - if `true`, the current callback will be called prior to this `consumer` being called
* - if `false`, the prior callback will be removed and never called.
* - if `null`, this will throw a runtime exception if an existing callback is present.
* - This is meant to help unintended overrides of existing callbacks when `append` is not explicitly specified
* @param consumer to be called when [FAILED]
* @return this
*/
@JvmSynthetic
fun onFailed(consumer: (WorkerStateEvent, UtilityTask<V>) -> Unit): UtilityTask<V> {
onFailedSet = true
this.setOnFailed { event -> consumer(event, this) }
fun onFailed(append: Boolean? = null, consumer: (WorkerStateEvent, UtilityTask<V>) -> Unit): UtilityTask<V> {
this.onFailedSet = true
val eventHandler = onFailed?.appendCallbacks(append, consumer) ?: EventHandler { event -> consumer(event, this) }
this.setOnFailed(eventHandler)
return this
}


private var onEndListener: ChangeListener<Worker.State>? = null

/**
* Builder-style function to set when the task ends, either by [SUCCEEDED], [CANCELLED], or [FAILED].
*
* @param append flag to determine behavior if an existing `onEnd` callback is present:
* - if `true`, the current callback will be called prior to this `consumer` being called
* - if `false`, the prior callback will be removed and never called.
* - if `null`, this will throw a runtime exception if an existing callback is present.
* - This is meant to help unintended overrides of existing callbacks when `append` is not explicitly specified
* @param consumer to be called when task ends
* @return this
*/
@JvmSynthetic
fun onEnd(consumer: (UtilityTask<V>) -> Unit): UtilityTask<V> {
this.stateProperty().addListener { _, _, newv ->
fun onEnd(append: Boolean? = null, consumer: (UtilityTask<V>) -> Unit): UtilityTask<V> {
//TODO Caleb: Consider renaming `onEnd` to `finally` since this is trigger on end for ANY reason, even if an
// Exception was thrown. Or Maybe a separate `finally` which does what this currently does, and then change `onEnd`
// to NOT trigger if an excpetion occures (that isn't handled by the exception handler)
onEndListener = onEndListener?.let { oldListener ->
stateProperty().removeListener(oldListener)
if (append == null)
throw TaskStateCallbackOverrideException("Overriding existing handler; If intentional, pass `false` for `append`")
if (append) {
ChangeListener { obs, oldv, newv ->
when (newv) {
SUCCEEDED, CANCELLED, FAILED -> {
oldListener.changed(obs, oldv, newv)
consumer(this)
}

else -> Unit
}
}
} else null
} ?: ChangeListener { _, _, newv ->
when (newv) {
SUCCEEDED, CANCELLED, FAILED -> consumer(this)
else -> Unit
}
}
this.stateProperty().addListener(onEndListener)
return this
}

fun onSuccess(consumer: BiConsumer<WorkerStateEvent, UtilityTask<V>>): UtilityTask<V> {
return onSuccess { e, t -> consumer.accept(e, t) }

/**
*
* @see [onSuccess]
*/
@JvmOverloads
fun onSuccess(append: Boolean? = null, consumer: BiConsumer<WorkerStateEvent, UtilityTask<V>>): UtilityTask<V> {
return onSuccess(append) { e, t -> consumer.accept(e, t) }
}

fun onCancelled(consumer: BiConsumer<WorkerStateEvent, UtilityTask<V>>): UtilityTask<V> {
return onCancelled { e, t -> consumer.accept(e, t) }
/**
*
* @see [onCancelled]
*/
@JvmOverloads
fun onCancelled(append: Boolean? = null, consumer: BiConsumer<WorkerStateEvent, UtilityTask<V>>): UtilityTask<V> {
return onCancelled(append) { e, t -> consumer.accept(e, t) }
}

fun onFailed(consumer: BiConsumer<WorkerStateEvent, UtilityTask<V>>): UtilityTask<V> {
return onFailed { e, t -> consumer.accept(e, t) }
/**
*
* @see [onFailed]
*/@JvmOverloads
fun onFailed(append: Boolean? = null, consumer: BiConsumer<WorkerStateEvent, UtilityTask<V>>): UtilityTask<V> {
return onFailed(append) { e, t -> consumer.accept(e, t) }
}

fun onEnd(consumer: Consumer<UtilityTask<V>>): UtilityTask<V> {
return onEnd { t -> consumer.accept(t) }
/**
*
* @see [onEnd]
*/
@JvmOverloads
fun onEnd(append: Boolean? = null, consumer: Consumer<UtilityTask<V>>): UtilityTask<V> {
return onEnd(append) { t -> consumer.accept(t) }
}

/**
* Submit this task to the [executorService].
*
* @param executorService to execute this task on.
* @return this task
*/
fun submit(executorService: ExecutorService) {
executorService.submit(this)
@JvmOverloads
fun submit(executorService: ExecutorService = this.executorService) : UtilityTask<V> {
this.executorService = executorService;
this.executorService.submit(this)
return this
}

/**
* Submit this task to the [executorService], and block while waiting for it to return.
* This will return after the task completes, but possibbly BEFORE the [onSuccess]/[onEnd] call finish.
*
* @param executorService to execute this task on.
* @return the result of this task, blocking if not yet done.
*/
@JvmOverloads
fun submitAndWait(executorService: ExecutorService = TASK_SERVICE): V {
executorService.submit(this)
fun submitAndWait(executorService: ExecutorService = this.executorService): V {
this.executorService = executorService
this.executorService.submit(this)
return this.get()
}

/**
* Submit this task on a default [ExecutorService].
*
* @return this
*/
fun submit(): UtilityTask<V> {
submit(TASK_SERVICE)
return this
private fun EventHandler<WorkerStateEvent>.appendCallbacks(append: Boolean? = false, consumer: (WorkerStateEvent, UtilityTask<V>) -> Unit): EventHandler<WorkerStateEvent>? {
if (append == null) throw TaskStateCallbackOverrideException("Overriding existing handler; If intentional, pass `false` for `append`")
if (!append) return null
return EventHandler { event ->
this.handle(event)
consumer(event, this@UtilityTask)
}
}

private class TaskStateCallbackOverrideException(override val message: String?) : RuntimeException(message)
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,27 @@ class WritableSubclassDelegate<T, K : T>(private val obs: WritableValue<T?>, pri
}
}

fun <T> ObservableValue<T>.addTriggeredListener(triggerWith : T = value, listener : (ObservableValue<out T>?, T, T) -> Unit) {
val changeListener = ChangeListener<T> { observable, oldValue, newValue -> listener(observable, oldValue, newValue) }
addListener(changeListener)
listener(this, value, triggerWith)
fun interface SelfRefrentialListener<T> : ChangeListener<T> {

override fun changed(observable: ObservableValue<out T>?, oldValue: T, newValue: T) {
changedWithSelf(observable, oldValue, newValue)
}

fun ChangeListener<T>.changedWithSelf(observable: ObservableValue<out T>?, oldValue: T, newValue: T)
}

fun <T> ObservableValue<T>.addWithListener(triggerWith : T? = value, listener: SelfRefrentialListener<T>) {
this.addListener(listener)
triggerWith?.let {
listener.changed(this, value, value)
}
}

fun <T> ObservableValue<T>.addTriggeredWithListener(triggerWith : T = value, listener: SelfRefrentialListener<T>) {
addWithListener(triggerWith, listener)
}

fun <T> ObservableValue<T>.addTriggeredListener(triggerWith: T = value, listener: ChangeListener<T>) {
this.addListener(listener)
listener.changed(this, value, value)
}
Loading

0 comments on commit 3ff79cd

Please sign in to comment.