diff --git a/lib/main.dart b/lib/main.dart index 026d0e8..cbe1238 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; import 'package:rimworld_modman/modloader.dart'; +import 'dart:io'; // Global variable to store loaded mods for access across the app late ModList modManager; void main() { + // Initialize the mod manager modManager = ModList(path: modsRoot); - modManager.load().listen((mod) { - print(mod); - }); - - // runApp(const RimWorldModManager()); + + // Start the app + runApp(const RimWorldModManager()); } class RimWorldModManager extends StatelessWidget { @@ -95,14 +95,14 @@ class _ModListPageState extends State { bool _isLoading = false; String _loadingStatus = ''; int _totalModsFound = 0; + bool _skipFileCount = true; // Skip file counting by default for faster loading @override Widget build(BuildContext context) { return Scaffold( - body: - _loadedMods.isEmpty && !_isLoading - ? _buildEmptyState() - : _buildModList(), + body: _loadedMods.isEmpty && !_isLoading + ? _buildEmptyState() + : _buildModList(), ); } @@ -119,6 +119,21 @@ class _ModListPageState extends State { 'Ready to scan for RimWorld mods.', 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), ElevatedButton( onPressed: _startLoadingMods, @@ -205,14 +220,27 @@ class _ModListPageState extends State { _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 entities = directory.listSync(); + final List modDirectories = + entities.whereType().map((dir) => dir.path).toList(); + + setState(() { + _totalModsFound = modDirectories.length; + _loadingStatus = 'Found $_totalModsFound mod directories. Loading...'; + }); + } + + // Use the serial loading with our skipFileCount option modManager - .load() + .load(skipFileCount: _skipFileCount) .listen( (mod) { setState(() { _loadedMods.add(mod); - _loadingStatus = 'Loaded ${_loadedMods.length} mods...'; + _loadingStatus = 'Loaded ${_loadedMods.length}/$_totalModsFound mods...'; }); }, onError: (error) { diff --git a/lib/modloader.dart b/lib/modloader.dart index ff55429..d89cbff 100644 --- a/lib/modloader.dart +++ b/lib/modloader.dart @@ -46,12 +46,16 @@ class Mod { 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'); if (!aboutFile.existsSync()) { throw Exception('About.xml file does not exist in $aboutFile'); } + final aboutXml = XmlDocument.parse(aboutFile.readAsStringSync()); + final xmlTime = stopwatch.elapsedMilliseconds; late final XmlElement metadata; try { @@ -99,7 +103,7 @@ class Mod { try { description = metadata.findElements('description').first.innerText; } catch (e) { - print('$name has no description'); + // Silent error for optional element } List hardDependencies = []; @@ -115,7 +119,7 @@ class Mod { ) .toList(); } catch (e) { - print('$name has no hard dependencies'); + // Silent error for optional element } List softDependencies = []; @@ -128,7 +132,7 @@ class Mod { .map((e) => e.innerText.toLowerCase()) .toList(); } catch (e) { - print('$name has no soft dependencies'); + // Silent error for optional element } List incompatabilities = []; @@ -141,21 +145,31 @@ class Mod { .map((e) => e.innerText.toLowerCase()) .toList(); } catch (e) { - print('$name has no incompatabilities'); + // Silent error for optional element } + + final metadataTime = stopwatch.elapsedMilliseconds - xmlTime; - final size = - Directory(path) - .listSync(recursive: true) - .where( - (entity) => - !entity.path - .split(Platform.pathSeparator) - .last - .startsWith('.'), - ) - .length; - + int size = 0; + if (!skipFileCount) { + size = Directory(path) + .listSync(recursive: true) + .where( + (entity) => + !entity.path + .split(Platform.pathSeparator) + .last + .startsWith('.'), + ) + .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( name: name, id: id, @@ -177,7 +191,8 @@ class ModList { ModList({required this.path}); - Stream load() async* { + Stream load({bool skipFileCount = false}) async* { + final stopwatch = Stopwatch()..start(); final directory = Directory(path); print('Loading configuration from: $path'); @@ -186,32 +201,34 @@ class ModList { final List modDirectories = entities.whereType().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) { try { - // Add a small delay to prevent UI freezing and give time for rendering - await Future.delayed(const Duration(milliseconds: 5)); + final modStart = stopwatch.elapsedMilliseconds; - final mod = Mod.fromDirectory(modDir); + final mod = Mod.fromDirectory(modDir, skipFileCount: skipFileCount); mods[mod.id] = mod; - print( - 'Loaded mod: ${mod.name} (ID: ${mod.id}) from directory: $modDir. ' - 'Size: ${mod.size}, ' - 'Hard Dependencies: ${mod.hardDependencies.join(', ')}, ' - 'Soft Dependencies: ${mod.softDependencies.join(', ')}, ' - 'Incompatibilities: ${mod.incompatabilities.join(', ')}', - ); + processedCount++; + + final modTime = stopwatch.elapsedMilliseconds - modStart; + if (processedCount % 50 == 0 || processedCount == totalMods) { + print('Progress: Loaded $processedCount/$totalMods mods (${stopwatch.elapsedMilliseconds}ms, avg ${stopwatch.elapsedMilliseconds/processedCount}ms per mod)'); + } yield mod; - print('Current total mods loaded: ${mods.length}'); } catch (e) { print('Error loading mod from directory: $modDir'); print('Error: $e'); } } + + final totalTime = stopwatch.elapsedMilliseconds; + print('Loading complete! Loaded ${mods.length} mods in ${totalTime}ms (${totalTime/mods.length}ms per mod)'); } else { - print('Mods root directory does not exist: $modsRoot'); + print('Mods root directory does not exist: $path'); } } }