33namespace RustPlusBot . Features . Connections . Listening ;
44
55/// <summary>Diffs successive team snapshots into presence transitions. One instance per connected window.</summary>
6- /// <remarks>Not thread-safe: the supervisor calls <see cref="Diff"/> from a single poll loop.</remarks>
76internal sealed class TeamStateTracker
87{
8+ private readonly object _gate = new ( ) ;
99 private Dictionary < ulong , TeamMemberSnapshot > ? _baseline ;
1010 private readonly Dictionary < ulong , DateTimeOffset > _stillSince = new ( ) ;
1111 private readonly HashSet < ulong > _afk = new ( ) ;
@@ -26,32 +26,35 @@ public IReadOnlyList<PlayerTransition> Diff(
2626
2727 var current = snapshot . Members . ToDictionary ( m => m . SteamId ) ;
2828
29- if ( _baseline is null )
29+ lock ( _gate )
3030 {
31- _baseline = current ;
32- foreach ( var m in current . Values )
31+ if ( _baseline is null )
3332 {
34- _stillSince [ m . SteamId ] = now ;
35- }
33+ _baseline = current ;
34+ foreach ( var m in current . Values )
35+ {
36+ _stillSince [ m . SteamId ] = now ;
37+ }
3638
37- return [ ] ;
38- }
39+ return [ ] ;
40+ }
3941
40- var transitions = new List < PlayerTransition > ( ) ;
41- foreach ( var ( id , nowMember ) in current )
42- {
43- if ( ! _baseline . TryGetValue ( id , out var was ) )
42+ var transitions = new List < PlayerTransition > ( ) ;
43+ foreach ( var ( id , nowMember ) in current )
4444 {
45- _stillSince [ id ] = now ; // prime new member's stillness clock
46- continue ;
45+ if ( ! _baseline . TryGetValue ( id , out var was ) )
46+ {
47+ _stillSince [ id ] = now ; // prime new member's stillness clock
48+ continue ;
49+ }
50+
51+ AddPresenceTransitions ( transitions , id , was , nowMember , snapshot ) ;
52+ UpdateAfk ( transitions , id , was , nowMember , now , afkThreshold , afkEpsilon ) ;
4753 }
4854
49- AddPresenceTransitions ( transitions , id , was , nowMember , snapshot ) ;
50- UpdateAfk ( transitions , id , was , nowMember , now , afkThreshold , afkEpsilon ) ;
55+ _baseline = current ;
56+ return transitions ;
5157 }
52-
53- _baseline = current ;
54- return transitions ;
5558 }
5659
5760 private static void AddPresenceTransitions (
@@ -119,17 +122,20 @@ private void UpdateAfk(
119122 /// <param name="now">The current wall-clock time used to compute each member's still duration.</param>
120123 public IReadOnlyList < AfkMember > CurrentAfk ( DateTimeOffset now )
121124 {
122- var result = new List < AfkMember > ( ) ;
123- foreach ( var id in _afk )
125+ lock ( _gate )
124126 {
125- if ( _baseline is not null && _baseline . TryGetValue ( id , out var m ) )
127+ var result = new List < AfkMember > ( ) ;
128+ foreach ( var id in _afk )
126129 {
127- var since = _stillSince . TryGetValue ( id , out var s ) ? s : now ;
128- result . Add ( new AfkMember ( id , m . Name , now - since ) ) ;
130+ if ( _baseline is not null && _baseline . TryGetValue ( id , out var m ) )
131+ {
132+ var since = _stillSince . TryGetValue ( id , out var s ) ? s : now ;
133+ result . Add ( new AfkMember ( id , m . Name , now - since ) ) ;
134+ }
129135 }
130- }
131136
132- return result ;
137+ return result ;
138+ }
133139 }
134140
135141 private static ( float X , float Y ) ? ResolveDeathLocation (
0 commit comments