Skip to content

Commit 21b4e54

Browse files
committed
Add server startup orchestrator to control init and listener order
- ServerSetup triggered explicitly before sockets bind - Lobby/Login/World servers started sequentially (Easier to add new servers in the future)
1 parent 45deaeb commit 21b4e54

File tree

20 files changed

+112
-79
lines changed

20 files changed

+112
-79
lines changed

Zolian.GameServer/App.xaml.cs

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public partial class App
3737
{
3838
private CancellationTokenSource ServerCtx { get; set; }
3939

40-
protected override void OnStartup(StartupEventArgs e)
40+
protected override async void OnStartup(StartupEventArgs e)
4141
{
4242
DispatcherUnhandledException += App_DispatcherUnhandledException;
4343
AppDomain.CurrentDomain.UnhandledException += GlobalUnhandledException;
@@ -100,49 +100,28 @@ protected override void OnStartup(StartupEventArgs e)
100100
serviceCollection.AddSingleton<IClientRegistry<ILobbyClient>, ClientRegistry<ILobbyClient>>();
101101
serviceCollection.AddSingleton<LobbyServer>();
102102
serviceCollection.AddSingleton<ILobbyServer<ILobbyClient>>(sp => sp.GetRequiredService<LobbyServer>());
103-
serviceCollection.AddSingleton<IHostedService>(sp => sp.GetRequiredService<LobbyServer>());
104103

105104
// Login
106105
serviceCollection.AddSingleton<IClientFactory<LoginClient>, ClientFactory<LoginClient>>();
107106
serviceCollection.AddSingleton<IClientRegistry<ILoginClient>, ClientRegistry<ILoginClient>>();
108107
serviceCollection.AddSingleton<LoginServer>();
109108
serviceCollection.AddSingleton<ILoginServer<ILoginClient>>(sp => sp.GetRequiredService<LoginServer>());
110-
serviceCollection.AddSingleton<IHostedService>(sp => sp.GetRequiredService<LoginServer>());
111109

112110
// World
113111
serviceCollection.AddSingleton<IClientFactory<WorldClient>, ClientFactory<WorldClient>>();
114112
serviceCollection.AddSingleton<IClientRegistry<IWorldClient>, ClientRegistry<IWorldClient>>();
115113
serviceCollection.AddSingleton<WorldServer>();
116114
serviceCollection.AddSingleton<IWorldServer<IWorldClient>>(sp => sp.GetRequiredService<WorldServer>());
117-
serviceCollection.AddSingleton<IHostedService>(sp => sp.GetRequiredService<WorldServer>());
115+
116+
// Hosted Services
117+
serviceCollection.AddSingleton<IHostedService, ServerOrchestrator>();
118118

119119
var serviceProvider = serviceCollection.BuildServiceProvider();
120120
serviceProvider.GetService<IServer>();
121121
var hostedServices = serviceProvider.GetServices<IHostedService>().ToArray();
122122

123-
// Start the hosted services in a dedicated long-running task
124-
_ = Task.Run(async () =>
125-
{
126-
try
127-
{
128-
// Start all hosted services
129-
await Task.WhenAll(hostedServices.Select(svc => svc.StartAsync(ServerCtx.Token)));
130-
131-
// Then wait until the token is cancelled
132-
try
133-
{
134-
await Task.Delay(Timeout.Infinite, ServerCtx.Token);
135-
}
136-
catch (OperationCanceledException)
137-
{
138-
// Expected when the token is canceled
139-
}
140-
}
141-
catch (Exception exception)
142-
{
143-
SentrySdk.CaptureException(exception);
144-
}
145-
}, ServerCtx.Token);
123+
foreach (var svc in hostedServices)
124+
await svc.StartAsync(ServerCtx.Token);
146125
}
147126
catch (Exception exception)
148127
{
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
5+
using Darkages.Network.Server;
6+
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.Hosting;
9+
10+
namespace Zolian.GameServer;
11+
12+
public sealed class ServerOrchestrator : BackgroundService
13+
{
14+
private readonly IServiceProvider _services;
15+
16+
public ServerOrchestrator(IServiceProvider services) => _services = services;
17+
18+
/// <summary>
19+
/// ServerOrchestrator is registered as an IHostedService; when the host starts,
20+
/// StartAsync() is called, which in turn runs ExecuteAsync() on a background task
21+
/// that performs server initialization and startup until shutdown is requested.
22+
/// </summary>
23+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
24+
{
25+
// Runs bootstrap/init deterministically
26+
// Triggers ServerSetup.Start() via Server class
27+
_ = _services.GetRequiredService<IServer>();
28+
29+
// Start listeners in order
30+
var lobby = _services.GetRequiredService<LobbyServer>();
31+
await lobby.StartAsync(stoppingToken).ConfigureAwait(false);
32+
33+
var login = _services.GetRequiredService<LoginServer>();
34+
await login.StartAsync(stoppingToken).ConfigureAwait(false);
35+
36+
var world = _services.GetRequiredService<WorldServer>();
37+
await world.StartAsync(stoppingToken).ConfigureAwait(false);
38+
39+
// Keep running until stopped
40+
await Task.Delay(Timeout.Infinite, stoppingToken).ConfigureAwait(false);
41+
}
42+
43+
public override async Task StopAsync(CancellationToken cancellationToken)
44+
{
45+
// Stop in reverse order if they were started
46+
// Use GetService so stop doesn’t throw if something never started
47+
var world = _services.GetService<WorldServer>();
48+
if (world is not null) await world.StopAsync(cancellationToken).ConfigureAwait(false);
49+
50+
var login = _services.GetService<LoginServer>();
51+
if (login is not null) await login.StopAsync(cancellationToken).ConfigureAwait(false);
52+
53+
var lobby = _services.GetService<LobbyServer>();
54+
if (lobby is not null) await lobby.StopAsync(cancellationToken).ConfigureAwait(false);
55+
56+
await base.StopAsync(cancellationToken).ConfigureAwait(false);
57+
}
58+
}

Zolian.GameServer/Zolian.GameServer.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
</PropertyGroup>
4242

4343
<ItemGroup>
44-
<PackageReference Include="Chaos-Networking" Version="2.8.1" />
44+
<PackageReference Include="Chaos-Networking" Version="2.8.4" />
4545
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.0-preview2.25289.6" />
4646
<PackageReference Include="Microsoft.DependencyValidation.Analyzers" Version="0.11.0" />
4747
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.1">

Zolian.Server.Base/CommandSystem/Commander.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ private static void Chaos(Argument[] args, object arg)
171171
Task.Delay(60000).ContinueWith(ct =>
172172
{
173173
if (!client.Aisling.GameMasterChaosCancel)
174-
connected.Client.Disconnect();
174+
connected.Client.CloseTransport();
175175

176176
connected.Client.SendServerMessage(ServerMessageType.GroupChat, "{=qDeath{=g: {=bChaos has been cancelled.");
177177
});

Zolian.Server.Base/GameScripts/Mundanes/Arena/ArenaHost.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public override void OnResponse(WorldClient client, ushort responseID, string ar
4242

4343
if (client.Aisling.Map.ID != 5232)
4444
{
45-
client.Disconnect();
45+
client.CloseTransport();
4646
return;
4747
}
4848

Zolian.Server.Base/GameScripts/Mundanes/Generic/TempleOfLight.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public override void OnResponse(WorldClient client, ushort responseID, string ar
4343

4444
if (client.Aisling.Map.ID != 500)
4545
{
46-
client.Disconnect();
46+
client.CloseTransport();
4747
return;
4848
}
4949

Zolian.Server.Base/GameScripts/Mundanes/Generic/TempleOfVoid.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public override void OnResponse(WorldClient client, ushort responseID, string ar
4343

4444
if (client.Aisling.Map.ID != 500)
4545
{
46-
client.Disconnect();
46+
client.CloseTransport();
4747
return;
4848
}
4949

Zolian.Server.Base/GameScripts/Mundanes/TempleOfVoid/VoidSphereWarp.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public override void OnResponse(WorldClient client, ushort responseID, string ar
3232
{
3333
if (client.Aisling.Map.ID != 1500)
3434
{
35-
client.Disconnect();
35+
client.CloseTransport();
3636
return;
3737
}
3838

Zolian.Server.Base/Network/Client/Abstractions/IClientFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace Darkages.Network.Client.Abstractions
55
{
6-
public interface IClientFactory<out T> where T : SocketClientBase
6+
public interface IClientFactory<out T> where T : SocketTransportBase
77
{
88
T CreateClient(Socket socket);
99
}

Zolian.Server.Base/Network/Client/LobbyClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class LobbyClient([NotNull] ILobbyServer<ILobbyClient> server, [NotNull]
2020
[NotNull] ILogger<LobbyClient> logger)
2121
: LobbyClientBase(socket, crypto, packetSerializer, logger), ILobbyClient
2222
{
23-
protected override ValueTask HandlePacketAsync(Span<byte> span)
23+
protected override ValueTask OnPacketAsync(Span<byte> span)
2424
{
2525
var opCode = span[3];
2626
var packet = new Packet(ref span, Crypto.IsClientEncrypted(opCode));

0 commit comments

Comments
 (0)