Skip to content

Instantly share code, notes, and snippets.

@jewelsea
Last active April 5, 2023 11:53
Show Gist options
  • Save jewelsea/5500981 to your computer and use it in GitHub Desktop.
Save jewelsea/5500981 to your computer and use it in GitHub Desktop.
Demo of interaction of various concurrency aspects with JavaFX
.log-entry {
-fx-background-color: lavender;
}
.info {
-fx-text-fill: goldenrod;
}
.warn {
-fx-text-fill: firebrick;
}
import javafx.animation.*;
import javafx.application.*;
import javafx.beans.*;
import javafx.beans.Observable;
import javafx.beans.property.*;
import javafx.collections.*;
import javafx.concurrent.Task;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.effect.InnerShadow;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.Duration;
import java.util.*;
import java.util.concurrent.*;
enum Location { DRAGON_CAVE, TOWN }
public class Greed extends Application {
private static final BlockingQueue<TravellingDwarf> dwarvesHeadingToCave = new ArrayBlockingQueue<>(3);
private static final BlockingQueue<TravellingDwarf> dwarvesHeadingToTown = new ArrayBlockingQueue<>(3);
private static final Random random = new Random();
private final Log log = Log.getInstance();
public static Random getRandom() {
return random;
}
public static void main(String[] args) throws InterruptedException {
final Town town = new Town();
final Thread feeder = new Thread(
new DwarvesLeavingTownHandler(town, dwarvesHeadingToCave),
"feeder"
);
feeder.setDaemon(true);
feeder.start();
final Thread returner = new Thread(
new DwarvesEnteringTownHandler(town, dwarvesHeadingToTown),
"returner"
);
returner.setDaemon(true);
returner.start();
launch(args);
}
@Override public void start(final Stage stage) throws Exception {
final DragonCave dragonCave = new DragonCave();
final ProgressBar goldLeft = new ProgressBar();
goldLeft.setStyle("-fx-accent: gold; -fx-color: lavender;");
goldLeft.progressProperty().bind(
dragonCave.goldProperty().divide(
dragonCave.MAX_GOLD * 1.0
)
);
goldLeft.setMaxWidth(Double.MAX_VALUE);
log.getView().setStyle("-fx-focus-color: transparent;");
final ImageView dragonView = new ImageView(
new Image(
"http://fc02.deviantart.net/fs70/i/2011/250/d/4/fire_dragon_from_a_volcano_by_wil_simpson-d497hc0.jpg",
200, 0, true, true
)
);
final InnerShadow effect = new InnerShadow();
effect.setColor(Color.LAVENDER);
dragonView.setEffect(effect);
dragonView.setMouseTransparent(true);
dragonView.setOpacity(0.1);
dragonView.visibleProperty().bind(
dragonCave.getDragon().sleepingProperty().not()
);
dragonCave.getDragon().getDwarvesEaten().addListener(new InvalidationListener() {
@Override
public void invalidated(Observable observable) {
if (dragonCave.getDragon().getDwarvesEaten().size() == Town.MAX_DWARVES) {
dragonView.visibleProperty().unbind();
dragonView.setOpacity(0.5);
}
}
});
StackPane logWithDragon = new StackPane();
logWithDragon.getChildren().setAll(
log.getView(),
dragonView
);
final VBox layout = new VBox();
layout.getChildren().setAll(
goldLeft,
logWithDragon
);
layout.getStylesheets().add(getClass().getResource("greed.css").toExternalForm());
stage.setTitle("Greed");
stage.setScene(
new Scene(
layout
)
);
stage.show();
final Thread eater = new Thread(
new DwarvesEnteringCaveHandler(
dragonCave,
dwarvesHeadingToCave,
dwarvesHeadingToTown
),
"eater"
);
eater.setDaemon(true);
eater.start();
}
}
class DwarvesEnteringCaveHandler extends Task<Void> {
final DragonCave cave;
final BlockingQueue<TravellingDwarf> dwarvesHeadingToCave;
final BlockingQueue<TravellingDwarf> dwarvesHeadingToTown;
public DwarvesEnteringCaveHandler(
DragonCave cave,
BlockingQueue<TravellingDwarf> dwarvesHeadingToCave,
BlockingQueue<TravellingDwarf> dwarvesHeadingToTown) {
this.cave = cave;
this.dwarvesHeadingToCave = dwarvesHeadingToCave;
this.dwarvesHeadingToTown = dwarvesHeadingToTown;
}
@Override protected Void call() {
while (true) {
try {
final TravellingDwarf enteringDwarf = dwarvesHeadingToCave.take();
final FutureTask<TravellingDwarf> caveVoyage = new FutureTask<>(
new Callable<TravellingDwarf>() {
@Override public TravellingDwarf call() throws Exception {
return cave.dwarfEnters(enteringDwarf);
}
}
);
Platform.runLater(caveVoyage);
final TravellingDwarf returningDwarf = caveVoyage.get();
if (returningDwarf != null) {
dwarvesHeadingToTown.put(returningDwarf);
}
} catch (InterruptedException ex) {
System.out.println("TravellingDwarfHandler interrupted");
return null;
} catch (ExecutionException e) {
System.out.println("Unexpected exception encountered entering the cave: " + e);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class DragonCave {
public static final int MAX_GOLD = 100;
private Log log = Log.getInstance();
private Dragon dragon = new Dragon();
Dragon getDragon() {
return dragon;
}
private ReadOnlyIntegerWrapper gold = new ReadOnlyIntegerWrapper(MAX_GOLD);
public ReadOnlyIntegerProperty goldProperty() {
return gold.getReadOnlyProperty();
}
public TravellingDwarf dwarfEnters(TravellingDwarf dwarf) {
if (dragon.isSleeping()) {
int goldTaken = Math.min(
dwarf.getGoldCarryingCapacity(),
gold.get()
);
gold.set(gold.get() - goldTaken);
log.info(dwarf.getName() + " took " + goldTaken + " gold");
return new TravellingDwarf(
dwarf.getName(),
Location.TOWN,
dwarf.getGoldCarryingCapacity(),
goldTaken
);
} else {
dragon.eat(dwarf);
log.warn(dwarf.getName() + " was eaten");
return null; // dragon is awake, dwarf is eaten and never returns.
}
}
}
class Dragon {
private static final Random random = Greed.getRandom();
private static final int MIN_SLEEP_TIME = 1600;
private static final int MAX_SLEEP_TIME = 3200 + MIN_SLEEP_TIME;
private final ReadOnlyBooleanWrapper sleeping = new ReadOnlyBooleanWrapper(random.nextBoolean());
private final ObservableList<TravellingDwarf> dwarvesEaten = FXCollections.observableArrayList();
public Dragon() {
Timeline timeline = new Timeline(
new KeyFrame(
Duration.millis(random.nextInt(MAX_SLEEP_TIME - MIN_SLEEP_TIME) + MIN_SLEEP_TIME),
new EventHandler<ActionEvent>() {
@Override public void handle(ActionEvent event) {
sleeping.set(!sleeping.get());
}
}
)
);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
public ReadOnlyBooleanProperty sleepingProperty() {
return sleeping.getReadOnlyProperty();
}
public boolean isSleeping() {
return sleeping.get();
}
public void eat(TravellingDwarf dwarf) {
dwarvesEaten.add(dwarf);
}
public ObservableList<TravellingDwarf> getDwarvesEaten() {
return FXCollections.unmodifiableObservableList(dwarvesEaten);
}
}
class DwarvesLeavingTownHandler implements Runnable {
private static final int MIN_PAUSE_INTERVAL = 800;
private static final int MAX_PAUSE_INTERVAL = 4000 - MIN_PAUSE_INTERVAL;
private final Random random = Greed.getRandom();
private final Town town;
private final BlockingQueue<TravellingDwarf> travellingDwarves;
public DwarvesLeavingTownHandler(Town town, BlockingQueue<TravellingDwarf> travellingDwarves) {
this.town = town;
this.travellingDwarves = travellingDwarves;
}
@Override public void run() {
while (true) {
try {
Thread.sleep(random.nextInt(MAX_PAUSE_INTERVAL - MIN_PAUSE_INTERVAL) + MIN_PAUSE_INTERVAL);
final TravellingDwarf dwarf = town.createTravellingDwarf(
Location.DRAGON_CAVE
);
if (dwarf != null) {
travellingDwarves.put(dwarf);
}
} catch (InterruptedException e) {
System.out.println("Simulation Interrupted");
return;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class DwarvesEnteringTownHandler implements Runnable {
private final BlockingQueue<TravellingDwarf> travellingDwarves;
private final Town town;
public DwarvesEnteringTownHandler(Town town, BlockingQueue<TravellingDwarf> travellingDwarves) {
this.town = town;
this.travellingDwarves = travellingDwarves;
}
@Override public void run() {
while (true) {
try {
final TravellingDwarf enteringDwarf = travellingDwarves.take();
town.returnToTown(enteringDwarf);
} catch (InterruptedException ex) {
System.out.println("TravellingDwarfHandler interrupted");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class Town {
private static final Random random = Greed.getRandom();
private static final String[] NAMES = {
"Bofur", "Bifur", "Balin", "Dwalin", "Fili", "Kili", "Gloin", "Oin", "Nori", "Ori", "Dori", "Bombur", "Thorin"
};
public static final int MAX_DWARVES = NAMES.length;
private static final Map<String, Integer> carryingCapacity = new HashMap<>();
static {
for (final String name: NAMES) {
carryingCapacity.put(name, (random.nextInt(8) + 2));
}
}
private final Location location = Location.TOWN;
private final List<String> dwarvesInTown = new ArrayList<>(Arrays.asList(NAMES));
private int gold = 0;
public synchronized boolean hasDwarves() {
return !dwarvesInTown.isEmpty();
}
public synchronized TravellingDwarf createTravellingDwarf(Location destination) {
if (destination == this.location) {
return null;
}
if (!hasDwarves()) {
return null;
}
String dwarfName = dwarvesInTown.remove(0);
return new TravellingDwarf(
dwarfName,
destination,
carryingCapacity.get(dwarfName),
0
);
}
public synchronized void returnToTown(TravellingDwarf enteringDwarf) {
gold += enteringDwarf.getGoldCarried();
dwarvesInTown.add(enteringDwarf.getName());
System.out.println("Town has " + gold + " gold");
}
}
class TravellingDwarf {
private final String name;
private final Location destination;
private final int goldCarryingCapacity;
private final int goldCarried;
public TravellingDwarf(String name, Location destination, int goldCarryingCapacity, int goldCarried) {
this.name = name;
this.destination = destination;
this.goldCarryingCapacity = goldCarryingCapacity;
this.goldCarried = goldCarried;
}
public String getName() {
return name;
}
public Location getDestination() {
return destination;
}
public int getGoldCarried() {
return goldCarried;
}
public int getGoldCarryingCapacity() {
return goldCarryingCapacity;
}
@Override
public String toString() {
return "TravellingDwarf{" +
"name='" + name + '\'' +
", destination=" + destination +
", goldCarryingCapacity=" + goldCarryingCapacity +
", goldCarried=" + goldCarried +
'}';
}
}
class Log {
private static final int MAX_LENGTH = 10000;
private enum Level { INFO, WARN }
private final ListView<LogItem> log = new ListView();
private class LogItem {
private final Level level;
private final String msg;
private LogItem(Level level, String msg) {
this.level = level;
this.msg = msg;
}
private Level getLevel() {
return level;
}
private String getMsg() {
return msg;
}
}
private static final Log instance = new Log();
public static Log getInstance() {
return instance;
}
private final StyleManager logStyles = new StyleManager(
"info", "warn"
);
public Log() {
log.setCellFactory(new Callback<ListView<LogItem>, ListCell<LogItem>>() {
@Override public ListCell<LogItem> call(ListView<LogItem> logItemListView) {
return new ListCell<LogItem>() {
@Override protected void updateItem(LogItem logItem, boolean empty) {
super.updateItem(logItem, empty);
if (!getStyleClass().contains("log-entry")) {
getStyleClass().add("log-entry");
}
if (logItem != null) {
setText(logItem.getMsg());
switch (logItem.getLevel()) {
case INFO:
logStyles.selectStyle("info", getStyleClass());
break;
case WARN:
logStyles.selectStyle("warn", getStyleClass());
break;
default:
logStyles.selectStyle(null, getStyleClass());
}
} else {
setText("");
logStyles.selectStyle(null, getStyleClass());
}
}
};
}
});
}
public ListView getView() {
return log;
}
public void info(String msg) {
log(Level.INFO, msg);
}
public void warn(String msg) {
log(Level.WARN, msg);
}
private void log(Level level, String msg) {
log.scrollTo(log.getItems().size() - 1);
if (log.getItems().size() >= MAX_LENGTH) {
log.getItems().remove(log.getItems().size() - 1);
}
log.getItems().add(new LogItem(level, msg));
}
}
class StyleManager {
private Set managedClasses = new TreeSet();
public StyleManager(String... styleClasses) {
Collections.addAll(managedClasses, styleClasses);
}
public void selectStyle(String styleClass, ObservableList<String> styleClasses) {
if (styleClass == null) {
styleClasses.removeAll(managedClasses);
return;
}
if (!styleClasses.contains(styleClass)) {
styleClasses.removeAll(managedClasses);
styleClasses.add(styleClass);
}
}
}
@jewelsea
Copy link
Author

jewelsea commented May 2, 2013

This is a demo app to provide a background sample for discussion of a JavaFX forum post: pass messages between main thread and FX application thread.

Sample is just demo-ware to try out different concurrency facilities and not necessarily a recommended strategy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment