diff --git a/build.gradle b/build.gradle index 6a30e1dbc3db4426ad26b3d9368f9c0ca527c774..e56089a891f96366e791474d5b0326423c450b2a 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ plugins { group 'de.tudresden.inf.st' ext { - mainClassName = 'de.tudresden.inf.st.mg.Main' + mainClassName = 'de.tudresden.inf.st.mg.ImmersiveSortingController' } // set the main class name for `gradle run` diff --git a/src/main/java/de/tudresden/inf/st/mg/Main.java b/src/main/java/de/tudresden/inf/st/mg/ImmersiveSortingController.java similarity index 52% rename from src/main/java/de/tudresden/inf/st/mg/Main.java rename to src/main/java/de/tudresden/inf/st/mg/ImmersiveSortingController.java index de555c72af11836d788b5276ff4d18759792af6f..d5efac843d5e4ff0f9d33d444218a8f83b95079d 100644 --- a/src/main/java/de/tudresden/inf/st/mg/Main.java +++ b/src/main/java/de/tudresden/inf/st/mg/ImmersiveSortingController.java @@ -3,9 +3,6 @@ package de.tudresden.inf.st.mg; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import com.google.protobuf.util.JsonFormat; -import de.tudresden.inf.st.ceti.Scene; -import de.tudresden.inf.st.mg.common.DiagramProvider; import de.tudresden.inf.st.mg.common.MotionGrammarConfig; import de.tudresden.inf.st.mg.common.MotionGrammarParser; import de.tudresden.inf.st.mg.common.TableDeserializer; @@ -13,6 +10,9 @@ import de.tudresden.inf.st.mg.jastadd.model.JastAddList; import de.tudresden.inf.st.mg.jastadd.model.RobotWorld; import de.tudresden.inf.st.mg.jastadd.model.Table; import de.tudresden.inf.st.mg.jastadd.model.World; +import io.javalin.Javalin; +import io.javalin.core.JavalinConfig; +import io.javalin.websocket.WsContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,19 +21,27 @@ import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Instant; import java.util.Comparator; +import java.util.Date; +import java.util.Map; import java.util.Random; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.ConcurrentHashMap; -public class Main { - - private static final Logger logger = LoggerFactory.getLogger(Main.class); +public class ImmersiveSortingController { public static final Path TIDY_AST_DIAGRAM_DIR = Path.of("src", "gen", "resources", "diagrams", "parsing", "tidy"); - + private static final Logger logger = LoggerFactory.getLogger(ImmersiveSortingController.class); + private static final Map<WsContext, String> clients = new ConcurrentHashMap<>(); + private static ImmersiveSortingController INSTANCE; private final RobotWorld world; + private ParseThread parse; + private Diagram ast; + private Diagram context; - public Main(boolean simulate) { + private boolean initialized = false; + + public ImmersiveSortingController(boolean simulate) { MotionGrammarConfig.astDiagramDir = TIDY_AST_DIAGRAM_DIR; try { Files.walk(TIDY_AST_DIAGRAM_DIR).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); @@ -51,11 +59,15 @@ public class Main { module.addDeserializer(Table.class, new TableDeserializer()); ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); mapper.registerModule(module); - world.setDemonstrationTable(mapper.readValue(Main.class.getResourceAsStream("config_scene_virtual-table.yaml"), Table.class)); + world.setDemonstrationTable(mapper.readValue(ImmersiveSortingController.class.getResourceAsStream("config_scene_virtual-table.yaml"), Table.class)); } catch (IOException e) { e.printStackTrace(); } + initializeWebserver(); + + initialized = true; + World.enableContextPrinting(true); world.printContext("initial"); @@ -90,7 +102,117 @@ public class Main { } - private ParseThread parse; + /** + * Runs the parser on the actual robot. For this to work, a robot controller must be connected to an MQTT server running + * on localhost:1883 + */ + public static void main(String[] args) { + ImmersiveSortingController controller = ImmersiveSortingController.getInstance(); + try { + Thread.sleep(Long.MAX_VALUE); + } catch (InterruptedException ignored) { + } + } + + public static ImmersiveSortingController getInstance() { + if (INSTANCE == null) { + INSTANCE = new ImmersiveSortingController(false); + } + return INSTANCE; + } + + public void startParse() { + if (parse != null && parse.isAlive()) { + parse.stopParsing(); + try { + parse.join(); + } catch (InterruptedException ignored) { + } + } + parse = new ParseThread(); + parse.start(); + } + + public void resetSelections() { + world.setSelectionList(new JastAddList<>()); + } + + public void printContext(String message) { + world.printContext(message); + } + + private void initializeWebserver() { + ast = new Diagram(new Date(), 0, "<no rule>", "", "ast"); + context = new Diagram(Date.from(Instant.now()), 0, "<no rule>", "", "context"); + + Javalin webserver = Javalin.create(JavalinConfig::enableCorsForAllOrigins).start(7070); + webserver.get("/", ctx -> ctx.result("the context is delivered at /context and the ast is delivered at /ast")); + + webserver.get("/ast/", ctx -> { + ctx.json(ast); + }); + + webserver.get("/context/", ctx -> { + ctx.json(context); + }); + + webserver.ws("ast-events", ws -> { + ws.onConnect(ctx -> { + System.err.println("somebody connected " + ctx.host()); + clients.put(ctx, ctx.host()); + }); + ws.onClose(ctx -> { + System.err.println("somebody disconnected"); + clients.remove(ctx); + }); + ws.onMessage(ctx -> { + if (initialized) { + logger.info("got message " + ctx.message()); + } else { + logger.error("ignoring message " + ctx.message() + ", because system is not initialized yet"); + return; + } + if ("parse".equals(ctx.message())) { + startParse(); + } else if ("reset".equals(ctx.message())) { + resetSelections(); + printContext("reset selection"); + } + }); + }); + } + + public void publish(Date timestamp, int step, String parseRule, Path diagramPath, String type) { + try { + if ("ast".equals(type)) { + ast = new Diagram(timestamp, step, parseRule, Files.readString(diagramPath), type); + clients.keySet().forEach(session -> session.send(ast)); + } else if ("context".equals(type)) { + context = new Diagram(timestamp, step, parseRule, Files.readString(diagramPath), type); + clients.keySet().forEach(session -> session.send(context)); + } + } catch (IOException e) { + System.err.println("Unable to read " + type + " diagram file " + diagramPath); + e.printStackTrace(); + } + } + + public static class Diagram { + public Date timestamp; + public int step; + public String parseRule; + public String diagram; + + public String type; + + public Diagram(Date timestamp, int step, String parseRule, String diagram, String type) { + this.timestamp = timestamp; + this.step = step; + this.parseRule = parseRule; + this.diagram = diagram; + this.type = type; + } + } class ParseThread extends Thread { @@ -104,7 +226,7 @@ public class Main { // parse (synchronously, long-running) try { - var result = parser.parse(); + parser.parse(); logger.info("parsing completed!"); } catch (MotionGrammarParser.ParseException e) { throw new RuntimeException(e); @@ -118,36 +240,4 @@ public class Main { } } - public void startParse() { - if (parse != null && parse.isAlive()) { - parse.stopParsing(); - try { - parse.join(); - } catch (InterruptedException e) { - } - } - parse = new ParseThread(); - parse.start(); - } - - public void resetSelections() { - world.setSelectionList(new JastAddList<>()); - } - - /** - * Runs the parser on the actual robot. For this to work, a robot controller must be connected to an MQTT server running - * on localhost:1883 - */ - public static void main(String[] args) { - DiagramProvider webController = DiagramProvider.getInstance(); - webController.setController(new Main(false)); - try { - Thread.sleep(Long.MAX_VALUE); - } catch (InterruptedException ignored) { - } - } - - public void printContext(String message) { - world.printContext(message); - } } diff --git a/src/main/java/de/tudresden/inf/st/mg/common/DiagramProvider.java b/src/main/java/de/tudresden/inf/st/mg/common/DiagramProvider.java deleted file mode 100644 index 1a3d247f4b37c3146aad3375f5d60e82a3e37eca..0000000000000000000000000000000000000000 --- a/src/main/java/de/tudresden/inf/st/mg/common/DiagramProvider.java +++ /dev/null @@ -1,108 +0,0 @@ -package de.tudresden.inf.st.mg.common; - -import de.tudresden.inf.st.mg.Main; -import io.javalin.Javalin; -import io.javalin.core.JavalinConfig; -import io.javalin.websocket.WsContext; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class DiagramProvider { - - private static DiagramProvider INSTANCE; - - private Main controller; - - private static final Map<WsContext, String> clients = new ConcurrentHashMap<>(); - - public static DiagramProvider getInstance() { - if (INSTANCE == null) { - INSTANCE = new DiagramProvider(); - } - return INSTANCE; - } - - private AST ast; - private AST context; - - private DiagramProvider() { - ast = new AST(new Date(), 0, "<no rule>", "", "ast"); - context = new AST(Date.from(Instant.now()), 0, "<no rule>", "", "context"); - - Javalin app = Javalin.create(JavalinConfig::enableCorsForAllOrigins).start(7070); - app.get("/", ctx -> ctx.result("the context is delivered at /context and the ast is delivered at /ast")); - - app.get("/ast/", ctx -> { - ctx.json(ast); - }); - - app.get("/context/", ctx -> { - ctx.json(context); - }); - - app.ws("ast-events", ws -> { - ws.onConnect(ctx -> { - System.err.println("somebody connected " + ctx.host()); - clients.put(ctx, ctx.host()); - }); - ws.onClose(ctx -> { - System.err.println("somebody disconnected"); - clients.remove(ctx); - }); - ws.onMessage(ctx -> { - System.out.println("got message " + ctx.message()); - if ("parse".equals(ctx.message()) && controller != null) { - controller.startParse(); - } else if ("reset".equals(ctx.message()) && controller != null) { - controller.resetSelections(); - controller.printContext("reset selection"); - } - }); - }); - } - - public void publish(Date timestamp, int step, String parseRule, Path diagramPath, String type) { - try { - if ("ast".equals(type)) { - ast = new AST(timestamp, step, parseRule, Files.readString(diagramPath), type); - clients.keySet().forEach(session -> session.send(ast)); - } else if ("context".equals(type)) { - context = new AST(timestamp, step, parseRule, Files.readString(diagramPath), type); - clients.keySet().forEach(session -> session.send(context)); - } - } catch (IOException e) { - System.err.println("Unable to read " + type + " diagram file " + diagramPath); - e.printStackTrace(); - } - } - - public void setController(Main controller) { - this.controller = controller; - } - - public static class AST { - public Date timestamp; - public int step; - public String parseRule; - public String diagram; - - public String type; - - public AST(Date timestamp, int step, String parseRule, String diagram, String type) { - this.timestamp = timestamp; - this.step = step; - this.parseRule = parseRule; - this.diagram = diagram; - this.type = type; - } - } - -}