reworked app startup
This commit is contained in:
		| @@ -4,6 +4,7 @@ using Avalonia.Markup.Xaml; | ||||
| using StarsAssistant.ViewModels; | ||||
| using StarsAssistant.Views; | ||||
| using Splat; | ||||
| using StarsAssistant.Services; | ||||
|  | ||||
| namespace StarsAssistant; | ||||
|  | ||||
| @@ -18,15 +19,21 @@ public partial class App : Application | ||||
|     { | ||||
|         if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) | ||||
|         { | ||||
|             Game game = new(desktop.Args); | ||||
|             game.RegisterServicesForGame(); | ||||
|  | ||||
|             desktop.MainWindow = new MainWindow | ||||
|             { | ||||
|                 DataContext = new MainWindowViewModel(), | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         var csvloader = Locator.Current.GetService<Services.CSVDataLoader>()!; | ||||
|         csvloader.StartPlanetCSVWatcher(); | ||||
|  | ||||
|             base.OnFrameworkInitializationCompleted(); | ||||
|  | ||||
|             game.StartBackgroundServices(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             throw new InvalidOperationException("This app supports only Desktop mode."); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -13,7 +13,7 @@ public class Planet | ||||
|     public string? Owner { get; set; } | ||||
|      | ||||
|     [Index(2)] | ||||
|     public string? StarbaseType { get; set; } | ||||
|     public string StarbaseType { get; set; } = String.Empty; | ||||
|      | ||||
|     [Index(3)] | ||||
|     public int ReportAge { get; set; } = 0; | ||||
|   | ||||
| @@ -14,7 +14,7 @@ public class Planet | ||||
|  | ||||
|     public string? OwnerId { get; set; } | ||||
|      | ||||
|     public string? StarbaseType { get; set; } | ||||
|     public string StarbaseType { get; set; } = String.Empty; | ||||
|      | ||||
|     public int ReportAge { get; set; } = 0; | ||||
|      | ||||
|   | ||||
| @@ -25,37 +25,6 @@ sealed class Program | ||||
|         var logger = new ConsoleLogger() { Level = LogLevel.Debug }; | ||||
|         Locator.CurrentMutable.RegisterConstant(logger, typeof(ILogger)); | ||||
|  | ||||
|         bool newGame = false; | ||||
|         string dbPath = "/home/torben/Nextcloud/Documents/Stars!/Games/goingth/GOINGTH.sqlite"; | ||||
|         Services.Game gameSvc; | ||||
|  | ||||
|         using (StarsDatabase starsDB = new(dbPath)) | ||||
|         { | ||||
|             if (Path.Exists(dbPath)) | ||||
|             { | ||||
|                 Model.Game dbGame = starsDB.Game.First(); | ||||
|                 gameSvc = new Services.Game(dbGame); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 starsDB.Database.EnsureCreated(); | ||||
|                 gameSvc = new() | ||||
|                 { | ||||
|                     BaseName = "GOINGTH", | ||||
|                     GamePath = "/home/torben/Nextcloud/Documents/Stars!/Games/goingth/" | ||||
|                 }; | ||||
|                 gameSvc.SaveToDatabase(starsDB); | ||||
|                 newGame = true; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         Locator.CurrentMutable.RegisterConstant(gameSvc, typeof(Services.Game)); | ||||
|         Locator.CurrentMutable.RegisterConstant(new Services.CSVDataLoader(), typeof(Services.CSVDataLoader)); | ||||
|         Locator.CurrentMutable.Register(() => new StarsDatabase(gameSvc.DatabaseFileName), typeof(StarsDatabase)); | ||||
|              | ||||
|         if (newGame) | ||||
|             __createTestData(); | ||||
|  | ||||
|         BuildAvaloniaApp() | ||||
|             .StartWithClassicDesktopLifetime(args); | ||||
|     } | ||||
| @@ -68,93 +37,4 @@ sealed class Program | ||||
|             .LogToTrace() | ||||
|             .UseReactiveUI(); | ||||
|  | ||||
|  | ||||
|     public static void __createTestData () | ||||
|     { | ||||
|         using var db = Locator.Current.GetService<StarsDatabase>()!; | ||||
|  | ||||
|         // Note: This sample requires the database to be created before running. | ||||
|         // Console.WriteLine($"Database path: {db.DbPath}."); | ||||
|  | ||||
|         Race r = new() | ||||
|         { | ||||
|             Name = "Atlantis", | ||||
|             PlayerRace = true, | ||||
|             PlayerFileId = 1, | ||||
|             ColonistsPerResource = 1000, | ||||
|             GrowthRatePercent = 19, | ||||
|             PRT = PRT.Other, | ||||
|             HasOBRM = true, | ||||
|             FactoryCost3 = false, | ||||
|             FactoryNumberPer10k = 8, | ||||
|             FactoryResCost = 8, | ||||
|             FactoryResPer10 = 15, | ||||
|             MineResCost = 3, | ||||
|             MineMineralsPer10 = 10, | ||||
|             MineNumberPer10k = 10 | ||||
|         }; | ||||
|         db.Add(r); | ||||
|         db.SaveChanges(); | ||||
|  | ||||
|         var loader = new CSV.PlanetLoader(); | ||||
|         loader.ImportForRace(Services.Game.Player); | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using CsvHelper.Configuration; | ||||
| using CsvHelper; | ||||
| using StarsAssistant.model; | ||||
|  | ||||
|  | ||||
| using var db = new StarsDatabase("stars.sqlite"); | ||||
|  | ||||
| db.Database.EnsureDeleted(); | ||||
| db.Database.EnsureCreated(); | ||||
|  | ||||
| // Note: This sample requires the database to be created before running. | ||||
| Console.WriteLine($"Database path: {db.DbPath}."); | ||||
|  | ||||
| var config = CsvConfiguration.FromAttributes<Planet>(CultureInfo.InvariantCulture); | ||||
| config.Delimiter = "\t"; | ||||
| config.Escape = '\0'; | ||||
| config.MissingFieldFound = null; | ||||
|  | ||||
| using (var reader = new StreamReader("/home/torben/goingth/GOINGTH.p1", Encoding.Latin1)) | ||||
| using (var csv = new CsvReader(reader, config)) | ||||
| { | ||||
|     List<Planet> records = csv.GetRecords<Planet>().ToList(); | ||||
|     foreach (Planet p in records) | ||||
|     { | ||||
|         db.Add(p); | ||||
|     } | ||||
|     db.SaveChanges(); | ||||
| } | ||||
| */ | ||||
|  | ||||
| /* | ||||
| // Create | ||||
| Console.WriteLine("Inserting a new blog"); | ||||
| db.Add(new Blog { Url = "http://blogs.msdn.com/adonet" }); | ||||
| db.SaveChanges(); | ||||
|  | ||||
| // Read | ||||
| Console.WriteLine("Querying for a blog"); | ||||
| var blog = db.Blogs | ||||
|     .OrderBy(b => b.BlogId) | ||||
|     .First(); | ||||
|  | ||||
| // Update | ||||
| Console.WriteLine("Updating the blog and adding a post"); | ||||
| blog.Url = "https://devblogs.microsoft.com/dotnet"; | ||||
| blog.Posts.Add( | ||||
|     new Post { Title = "Hello World", Content = "I wrote an app using EF Core!" }); | ||||
| db.SaveChanges(); | ||||
|  | ||||
| // Delete | ||||
| // Console.WriteLine("Delete the blog"); | ||||
| // db.Remove(blog); | ||||
| // db.SaveChanges(); | ||||
| */ | ||||
| @@ -16,30 +16,58 @@ public partial class CSVDataLoader : IEnableLogger | ||||
|     /// </summary> | ||||
|     protected Services.Game Game = Locator.Current.GetService<Services.Game>()!; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Regex to match fs watcher results to Stars file types. | ||||
|     /// </summary> | ||||
|     /// <returns>Precompiled RegEx</returns> | ||||
|     [GeneratedRegex(@".*\.(?<type>[pf])(?<player>\d)+$")] | ||||
|     private static partial Regex MyRegex(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// RegEx Instance to match Stars file types. | ||||
|     /// </summary> | ||||
|     protected Regex FileTypeRegEx = MyRegex(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Instance of the FSWatcher, to which we subscribe. | ||||
|     /// </summary> | ||||
|     protected IObservable<FileSystemEventArgs> Watcher; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Active subscription to FsWatcher. | ||||
|     /// </summary> | ||||
|     protected IDisposable? Subscription; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Construct the instance and prepare our FS Watcher. | ||||
|     /// </summary> | ||||
|     public CSVDataLoader()  | ||||
|     { | ||||
|          | ||||
|     } | ||||
|  | ||||
|     public void StartPlanetCSVWatcher()  | ||||
|     { | ||||
|         // string[] filters = { "*.p*", "*.f*", "*.map" }; | ||||
|  | ||||
|         var watcher = FsWatcher | ||||
|         Watcher = FsWatcher | ||||
|             .ObserveFileSystem(Game.GamePath, [ Game.PlanetFileSearchPattern ]) | ||||
|             .ThrottleAndDistinct(2, RxApp.TaskpoolScheduler) | ||||
|             .Log(this, $"{DateTime.Now.ToLongTimeString()} FsEvent", fsEvent => $"{fsEvent.FullPath} {fsEvent.ChangeType}") | ||||
|             .ObserveOn(RxApp.TaskpoolScheduler); | ||||
|          | ||||
|         watcher.Subscribe(fsEvent => this.LoadPlanetFile(fsEvent.FullPath)); | ||||
|     } | ||||
|  | ||||
|     protected void LoadPlanetFile(string fileName) | ||||
|     /// <summary> | ||||
|     /// Start the CSV watcher, and capture our disposable, so that we can shut  | ||||
|     /// down the watcher if needed. | ||||
|     /// </summary> | ||||
|     public void StartCSVWatcher()  | ||||
|     { | ||||
|         if (Subscription != null) | ||||
|             throw new InvalidOperationException("CSV Watcher is active, can't start it again."); | ||||
|  | ||||
|         Subscription = Watcher.Subscribe(fsEvent => this.LoadFile(fsEvent.FullPath)); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Subscription to File Processing, called by our subscription to the watcher. | ||||
|     /// </summary> | ||||
|     /// <param name="fileName">The File to process</param> | ||||
|     protected void LoadFile(string fileName) | ||||
|     { | ||||
|         Match m = FileTypeRegEx.Match(fileName); | ||||
|         if (! m.Success) | ||||
| @@ -50,9 +78,18 @@ public partial class CSVDataLoader : IEnableLogger | ||||
|  | ||||
|         string type = m.Groups["type"].Value; | ||||
|         string player = m.Groups["player"].Value; | ||||
|  | ||||
|         this.Log().Debug($"Got file type {type} for player {player}"); | ||||
|  | ||||
|         switch (type) | ||||
|         { | ||||
|             case "p": | ||||
|                 var loader = new CSV.PlanetLoader(); | ||||
|                 loader.ImportForRace(Services.Game.Player); | ||||
|                 break; | ||||
|  | ||||
|             default: | ||||
|                 this.Log().Warn($"Planet loader got unknown file type ${type}. Ignoring file."); | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| }    | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| using System.Reflection.PortableExecutable; | ||||
| using Splat; | ||||
| using StarsAssistant.Model; | ||||
|  | ||||
| namespace StarsAssistant.Services; | ||||
|  | ||||
| @@ -22,26 +23,26 @@ public class Game | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Save this record in the database. Uses the service locator to access the  | ||||
|     /// database unless you specify an instance. This is needed during initial | ||||
|     /// game creation, where the services are not yet established. | ||||
|     /// Helper to construct the game from a command line. | ||||
|     /// </summary> | ||||
|     /// <param name="db">Optional DB instance if services are yet unavailable.</param> | ||||
|     public void SaveToDatabase(Model.StarsDatabase? db = null)  | ||||
|     /// <param name="args">The command line to parse.</param> | ||||
|     public Game(string[]? args) | ||||
|     { | ||||
|         db ??= Locator.Current.GetService<Model.StarsDatabase>()!; | ||||
|         GamePath = "/home/torben/Nextcloud/Documents/Stars!/Games/goingth/"; | ||||
|         BaseName = "GOINGTH"; | ||||
|         string dbPath = DatabaseFileName; | ||||
|  | ||||
|         Model.Game? dbGame = db.Game.FirstOrDefault(); | ||||
|         if (dbGame == null) | ||||
|         using StarsDatabase starsDB = new(dbPath); | ||||
|         if (Path.Exists(dbPath)) | ||||
|         { | ||||
|             dbGame = new Model.Game(); | ||||
|             db.Add(dbGame); | ||||
|             db.SaveChanges(); | ||||
|             Model.Game dbGame = starsDB.Game.First(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             starsDB.Database.EnsureCreated(); | ||||
|             SaveToDatabase(starsDB); | ||||
|             __doCreateTestData = true; | ||||
|         } | ||||
|         dbGame.GamePath = GamePath; | ||||
|         dbGame.BaseName = BaseName;  | ||||
|         db.Update(dbGame); | ||||
|         db.SaveChanges(); | ||||
|     } | ||||
|      | ||||
|     /// <summary> | ||||
| @@ -68,9 +69,9 @@ public class Game | ||||
|     /// <summary> | ||||
|     /// Internal helper to lazily load the current player from the DB. | ||||
|     /// </summary> | ||||
|     private readonly static Lazy<Model.Race>LazyPlayer = new ( () => { | ||||
|         using var db = Locator.Current.GetService<Model.StarsDatabase>()!; | ||||
|         Model.Race result = db.Race | ||||
|     private readonly static Lazy<Race>LazyPlayer = new ( () => { | ||||
|         using var db = Locator.Current.GetService<StarsDatabase>()!; | ||||
|         Race result = db.Race | ||||
|             .Where(r => r.PlayerRace == true) | ||||
|             .First(); | ||||
|         return result; | ||||
| @@ -79,13 +80,99 @@ public class Game | ||||
|     /// <summary> | ||||
|     /// Get the Race object for the current player, lazy initialized. | ||||
|     /// </summary> | ||||
|     public static Model.Race Player => LazyPlayer.Value; | ||||
|     public static Race Player => LazyPlayer.Value; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Get the name of a planet file for a given race. | ||||
|     /// </summary> | ||||
|     /// <param name="r">The race to load.</param> | ||||
|     /// <returns>Fully qualified file path.</returns> | ||||
|     public string PlanetFileForRace (Model.Race r) => Path.Combine(GamePath, $"{BaseName}.p{r.PlayerFileId}"); | ||||
|     public string PlanetFileForRace (Race r) => Path.Combine(GamePath, $"{BaseName}.p{r.PlayerFileId}"); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Save this record in the database. Uses the service locator to access the  | ||||
|     /// database unless you specify an instance. This is needed during initial | ||||
|     /// game creation, where the services are not yet established. | ||||
|     /// </summary> | ||||
|     /// <param name="db">Optional DB instance if services are yet unavailable.</param> | ||||
|     public void SaveToDatabase(StarsDatabase? db = null)  | ||||
|     { | ||||
|         db ??= Locator.Current.GetService<StarsDatabase>()!; | ||||
|          | ||||
|         Model.Game? dbGame = db.Game.FirstOrDefault(); | ||||
|         if (dbGame == null) | ||||
|         { | ||||
|             dbGame = new Model.Game(); | ||||
|             db.Add(dbGame); | ||||
|             db.SaveChanges(); | ||||
|         } | ||||
|         dbGame.GamePath = GamePath; | ||||
|         dbGame.BaseName = BaseName;  | ||||
|         db.Update(dbGame); | ||||
|         db.SaveChanges(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Registers all services depending on this Game, in the order required. | ||||
|     /// This is called once the framework is initialized but before the main  | ||||
|     /// window is active and has its models. | ||||
|     /// </summary> | ||||
|     public void RegisterServicesForGame() | ||||
|     { | ||||
|         Locator.CurrentMutable.RegisterConstant(this, typeof(Services.Game)); | ||||
|         Locator.CurrentMutable.RegisterConstant(new CSVDataLoader(), typeof(CSVDataLoader)); | ||||
|         Locator.CurrentMutable.Register(() => new StarsDatabase(DatabaseFileName), typeof(StarsDatabase)); | ||||
|  | ||||
|         // TESTING HELPER | ||||
|         if (__doCreateTestData) | ||||
|             __createTestData(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Starts all required background servies, so that we are in normal UI | ||||
|     /// operation mode. This is called after the UI is up and running. | ||||
|     /// </summary> | ||||
|     public void StartBackgroundServices() | ||||
|     { | ||||
|         CSVDataLoader csvloader = Locator.Current.GetService<Services.CSVDataLoader>()!; | ||||
|         csvloader.StartCSVWatcher(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private bool __doCreateTestData = false; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// TESTING HELPER | ||||
|     /// </summary> | ||||
|     private void __createTestData () | ||||
|     { | ||||
|         using var db = Locator.Current.GetService<StarsDatabase>()!; | ||||
|  | ||||
|         // Note: This sample requires the database to be created before running. | ||||
|         // Console.WriteLine($"Database path: {db.DbPath}."); | ||||
|  | ||||
|         Race r = new() | ||||
|         { | ||||
|             Name = "Atlantis", | ||||
|             PlayerRace = true, | ||||
|             PlayerFileId = 1, | ||||
|             ColonistsPerResource = 1000, | ||||
|             GrowthRatePercent = 19, | ||||
|             PRT = PRT.Other, | ||||
|             HasOBRM = true, | ||||
|             FactoryCost3 = false, | ||||
|             FactoryNumberPer10k = 8, | ||||
|             FactoryResCost = 8, | ||||
|             FactoryResPer10 = 15, | ||||
|             MineResCost = 3, | ||||
|             MineMineralsPer10 = 10, | ||||
|             MineNumberPer10k = 10 | ||||
|         }; | ||||
|         db.Add(r); | ||||
|         db.SaveChanges(); | ||||
|  | ||||
|         var loader = new CSV.PlanetLoader(); | ||||
|         loader.ImportForRace(Services.Game.Player); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user