Refactored Race, MockRace, and VisualiserRace to use RaceClock instead of keeping their own timers.
Moved FPS tracking to Race class, so both VisualiserRace and MockRace can monitor their FPS. LatestMessages is now observable. It notifies observers when an XMLMessage is received. Boat now has StringProperty for name and country/abbreviation. Moved the MockRace timescale value to Constants.RaceTimeScale. This is passed in to MockRace on construction. Tidied up StartController. Copied the visualiser's resources into the resources folder. Refactored RaceClock. Added comments. Tidied code a bit. Moved to shared.model. Started work on RaceController.
This commit is contained in:
@@ -3,10 +3,9 @@ package visualiser.Controllers;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import seng302.Model.Boat;
|
||||
import seng302.Model.RaceClock;
|
||||
import seng302.VisualiserInput;
|
||||
import visualiser.app.VisualiserInput;
|
||||
import visualiser.model.VisualiserBoat;
|
||||
import visualiser.model.VisualiserRace;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
@@ -21,8 +20,13 @@ public class MainController extends Controller {
|
||||
@FXML private ConnectionController connectionController;
|
||||
@FXML private FinishController finishController;
|
||||
|
||||
public void beginRace(VisualiserInput visualiserInput, RaceClock raceClock) {
|
||||
raceController.startRace(visualiserInput, raceClock);
|
||||
/**
|
||||
* Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race).
|
||||
* @param visualiserInput The object used to read packets from the race server.
|
||||
* @param visualiserRace The object modelling the race.
|
||||
*/
|
||||
public void beginRace(VisualiserInput visualiserInput, VisualiserRace visualiserRace) {
|
||||
raceController.startRace(visualiserInput, visualiserRace);
|
||||
}
|
||||
|
||||
public void enterLobby(Socket socket) {
|
||||
@@ -41,10 +45,12 @@ public class MainController extends Controller {
|
||||
*/
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
|
||||
startController.setParent(this);
|
||||
raceController.setParent(this);
|
||||
connectionController.setParent(this);
|
||||
finishController.setParent(this);
|
||||
|
||||
AnchorPane.setTopAnchor(startController.startWrapper(), 0.0);
|
||||
AnchorPane.setBottomAnchor(startController.startWrapper(), 0.0);
|
||||
AnchorPane.setLeftAnchor(startController.startWrapper(), 0.0);
|
||||
|
||||
@@ -10,45 +10,84 @@ import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import visualiser.model.RaceClock;
|
||||
import visualiser.app.VisualiserInput;
|
||||
import shared.model.RaceClock;
|
||||
import visualiser.model.Sparkline;
|
||||
import visualiser.model.VisualiserBoat;
|
||||
import visualiser.model.VisualiserRace;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* Created by fwy13 on 15/03/2017.
|
||||
* Controller used to display a running race.
|
||||
*/
|
||||
public class RaceController extends Controller {
|
||||
|
||||
/**
|
||||
* The object used to read packets from the connected server.
|
||||
*/
|
||||
private VisualiserInput visualiserInput;
|
||||
|
||||
/**
|
||||
* The race object which describes the currently occurring race.
|
||||
*/
|
||||
private VisualiserRace visualiserRace;
|
||||
|
||||
private ResizableRaceCanvas raceMap;
|
||||
private ResizableRaceMap raceBoundaries;
|
||||
|
||||
private RaceClock raceClock;
|
||||
/**
|
||||
* The sparkline graph.
|
||||
*/
|
||||
private Sparkline sparkline;
|
||||
|
||||
private int legNum;
|
||||
|
||||
@FXML GridPane canvasBase;
|
||||
@FXML Pane arrow;
|
||||
@FXML SplitPane race;
|
||||
@FXML StackPane arrowPane;
|
||||
@FXML Label timer;
|
||||
@FXML Label FPS;
|
||||
@FXML Label timeZone;
|
||||
@FXML CheckBox showFPS;
|
||||
@FXML TableView<Boat> boatInfoTable;
|
||||
@FXML TableColumn<Boat, String> boatPlacingColumn;
|
||||
@FXML TableColumn<Boat, String> boatTeamColumn;
|
||||
@FXML TableColumn<Boat, String> boatMarkColumn;
|
||||
@FXML TableColumn<Boat, String> boatSpeedColumn;
|
||||
@FXML LineChart<Number, Number> sparklineChart;
|
||||
@FXML AnchorPane annotationPane;
|
||||
@FXML private GridPane canvasBase;
|
||||
@FXML private Pane arrow;
|
||||
@FXML private SplitPane race;
|
||||
@FXML private StackPane arrowPane;
|
||||
@FXML private Label timer;
|
||||
@FXML private Label FPS;
|
||||
@FXML private Label timeZone;
|
||||
@FXML private CheckBox showFPS;
|
||||
@FXML private TableView<VisualiserBoat> boatInfoTable;
|
||||
@FXML private TableColumn<VisualiserBoat, String> boatPlacingColumn;
|
||||
@FXML private TableColumn<VisualiserBoat, String> boatTeamColumn;
|
||||
@FXML private TableColumn<VisualiserBoat, String> boatMarkColumn;
|
||||
@FXML private TableColumn<VisualiserBoat, String> boatSpeedColumn;
|
||||
@FXML private LineChart<Number, Number> sparklineChart;
|
||||
@FXML private AnchorPane annotationPane;
|
||||
|
||||
|
||||
/**
|
||||
* Ctor.
|
||||
*/
|
||||
public RaceController() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
initialiseFpsToggle();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialises a listener for the fps toggle.
|
||||
*/
|
||||
private void initialiseFpsToggle() {
|
||||
|
||||
showFPS.selectedProperty().addListener((ov, old_val, new_val) -> {
|
||||
if (showFPS.isSelected()) {
|
||||
FPS.setVisible(true);
|
||||
|
||||
} else {
|
||||
FPS.setVisible(false);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the ResizableRaceCanvas (raceMap) with most recent data
|
||||
@@ -79,17 +118,7 @@ public class RaceController extends Controller {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
//listener for fps
|
||||
showFPS.selectedProperty().addListener((ov, old_val, new_val) -> {
|
||||
if (showFPS.isSelected()) {
|
||||
FPS.setVisible(true);
|
||||
} else {
|
||||
FPS.setVisible(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates and sets initial display for Sparkline for race positions.
|
||||
@@ -107,14 +136,16 @@ public class RaceController extends Controller {
|
||||
sparkline.updateSparkline(boatsInRace);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes and runs the race, based on the user's chosen scale factor
|
||||
* Currently uses an example racecourse
|
||||
*
|
||||
* @param visualiserInput input from network
|
||||
* @param raceClock The RaceClock to use for the race's countdown/elapsed duration + timezone.
|
||||
* Displays a specified race.
|
||||
* @param visualiserInput Object used to read packets from server.
|
||||
* @param visualiserRace Object modelling the race.
|
||||
*/
|
||||
public void startRace(VisualiserInput visualiserInput, RaceClock raceClock) {
|
||||
public void startRace(VisualiserInput visualiserInput, VisualiserRace visualiserRace) {
|
||||
|
||||
this.visualiserInput = visualiserInput;
|
||||
this.visualiserRace = visualiserRace;
|
||||
|
||||
legNum = visualiserInput.getCourse().getLegs().size()-1;
|
||||
|
||||
@@ -154,17 +185,7 @@ public class RaceController extends Controller {
|
||||
raceMap.setRaceClock(raceClock);
|
||||
|
||||
//TODO move this list of colors somewhere more sensible.
|
||||
List<Color> colours = new ArrayList<>(Arrays.asList(
|
||||
Color.BLUEVIOLET,
|
||||
Color.BLACK,
|
||||
Color.RED,
|
||||
Color.ORANGE,
|
||||
Color.DARKOLIVEGREEN,
|
||||
Color.LIMEGREEN,
|
||||
Color.PURPLE,
|
||||
Color.DARKGRAY,
|
||||
Color.YELLOW
|
||||
));
|
||||
|
||||
StreamedRace newRace = new StreamedRace(visualiserInput, colours, this);
|
||||
|
||||
initializeFPS();
|
||||
|
||||
@@ -2,51 +2,72 @@ package visualiser.Controllers;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import network.Messages.Enums.RaceStatusEnum;
|
||||
import network.Messages.LatestMessages;
|
||||
import shared.dataInput.*;
|
||||
import shared.exceptions.InvalidBoatDataException;
|
||||
import shared.exceptions.InvalidRaceDataException;
|
||||
import shared.exceptions.InvalidRegattaDataException;
|
||||
import shared.exceptions.XMLReaderException;
|
||||
import visualiser.app.VisualiserInput;
|
||||
import visualiser.model.RaceClock;
|
||||
import visualiser.model.VisualiserBoat;
|
||||
import visualiser.model.VisualiserRace;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Controller to for waiting for the race to start
|
||||
* Controller to for waiting for the race to start.
|
||||
*/
|
||||
public class StartController extends Controller implements Observer {
|
||||
|
||||
@FXML private GridPane start;
|
||||
@FXML private AnchorPane startWrapper;
|
||||
|
||||
/**
|
||||
* The name of the race/regatta.
|
||||
*/
|
||||
@FXML private Label raceTitleLabel;
|
||||
|
||||
/**
|
||||
* The time the race starts at.
|
||||
*/
|
||||
@FXML private Label raceStartLabel;
|
||||
|
||||
/**
|
||||
* The current time at the race location.
|
||||
*/
|
||||
@FXML private Label timeZoneTime;
|
||||
|
||||
/**
|
||||
* Time until the race starts.
|
||||
*/
|
||||
@FXML private Label timer;
|
||||
|
||||
@FXML private TableView<VisualiserBoat> boatNameTable;
|
||||
@FXML private TableColumn<VisualiserBoat, String> boatNameColumn;
|
||||
@FXML private TableColumn<VisualiserBoat, String> boatCodeColumn;
|
||||
@FXML private Label timeZoneTime;
|
||||
@FXML private Label timer;
|
||||
|
||||
/**
|
||||
* The status of the race.
|
||||
*/
|
||||
@FXML private Label raceStatusLabel;
|
||||
|
||||
|
||||
private RaceClock raceClock;
|
||||
|
||||
private int raceStat;
|
||||
|
||||
/**
|
||||
* The object used to read packets from the connected server.
|
||||
*/
|
||||
private VisualiserInput visualiserInput;
|
||||
|
||||
/**
|
||||
@@ -54,41 +75,159 @@ public class StartController extends Controller implements Observer {
|
||||
*/
|
||||
private VisualiserRace visualiserRace;
|
||||
|
||||
///Tracks whether the race has been started (that is, has startRaceNoScaling() be called).
|
||||
private boolean hasRaceStarted = false;
|
||||
/**
|
||||
* An array of colors used to assign colors to each boat - passed in to the VisualiserRace constructor.
|
||||
*/
|
||||
List<Color> colors = new ArrayList<>(Arrays.asList(
|
||||
Color.BLUEVIOLET,
|
||||
Color.BLACK,
|
||||
Color.RED,
|
||||
Color.ORANGE,
|
||||
Color.DARKOLIVEGREEN,
|
||||
Color.LIMEGREEN,
|
||||
Color.PURPLE,
|
||||
Color.DARKGRAY,
|
||||
Color.YELLOW
|
||||
));
|
||||
|
||||
|
||||
//Tracks whether or not a clock has been created and setup, which occurs after receiving enough information.
|
||||
private boolean hasCreatedClock = false;
|
||||
|
||||
/**
|
||||
* Begins the race with a scale factor of 1
|
||||
* Ctor.
|
||||
*/
|
||||
private void startRaceNoScaling() {
|
||||
//while(visualiserInput.getRaceStatus() == null);//TODO probably remove this.
|
||||
|
||||
countdownTimer();
|
||||
public StartController() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources){
|
||||
this.visualiserRace = new VisualiserRace();
|
||||
raceData.addObserver(this);
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts the race.
|
||||
* Called once we have received all XML files from the server.
|
||||
* @param latestMessages The set of latest race messages to use for race.
|
||||
* @throws XMLReaderException Thrown if XML file cannot be parsed.
|
||||
* @throws InvalidRaceDataException Thrown if XML file cannot be parsed.
|
||||
* @throws InvalidBoatDataException Thrown if XML file cannot be parsed.
|
||||
* @throws InvalidRegattaDataException Thrown if XML file cannot be parsed.
|
||||
*/
|
||||
private void startRace(LatestMessages latestMessages) throws XMLReaderException, InvalidRaceDataException, InvalidBoatDataException, InvalidRegattaDataException {
|
||||
|
||||
//Create data sources from latest messages for the race.
|
||||
RaceDataSource raceDataSource = new RaceXMLReader(latestMessages.getRaceXMLMessage().getXmlMessage());
|
||||
BoatDataSource boatDataSource = new BoatXMLReader(latestMessages.getBoatXMLMessage().getXmlMessage());
|
||||
RegattaDataSource regattaDataSource = new RegattaXMLReader(latestMessages.getRegattaXMLMessage().getXmlMessage());
|
||||
|
||||
//Create race.
|
||||
this.visualiserRace = new VisualiserRace(boatDataSource, raceDataSource, regattaDataSource, latestMessages, this.colors);
|
||||
|
||||
|
||||
//Initialise the boat table.
|
||||
initialiseBoatTable(this.visualiserRace);
|
||||
|
||||
//Initialise the race name.
|
||||
initialiseRaceName(this.visualiserRace);
|
||||
|
||||
//Initialises the race clock.
|
||||
initialiseRaceClock(this.visualiserRace);
|
||||
|
||||
//Starts the race countdown timer.
|
||||
countdownTimer();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public AnchorPane startWrapper(){
|
||||
return startWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiliases the tables that are to be shown on the pane
|
||||
*/
|
||||
private void initialiseTables() {
|
||||
List<VisualiserBoat> boats = raceData.getBoats();
|
||||
ObservableList<VisualiserBoat> observableBoats = FXCollections.observableArrayList(boats);
|
||||
|
||||
boatNameTable.setItems(observableBoats);
|
||||
boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().getName());
|
||||
boatCodeColumn.setCellValueFactory(new PropertyValueFactory<>("abbrev"));
|
||||
/**
|
||||
* Initialises the boat table that is to be shown on the pane.
|
||||
* @param visualiserRace The race to get data from.
|
||||
*/
|
||||
private void initialiseBoatTable(VisualiserRace visualiserRace) {
|
||||
|
||||
//Get the boats.
|
||||
ObservableList<VisualiserBoat> boats = visualiserRace.getBoats();
|
||||
|
||||
//Populate table.
|
||||
boatNameTable.setItems(boats);
|
||||
boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
|
||||
boatCodeColumn.setCellValueFactory(cellData -> cellData.getValue().countryProperty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises the race name which is shown on the pane.
|
||||
* @param visualiserRace The race to get data from.
|
||||
*/
|
||||
private void initialiseRaceName(VisualiserRace visualiserRace) {
|
||||
|
||||
raceTitleLabel.setText(visualiserRace.getRegattaName());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises the race clock/timer labels for the start time, current time, and remaining time.
|
||||
* @param visualiserRace The race to get data from.
|
||||
*/
|
||||
private void initialiseRaceClock(VisualiserRace visualiserRace) {
|
||||
|
||||
//Start time.
|
||||
initialiseRaceClockStartTime(visualiserRace);
|
||||
|
||||
//Current time.
|
||||
initialiseRaceClockCurrentTime(visualiserRace);
|
||||
|
||||
//Remaining time.
|
||||
initialiseRaceClockDuration(visualiserRace);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialises the race current time label.
|
||||
* @param visualiserRace The race to get data from.
|
||||
*/
|
||||
private void initialiseRaceClockStartTime(VisualiserRace visualiserRace) {
|
||||
|
||||
visualiserRace.getRaceClock().startingTimeProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Platform.runLater(() -> {
|
||||
raceStartLabel.setText(newValue);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialises the race current time label.
|
||||
* @param visualiserRace The race to get data from.
|
||||
*/
|
||||
private void initialiseRaceClockCurrentTime(VisualiserRace visualiserRace) {
|
||||
|
||||
visualiserRace.getRaceClock().currentTimeProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Platform.runLater(() -> {
|
||||
timeZoneTime.setText(newValue);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises the race duration label.
|
||||
* @param visualiserRace The race to get data from.
|
||||
*/
|
||||
private void initialiseRaceClockDuration(VisualiserRace visualiserRace) {
|
||||
|
||||
visualiserRace.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Platform.runLater(() -> {
|
||||
timer.setText(newValue);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,103 +237,61 @@ public class StartController extends Controller implements Observer {
|
||||
new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long arg0) {
|
||||
raceStat = visualiserInput.getRaceStatus().getRaceStatus();
|
||||
raceStatusLabel.setText("Race Status: " + visualiserInput.getRaceStatus().getRaceStatus());
|
||||
if (raceStat==2 || raceStat == 3) {
|
||||
|
||||
//Get the current race status.
|
||||
RaceStatusEnum raceStatus = visualiserRace.getRaceStatusEnum();
|
||||
|
||||
//Display it.
|
||||
raceStatusLabel.setText("Race Status: " + raceStatus.name());
|
||||
|
||||
//If the race has reached the preparatory phase, or has started...
|
||||
if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) {
|
||||
//Stop this timer.
|
||||
stop();
|
||||
|
||||
//Hide this, and display the race controller.
|
||||
startWrapper.setVisible(false);
|
||||
start.setVisible(false);
|
||||
|
||||
parent.beginRace(visualiserInput, raceClock);
|
||||
parent.beginRace(visualiserInput, visualiserRace);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the clock that displays the time of at the current race venue.
|
||||
*/
|
||||
private void setRaceClock() {
|
||||
raceClock = new RaceClock(raceData.getZonedDateTime());
|
||||
|
||||
raceClock.timeStringProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Platform.runLater(() -> {
|
||||
timeZoneTime.setText(newValue);
|
||||
});
|
||||
});
|
||||
//TEMP REMOVE
|
||||
//timeZoneTime.textProperty().bind(raceClock.timeStringProperty());
|
||||
raceClock.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time that the race is going to start.
|
||||
* Function to handle changes in objects we observe.
|
||||
* We observe LatestMessages.
|
||||
* @param o The observed object.
|
||||
* @param arg The {@link Observable#notifyObservers(Object)} parameter.
|
||||
*/
|
||||
private void setStartingTime() {
|
||||
String dateFormat = "'Starting time:' HH:mm dd/MM/YYYY";
|
||||
Platform.runLater(()-> {
|
||||
long utcTime = visualiserInput.getRaceStatus().getExpectedStartTime();
|
||||
raceClock.setStartingTime(raceClock.getLocalTime(utcTime));
|
||||
raceStartLabel.setText(DateTimeFormatter.ofPattern(dateFormat).format(raceClock.getStartingTime()));
|
||||
|
||||
raceClock.durationProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Platform.runLater(() -> {
|
||||
timer.setText(newValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* set the current time, may be used to update the time on the clock.
|
||||
*/
|
||||
private void setCurrentTime() {
|
||||
Platform.runLater(()->
|
||||
raceClock.setUTCTime(visualiserInput.getRaceStatus().getCurrentTime())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Observable o, Object arg) {
|
||||
if(o instanceof StreamedCourse) {
|
||||
StreamedCourse streamedCourse = (StreamedCourse) o;
|
||||
if(streamedCourse.hasReadBoats()) {
|
||||
initialiseTables();
|
||||
}
|
||||
if(streamedCourse.hasReadRegatta()) {
|
||||
Platform.runLater(() -> raceTitleLabel.setText(streamedCourse.getRegattaName()));
|
||||
}
|
||||
if (streamedCourse.hasReadCourse()) {
|
||||
|
||||
//Check that we actually have LatestMessages.
|
||||
if (o instanceof LatestMessages) {
|
||||
LatestMessages latestMessages = (LatestMessages) o;
|
||||
|
||||
//If we've received all of the xml files, start the race. Only start it if it hasn't already been created.
|
||||
if (latestMessages.hasAllXMLMessages() && this.visualiserRace == null) {
|
||||
|
||||
//Need to handle it in the javafx thread.
|
||||
Platform.runLater(() -> {
|
||||
if (!this.hasCreatedClock) {
|
||||
this.hasCreatedClock = true;
|
||||
setRaceClock();
|
||||
if (visualiserInput.getRaceStatus() == null) {
|
||||
return;//TEMP BUG FIX if the race isn't sending race status messages (our mock currently doesn't), then it would block the javafx thread with the previous while loop.
|
||||
}// TODO - replace with observer on VisualiserInput
|
||||
setStartingTime();
|
||||
setCurrentTime();
|
||||
try {
|
||||
this.startRace(latestMessages);
|
||||
|
||||
} catch (XMLReaderException | InvalidBoatDataException | InvalidRaceDataException | InvalidRegattaDataException e) {
|
||||
//We currently don't handle this in meaningful way, as it should never occur.
|
||||
//If we reach this point it means that malformed XML files were sent.
|
||||
e.printStackTrace();
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//TODO this is a somewhat temporary fix for when not all of the race data (boats, course, regatta) is received in time.
|
||||
//Previously, startRaceNoScaling was called in the enterLobby function after the visualiserInput was started, but when connecting to the official data source it sometimes didn't send all of the race data, causing startRaceNoScaling to start, even though we didn't have enough information to start it.
|
||||
if (streamedCourse.hasReadBoats() && streamedCourse.hasReadCourse() && streamedCourse.hasReadRegatta()) {
|
||||
Platform.runLater(() -> {
|
||||
if (!this.hasRaceStarted) {
|
||||
if(visualiserInput.getRaceStatus() == null) {
|
||||
}
|
||||
else {
|
||||
this.hasRaceStarted = true;
|
||||
startRaceNoScaling();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,8 +301,12 @@ public class StartController extends Controller implements Observer {
|
||||
public void enterLobby(Socket socket) {
|
||||
startWrapper.setVisible(true);
|
||||
try {
|
||||
visualiserInput = new VisualiserInput(socket, raceData);
|
||||
new Thread(visualiserInput).start();
|
||||
//Begin reading packets from the socket/server.
|
||||
this.visualiserInput = new VisualiserInput(socket);
|
||||
//Store a reference to latestMessages so that we can observe it.
|
||||
LatestMessages latestMessages = this.visualiserInput.getLatestMessages();
|
||||
latestMessages.addObserver(this);
|
||||
new Thread(this.visualiserInput).start();
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -78,8 +78,13 @@ public class VisualiserInput implements Runnable {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the LatestMessages object, which can be queried for any received race related messages.
|
||||
* @return The LatestMessages object.
|
||||
*/
|
||||
public LatestMessages getLatestMessages() {
|
||||
return latestMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the time since last heartbeat, in milliseconds.
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
package visualiser.model;
|
||||
|
||||
import com.github.bfsmith.geotimezone.TimeZoneLookup;
|
||||
import com.github.bfsmith.geotimezone.TimeZoneResult;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import shared.model.GPSCoordinate;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* This class is used to implement a clock which keeps track of and
|
||||
* displays times relevant to a race. This is displayed on the
|
||||
* {@link ResizableRaceCanvas} via the
|
||||
* {@link visualiser.Controllers.RaceController} and the
|
||||
* {@link visualiser.Controllers.StartController}.
|
||||
*/
|
||||
public class RaceClock implements Runnable {
|
||||
private long lastTime;
|
||||
private final ZoneId zoneId;
|
||||
private ZonedDateTime time;
|
||||
private ZonedDateTime startingTime;
|
||||
private final StringProperty timeString;
|
||||
private StringProperty duration;
|
||||
|
||||
public RaceClock(ZonedDateTime zonedDateTime) {
|
||||
this.zoneId = zonedDateTime.getZone();
|
||||
this.timeString = new SimpleStringProperty();
|
||||
this.duration = new SimpleStringProperty();
|
||||
this.time = zonedDateTime;
|
||||
setTime(time);
|
||||
}
|
||||
|
||||
public static ZonedDateTime getCurrentZonedDateTime(GPSCoordinate gpsCoordinate) {
|
||||
TimeZoneLookup timeZoneLookup = new TimeZoneLookup();
|
||||
TimeZoneResult timeZoneResult = timeZoneLookup.getTimeZone(gpsCoordinate.getLatitude(), gpsCoordinate.getLongitude());
|
||||
ZoneId zone = ZoneId.of(timeZoneResult.getResult());
|
||||
return LocalDateTime.now(zone).atZone(zone);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
updateTime();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets time to arbitrary zoned time.
|
||||
*
|
||||
* @param time arbitrary time with timezone.
|
||||
*/
|
||||
private void setTime(ZonedDateTime time) {
|
||||
this.time = time;
|
||||
this.timeString.set(DateTimeFormatter.ofPattern("HH:mm:ss dd/MM/YYYY Z").format(time));
|
||||
this.lastTime = System.currentTimeMillis();
|
||||
|
||||
|
||||
if(startingTime != null) {
|
||||
long seconds = Duration.between(startingTime.toLocalDateTime(), time.toLocalDateTime()).getSeconds();
|
||||
if(seconds < 0)
|
||||
duration.set(String.format("Starting in: %02d:%02d:%02d", -seconds/3600, -(seconds%3600)/60, -seconds%60));
|
||||
else
|
||||
duration.set(String.format("Time: %02d:%02d:%02d", seconds/3600, (seconds%3600)/60, seconds%60));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets time to given UTC time in seconds from Unix epoch, preserving timezone.
|
||||
* @param time UTC time
|
||||
*/
|
||||
public void setUTCTime(long time) {
|
||||
Date utcTime = new Date(time);
|
||||
setTime(utcTime.toInstant().atZone(this.zoneId));
|
||||
}
|
||||
|
||||
public ZonedDateTime getStartingTime() {
|
||||
return startingTime;
|
||||
}
|
||||
|
||||
public void setStartingTime(ZonedDateTime startingTime) {
|
||||
this.startingTime = startingTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ZonedDateTime corresponding to local time zone and given UTC time.
|
||||
* @param time time in mills
|
||||
* @return local date time
|
||||
*/
|
||||
public ZonedDateTime getLocalTime(long time) {
|
||||
Date utcTime = new Date(time);
|
||||
return utcTime.toInstant().atZone(this.zoneId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates time by duration elapsed since last update.
|
||||
*/
|
||||
private void updateTime() {
|
||||
this.time = this.time.plus(Duration.of(System.currentTimeMillis() - this.lastTime, ChronoUnit.MILLIS));
|
||||
this.lastTime = System.currentTimeMillis();
|
||||
setTime(time);
|
||||
}
|
||||
|
||||
public String getDuration() {
|
||||
return duration.get();
|
||||
}
|
||||
|
||||
public StringProperty durationProperty() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public String getTimeZone() {
|
||||
return zoneId.toString();
|
||||
}
|
||||
|
||||
public ZonedDateTime getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public StringProperty timeStringProperty() {
|
||||
return timeString;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import javafx.scene.transform.Rotate;
|
||||
import seng302.Mock.StreamedCourse;
|
||||
import seng302.RaceDataSource;
|
||||
import seng302.RaceMap;
|
||||
import shared.model.RaceClock;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.util.Map;
|
||||
*/
|
||||
public class Sparkline {
|
||||
private ArrayList<String> colours;
|
||||
private ArrayList<Boat> startBoats = new ArrayList<>();
|
||||
private ArrayList<VisualiserBoat> startBoats = new ArrayList<>();
|
||||
private Map<Integer, String> boatColours = new HashMap<>();
|
||||
private Integer legNum;
|
||||
private Integer sparkLineNumber = 0;
|
||||
|
||||
@@ -15,9 +15,7 @@ import shared.dataInput.BoatDataSource;
|
||||
import shared.dataInput.RaceDataSource;
|
||||
import shared.dataInput.RegattaDataSource;
|
||||
import shared.model.*;
|
||||
import visualiser.Controllers.FinishController;
|
||||
import visualiser.Controllers.RaceController;
|
||||
import visualiser.app.VisualiserInput;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -41,10 +39,6 @@ public class VisualiserRace extends Race {
|
||||
*/
|
||||
private ObservableList<Mark> boatMarkers;
|
||||
|
||||
//TODO remove these controller references once refactored
|
||||
private RaceController controller;
|
||||
protected FinishController finishController;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@@ -52,10 +46,10 @@ public class VisualiserRace extends Race {
|
||||
* @param boatDataSource Data source for boat related data (yachts and marker boats).
|
||||
* @param raceDataSource Data source for race related data (participating boats, legs, etc...).
|
||||
* @param regattaDataSource Data source for race related data (course name, location, timezone, etc...).
|
||||
* @param colors A collection of colors used to assign a color to each boat.
|
||||
* @param latestMessages The LatestMessages to send events to.
|
||||
* @param colors A collection of colors used to assign a color to each boat.
|
||||
*/
|
||||
public VisualiserRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, List<Color> colors, LatestMessages latestMessages, RaceController controller) {
|
||||
public VisualiserRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, List<Color> colors) {
|
||||
|
||||
super(boatDataSource, raceDataSource, regattaDataSource, latestMessages);
|
||||
|
||||
@@ -64,9 +58,6 @@ public class VisualiserRace extends Race {
|
||||
|
||||
this.boatMarkers = FXCollections.observableArrayList(boatDataSource.getMarkerBoats().values());
|
||||
|
||||
|
||||
this.controller = controller;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +139,7 @@ public class VisualiserRace extends Race {
|
||||
for (VisualiserBoat boat : boats) {
|
||||
|
||||
boat.setCurrentLeg(startingLeg);
|
||||
boat.setTimeSinceLastMark(controller.getRaceClock().getTime());
|
||||
boat.setTimeSinceLastMark(this.raceClock.getCurrentTime());
|
||||
|
||||
}
|
||||
|
||||
@@ -209,7 +200,7 @@ public class VisualiserRace extends Race {
|
||||
if (legNumber >= 1 && legNumber < legs.size()) {
|
||||
if (boat.getCurrentLeg() != legs.get(legNumber)) {
|
||||
boat.setCurrentLeg(legs.get(legNumber));
|
||||
boat.setTimeSinceLastMark(controller.getRaceClock().getTime());
|
||||
boat.setTimeSinceLastMark(this.raceClock.getCurrentTime());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,54 +270,41 @@ public class VisualiserRace extends Race {
|
||||
//Wind speed.
|
||||
this.windSpeed = raceStatus.getWindSpeedKnots();
|
||||
|
||||
//Current race time.
|
||||
this.raceClock.setUTCTime(raceStatus.getCurrentTime());
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void setController(RaceController controller) {
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runnable for the thread.
|
||||
*/
|
||||
public void run() {
|
||||
setControllerListeners();
|
||||
Platform.runLater(() -> controller.createSparkLine(boats));
|
||||
initialiseBoats();
|
||||
startRaceStream();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the calculated fps to the fps label
|
||||
*
|
||||
* @param fps The new calculated fps value
|
||||
*/
|
||||
private void updateFPS(int fps) {
|
||||
Platform.runLater(() -> controller.setFrames("FPS: " + fps));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts the Race Simulation, playing the race start to finish with the timescale.
|
||||
* This prints the boats participating, the order that the events occur in time order, and the respective information of the events.
|
||||
* Starts the race.
|
||||
* This updates the race based on {@link #latestMessages}.
|
||||
*/
|
||||
private void startRaceStream() {
|
||||
|
||||
System.setProperty("javafx.animation.fullspeed", "true");
|
||||
|
||||
|
||||
|
||||
new AnimationTimer() {
|
||||
|
||||
final long timeRaceStarted = System.currentTimeMillis(); //start time of loop
|
||||
//final long timeRaceStarted = System.currentTimeMillis(); //start time of loop
|
||||
int fps = 0; //init fps value
|
||||
long timeCurrent = System.currentTimeMillis(); //current time
|
||||
//long timeCurrent = System.currentTimeMillis(); //current time
|
||||
|
||||
@Override
|
||||
public void handle(long arg0) {
|
||||
|
||||
totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted;
|
||||
//totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted;
|
||||
|
||||
|
||||
|
||||
@@ -343,22 +321,11 @@ public class VisualiserRace extends Race {
|
||||
updateRaceStatus(latestMessages.getRaceStatus());
|
||||
|
||||
|
||||
//TODO tidy this circular dependency up
|
||||
if (getRaceStatusEnum() == RaceStatusEnum.FINISHED) {
|
||||
controller.finishRace(boats);
|
||||
stop();
|
||||
}
|
||||
|
||||
controller.updateMap(boats, boatMarkers);
|
||||
|
||||
fps++;
|
||||
|
||||
if ((System.currentTimeMillis() - timeCurrent) > 1000) {
|
||||
updateFPS(fps);
|
||||
lastFPS = fps;
|
||||
fps = 0;
|
||||
timeCurrent = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
@@ -400,12 +367,7 @@ public class VisualiserRace extends Race {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update call for the controller.
|
||||
*/
|
||||
private void setControllerListeners() {
|
||||
if (controller != null) controller.setInfoTable(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the boats participating in the race.
|
||||
|
||||
Reference in New Issue
Block a user