Add metrics and a UI of some sort

This commit is contained in:
2025-03-15 23:04:08 +01:00
parent 2228aeeafa
commit ebfce153ea
2 changed files with 88 additions and 43 deletions

View File

@@ -1,16 +1,16 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rimworld_modman/modloader.dart'; import 'package:rimworld_modman/modloader.dart';
import 'dart:io';
// Global variable to store loaded mods for access across the app // Global variable to store loaded mods for access across the app
late ModList modManager; late ModList modManager;
void main() { void main() {
// Initialize the mod manager
modManager = ModList(path: modsRoot); modManager = ModList(path: modsRoot);
modManager.load().listen((mod) {
print(mod); // Start the app
}); runApp(const RimWorldModManager());
// runApp(const RimWorldModManager());
} }
class RimWorldModManager extends StatelessWidget { class RimWorldModManager extends StatelessWidget {
@@ -95,14 +95,14 @@ class _ModListPageState extends State<ModListPage> {
bool _isLoading = false; bool _isLoading = false;
String _loadingStatus = ''; String _loadingStatus = '';
int _totalModsFound = 0; int _totalModsFound = 0;
bool _skipFileCount = true; // Skip file counting by default for faster loading
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: body: _loadedMods.isEmpty && !_isLoading
_loadedMods.isEmpty && !_isLoading ? _buildEmptyState()
? _buildEmptyState() : _buildModList(),
: _buildModList(),
); );
} }
@@ -119,6 +119,21 @@ class _ModListPageState extends State<ModListPage> {
'Ready to scan for RimWorld mods.', 'Ready to scan for RimWorld mods.',
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
), ),
const SizedBox(height: 12),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Checkbox(
value: _skipFileCount,
onChanged: (value) {
setState(() {
_skipFileCount = value ?? true;
});
},
),
const Text('Skip file counting (faster loading)'),
],
),
const SizedBox(height: 24), const SizedBox(height: 24),
ElevatedButton( ElevatedButton(
onPressed: _startLoadingMods, onPressed: _startLoadingMods,
@@ -205,14 +220,27 @@ class _ModListPageState extends State<ModListPage> {
_loadingStatus = 'Scanning for mods...'; _loadingStatus = 'Scanning for mods...';
}); });
// Use batch loading for better performance // First get the mod directories to know the total count
final directory = Directory(modsRoot);
if (directory.existsSync()) {
final List<FileSystemEntity> entities = directory.listSync();
final List<String> modDirectories =
entities.whereType<Directory>().map((dir) => dir.path).toList();
setState(() {
_totalModsFound = modDirectories.length;
_loadingStatus = 'Found $_totalModsFound mod directories. Loading...';
});
}
// Use the serial loading with our skipFileCount option
modManager modManager
.load() .load(skipFileCount: _skipFileCount)
.listen( .listen(
(mod) { (mod) {
setState(() { setState(() {
_loadedMods.add(mod); _loadedMods.add(mod);
_loadingStatus = 'Loaded ${_loadedMods.length} mods...'; _loadingStatus = 'Loaded ${_loadedMods.length}/$_totalModsFound mods...';
}); });
}, },
onError: (error) { onError: (error) {

View File

@@ -46,12 +46,16 @@ class Mod {
required this.size, required this.size,
}); });
static Mod fromDirectory(String path) { static Mod fromDirectory(String path, {bool skipFileCount = false}) {
final stopwatch = Stopwatch()..start();
final aboutFile = File('$path/About/About.xml'); final aboutFile = File('$path/About/About.xml');
if (!aboutFile.existsSync()) { if (!aboutFile.existsSync()) {
throw Exception('About.xml file does not exist in $aboutFile'); throw Exception('About.xml file does not exist in $aboutFile');
} }
final aboutXml = XmlDocument.parse(aboutFile.readAsStringSync()); final aboutXml = XmlDocument.parse(aboutFile.readAsStringSync());
final xmlTime = stopwatch.elapsedMilliseconds;
late final XmlElement metadata; late final XmlElement metadata;
try { try {
@@ -99,7 +103,7 @@ class Mod {
try { try {
description = metadata.findElements('description').first.innerText; description = metadata.findElements('description').first.innerText;
} catch (e) { } catch (e) {
print('$name has no description'); // Silent error for optional element
} }
List<String> hardDependencies = []; List<String> hardDependencies = [];
@@ -115,7 +119,7 @@ class Mod {
) )
.toList(); .toList();
} catch (e) { } catch (e) {
print('$name has no hard dependencies'); // Silent error for optional element
} }
List<String> softDependencies = []; List<String> softDependencies = [];
@@ -128,7 +132,7 @@ class Mod {
.map((e) => e.innerText.toLowerCase()) .map((e) => e.innerText.toLowerCase())
.toList(); .toList();
} catch (e) { } catch (e) {
print('$name has no soft dependencies'); // Silent error for optional element
} }
List<String> incompatabilities = []; List<String> incompatabilities = [];
@@ -141,21 +145,31 @@ class Mod {
.map((e) => e.innerText.toLowerCase()) .map((e) => e.innerText.toLowerCase())
.toList(); .toList();
} catch (e) { } catch (e) {
print('$name has no incompatabilities'); // Silent error for optional element
} }
final metadataTime = stopwatch.elapsedMilliseconds - xmlTime;
final size = int size = 0;
Directory(path) if (!skipFileCount) {
.listSync(recursive: true) size = Directory(path)
.where( .listSync(recursive: true)
(entity) => .where(
!entity.path (entity) =>
.split(Platform.pathSeparator) !entity.path
.last .split(Platform.pathSeparator)
.startsWith('.'), .last
) .startsWith('.'),
.length; )
.length;
}
final fileCountTime = stopwatch.elapsedMilliseconds - metadataTime - xmlTime;
final totalTime = stopwatch.elapsedMilliseconds;
// Uncomment for detailed timing
print('Mod $name timing: XML=${xmlTime}ms, Metadata=${metadataTime}ms, FileCount=${fileCountTime}ms, Total=${totalTime}ms');
return Mod( return Mod(
name: name, name: name,
id: id, id: id,
@@ -177,7 +191,8 @@ class ModList {
ModList({required this.path}); ModList({required this.path});
Stream<Mod> load() async* { Stream<Mod> load({bool skipFileCount = false}) async* {
final stopwatch = Stopwatch()..start();
final directory = Directory(path); final directory = Directory(path);
print('Loading configuration from: $path'); print('Loading configuration from: $path');
@@ -186,32 +201,34 @@ class ModList {
final List<String> modDirectories = final List<String> modDirectories =
entities.whereType<Directory>().map((dir) => dir.path).toList(); entities.whereType<Directory>().map((dir) => dir.path).toList();
print('Found ${modDirectories.length} mod directories:'); print('Found ${modDirectories.length} mod directories (${stopwatch.elapsedMilliseconds}ms)');
int processedCount = 0;
int totalMods = modDirectories.length;
for (final modDir in modDirectories) { for (final modDir in modDirectories) {
try { try {
// Add a small delay to prevent UI freezing and give time for rendering final modStart = stopwatch.elapsedMilliseconds;
await Future.delayed(const Duration(milliseconds: 5));
final mod = Mod.fromDirectory(modDir); final mod = Mod.fromDirectory(modDir, skipFileCount: skipFileCount);
mods[mod.id] = mod; mods[mod.id] = mod;
print( processedCount++;
'Loaded mod: ${mod.name} (ID: ${mod.id}) from directory: $modDir. '
'Size: ${mod.size}, ' final modTime = stopwatch.elapsedMilliseconds - modStart;
'Hard Dependencies: ${mod.hardDependencies.join(', ')}, ' if (processedCount % 50 == 0 || processedCount == totalMods) {
'Soft Dependencies: ${mod.softDependencies.join(', ')}, ' print('Progress: Loaded $processedCount/$totalMods mods (${stopwatch.elapsedMilliseconds}ms, avg ${stopwatch.elapsedMilliseconds/processedCount}ms per mod)');
'Incompatibilities: ${mod.incompatabilities.join(', ')}', }
);
yield mod; yield mod;
print('Current total mods loaded: ${mods.length}');
} catch (e) { } catch (e) {
print('Error loading mod from directory: $modDir'); print('Error loading mod from directory: $modDir');
print('Error: $e'); print('Error: $e');
} }
} }
final totalTime = stopwatch.elapsedMilliseconds;
print('Loading complete! Loaded ${mods.length} mods in ${totalTime}ms (${totalTime/mods.length}ms per mod)');
} else { } else {
print('Mods root directory does not exist: $modsRoot'); print('Mods root directory does not exist: $path');
} }
} }
} }