1- using System . Text . Json ;
1+ using System . Threading . Channels ;
22using Grpc . Core ;
3- using Npgsql ;
43using RIN . Core . DB ;
54using RIN . InternalAPI . Models ;
65
76namespace RIN . InternalAPI . Services
87{
98 public class GameServerAPI : IGameServerAPI
109 {
11- private readonly DB DB ;
10+ private readonly DB Db ;
1211 private readonly ILogger < GameServerAPI > Logger ;
12+ private readonly DbEventBus EventBus ;
13+ private readonly IHostApplicationLifetime Lifetime ;
1314
14- public GameServerAPI ( DB db , ILogger < GameServerAPI > logger )
15+ public GameServerAPI ( DB db , ILogger < GameServerAPI > logger , DbEventBus eventBus , IHostApplicationLifetime lifetime )
1516 {
16- DB = db ;
17+ Db = db ;
1718 Logger = logger ;
19+ EventBus = eventBus ;
20+ Lifetime = lifetime ;
1821 }
1922
2023 public async ValueTask < PingResp > Ping ( PingReq req )
@@ -30,13 +33,13 @@ public async ValueTask<PingResp> Ping(PingReq req)
3033
3134 public async ValueTask < CharacterAndBattleframeVisuals > GetCharacterAndBattleframeVisuals ( CharacterID req )
3235 {
33- var result = await DB . GetBasicCharacterAndVisualData ( req . ID ) ;
36+ var result = await Db . GetBasicCharacterAndVisualData ( req . ID ) ;
3437 var bfVisuals = PlayerBattleframeVisuals . CreateDefault ( ) ;
3538
3639 var resp = new CharacterAndBattleframeVisuals
3740 {
38- CharacterInfo = result . Item1 ,
39- CharacterVisuals = result . Item2 ,
41+ CharacterInfo = result . info ,
42+ CharacterVisuals = result . visuals ,
4043 BattleframeVisuals = bfVisuals
4144 } ;
4245
@@ -45,73 +48,52 @@ public async ValueTask<CharacterAndBattleframeVisuals> GetCharacterAndBattlefram
4548
4649 public async Task Stream ( IAsyncStreamReader < Command > commands , IServerStreamWriter < Event > events , ServerCallContext context )
4750 {
48- await using var connection = new NpgsqlConnection ( DB . ConnStr ) ;
49- await connection . OpenAsync ( ) ;
51+ var channel = Channel . CreateUnbounded < Event > ( ) ;
52+ var subscriptionId = EventBus . Subscribe ( channel ) ;
5053
51- await using var cmd = new NpgsqlCommand ( "LISTEN events" , connection ) ;
52- await cmd . ExecuteNonQueryAsync ( ) ;
53-
54- connection . Notification += async ( _ , e ) =>
54+ using var cts = CancellationTokenSource . CreateLinkedTokenSource ( context . CancellationToken , Lifetime . ApplicationStopping ) ;
55+ var token = cts . Token ;
56+ try
5557 {
56- var dbEvent = e . Payload . Split ( [ "->" ] , StringSplitOptions . None ) ;
57- var eventType = dbEvent [ 0 ] ;
58- var payloadJson = dbEvent [ 1 ] ;
59-
60- var type = Type . GetType ( $ "RIN.InternalAPI.Models.{ eventType } ") ;
61-
62- if ( type == null )
63- {
64- Logger . LogError ( "Unknown event type: {eventType}" , eventType ) ;
65- return ;
66- }
67-
68- var payload = JsonSerializer . Deserialize ( payloadJson , type ) ;
69-
70- if ( payload is not Event evt )
71- {
72- Logger . LogError ( "Failed to deserialize payload for event type: {eventType}" , eventType ) ;
73- return ;
74- }
75-
76- if ( evt is CharacterVisualsUpdated cvu )
77- {
78- var updatedVisuals = await GetCharacterAndBattleframeVisuals (
79- new CharacterID { ID = ( long ) cvu . CharacterGuid } ) ;
80-
81- cvu . CharacterAndBattleframeVisuals = updatedVisuals ;
82-
83- await events . WriteAsync ( cvu ) ;
84- }
85- else
58+ var sendEventsTask = Task . Run ( async ( ) =>
8659 {
87- await events . WriteAsync ( evt ) ;
88- }
89- } ;
60+ await foreach ( var evt in channel . Reader . ReadAllAsync ( token ) )
61+ {
62+ await events . WriteAsync ( evt ) ;
63+ }
64+ } ) ;
9065
91- var commandsTask = Task . Run ( async ( ) =>
92- {
93- await foreach ( var command in commands . ReadAllAsync ( ) )
66+ var commandsTask = Task . Run ( async ( ) =>
9467 {
95- Logger . LogInformation ( "Received command: {command}" , command ) ;
96-
97- switch ( command )
68+ await foreach ( var command in commands . ReadAllAsync ( token ) )
9869 {
99- case SaveGameSessionData data :
100- await DB . UpdateCharacterAfterGameSession ( ( long ) data . CharacterId , ( int ) data . ZoneId , ( int ) data . OutpostId , ( int ) data . TimePlayed ) ;
101- break ;
102- case SaveLgvRaceFinish race :
103- await DB . SaveLgvRaceFinish ( ( long ) race . CharacterGuid , ( int ) race . LeaderboardId , ( long ) race . TimeMs ) ;
104- break ;
70+ Logger . LogInformation ( "Received command: {command}" , command ) ;
71+
72+ switch ( command )
73+ {
74+ case SaveGameSessionData data :
75+ await Db . UpdateCharacterAfterGameSession ( ( long ) data . CharacterId , ( int ) data . ZoneId , ( int ) data . OutpostId , ( int ) data . TimePlayed ) ;
76+ break ;
77+ case SaveLgvRaceFinish race :
78+ await Db . SaveLgvRaceFinish ( ( long ) race . CharacterGuid , ( int ) race . LeaderboardId , ( long ) race . TimeMs ) ;
79+ break ;
80+ }
10581 }
106- }
107- } ) ;
82+ } ) ;
10883
109- while ( ! context . CancellationToken . IsCancellationRequested )
84+ await Task . WhenAny ( sendEventsTask , commandsTask ) ;
85+ await cts . CancelAsync ( ) ;
86+ await Task . WhenAll ( sendEventsTask , commandsTask ) ;
87+ }
88+ catch ( Exception ex ) when ( ex is not OperationCanceledException )
11089 {
111- await connection . WaitAsync ( context . CancellationToken ) ;
90+ Logger . LogError ( ex , "GRPC Stream crashed" ) ;
91+ }
92+ finally
93+ {
94+ channel . Writer . TryComplete ( ) ;
95+ EventBus . Unsubscribe ( subscriptionId ) ;
11296 }
113-
114- await commandsTask ;
11597 }
11698 }
11799}
0 commit comments