started to re-implement actions with promises
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -4,7 +4,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>de.srsoftware</groupId>
|
||||
<artifactId>web4rail</artifactId>
|
||||
<version>0.5.6</version>
|
||||
<version>0.6.1</version>
|
||||
<name>Web4Rail</name>
|
||||
<packaging>jar</packaging>
|
||||
<description>Java Model Railway Control</description>
|
||||
|
||||
@@ -15,6 +15,7 @@ import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -73,6 +74,23 @@ public class Application {
|
||||
if (response instanceof Page) {
|
||||
html = ((Page)response).html().toString().getBytes(UTF8);
|
||||
client.getResponseHeaders().add("content-type", "text/html");
|
||||
} else if (response instanceof CompletableFuture) {
|
||||
CompletableFuture<?> promise = (CompletableFuture<?>) response;
|
||||
promise.thenAccept(object -> {
|
||||
try {
|
||||
send(client,object);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Was not able to send {}!",object);
|
||||
}
|
||||
}).exceptionally(ex -> {
|
||||
try {
|
||||
send(client,ex.getMessage());
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Was not able to send {}!",ex);
|
||||
}
|
||||
throw new RuntimeException(ex);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
html = (response == null ? "" : response.toString()).getBytes(UTF8);
|
||||
client.getResponseHeaders().add("content-type", "text/plain");
|
||||
|
||||
24
src/main/java/de/srsoftware/web4rail/Command.java
Normal file
24
src/main/java/de/srsoftware/web4rail/Command.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package de.srsoftware.web4rail;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Command extends CompletableFuture<ControlUnit.Reply> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Command.class);
|
||||
private String command;
|
||||
|
||||
public Command(String command) {
|
||||
this.command = command;
|
||||
this.orTimeout(500, TimeUnit.MILLISECONDS);
|
||||
LOG.debug("Created new Command({}).",command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return command;
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ public class ControlUnit extends Thread implements Constants{
|
||||
private static final String PORT = "port";
|
||||
private static final String BUS = "bus";
|
||||
|
||||
private class Reply{
|
||||
public class Reply{
|
||||
private long secs;
|
||||
private int milis;
|
||||
private int code;
|
||||
@@ -48,10 +48,18 @@ public class ControlUnit extends Thread implements Constants{
|
||||
LOG.info("recv {}.{} {} {}.",secs,milis,code,message);
|
||||
}
|
||||
|
||||
public String message() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Reply("+secs+"."+milis+" / "+code+" / "+message+")";
|
||||
}
|
||||
|
||||
public boolean is(int code) {
|
||||
return code == this.code;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +68,7 @@ public class ControlUnit extends Thread implements Constants{
|
||||
private int port = DEFAULT_PORT;
|
||||
private int bus = 0;
|
||||
private boolean stopped = true;
|
||||
private LinkedList<String> queue = new LinkedList<String>();
|
||||
private LinkedList<Command> queue = new LinkedList<Command>();
|
||||
private Socket socket;
|
||||
private Scanner scanner;
|
||||
private boolean power = false;
|
||||
@@ -156,8 +164,10 @@ public class ControlUnit extends Thread implements Constants{
|
||||
return win;
|
||||
}
|
||||
|
||||
public void queue(String command) {
|
||||
queue.add(command);
|
||||
public Command queue(String command) {
|
||||
Command promise = new Command(command);
|
||||
queue.add(promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,7 +186,10 @@ public class ControlUnit extends Thread implements Constants{
|
||||
try {
|
||||
if (queue.isEmpty()) {
|
||||
Thread.sleep(10);
|
||||
} else send(queue.poll());
|
||||
} else {
|
||||
Command command = queue.pop();
|
||||
command.complete(send(command));
|
||||
}
|
||||
} catch (InterruptedException | IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -200,9 +213,9 @@ public class ControlUnit extends Thread implements Constants{
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private Reply send(String command) throws IOException {
|
||||
private Reply send(Command command) throws IOException {
|
||||
if (command == null) return null;
|
||||
writeln(command);
|
||||
writeln(command.toString());
|
||||
return new Reply(scanner);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Stack;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -426,8 +427,9 @@ public class Plan implements Constants{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void place(Tile tile) throws IOException {
|
||||
public Tile place(Tile tile) throws IOException {
|
||||
stream("place "+tile.tag(null));
|
||||
return tile;
|
||||
}
|
||||
|
||||
private Object planAction(HashMap<String, String> params) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, IOException {
|
||||
@@ -580,7 +582,7 @@ public class Plan implements Constants{
|
||||
stream(t("Warning: {}",t("Ghost train @ {}",contact)));
|
||||
}
|
||||
|
||||
public void queue(String command) {
|
||||
controlUnit.queue(command);
|
||||
public CompletableFuture<ControlUnit.Reply> queue(String command) {
|
||||
return controlUnit.queue(command);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
@@ -21,6 +22,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import de.keawe.tools.translations.Translation;
|
||||
import de.srsoftware.tools.Tag;
|
||||
import de.srsoftware.web4rail.ControlUnit.Reply;
|
||||
import de.srsoftware.web4rail.Plan.Direction;
|
||||
import de.srsoftware.web4rail.actions.Action;
|
||||
import de.srsoftware.web4rail.actions.ActivateRoute;
|
||||
@@ -290,13 +292,33 @@ public class Route implements Constants{
|
||||
file.close();
|
||||
}
|
||||
|
||||
public Route lock(Train train) throws IOException {
|
||||
this.train = train;
|
||||
for (Entry<Turnout, State> entry : turnouts.entrySet()) {
|
||||
entry.getKey().state(entry.getValue());
|
||||
public CompletableFuture<Reply> lock(Train train) throws IOException {
|
||||
Vector<Tile> locked = new Vector<Tile>();
|
||||
try {
|
||||
for (Tile tile : path) locked.add(tile.lock(this)); // try to lock all tiles along the path
|
||||
} catch (Exception e) { // if something fails: unlock all tiles locked so far
|
||||
for (Tile tile : locked) try {
|
||||
tile.unlock();
|
||||
} catch (IOException ex) {
|
||||
LOG.warn("Problem while unlocking {}",ex);
|
||||
}
|
||||
for (Tile tile : path) tile.lock(this);
|
||||
return this;
|
||||
throw e;
|
||||
}
|
||||
CompletableFuture<Reply> promise = null;
|
||||
for (Entry<Turnout, State> entry : turnouts.entrySet()) {// try to switch all turnouts of this route
|
||||
CompletableFuture<Reply> reply = entry.getKey().state(entry.getValue()); // switching a turnout is an asynchronous process, so it returns a CompletableFuture here
|
||||
promise = promise == null ? reply : promise.thenCombine(reply, (a,b) -> a);
|
||||
}
|
||||
promise.exceptionally(ex -> {
|
||||
for (Tile tile : locked) try {
|
||||
tile.unlock();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Problem while unlocking {}",e);
|
||||
}
|
||||
throw new RuntimeException(ex);
|
||||
}).thenRun(() -> this.train = train);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
public List<Route> multiply(int size) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import java.util.HashSet;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Random;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
@@ -331,8 +332,8 @@ public class Train implements Constants {
|
||||
this.speed = v;
|
||||
}
|
||||
|
||||
public String start() throws IOException {
|
||||
if (block == null) return t("{} not in a block",this);
|
||||
public CompletableFuture<String> start() throws IOException {
|
||||
if (block == null) return CompletableFuture.failedFuture(new RuntimeException(t("{} not in a block",this)));
|
||||
if (route != null) route.unlock().setSignals(Signal.STOP);
|
||||
HashSet<Route> routes = block.routes();
|
||||
Vector<Route> availableRoutes = new Vector<Route>();
|
||||
@@ -351,12 +352,18 @@ public class Train implements Constants {
|
||||
availableRoutes.add(rt);
|
||||
}
|
||||
Random rand = new Random();
|
||||
if (availableRoutes.isEmpty()) return t("No free routes from {}",block);
|
||||
int sel = rand.nextInt(availableRoutes.size());
|
||||
route = availableRoutes.get(sel).lock(this).setSignals(null);
|
||||
if (availableRoutes.isEmpty()) return CompletableFuture.failedFuture(new RuntimeException(t("No free routes from {}",block)));
|
||||
route = availableRoutes.get(rand.nextInt(availableRoutes.size()));
|
||||
return route.lock(this).thenApply(reply -> {
|
||||
try {
|
||||
route.setSignals(null);
|
||||
if (direction != route.startDirection) turn();
|
||||
setSpeed(100);
|
||||
return t("started {}",this);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Object stop() {
|
||||
@@ -375,7 +382,11 @@ public class Train implements Constants {
|
||||
}
|
||||
|
||||
private void turn() throws IOException {
|
||||
if (direction != null) direction = direction.inverse();
|
||||
LOG.debug("train.turn()");
|
||||
if (direction != null) {
|
||||
direction = direction.inverse();
|
||||
for (Locomotive loco : locos) loco.turn();
|
||||
}
|
||||
if (block != null) block.train(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -155,9 +155,10 @@ public abstract class Tile implements Constants{
|
||||
return this;
|
||||
}
|
||||
|
||||
public void lock(Route route) throws IOException {
|
||||
this.route = route;
|
||||
plan.place(this);
|
||||
public Tile lock(Route lockingRoute) throws IOException {
|
||||
if (route != null && route != lockingRoute) throw new IllegalStateException(this.toString());
|
||||
route = lockingRoute;
|
||||
return plan.place(this);
|
||||
}
|
||||
|
||||
public Plan plan() {
|
||||
|
||||
@@ -3,10 +3,12 @@ package de.srsoftware.web4rail.tiles;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import de.srsoftware.tools.Tag;
|
||||
import de.srsoftware.web4rail.ControlUnit.Reply;
|
||||
import de.srsoftware.web4rail.Device;
|
||||
import de.srsoftware.web4rail.Protocol;
|
||||
import de.srsoftware.web4rail.tags.Fieldset;
|
||||
@@ -35,29 +37,8 @@ public abstract class Turnout extends Tile implements Device{
|
||||
@Override
|
||||
public Object click() throws IOException {
|
||||
LOG.debug("Turnout.click()");
|
||||
Object o = super.click();
|
||||
if (address != 0 && !initialized) {
|
||||
String p = null;
|
||||
switch (protocol) {
|
||||
case DCC14:
|
||||
case DCC27:
|
||||
case DCC28:
|
||||
case DCC128:
|
||||
p = "N";
|
||||
break;
|
||||
case MOTO:
|
||||
p = "M";
|
||||
break;
|
||||
case SELECTRIX:
|
||||
p = "S";
|
||||
break;
|
||||
default:
|
||||
p = "P";
|
||||
}
|
||||
plan.queue("INIT {} GA "+address+" "+p);
|
||||
initialized = true;
|
||||
}
|
||||
return o;
|
||||
init();
|
||||
return super.click();
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
@@ -119,7 +100,7 @@ public abstract class Turnout extends Tile implements Device{
|
||||
return state;
|
||||
}
|
||||
|
||||
public abstract void state(State newState) throws IOException;
|
||||
public abstract CompletableFuture<Reply> state(State newState) throws IOException;
|
||||
|
||||
@Override
|
||||
public Tag tag(Map<String, Object> replacements) throws IOException {
|
||||
|
||||
@@ -3,8 +3,10 @@ package de.srsoftware.web4rail.tiles;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import de.srsoftware.web4rail.Connector;
|
||||
import de.srsoftware.web4rail.ControlUnit.Reply;
|
||||
import de.srsoftware.web4rail.Plan.Direction;
|
||||
|
||||
public class Turnout3E extends Turnout{
|
||||
@@ -29,8 +31,9 @@ public class Turnout3E extends Turnout{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void state(State newState) throws IOException {
|
||||
public CompletableFuture<Reply> state(State newState) throws IOException {
|
||||
// TODO Auto-generated method stub
|
||||
LOG.warn("Turnout3E.state({}) not implemented, yet!",newState);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package de.srsoftware.web4rail.tiles;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import de.srsoftware.tools.Tag;
|
||||
import de.srsoftware.web4rail.ControlUnit.Reply;
|
||||
import de.srsoftware.web4rail.tags.Fieldset;
|
||||
import de.srsoftware.web4rail.tags.Input;
|
||||
import de.srsoftware.web4rail.tags.Label;
|
||||
@@ -18,7 +20,13 @@ public class TurnoutL extends Turnout {
|
||||
Object o = super.click();
|
||||
if (route != null) {
|
||||
plan.stream(t("{} is locked by {}!",this,route));
|
||||
} else state(state == State.STRAIGHT ? State.LEFT : State.STRAIGHT);
|
||||
} else {
|
||||
CompletableFuture<Reply> promise = state(state == State.STRAIGHT ? State.LEFT : State.STRAIGHT);
|
||||
promise.exceptionally(ex -> {
|
||||
LOG.warn("Failed to toggle turnout: ",ex);
|
||||
throw new RuntimeException(ex);
|
||||
}).thenAccept(reply -> LOG.debug("Success: {}",reply));
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -45,21 +53,25 @@ public class TurnoutL extends Turnout {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void state(State newState) throws IOException {
|
||||
public CompletableFuture<Reply> state(State newState) throws IOException {
|
||||
init();
|
||||
LOG.debug("Setting {} to {}",this,newState);
|
||||
int p = 0;
|
||||
LOG.debug("Requesting to set {} to {}",this,newState);
|
||||
CompletableFuture<Reply> result;
|
||||
switch (newState) {
|
||||
case LEFT:
|
||||
p = portB;
|
||||
result = plan.queue("SET {} GA "+address+" "+portB+" 1 "+delay);
|
||||
break;
|
||||
case STRAIGHT:
|
||||
p = portA;
|
||||
result = plan.queue("SET {} GA "+address+" "+portA+" 1 "+delay);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (p != 0) plan.queue("SET {} GA "+address+" "+p+" 1 "+delay);
|
||||
return result.thenApply(reply -> {
|
||||
LOG.debug("{} received {}",TurnoutL.this,reply);
|
||||
if (!reply.is(200)) throw new RuntimeException(reply.message());
|
||||
state = newState;
|
||||
plan.stream("place "+tag(null));
|
||||
return reply;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package de.srsoftware.web4rail.tiles;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import de.srsoftware.tools.Tag;
|
||||
import de.srsoftware.web4rail.ControlUnit.Reply;
|
||||
import de.srsoftware.web4rail.tags.Fieldset;
|
||||
import de.srsoftware.web4rail.tags.Input;
|
||||
import de.srsoftware.web4rail.tags.Label;
|
||||
@@ -45,21 +47,24 @@ public class TurnoutR extends Turnout {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void state(State newState) throws IOException {
|
||||
public CompletableFuture<Reply> state(State newState) throws IOException {
|
||||
init();
|
||||
LOG.debug("Setting {} to {}",this,newState);
|
||||
int p = 0;
|
||||
CompletableFuture<Reply> result;
|
||||
switch (newState) {
|
||||
case RIGHT:
|
||||
p = portB;
|
||||
result = plan.queue("SET {} GA "+address+" "+portB+" 1 "+delay);
|
||||
break;
|
||||
case STRAIGHT:
|
||||
p = portA;
|
||||
result = plan.queue("SET {} GA "+address+" "+portA+" 1 "+delay);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (p != 0) plan.queue("SET {} GA "+address+" "+p+" 1 "+delay);
|
||||
state = newState;
|
||||
plan.stream("place "+tag(null));
|
||||
return result.thenApply(reply -> {
|
||||
LOG.debug("{} received {}",reply);
|
||||
if (reply.is(200)) state = newState;
|
||||
return reply;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user