Skip to content

Commit 83d7fd5

Browse files
authored
feature: all in one (single) (#202)
* CHG use keyed options to allow multiple definitions in one app * CHG make packet provider and related services keyed so they can be registered multiple times and injected by key * ADD Single executable to host both auth and game in one executable * feat(single): implement single exe * refactor(build): renamed Game + Auth to Game.Server + Auth.Server Executables are now just a tiny wrapper around the library to make QuantumCore.Single not depend on executables (causes many issues with build output) * feat(ci): single exe * feat(single): Add commandline parser to allow --version and --help * chore: Set author to MeikelLP to make auto generated copyright more useful * refactor(single): Moved Game.Server + Auth.Server to Libraries/ dir * fix: launchSettings.json * fix: project references * feat: Use IFileProvider Additionally removed the proto loaders and merged them with their respective manager * refactor(game): Use IFileProvider * refactor(game): Remove additional config files from data dir * fix: tests * refactor(game): Use IFileProvider * refactor(game): Use IFileProvider * refactor(game): Use ILoadable * fix(game): Duplicate service registration * fix: tests * fix: startup crash if no data/ dir existed * docs: renamed API to configuration * docs: Improve configuration docs * chore(docs): Add recommended truncate for blog posts * fix(tests) Bumped FluentAssertions to 7.0.0-alpha.5 for fluentassertions/fluentassertions#2565 * fix: InMemory cache wrong expiry * fix(game): service registration * fix: default IP is loopback * fix(auth): startup * fix(tests) * fix(game): remove unnecessary file IO * refactor: use constants for "auth" and "game" named options * chore: cleanup csproj * fix(single): bump GitVersion.MsBuild to latest to possibly fix CI issue * fix(ci): unshallow fetch * fix(ci): unshallow fetch * fix(single): app launch * fix(ci): self-contained publish for single * chore(ci): rename publish artifacts job * fix(game): ping handler casting wrongly * chore(docs): clarify the client directory * fix(ci): run single builds only on master/tag
1 parent 112a309 commit 83d7fd5

File tree

322 files changed

+1892
-1462
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

322 files changed

+1892
-1462
lines changed

.github/workflows/dotnet-pipeline.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ jobs:
1818

1919
steps:
2020
- uses: actions/checkout@v4
21+
with:
22+
fetch-depth: '0'
2123

2224
# setup .NET
2325
- name: Setup .NET Core SDK 8
@@ -30,6 +32,30 @@ jobs:
3032

3133
- name: Test
3234
run: dotnet test --no-build src/QuantumCore.sln
35+
publish_single:
36+
needs:
37+
- test
38+
runs-on: ubuntu-latest
39+
# only deploy if on master or tag
40+
if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/master'
41+
strategy:
42+
matrix:
43+
os: [ win-x64, osx-x64, linux-x64, linux-musl-x64 ]
44+
steps:
45+
- uses: actions/checkout@v3
46+
with:
47+
fetch-depth: '0'
48+
- name: Setup .NET Core SDK 8
49+
uses: actions/setup-dotnet@v4
50+
with:
51+
dotnet-version: 8.0.x
52+
- name: Build
53+
run: dotnet publish -r ${{ matrix.os }} --verbosity minimal -p DebugType=None -p DebugSymbols=false -p PublishSingleFile=true --self-contained src/Executables/Single/
54+
- name: Archive build artifact
55+
uses: actions/upload-artifact@v4
56+
with:
57+
name: QuantumCore Single ${{ matrix.os }}
58+
path: src/Executables/Single/bin/Release/net8.0/${{ matrix.os }}/publish
3359
deploy:
3460
needs:
3561
- test

docs/docs/Getting Started/developer.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ git clone https://github.com/MeikelLP/quantum-core-x.git
1818

1919
### 2. Generate a `mob_proto` and `item_proto`
2020

21-
In the client directory there should be a folder called `Eternexus` with an folder `--dump_proto--`. Just execute the `dump_proto.exe`. It should generate you 2 files:
21+
In the (TMP4) client directory there should be a folder called `Eternexus` with an folder `--dump_proto--`. Just execute the `dump_proto.exe`. It should generate you 2 files:
2222

2323
* `item_proto`
2424
* `mob_proto`

src/Core.Networking/PacketReader.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Runtime.CompilerServices;
33
using System.Text.Json;
44
using Microsoft.Extensions.Configuration;
5+
using Microsoft.Extensions.DependencyInjection;
56
using Microsoft.Extensions.Hosting;
67
using Microsoft.Extensions.Logging;
78

@@ -14,11 +15,11 @@ public class PacketReader : IPacketReader
1415
private readonly IHostEnvironment _env;
1516
private readonly int _bufferSize;
1617

17-
public PacketReader(ILogger<PacketReader> logger, IPacketManager packetManager, IConfiguration configuration,
18-
IHostEnvironment env)
18+
public PacketReader([ServiceKey] string serviceKey, ILogger<PacketReader> logger, IConfiguration configuration,
19+
IServiceProvider serviceProvider, IHostEnvironment env)
1920
{
2021
_logger = logger;
21-
_packetManager = packetManager;
22+
_packetManager = serviceProvider.GetRequiredKeyedService<IPacketManager>(serviceKey);
2223
_env = env;
2324
_bufferSize = configuration.GetValue<int?>("BufferSize") ?? 4096;
2425
_logger.LogDebug("Using buffer size {BufferSize}", _bufferSize);
@@ -42,7 +43,7 @@ public async IAsyncEnumerable<object> EnumerateAsync(Stream stream,
4243
}
4344
catch (IOException)
4445
{
45-
_logger.LogDebug("Connection was most likely closed while reading a packet. This may be fine");
46+
_logger.LogDebug("Connection was closed while reading a packet header. This may be fine");
4647
break;
4748
}
4849

@@ -66,7 +67,7 @@ public async IAsyncEnumerable<object> EnumerateAsync(Stream stream,
6667
}
6768
catch (IOException)
6869
{
69-
_logger.LogDebug("Connection was most likely closed while reading a packet. This may be fine");
70+
_logger.LogDebug("Connection was closed while reading a packet sub header. This may be fine");
7071
break;
7172
}
7273

@@ -111,7 +112,7 @@ public async IAsyncEnumerable<object> EnumerateAsync(Stream stream,
111112
}
112113
catch (IOException)
113114
{
114-
_logger.LogDebug("Connection was most likely closed while reading a packet. This may be fine");
115+
_logger.LogDebug("Connection was closed while reading a packet body. This may be fine");
115116
break;
116117
}
117118

@@ -132,7 +133,7 @@ public async IAsyncEnumerable<object> EnumerateAsync(Stream stream,
132133
}
133134
catch (IOException)
134135
{
135-
_logger.LogDebug("Connection was most likely closed while reading a packet. This may be fine");
136+
_logger.LogDebug("Connection was closed while reading a packet sequence. This may be fine");
136137
break;
137138
}
138139
}
@@ -159,4 +160,4 @@ private async Task<byte[]> GetAsMuchDataAsPossibleAsync(Stream stream)
159160
Array.Resize(ref bytes, totalRead);
160161
return bytes;
161162
}
162-
}
163+
}

src/Core/Core/Networking/ServerBase.cs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using Microsoft.Extensions.Options;
99
using QuantumCore.API;
1010
using QuantumCore.API.PluginTypes;
11-
using QuantumCore.Core.Utils;
1211
using QuantumCore.Networking;
1312

1413
namespace QuantumCore.Core.Networking
@@ -27,28 +26,30 @@ public abstract class ServerBase<T> : BackgroundService, IServerBase
2726
private readonly PluginExecutor _pluginExecutor;
2827
private readonly IServiceProvider _serviceProvider;
2928
private readonly string _serverMode;
29+
protected IServiceScope Scope { get; }
3030

31-
public int Port { get; }
31+
public ushort Port { get; }
32+
public IPAddress IpAddress { get; }
3233

3334
public ServerBase(IPacketManager packetManager, ILogger logger, PluginExecutor pluginExecutor,
34-
IServiceProvider serviceProvider, string mode,
35-
IOptions<HostingOptions> hostingOptions)
35+
IServiceProvider serviceProvider, string mode)
3636
{
3737
_logger = logger;
3838
_pluginExecutor = pluginExecutor;
3939
_serviceProvider = serviceProvider;
4040
_serverMode = mode;
4141
PacketManager = packetManager;
42-
Port = hostingOptions.Value.Port;
42+
Scope = serviceProvider.CreateScope();
43+
var hostingOptions = Scope.ServiceProvider.GetRequiredService<IOptionsSnapshot<HostingOptions>>().Get(mode);
44+
Port = hostingOptions.Port;
4345

4446
// Start server timer
4547
_serverTimer.Start();
46-
47-
var localAddr = IPAddress.Parse(hostingOptions.Value.IpAddress ?? "0.0.0.0");
48-
IpUtils.PublicIP = localAddr;
49-
Listener = new TcpListener(localAddr, Port);
50-
51-
_logger.LogInformation("Initialize tcp server listening on {IP}:{Port}", localAddr, Port);
48+
var desiredIpAddress = IPAddress.TryParse(hostingOptions.IpAddress, out var ipAddress)
49+
? ipAddress
50+
: IPAddress.Loopback;
51+
IpAddress = desiredIpAddress;
52+
Listener = new TcpListener(IpAddress, Port);
5253
}
5354

5455
public long ServerTime => _serverTimer.ElapsedMilliseconds;
@@ -64,11 +65,13 @@ await _pluginExecutor.ExecutePlugins<IConnectionLifetimeListener>(_logger,
6465
public override Task StartAsync(CancellationToken token)
6566
{
6667
base.StartAsync(token);
67-
_logger.LogInformation("Start listening for connections...");
6868

6969
Listener.Start();
7070
Listener.BeginAcceptTcpClient(OnClientAccepted, Listener);
7171

72+
_logger.LogInformation("Start listening for connections on {IP}:{Port} ({Mode})", IpAddress, Port,
73+
_serverMode);
74+
7275
return Task.CompletedTask;
7376
}
7477

@@ -116,11 +119,11 @@ public async Task CallListener(IConnection connection, IPacketSerializable packe
116119
}
117120

118121
object context;
119-
if (_serverMode == "game")
122+
if (_serverMode == HostingOptions.ModeGame)
120123
{
121124
context = GetGameContextPacket(connection, packet, details.PacketType);
122125
}
123-
else if (_serverMode == "auth")
126+
else if (_serverMode == HostingOptions.ModeAuth)
124127
{
125128
context = GetAuthContextPacket(connection, packet, details.PacketType);
126129
}
@@ -188,6 +191,7 @@ public async override Task StopAsync(CancellationToken cancellationToken)
188191
await _stoppingToken.CancelAsync();
189192
await base.StopAsync(cancellationToken);
190193
_stoppingToken.Dispose();
194+
Scope.Dispose();
191195
}
192196
}
193-
}
197+
}

src/Core/Core/Utils/IpUtils.cs

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/Core/Extensions/ServiceExtensions.cs

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,51 @@ public static class ServiceExtensions
1616
private const string MessageTemplate = "[{Timestamp:HH:mm:ss.fff}][{Level:u3}]{Message:lj} " +
1717
"{NewLine:1}{Exception:1}";
1818

19+
/// <summary>
20+
/// Used to register a packet provider per application type.
21+
/// The application types might have duplicate packet definitions (by header) but they still might be handled
22+
/// differently. Thus multiple packet providers may be registered if necessary with each registered as a keyed
23+
/// service.
24+
/// </summary>
25+
/// <param name="services"></param>
26+
/// <param name="mode"></param>
27+
/// <typeparam name="T"></typeparam>
28+
/// <returns></returns>
29+
public static IServiceCollection AddPacketProvider<T>(this IServiceCollection services, string mode)
30+
where T : class, IPacketLocationProvider
31+
{
32+
services.AddKeyedSingleton<IPacketLocationProvider, T>(mode);
33+
services.AddKeyedSingleton<IPacketReader, PacketReader>(mode);
34+
services.AddKeyedSingleton<IPacketManager>(mode, (provider, key) =>
35+
{
36+
var packetLocationProvider = provider.GetRequiredKeyedService<IPacketLocationProvider>(key);
37+
var assemblies = packetLocationProvider.GetPacketAssemblies();
38+
var packetTypes = assemblies.SelectMany(x => x.ExportedTypes)
39+
.Where(x => x.IsAssignableTo(typeof(IPacketSerializable)) &&
40+
x.GetCustomAttribute<PacketAttribute>()?.Direction.HasFlag(EDirection.Incoming) == true)
41+
.OrderBy(x => x.FullName)
42+
.ToArray();
43+
var handlerTypes = assemblies.SelectMany(x => x.ExportedTypes)
44+
.Where(x =>
45+
x.IsAssignableTo(typeof(IPacketHandler)) &&
46+
x is {IsClass: true, IsAbstract: false, IsInterface: false})
47+
.OrderBy(x => x.FullName)
48+
.ToArray();
49+
return ActivatorUtilities.CreateInstance<PacketManager>(provider,
50+
new object[] {(IEnumerable<Type>) packetTypes, handlerTypes});
51+
});
52+
return services;
53+
}
54+
1955
/// <summary>
2056
/// Services required by Auth & Game
2157
/// </summary>
2258
/// <param name="services"></param>
2359
/// <param name="pluginCatalog"></param>
24-
/// <param name="configuration"></param>
2560
/// <returns></returns>
2661
public static IServiceCollection AddCoreServices(this IServiceCollection services, IPluginCatalog pluginCatalog,
2762
IConfiguration configuration)
2863
{
29-
services.AddOptions<HostingOptions>()
30-
.BindConfiguration("Hosting")
31-
.ValidateDataAnnotations();
3264
services.AddCustomLogging(configuration);
3365
services.AddSingleton<IPacketManager>(provider =>
3466
{
@@ -50,7 +82,6 @@ public static IServiceCollection AddCoreServices(this IServiceCollection service
5082
.ToArray();
5183
return ActivatorUtilities.CreateInstance<PacketManager>(provider, [packetTypes, handlerTypes]);
5284
});
53-
services.AddSingleton<IPacketReader, PacketReader>();
5485
services.AddSingleton<PluginExecutor>();
5586
services.AddPluginFramework()
5687
.AddPluginCatalog(pluginCatalog)
@@ -112,4 +143,4 @@ private static IServiceCollection AddCustomLogging(this IServiceCollection servi
112143
});
113144
return services;
114145
}
115-
}
146+
}

src/Core/HostingOptions.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ namespace QuantumCore;
44

55
public class HostingOptions
66
{
7-
[Required]
8-
public int Port { get; set; }
7+
public const string ModeAuth = "auth";
8+
public const string ModeGame = "game";
9+
[Required] public ushort Port { get; set; }
910
public string? IpAddress { get; set; }
10-
}
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Reflection;
2+
3+
namespace QuantumCore;
4+
5+
/// <summary>
6+
/// This will be registered as a keyed service.
7+
/// Used to define where to load packet types and handlers from
8+
/// </summary>
9+
public interface IPacketLocationProvider
10+
{
11+
IReadOnlyCollection<Assembly> GetPacketAssemblies();
12+
}

src/CorePluginAPI/Game/IGame.cs

Lines changed: 0 additions & 11 deletions
This file was deleted.

src/CorePluginAPI/Game/World/IWorld.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
namespace QuantumCore.API.Game.World
44
{
5-
public interface IWorld
5+
public interface IWorld : ILoadable
66
{
7-
Task Load();
7+
Task InitAsync();
88
void Update(double elapsedTime);
99
#nullable enable
1010
IMap? GetMapAt(uint x, uint y);

0 commit comments

Comments
 (0)