ConanServerManager/lib/statemachine.dart

258 lines
7.8 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:libac_dart/nbt/Stream.dart';
import 'package:libac_dart/packets/packets.dart';
import 'package:libac_dart/utils/IOTools.dart';
import 'package:servermanager/game.dart';
import 'package:servermanager/proton.dart';
import 'package:servermanager/structs/SessionData.dart';
import 'package:servermanager/structs/settings.dart';
enum States {
Idle, // For when the state machine is waiting for a state change
PreStart, // Backup task
Starting, // For when the server is starting up
ModUpdateCheck, // For when the mods are being checked in the jail folder against the live mods
WarnPlayersNonIntrusive, // Gives a non-instrusive warning about the upcoming restart
WarnPlayersIntrusive, // Sends a message intrusively about the upcoming restart
FullStop, // Intends to set the inactive state, and immediately stops the server
Inactive // The inactive state will be used for when the state machine is not supposed to be doing anything.
}
enum WarnType { Intrusive, NonIntrusive }
enum WarnIntervals {
FIVE_SECONDS(
seconds: 5, type: WarnType.NonIntrusive, warning: "Tiiiiiimber!"),
TEN_SECONDS(
seconds: 10,
type: WarnType.Intrusive,
warning: "The server has 10 seconds until restart"),
TWENTY_SECONDS(
seconds: 20,
type: WarnType.Intrusive,
warning: "The server is about to go down in 20 seconds"),
THIRTY_SECONDS(
seconds: 30,
type: WarnType.NonIntrusive,
warning: "The server will restart in 30 seconds"),
ONE_MINUTE(
seconds: 60,
type: WarnType.NonIntrusive,
warning: "The server will restart in 1 minute"),
FIVE_MIN(
seconds: (5 * 60),
type: WarnType.NonIntrusive,
warning: "The server will restart in 5 minutes"),
TEN_MIN(
seconds: (10 * 60),
type: WarnType.NonIntrusive,
warning: "The server will restart in 10 minutes"),
ONE_HOUR(
seconds: (1 * 60 * 60),
type: WarnType.NonIntrusive,
warning: "The server will restart in 1 hour"),
TWO_HOURS(
seconds: (2 * 60 * 60),
type: WarnType.NonIntrusive,
warning: "The server will restart in 2 hours"),
NONE(
seconds: -1,
type: WarnType.NonIntrusive,
warning:
""); // -1 is a impossible value, this makes this one a good default value
final int seconds;
final WarnType type;
final String warning;
const WarnIntervals(
{required this.seconds, required this.type, required this.warning});
}
class StateMachine {
static Process? PROC;
static Completer<void> DeadProcKillswitch = Completer();
void resetKillswitch() {
DeadProcKillswitch = Completer();
}
static void onStdOutData(List<int> stdOutData) {
StringBuilder builder = StringBuilder();
for (int i in stdOutData) {
builder.append(String.fromCharCode(i));
}
print(builder.toString());
}
static Future<void> monitorProcess() async {
PROC!.stdout.listen(onStdOutData);
try {
int code = await PROC!.exitCode;
DeadProcKillswitch.complete();
} catch (E) {
DeadProcKillswitch.complete();
}
}
var _currentState = States.Inactive;
StreamController<States> _stateController = StreamController.broadcast();
Stream<States> get stateChanges => _stateController.stream;
States get currentState => _currentState;
void changeState(States n) {
_currentState = n;
_stateController.add(n);
}
Future<void> runTask() async {
if (currentState == States.Idle) {
return; // Nothing to do here
} else if (currentState == States.FullStop) {
Settings settings = Settings();
await settings.sendRconCommand("shutdown");
changeState(States.Inactive);
} else if (currentState == States.Starting) {
// Server startup in progress
Settings settings = Settings();
await settings.RunUpdate(valid: false);
await doDownloadMods(false);
settings.inst!.mods = await doScanMods();
settings.Write();
await settings.writeOutModListFile();
var conanArgs = [
"-RconEnabled=1",
"-RconPassword=${settings.inst!.serverSettings.RconPassword}",
"-RconPort=${settings.inst!.serverSettings.RconPort}",
"-Port=${settings.inst!.serverSettings.GamePort}",
"-QueryPort=${settings.inst!.serverSettings.QueryPort}",
"-log",
"-console"
];
// Start the server now
if (Platform.isWindows) {
PROC = await Process.start(
PathHelper.combine(
settings.getServerPath(), "ConanSandboxServer.exe"),
conanArgs,
workingDirectory: settings.getServerPath());
} else {
runDetachedProton(
PathHelper.combine(
settings.getServerPath(), "ConanSandboxServer.exe"),
conanArgs,
settings.getServerPath());
}
changeState(States.Idle);
} else if (currentState == States.PreStart) {
// Perform Backup Task
// Finally, allow the server to start up
changeState(States.Starting);
}
}
StateMachine() {
stateChanges.listen((event) async {
await runTask();
});
}
Timer? task;
/// You should only start this task once the server is ready to be started.
/// This task will start the server on first-start
///
/// It will monitor the states for shutdown, it will also monitor the process handle
///
/// This is what runs the automatic restart timers as well. All state change logic that should happen automatically is contained in here.
Future<void> startScheduler() async {
Settings settings = Settings();
SessionData.timer = settings.inst!.timer.time.copy();
changeState(States.PreStart);
resetKillswitch();
// Schedule the server task
task = Timer.periodic(Duration(seconds: 1), (timer) {
switch (currentState) {
case States.Inactive:
{
timer.cancel();
//Settings settings = Settings();
if (settings.inst!.pterodactylMode) {
// Shut down the server processes now
PacketServer.socket!.close();
} else {
resetKillswitch();
SessionData.timer = settings.inst!.timer.time.copy();
changeState(States.PreStart);
}
break;
}
case States.Idle:
{
// Restart timers and such
SessionData.timer.tickDown();
SessionData.operating_time.tickUp();
// Check if we should send an alert
int sec = SessionData.timer.getTotalSeconds();
WarnIntervals current = SessionData.CURRENT_INTERVAL;
bool send = false;
for (WarnIntervals WI in WarnIntervals.values) {
if (WI.seconds <= sec && WI.seconds < current.seconds) {
current = WI;
send = true;
break;
}
}
if (send) {
// Send the alert message
SessionData.CURRENT_INTERVAL = current;
if (current.type == WarnType.Intrusive) {
settings.sendRconCommand("broadcast ${current.warning}");
}
}
// Check Shutdown Pending
if (SessionData.shutdownPending) {
// Shut down the server
changeState(States.FullStop);
SessionData.shutdownPending = false;
}
// Check Total Seconds
if (SessionData.timer.getTotalSeconds() == 0) {
SessionData.shutdownPending = true;
}
// Check Dead Process
if (DeadProcKillswitch.isCompleted) {
// Switch state
changeState(States.FullStop); // This has the stop logic
}
break;
}
default:
{
break;
}
}
});
}
}