reworked app startup

This commit is contained in:
Torben Nehmer 2024-09-22 18:54:16 +02:00
parent e01a50ec9c
commit 269846f457
6 changed files with 176 additions and 165 deletions

View File

@ -4,6 +4,7 @@ using Avalonia.Markup.Xaml;
using StarsAssistant.ViewModels; using StarsAssistant.ViewModels;
using StarsAssistant.Views; using StarsAssistant.Views;
using Splat; using Splat;
using StarsAssistant.Services;
namespace StarsAssistant; namespace StarsAssistant;
@ -18,15 +19,21 @@ public partial class App : Application
{ {
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{ {
Game game = new(desktop.Args);
game.RegisterServicesForGame();
desktop.MainWindow = new MainWindow desktop.MainWindow = new MainWindow
{ {
DataContext = new MainWindowViewModel(), DataContext = new MainWindowViewModel(),
}; };
}
var csvloader = Locator.Current.GetService<Services.CSVDataLoader>()!;
csvloader.StartPlanetCSVWatcher();
base.OnFrameworkInitializationCompleted(); base.OnFrameworkInitializationCompleted();
game.StartBackgroundServices();
}
else
{
throw new InvalidOperationException("This app supports only Desktop mode.");
}
} }
} }

View File

@ -13,7 +13,7 @@ public class Planet
public string? Owner { get; set; } public string? Owner { get; set; }
[Index(2)] [Index(2)]
public string? StarbaseType { get; set; } public string StarbaseType { get; set; } = String.Empty;
[Index(3)] [Index(3)]
public int ReportAge { get; set; } = 0; public int ReportAge { get; set; } = 0;

View File

@ -14,7 +14,7 @@ public class Planet
public string? OwnerId { get; set; } public string? OwnerId { get; set; }
public string? StarbaseType { get; set; } public string StarbaseType { get; set; } = String.Empty;
public int ReportAge { get; set; } = 0; public int ReportAge { get; set; } = 0;

View File

@ -25,37 +25,6 @@ sealed class Program
var logger = new ConsoleLogger() { Level = LogLevel.Debug }; var logger = new ConsoleLogger() { Level = LogLevel.Debug };
Locator.CurrentMutable.RegisterConstant(logger, typeof(ILogger)); 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() BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args); .StartWithClassicDesktopLifetime(args);
} }
@ -68,93 +37,4 @@ sealed class Program
.LogToTrace() .LogToTrace()
.UseReactiveUI(); .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();
*/

View File

@ -16,30 +16,58 @@ public partial class CSVDataLoader : IEnableLogger
/// </summary> /// </summary>
protected Services.Game Game = Locator.Current.GetService<Services.Game>()!; 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)+$")] [GeneratedRegex(@".*\.(?<type>[pf])(?<player>\d)+$")]
private static partial Regex MyRegex(); private static partial Regex MyRegex();
/// <summary>
/// RegEx Instance to match Stars file types.
/// </summary>
protected Regex FileTypeRegEx = MyRegex(); 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 CSVDataLoader()
{
}
public void StartPlanetCSVWatcher()
{ {
// string[] filters = { "*.p*", "*.f*", "*.map" }; // string[] filters = { "*.p*", "*.f*", "*.map" };
Watcher = FsWatcher
var watcher = FsWatcher
.ObserveFileSystem(Game.GamePath, [ Game.PlanetFileSearchPattern ]) .ObserveFileSystem(Game.GamePath, [ Game.PlanetFileSearchPattern ])
.ThrottleAndDistinct(2, RxApp.TaskpoolScheduler) .ThrottleAndDistinct(2, RxApp.TaskpoolScheduler)
.Log(this, $"{DateTime.Now.ToLongTimeString()} FsEvent", fsEvent => $"{fsEvent.FullPath} {fsEvent.ChangeType}") .Log(this, $"{DateTime.Now.ToLongTimeString()} FsEvent", fsEvent => $"{fsEvent.FullPath} {fsEvent.ChangeType}")
.ObserveOn(RxApp.TaskpoolScheduler); .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); Match m = FileTypeRegEx.Match(fileName);
if (! m.Success) if (! m.Success)
@ -50,9 +78,18 @@ public partial class CSVDataLoader : IEnableLogger
string type = m.Groups["type"].Value; string type = m.Groups["type"].Value;
string player = m.Groups["player"].Value; string player = m.Groups["player"].Value;
this.Log().Debug($"Got file type {type} for player {player}"); this.Log().Debug($"Got file type {type} for player {player}");
switch (type)
{
case "p":
var loader = new CSV.PlanetLoader(); var loader = new CSV.PlanetLoader();
loader.ImportForRace(Services.Game.Player); loader.ImportForRace(Services.Game.Player);
break;
default:
this.Log().Warn($"Planet loader got unknown file type ${type}. Ignoring file.");
break;
}
} }
} }

View File

@ -1,5 +1,6 @@
using System.Reflection.PortableExecutable; using System.Reflection.PortableExecutable;
using Splat; using Splat;
using StarsAssistant.Model;
namespace StarsAssistant.Services; namespace StarsAssistant.Services;
@ -22,26 +23,26 @@ public class Game
} }
/// <summary> /// <summary>
/// Save this record in the database. Uses the service locator to access the /// Helper to construct the game from a command line.
/// database unless you specify an instance. This is needed during initial
/// game creation, where the services are not yet established.
/// </summary> /// </summary>
/// <param name="db">Optional DB instance if services are yet unavailable.</param> /// <param name="args">The command line to parse.</param>
public void SaveToDatabase(Model.StarsDatabase? db = null) 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(); using StarsDatabase starsDB = new(dbPath);
if (dbGame == null) if (Path.Exists(dbPath))
{ {
dbGame = new Model.Game(); Model.Game dbGame = starsDB.Game.First();
db.Add(dbGame); }
db.SaveChanges(); else
{
starsDB.Database.EnsureCreated();
SaveToDatabase(starsDB);
__doCreateTestData = true;
} }
dbGame.GamePath = GamePath;
dbGame.BaseName = BaseName;
db.Update(dbGame);
db.SaveChanges();
} }
/// <summary> /// <summary>
@ -68,9 +69,9 @@ public class Game
/// <summary> /// <summary>
/// Internal helper to lazily load the current player from the DB. /// Internal helper to lazily load the current player from the DB.
/// </summary> /// </summary>
private readonly static Lazy<Model.Race>LazyPlayer = new ( () => { private readonly static Lazy<Race>LazyPlayer = new ( () => {
using var db = Locator.Current.GetService<Model.StarsDatabase>()!; using var db = Locator.Current.GetService<StarsDatabase>()!;
Model.Race result = db.Race Race result = db.Race
.Where(r => r.PlayerRace == true) .Where(r => r.PlayerRace == true)
.First(); .First();
return result; return result;
@ -79,13 +80,99 @@ public class Game
/// <summary> /// <summary>
/// Get the Race object for the current player, lazy initialized. /// Get the Race object for the current player, lazy initialized.
/// </summary> /// </summary>
public static Model.Race Player => LazyPlayer.Value; public static Race Player => LazyPlayer.Value;
/// <summary> /// <summary>
/// Get the name of a planet file for a given race. /// Get the name of a planet file for a given race.
/// </summary> /// </summary>
/// <param name="r">The race to load.</param> /// <param name="r">The race to load.</param>
/// <returns>Fully qualified file path.</returns> /// <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);
}
} }