// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.Web.AdminPanel.Pages; using System.ComponentModel.DataAnnotations; using System.Threading; using Blazored.Toast.Services; using Microsoft.AspNetCore.Components; using MUnique.OpenMU.DataModel.Configuration; using MUnique.OpenMU.Interfaces; using MUnique.OpenMU.Persistence; using MUnique.OpenMU.Web.AdminPanel.Properties; using MUnique.OpenMU.Web.Shared.Components.Modal; /// /// Razor page that shows objects of the specified type in a grid. /// public partial class CreateGameServerConfig : ComponentBase, IAsyncDisposable { private Task? _loadTask; private CancellationTokenSource? _disposeCts; private GameServerViewModel? _viewModel; private string? _initState; /// /// Gets or sets the context provider. /// [Inject] public IPersistenceContextProvider ContextProvider { get; set; } = null!; /// /// Gets or sets the server initializer. /// [Inject] public IGameServerInstanceManager ServerInstanceManager { get; set; } = null!; /// /// Gets or sets the data source. /// [Inject] public IDataSource DataSource { get; set; } = null!; /// /// Gets or sets the modal service. /// [Inject] public IModalService ModalService { get; set; } = null!; /// /// Gets or sets the toast service. /// [Inject] public IToastService ToastService { get; set; } = null!; /// /// Gets or sets the navigation manager. /// [Inject] public NavigationManager NavigationManager { get; set; } = null!; /// public async ValueTask DisposeAsync() { await (this._disposeCts?.CancelAsync() ?? Task.CompletedTask).ConfigureAwait(false); this._disposeCts?.Dispose(); this._disposeCts = null; try { await (this._loadTask ?? Task.CompletedTask).ConfigureAwait(false); } catch (OperationCanceledException) { // We can ignore that. } catch { // And we should not throw exceptions in the dispose method. } } /// protected override async Task OnParametersSetAsync() { var cts = new CancellationTokenSource(); this._disposeCts = cts; this._loadTask = Task.Run(() => this.LoadDataAsync(cts.Token), cts.Token); await base.OnParametersSetAsync().ConfigureAwait(true); } private async Task LoadDataAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var gameConfiguration = await this.DataSource.GetOwnerAsync(default, cancellationToken).ConfigureAwait(true); using var persistenceContext = this.ContextProvider.CreateNewContext(gameConfiguration); var serverConfigs = await persistenceContext.GetAsync(cancellationToken).ConfigureAwait(false); var clients = await persistenceContext.GetAsync(cancellationToken).ConfigureAwait(false); var existingServerDefinitions = (await persistenceContext.GetAsync(cancellationToken).ConfigureAwait(false)).ToList(); var nextServerId = 0; var networkPort = 55901; if (existingServerDefinitions.Count > 0) { nextServerId = existingServerDefinitions.Max(s => s.ServerID) + 1; networkPort = existingServerDefinitions.Max(s => s.Endpoints.FirstOrDefault()?.NetworkPort ?? 55900) + 1; } this._viewModel = new GameServerViewModel { ServerConfiguration = serverConfigs.FirstOrDefault(), ServerId = (byte)nextServerId, ExperienceRate = 1.0f, PvpEnabled = true, NetworkPort = networkPort, Client = clients.FirstOrDefault(), }; await this.InvokeAsync(this.StateHasChanged).ConfigureAwait(false); } private async ValueTask CreateDefinitionByViewModelAsync(IContext context) { if (this._viewModel is null) { throw new InvalidOperationException("View model not initialized."); } var result = context.CreateNew(); result.ServerID = this._viewModel.ServerId; result.Description = this._viewModel.Description; result.PvpEnabled = this._viewModel.PvpEnabled; result.ExperienceRate = this._viewModel.ExperienceRate; result.GameConfiguration = await this.DataSource.GetOwnerAsync(); result.ServerConfiguration = this._viewModel.ServerConfiguration!; var endpoint = context.CreateNew(); endpoint.NetworkPort = (ushort)this._viewModel.NetworkPort; endpoint.Client = this._viewModel.Client!; result.Endpoints.Add(endpoint); return result; } private async Task OnSaveButtonClickAsync() { try { var gameConfiguration = await this.DataSource.GetOwnerAsync().ConfigureAwait(false); using var saveContext = this.ContextProvider.CreateNewTypedContext(typeof(DataModel.Configuration.GameServerDefinition), true, gameConfiguration); var existingServerDefinitions = (await saveContext.GetAsync().ConfigureAwait(false)).ToList(); if (existingServerDefinitions.Any(def => def.ServerID == this._viewModel?.ServerId)) { this.ToastService.ShowError(string.Format(Resources.ServerWithIdAlreadyExists, this._viewModel?.ServerId)); return; } if (existingServerDefinitions.Any(def => def.Endpoints.Any(endpoint => endpoint.NetworkPort == this._viewModel?.NetworkPort))) { this.ToastService.ShowError(string.Format(Resources.ServerWithPortAlreadyExists, this._viewModel?.NetworkPort)); return; } this._initState = Resources.CreatingConfigurationInfo; await this.InvokeAsync(this.StateHasChanged); var gameServerDefinition = await this.CreateDefinitionByViewModelAsync(saveContext).ConfigureAwait(false); this._initState = Resources.SavingConfigurationInfo; await this.InvokeAsync(this.StateHasChanged); var success = await saveContext.SaveChangesAsync().ConfigureAwait(true); // if success, init new game server instance if (success) { this.ToastService.ShowSuccess(Resources.GameServerConfigurationSavedInfo); this._initState = Resources.InitializingGameServerInfo; await this.InvokeAsync(this.StateHasChanged); await this.ServerInstanceManager.InitializeGameServerAsync(gameServerDefinition.ServerID); this.NavigationManager.NavigateTo("servers"); return; } this.ToastService.ShowError(Resources.NoChangesSaved); } catch (Exception ex) { this.ToastService.ShowError(string.Format(Resources.UnexpectedErrorOccurred, ex.Message)); } this._initState = null; } /// /// The view model for a . /// public class GameServerViewModel { /// /// Gets or sets the server identifier. /// public byte ServerId { get; set; } /// /// Gets or sets the description. /// public string Description { get; set; } = string.Empty; /// /// Gets or sets the experience rate. /// /// /// The experience rate. /// [Range(0, float.MaxValue)] public float ExperienceRate { get; set; } /// /// Gets or sets a value indicating whether PVP is enabled on this server. /// public bool PvpEnabled { get; set; } /// /// Gets or sets the server configuration. /// [Required] public GameServerConfiguration? ServerConfiguration { get; set; } /// /// Gets or sets the client which is expected to connect. /// [Required] public GameClientDefinition? Client { get; set; } /// /// Gets or sets the network port on which the server is listening. /// [Range(1, ushort.MaxValue)] public int NetworkPort { get; set; } } }