Skip to content

Commit

Permalink
Merge pull request #94 from ds306e18/runner-upgrade
Browse files Browse the repository at this point in the history
Improved RLBot Runner process so that it doesn't have to be shut down after each match
  • Loading branch information
NicEastvillage authored Oct 17, 2019
2 parents e8b5972 + c42f618 commit 956c336
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 21 deletions.
6 changes: 6 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

#### Unreleased Changes
- Now writes overlay data to a json next to the cleopetra.jar. This can be disabled in RLBotSettings tab. - NicEastvillage
- Improved the RLBot runner process. - NicEastvillage
- The RLBot console window (RLBot runner) now does not need to be shut down after each match. #88
- CleoPetra issues the RLBot runner to start and stop new matches through socket communication.
- RLBot.exe is not shut down between each match, which means:
- Skyborg's overlay will work properly.
- Rendering and bot percentages does not have to be toggled each match.
- Improved how round robin decides the top teams of the stage. #92 - NicEastvillage


Expand Down
118 changes: 102 additions & 16 deletions src/dk/aau/cs/ds306e18/tournament/rlbot/MatchRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,47 @@
import dk.aau.cs.ds306e18.tournament.utility.Alerts;
import dk.aau.cs.ds306e18.tournament.utility.OverlayData;

import java.io.*;
import java.net.ConnectException;
import java.net.Socket;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;

/**
* This class maintains a RLBot runner process for starting matches. The runner process is the run.py running in
* a separate command prompt. Communication between CleoPetra and the RLBot runner happens through a socket and
* consists of simple commands like START, STOP, EXIT, which are defined in the subclass Command. If the RLBot runner
* is not running when a command is issued, a new instance of the RLBot runner is started.
*/
public class MatchRunner {

// The first %s will be replaced with the directory of the rlbot.cfg. The second %s will be the drive 'C:' to change drive.
private static final String COMMAND_FORMAT = "cmd.exe /c start cmd /c \"cd %s & %s & python run.py\"";
private static final String ADDR = "127.0.0.1";
private static final int PORT = 35353; // TODO Make user able to change the port in a settings file

/** Starts the given match in Rocket League. */
private enum Command {
START("START"), // Start the match described by the rlbot.cfg
STOP("STOP"), // Stop the current match and all bot pids
EXIT("EXIT"); // Close the run.py process

private final String cmd;

Command(String cmd) {
this.cmd = cmd;
}

@Override
public String toString() {
return cmd;
}
}

/**
* Starts the given match in Rocket League.
*/
public static boolean startMatch(MatchConfig matchConfig, Match match) {

// Checks settings and modifies rlbot.cfg file if everything is okay
Expand All @@ -30,30 +60,86 @@ public static boolean startMatch(MatchConfig matchConfig, Match match) {
return false;
}

// Write overlay data to current_match.json
if (Tournament.get().getRlBotSettings().writeOverlayDataEnabled()) {
try {
OverlayData.write(match);
} catch (IOException e) {
Alerts.errorNotification("Could not write overlay data", "Failed to write overlay data to " + OverlayData.CURRENT_MATCH_PATH);
e.printStackTrace();
}
}

return sendCommandToRLBot(Command.START, true);
}

/**
* Starts the RLBot runner, aka. the run.py, as a separate process in a separate cmd.
* The runner might not be ready to accept commands immediately. This method returns true on success.
*/
public static boolean startRLBotRunner() {
try {
// TODO; does not support Linux, refactor when relevant
Alerts.infoNotification("Starting RLBot runner", "Attempting to start new instance of run.py for running matches.");
Path pathToDirectory = SettingsDirectory.RUN_PY.getParent();
String command = String.format(COMMAND_FORMAT, pathToDirectory, pathToDirectory.toString().substring(0, 2));
System.out.println("Starting RLBot framework with command: " + command);
Runtime.getRuntime().exec(command);
if (Tournament.get().getRlBotSettings().writeOverlayDataEnabled()) {
String cmd = String.format(COMMAND_FORMAT, pathToDirectory, pathToDirectory.toString().substring(0, 2));
Runtime.getRuntime().exec(cmd);
return true;
} catch (IOException e) {
e.printStackTrace();
Alerts.errorNotification("Could not start RLBot runner", "Something went wrong starting the run.py.");
return false;
}
}

/**
* Closes the RLBot runner.
*/
public static void closeRLBotRunner() {
// If the command fails, the runner is probably not running anyway, so ignore any errors.
sendCommandToRLBot(Command.EXIT, false);
}

/**
* Stops the current match.
*/
public static void stopMatch() {
sendCommandToRLBot(Command.STOP, false);
}

/**
* Sends the given command to the RLBot runner. If the runner does not respond, this method will optionally
* start a new RLBot runner instance and retry sending the command. If we believe the command was send
* and received, this method returns true, otherwise false.
*/
private static boolean sendCommandToRLBot(Command cmd, boolean startRLBotIfMissingAndRetry) {
try (Socket sock = new Socket(ADDR, PORT);
PrintWriter writer = new PrintWriter(sock.getOutputStream(), true)) {
writer.print(cmd.toString());
writer.flush();
return true;
} catch (ConnectException e) {
// The run.py did not respond. Starting a new instance if allowed
if (startRLBotIfMissingAndRetry) {
try {
OverlayData.write(match);
} catch (IOException e) {
Alerts.errorNotification("Could not write overlay data", "Failed to write overlay data to " + OverlayData.CURRENT_MATCH_PATH);
e.printStackTrace();
startRLBotRunner();
Thread.sleep(200);
// Retry
return sendCommandToRLBot(cmd, false);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}

return true;

} catch (Exception err) {
err.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
Alerts.errorNotification("IO Exception", "Failed to open socket and send message to run.py");
}

return false;
}

/** Returns true if the given match can be started. */
/**
* Returns true if the given match can be started.
*/
public static boolean canStartMatch(Match match) {
try {
checkMatch(match);
Expand Down
98 changes: 96 additions & 2 deletions src/dk/aau/cs/ds306e18/tournament/settings/files/run.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,104 @@
import time
import socket
from threading import Event, Thread

from rlbot.setup_manager import SetupManager, setup_manager_context


PORT = 35353


class Command:
START = b"START"
STOP = b"STOP"
EXIT = b"EXIT"


def setup_match(manager: SetupManager):
manager.shut_down(kill_all_pids=True, quiet=True) # Stop any running pids
manager.load_config()
manager.launch_early_start_bot_processes()
manager.start_match()
manager.launch_bot_processes()


def wait_for_all_bots(manager: SetupManager):
while not manager.has_received_metadata_from_all_bots():
manager.try_recieve_agent_metadata()
time.sleep(0.1)


def start_match(manager: SetupManager):
game_interface = manager.game_interface
setup_match(manager)
wait_for_all_bots(manager)


def stop_match(manager: SetupManager):
manager.shut_down(kill_all_pids=True, quiet=True)


def match_running(start_event: Event, stop_event: Event, exit_event: Event):
with setup_manager_context() as manager:
while True:
if start_event.is_set():
start_match(manager)
start_event.clear()
elif stop_event.is_set():
stop_match(manager)
stop_event.clear()
elif exit_event.is_set():
exit_event.clear()
break


if __name__ == '__main__':
print("""/<<<<<<<<<<<<<<<<<<<o>>>>>>>>>>>>>>>>>>>\\
| Hello! I am CleoPetra's little helper |
| and your view into the RLBot process. |
| Keep me open while using CleoPetra! |
\\<<<<<<<<<<<<<<<<<<<o>>>>>>>>>>>>>>>>>>>/
""")

try:
from rlbot import runner
runner.main()
start_event = Event()
stop_event = Event()
exit_event = Event()

match_runner = Thread(target=match_running, args=(start_event, stop_event, exit_event))
match_runner.start()

# AF_INET is the Internet address family for IPv4. SOCK_STREAM is the socket type for TCP
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('127.0.0.1', PORT))
s.listen()
print(f"Listening for CleoPetra on 127.0.0.1:{PORT} ...")
while True:
conn, addr = s.accept()
with conn:
print('Connected by', addr)
data = conn.recv(1024)

if data == Command.START:
print("Starting match!")
start_event.set()
elif data == Command.STOP:
print("Stopping match!")
stop_event.set()
elif data == Command.EXIT:
print("Exiting!")
exit_event.set()
break
else:
print(f"Unknown command received: {data}")

conn.sendall(b'OK')

match_runner.join()

except Exception as e:
print("Encountered exception: ", e)
print("Press enter to close.")
input()
finally:
print("run.py stopped. You can close this!")
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dk.aau.cs.ds306e18.tournament.ui;

import dk.aau.cs.ds306e18.tournament.model.Tournament;
import dk.aau.cs.ds306e18.tournament.rlbot.MatchRunner;
import dk.aau.cs.ds306e18.tournament.rlbot.RLBotSettings;
import dk.aau.cs.ds306e18.tournament.rlbot.configuration.MatchConfig;
import dk.aau.cs.ds306e18.tournament.rlbot.configuration.MatchConfigOptions.*;
Expand All @@ -23,6 +24,9 @@ public class RLBotSettingsTabController {
public RadioButton skipReplaysRadioButton;
public RadioButton instantStartRadioButton;
public RadioButton writeOverlayDataRadioButton;
public Button rlbotRunnerOpenButton;
public Button rlbotRunnerCloseButton;
public Button rlbotRunnerStopMatchButton;
public ChoiceBox<MatchLength> matchLengthChoiceBox;
public ChoiceBox<MaxScore> maxScoreChoiceBox;
public ChoiceBox<Overtime> overtimeChoiceBox;
Expand Down Expand Up @@ -134,4 +138,16 @@ public void update() {
// Other settings
writeOverlayDataRadioButton.setSelected(settings.writeOverlayDataEnabled());
}

public void onActionRLBotRunnerOpen(ActionEvent actionEvent) {
MatchRunner.startRLBotRunner();
}

public void onActionRLBotRunnerClose(ActionEvent actionEvent) {
MatchRunner.closeRLBotRunner();
}

public void onActionRLBotRunnerStopMatch(ActionEvent actionEvent) {
MatchRunner.stopMatch();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>

<GridPane id="mainGrid" fx:id="bracketOverviewTab" prefHeight="572.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dk.aau.cs.ds306e18.tournament.ui.BracketOverviewTabController">
<GridPane id="mainGrid" fx:id="bracketOverviewTab" prefHeight="720.0" prefWidth="1080.0" xmlns="http://javafx.com/javafx/1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dk.aau.cs.ds306e18.tournament.ui.BracketOverviewTabController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="300.0" minWidth="10.0" prefWidth="100.0" />
Expand Down
26 changes: 26 additions & 0 deletions src/dk/aau/cs/ds306e18/tournament/ui/layout/RLBotSettingsTab.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,32 @@
<RadioButton fx:id="writeOverlayDataRadioButton" layoutX="494.0" layoutY="13.0" mnemonicParsing="false" GridPane.columnIndex="1" />
</children>
</GridPane>
<Separator layoutX="30.0" layoutY="279.0" prefWidth="200.0" />
<Text layoutX="30.0" layoutY="309.0" strokeType="OUTSIDE" strokeWidth="0.0" text="RLBot Runner">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Text>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="CleoPetra's little helper for running matches.&#10;It will be open automatically if missing.&#10;Rocket League will also be opened, when the RLBot Runner is opened.">
<font>
<Font size="14.0" />
</font>
</Text>
<GridPane hgap="8.0" maxHeight="-Infinity" maxWidth="-Infinity">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Button fx:id="rlbotRunnerOpenButton" mnemonicParsing="false" onAction="#onActionRLBotRunnerOpen" prefHeight="27.0" prefWidth="101.0" text="Open" />
<Button fx:id="rlbotRunnerCloseButton" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" onAction="#onActionRLBotRunnerClose" prefHeight="27.0" prefWidth="101.0" text="Close" GridPane.columnIndex="1" />
<Button fx:id="rlbotRunnerStopMatchButton" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" onAction="#onActionRLBotRunnerStopMatch" prefHeight="27.0" prefWidth="101.0" text="Stop match" GridPane.columnIndex="2" />
</children>
</GridPane>
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
Expand Down
4 changes: 2 additions & 2 deletions src/dk/aau/cs/ds306e18/tournament/utility/Alerts.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static void infoNotification (String title, String text){
.title(title)
.text(text)
.graphic(null)
.hideAfter(Duration.seconds(3))
.hideAfter(Duration.seconds(5))
.position(Pos.BOTTOM_RIGHT)
.owner(window)
.showInformation();
Expand All @@ -38,7 +38,7 @@ public static void errorNotification (String title, String text){
.title(title)
.text(text)
.graphic(null)
.hideAfter(Duration.seconds(5))
.hideAfter(Duration.seconds(8))
.position(Pos.BOTTOM_RIGHT)
.owner(window)
.showError();
Expand Down

0 comments on commit 956c336

Please sign in to comment.