Add state machine
This commit is contained in:
parent
1bd6da4f1b
commit
fee32c7701
7 changed files with 312 additions and 23 deletions
|
@ -5,7 +5,9 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:servermanager/autorestart.dart';
|
import 'package:servermanager/autorestart.dart';
|
||||||
import 'package:servermanager/mod.dart';
|
import 'package:servermanager/mod.dart';
|
||||||
import 'package:servermanager/pathtools.dart';
|
import 'package:servermanager/pathtools.dart';
|
||||||
|
import 'package:servermanager/serversettings.dart';
|
||||||
import 'package:servermanager/settings.dart';
|
import 'package:servermanager/settings.dart';
|
||||||
|
import 'package:servermanager/statemachine.dart';
|
||||||
|
|
||||||
Future<void> doDownloadMods(String modsFolder) async {
|
Future<void> doDownloadMods(String modsFolder) async {
|
||||||
Settings settings = Settings();
|
Settings settings = Settings();
|
||||||
|
@ -100,7 +102,6 @@ class GameServerPageState extends State<GameServerPage> {
|
||||||
Settings settings;
|
Settings settings;
|
||||||
GameServerPageState({required this.settings});
|
GameServerPageState({required this.settings});
|
||||||
var downloading = false;
|
var downloading = false;
|
||||||
var running = false;
|
|
||||||
late Stream<List<int>> download_stream;
|
late Stream<List<int>> download_stream;
|
||||||
TextEditingController ValueControl = TextEditingController();
|
TextEditingController ValueControl = TextEditingController();
|
||||||
|
|
||||||
|
@ -223,34 +224,35 @@ class GameServerPageState extends State<GameServerPage> {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text("Configure Server Ports"),
|
||||||
|
leading: Icon(Icons.numbers),
|
||||||
|
onTap: () async {
|
||||||
|
var reply = await Navigator.pushNamed(
|
||||||
|
context, "/server/ports",
|
||||||
|
arguments: settings.inst!.serverSettings);
|
||||||
|
setState(() {
|
||||||
|
settings.inst!.serverSettings = reply as ServerSettings;
|
||||||
|
settings.Write();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text("Status:"),
|
title: Text("Status:"),
|
||||||
subtitle: Text(running ? "Active" : "Not Running"),
|
subtitle: Text(settings.subsys.currentState != States.Inactive
|
||||||
leading: Icon(running ? Icons.play_arrow : Icons.error),
|
? "Active"
|
||||||
|
: "Not Running"),
|
||||||
|
leading: Icon(settings.subsys.currentState != States.Inactive
|
||||||
|
? Icons.play_arrow
|
||||||
|
: Icons.error),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
// Toggle state
|
// Toggle state
|
||||||
setState(() {
|
setState(() {
|
||||||
running = !running;
|
if (settings.subsys.currentState == States.Inactive) {
|
||||||
|
settings.subsys.changeState(States.Starting);
|
||||||
|
} else
|
||||||
|
settings.subsys.changeState(States.FullStop);
|
||||||
});
|
});
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
|
||||||
content: Text(running
|
|
||||||
? "Server is now starting..."
|
|
||||||
: "Stopping the server")));
|
|
||||||
|
|
||||||
if (!running) {
|
|
||||||
// Send message indicating the server is stopping, then wait for 30 seconds and then stop the server
|
|
||||||
} else {
|
|
||||||
await settings.RunUpdate();
|
|
||||||
await doDownloadMods(settings.getModPath());
|
|
||||||
setState(() async {
|
|
||||||
settings.inst!.mods =
|
|
||||||
await doScanMods(settings.getModPath());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Generate the actual mod list now
|
|
||||||
await settings.writeOutModListFile();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:servermanager/game.dart';
|
||||||
import 'package:servermanager/home.dart';
|
import 'package:servermanager/home.dart';
|
||||||
import 'package:servermanager/mod.dart';
|
import 'package:servermanager/mod.dart';
|
||||||
import 'package:servermanager/proton.dart';
|
import 'package:servermanager/proton.dart';
|
||||||
|
import 'package:servermanager/serversettings.dart';
|
||||||
import 'package:servermanager/settings.dart';
|
import 'package:servermanager/settings.dart';
|
||||||
import 'package:servermanager/settingsEntry.dart';
|
import 'package:servermanager/settingsEntry.dart';
|
||||||
import 'package:servermanager/steamcmd.dart';
|
import 'package:servermanager/steamcmd.dart';
|
||||||
|
@ -15,6 +16,8 @@ Future<void> main() async {
|
||||||
Hive.registerAdapter(CredentialsAdapter());
|
Hive.registerAdapter(CredentialsAdapter());
|
||||||
Hive.registerAdapter(ModAdapter());
|
Hive.registerAdapter(ModAdapter());
|
||||||
Hive.registerAdapter(SettingsEntryAdapter());
|
Hive.registerAdapter(SettingsEntryAdapter());
|
||||||
|
Hive.registerAdapter(AutomaticRestartInfoAdapter());
|
||||||
|
Hive.registerAdapter(ServerSettingsAdapter());
|
||||||
runApp(MyApp());
|
runApp(MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +41,7 @@ class MyApp extends StatelessWidget {
|
||||||
),
|
),
|
||||||
"/server": (context) => GameServerPage(settings: appSettings),
|
"/server": (context) => GameServerPage(settings: appSettings),
|
||||||
"/server/autorestart": (context) => AutoRestartPage(),
|
"/server/autorestart": (context) => AutoRestartPage(),
|
||||||
|
"/server/ports": (context) => ServerSettingsPage(),
|
||||||
"/server/mods": (context) => ModManager(settings: appSettings),
|
"/server/mods": (context) => ModManager(settings: appSettings),
|
||||||
"/server/mods/edit": (context) => ModPage(),
|
"/server/mods/edit": (context) => ModPage(),
|
||||||
"/steamcmd/creds": (context) => CredentialsPrompt()
|
"/steamcmd/creds": (context) => CredentialsPrompt()
|
||||||
|
|
|
@ -42,6 +42,33 @@ Future<void> runProton(String command, List<String> argx) async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> runDetachedProton(
|
||||||
|
String command, List<String> argx, String workingDir) async {
|
||||||
|
Settings settings = Settings();
|
||||||
|
Directory dir =
|
||||||
|
Directory("${settings.game_path}${Platform.pathSeparator}pfx");
|
||||||
|
|
||||||
|
if (dir.existsSync()) {
|
||||||
|
await dir.delete(recursive: true);
|
||||||
|
}
|
||||||
|
await dir.create(recursive: true);
|
||||||
|
|
||||||
|
Map<String, String> env = Map.from(Platform.environment);
|
||||||
|
env["STEAM_COMPAT_CLIENT_INSTALL_PATH"] = "~/.steam";
|
||||||
|
env["STEAM_COMPAT_DATA_PATH"] = dir.path;
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<String> args = ["run", command];
|
||||||
|
args.addAll(argx);
|
||||||
|
|
||||||
|
Process.start("proton", args, // Run arbitrary command with arguments
|
||||||
|
environment: env,
|
||||||
|
workingDirectory: workingDir);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error executing command: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ProtonState extends State<Proton> {
|
class ProtonState extends State<Proton> {
|
||||||
Settings settings;
|
Settings settings;
|
||||||
ProtonState({required this.settings});
|
ProtonState({required this.settings});
|
||||||
|
|
165
lib/serversettings.dart
Normal file
165
lib/serversettings.dart
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
import 'dart:ffi';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
|
part 'serversettings.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: 5)
|
||||||
|
class ServerSettings {
|
||||||
|
@HiveField(0, defaultValue: "Password01234")
|
||||||
|
final String RconPassword;
|
||||||
|
|
||||||
|
@HiveField(1, defaultValue: 7779)
|
||||||
|
final int RconPort;
|
||||||
|
|
||||||
|
@HiveField(2, defaultValue: 7780)
|
||||||
|
final int GamePort;
|
||||||
|
|
||||||
|
@HiveField(3, defaultValue: 7782)
|
||||||
|
final int QueryPort;
|
||||||
|
|
||||||
|
const ServerSettings(
|
||||||
|
{required this.RconPassword,
|
||||||
|
required this.RconPort,
|
||||||
|
required this.GamePort,
|
||||||
|
required this.QueryPort});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerSettingsPage extends StatefulWidget {
|
||||||
|
ServerSettingsPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ServerSettingsState createState() => ServerSettingsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerSettingsState extends State<ServerSettingsPage> {
|
||||||
|
bool firstRun = true;
|
||||||
|
String pass = "";
|
||||||
|
TextEditingController passwordController = TextEditingController();
|
||||||
|
int rconPort = 0;
|
||||||
|
int gPort = 0;
|
||||||
|
int qPort = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (firstRun) {
|
||||||
|
var args = ModalRoute.of(context)!.settings.arguments as ServerSettings;
|
||||||
|
|
||||||
|
passwordController.text = args.RconPassword;
|
||||||
|
rconPort = args.RconPort;
|
||||||
|
gPort = args.GamePort;
|
||||||
|
qPort = args.QueryPort;
|
||||||
|
|
||||||
|
firstRun = false;
|
||||||
|
}
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text("Server Settings"),
|
||||||
|
backgroundColor: Color.fromARGB(255, 100, 0, 0),
|
||||||
|
),
|
||||||
|
body: WillPopScope(
|
||||||
|
onWillPop: () async {
|
||||||
|
Navigator.pop(
|
||||||
|
context,
|
||||||
|
ServerSettings(
|
||||||
|
RconPassword: passwordController.text,
|
||||||
|
RconPort: rconPort,
|
||||||
|
GamePort: gPort,
|
||||||
|
QueryPort: qPort));
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 256,
|
||||||
|
child: ListTile(title: Text("Rcon Password")),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: passwordController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8))),
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 256,
|
||||||
|
child: ListTile(
|
||||||
|
title: Text("Rcon Port"),
|
||||||
|
subtitle: Text("${rconPort}"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Slider(
|
||||||
|
value: rconPort.toDouble(),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
rconPort = value.toInt();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
min: 5000,
|
||||||
|
max: 8000,
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 256,
|
||||||
|
child: ListTile(
|
||||||
|
title: Text("Game Port"),
|
||||||
|
subtitle: Text("${gPort}"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Slider(
|
||||||
|
value: gPort.toDouble(),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
gPort = value.toInt();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
min: 5000,
|
||||||
|
max: 8000,
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 256,
|
||||||
|
child: ListTile(
|
||||||
|
title: Text("Query Port"),
|
||||||
|
subtitle: Text("${qPort}"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Slider(
|
||||||
|
value: qPort.toDouble(),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
qPort = value.toInt();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
min: 5000,
|
||||||
|
max: 8000,
|
||||||
|
))
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import 'package:hive/hive.dart';
|
||||||
import 'package:servermanager/mod.dart';
|
import 'package:servermanager/mod.dart';
|
||||||
import 'package:servermanager/pathtools.dart';
|
import 'package:servermanager/pathtools.dart';
|
||||||
import 'package:servermanager/settingsEntry.dart';
|
import 'package:servermanager/settingsEntry.dart';
|
||||||
|
import 'package:servermanager/statemachine.dart';
|
||||||
|
|
||||||
class Settings {
|
class Settings {
|
||||||
Settings._();
|
Settings._();
|
||||||
|
@ -12,6 +13,8 @@ class Settings {
|
||||||
String steamcmd_path = "";
|
String steamcmd_path = "";
|
||||||
String game_path = "";
|
String game_path = "";
|
||||||
|
|
||||||
|
StateMachine subsys = StateMachine();
|
||||||
|
|
||||||
factory Settings() {
|
factory Settings() {
|
||||||
return Instance;
|
return Instance;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:hive/hive.dart';
|
||||||
import 'package:servermanager/autorestart.dart';
|
import 'package:servermanager/autorestart.dart';
|
||||||
import 'package:servermanager/credentials.dart';
|
import 'package:servermanager/credentials.dart';
|
||||||
import 'package:servermanager/mod.dart';
|
import 'package:servermanager/mod.dart';
|
||||||
|
import 'package:servermanager/serversettings.dart';
|
||||||
|
|
||||||
part 'settingsEntry.g.dart';
|
part 'settingsEntry.g.dart';
|
||||||
|
|
||||||
|
@ -15,4 +16,16 @@ class SettingsEntry {
|
||||||
|
|
||||||
@HiveField(4, defaultValue: AutomaticRestartInfo())
|
@HiveField(4, defaultValue: AutomaticRestartInfo())
|
||||||
AutomaticRestartInfo timer = AutomaticRestartInfo();
|
AutomaticRestartInfo timer = AutomaticRestartInfo();
|
||||||
|
|
||||||
|
@HiveField(5,
|
||||||
|
defaultValue: ServerSettings(
|
||||||
|
RconPassword: "Password01234",
|
||||||
|
RconPort: 7779,
|
||||||
|
GamePort: 7780,
|
||||||
|
QueryPort: 7782))
|
||||||
|
ServerSettings serverSettings = ServerSettings(
|
||||||
|
RconPassword: "Password01234",
|
||||||
|
RconPort: 7779,
|
||||||
|
GamePort: 7780,
|
||||||
|
QueryPort: 7782);
|
||||||
}
|
}
|
||||||
|
|
75
lib/statemachine.dart
Normal file
75
lib/statemachine.dart
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:servermanager/game.dart';
|
||||||
|
import 'package:servermanager/pathtools.dart';
|
||||||
|
import 'package:servermanager/proton.dart';
|
||||||
|
import 'package:servermanager/settings.dart';
|
||||||
|
|
||||||
|
enum States {
|
||||||
|
Idle, // For when the state machine is waiting for a state change
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
|
||||||
|
class StateMachine {
|
||||||
|
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.Starting) {
|
||||||
|
// Server startup in progress
|
||||||
|
Settings settings = Settings();
|
||||||
|
await settings.RunUpdate();
|
||||||
|
await doDownloadMods(settings.getModPath());
|
||||||
|
settings.inst!.mods = await doScanMods(settings.getModPath());
|
||||||
|
|
||||||
|
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}"
|
||||||
|
];
|
||||||
|
// Start the server now
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StateMachine() {
|
||||||
|
stateChanges.listen((event) async {
|
||||||
|
await runTask();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue