121 lines
4.9 KiB
C#
121 lines
4.9 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Options;
|
|
using StackExchange.Redis;
|
|
using Vanguard.Content;
|
|
using Vanguard.Rules.Cards;
|
|
using Vanguard.Server.Api;
|
|
using Vanguard.Server.Configuration;
|
|
using Vanguard.Server.Matches;
|
|
using Vanguard.Server.Matchmaking;
|
|
using Vanguard.Server.Profiles;
|
|
using Vanguard.Server.Realtime;
|
|
using Vanguard.Server.Storage;
|
|
using Vanguard.Server.Tickets;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
builder.Services.Configure<GameOptions>(builder.Configuration.GetSection("Game"));
|
|
builder.Services.Configure<ContentOptions>(builder.Configuration.GetSection("Content"));
|
|
builder.Services.Configure<StorageOptions>(builder.Configuration.GetSection("Storage"));
|
|
builder.Services.Configure<RedisOptions>(builder.Configuration.GetSection("Redis"));
|
|
|
|
builder.Services.AddSingleton(sp => sp.GetRequiredService<IOptions<GameOptions>>().Value);
|
|
builder.Services.AddSingleton(sp => sp.GetRequiredService<IOptions<ContentOptions>>().Value);
|
|
builder.Services.AddSingleton(sp => sp.GetRequiredService<IOptions<StorageOptions>>().Value);
|
|
builder.Services.AddSingleton(sp => sp.GetRequiredService<IOptions<RedisOptions>>().Value);
|
|
builder.Services.AddSingleton(TimeProvider.System);
|
|
|
|
builder.Services.AddSingleton(sp =>
|
|
{
|
|
var content = sp.GetRequiredService<ContentOptions>();
|
|
var directory = ContentPaths.Resolve(content.Path);
|
|
return CatalogLoader.LoadDirectory(directory);
|
|
});
|
|
|
|
var storage = builder.Configuration.GetSection("Storage").Get<StorageOptions>() ?? new StorageOptions();
|
|
var storageProvider = storage.Provider?.Trim() ?? "None";
|
|
|
|
if (storageProvider.Equals("Postgres", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
if (string.IsNullOrWhiteSpace(storage.ConnectionString))
|
|
throw new InvalidOperationException("Storage:ConnectionString обязателен для Provider=Postgres.");
|
|
|
|
builder.Services.AddDbContextFactory<MatchDbContext>(options =>
|
|
options.UseNpgsql(storage.ConnectionString));
|
|
builder.Services.AddSingleton<IMatchStore, EfMatchStore>();
|
|
}
|
|
else if (storageProvider.Equals("Memory", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
builder.Services.AddDbContextFactory<MatchDbContext>(options =>
|
|
options.UseInMemoryDatabase("vanguard-matches"));
|
|
builder.Services.AddSingleton<IMatchStore, EfMatchStore>();
|
|
}
|
|
else if (storageProvider.Equals("None", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
builder.Services.AddSingleton<IMatchStore, NullMatchStore>();
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Неизвестный Storage:Provider «{storageProvider}». Ожидается None, Memory или Postgres.");
|
|
}
|
|
|
|
var redis = builder.Configuration.GetSection("Redis").Get<RedisOptions>() ?? new RedisOptions();
|
|
|
|
if (!string.IsNullOrWhiteSpace(redis.Configuration))
|
|
{
|
|
builder.Services.AddSingleton<IConnectionMultiplexer>(_ =>
|
|
{
|
|
var configuration = ConfigurationOptions.Parse(redis.Configuration);
|
|
configuration.AbortOnConnectFail = false;
|
|
return ConnectionMultiplexer.Connect(configuration);
|
|
});
|
|
builder.Services.AddSingleton<ITicketStore, RedisTicketStore>();
|
|
builder.Services.AddSingleton<IQueueStore, RedisQueueStore>();
|
|
}
|
|
else
|
|
{
|
|
builder.Services.AddSingleton<ITicketStore, InMemoryTicketStore>();
|
|
builder.Services.AddSingleton<IQueueStore, InMemoryQueueStore>();
|
|
}
|
|
|
|
builder.Services.AddSingleton<GameTicketService>();
|
|
builder.Services.AddSingleton<PlayerProfileStore>();
|
|
builder.Services.AddSingleton<IMatchResultSink>(sp => sp.GetRequiredService<PlayerProfileStore>());
|
|
builder.Services.AddSingleton<MatchGrainRegistry>();
|
|
builder.Services.AddSingleton<MatchRecoveryService>();
|
|
builder.Services.AddSingleton<MatchmakingService>();
|
|
builder.Services.AddHostedService<MatchTicker>();
|
|
builder.Services.AddHostedService<MatchmakingWorker>();
|
|
|
|
var app = builder.Build();
|
|
|
|
// каталог грузим сразу: сервер без карт бесполезен, лучше упасть на старте
|
|
app.Services.GetRequiredService<CardCatalog>();
|
|
|
|
if (app.Services.GetRequiredService<IMatchStore>() is EfMatchStore)
|
|
{
|
|
var factory = app.Services.GetRequiredService<IDbContextFactory<MatchDbContext>>();
|
|
await using var db = await factory.CreateDbContextAsync();
|
|
await db.Database.EnsureCreatedAsync();
|
|
}
|
|
|
|
// незавершённые матчи поднимаем до того, как сервер начнёт принимать подключения
|
|
await app.Services.GetRequiredService<MatchRecoveryService>().RecoverAsync();
|
|
await app.Services.GetRequiredService<MatchmakingService>().LoadQueuedAsync();
|
|
|
|
app.UseWebSockets(new WebSocketOptions
|
|
{
|
|
KeepAliveInterval = TimeSpan.FromSeconds(30),
|
|
});
|
|
|
|
app.MapGameApi();
|
|
app.Map("/game", GameWebSocketEndpoint.HandleAsync);
|
|
|
|
app.Run();
|
|
|
|
/// <summary>Точка входа доступна интеграционным тестам через WebApplicationFactory.</summary>
|
|
public partial class Program
|
|
{
|
|
}
|