Client LuaCsForBarotrauma
SteamDedicatedServerProvider.cs
1 #nullable enable
2 using System;
3 using System.Collections.Concurrent;
4 using System.Collections.Generic;
5 using System.Threading.Tasks;
6 using System.Xml.Linq;
8 using Barotrauma.Steam;
9 
10 namespace Barotrauma
11 {
13  {
15  {
16  public readonly UInt16 QueryPort;
17 
18  public DataSource(UInt16 queryPort)
19  {
20  QueryPort = queryPort;
21  }
22 
25  public new static Option<DataSource> Parse(XElement element)
26  => element.TryGetAttributeInt("QueryPort", out var result)
27  ? result switch
28  {
29  var invalidPort when invalidPort <= 0 || invalidPort > UInt16.MaxValue => Option<DataSource>.None(),
30  var queryPort => Option<DataSource>.Some(new DataSource((UInt16)queryPort))
31  }
32  : Option<DataSource>.None();
33 
34  public override void Write(XElement element) => element.SetAttributeValue("QueryPort", QueryPort);
35  }
36 
37  private static Option<ServerInfo> InfoFromListEntry(Steamworks.Data.ServerInfo entry) =>
38  entry.Name.IsNullOrEmpty() || entry.Address is null
39  ? Option<ServerInfo>.None()
40  : Option<ServerInfo>.Some(new ServerInfo(new LidgrenEndpoint(entry.Address, entry.ConnectionPort))
41  {
42  ServerName = entry.Name,
43  HasPassword = entry.Passworded,
44  PlayerCount = entry.Players,
45  MaxPlayers = entry.MaxPlayers,
46  MetadataSource = Option<ServerInfo.DataSource>.Some(new DataSource((UInt16)entry.QueryPort))
47  });
48 
49  private void HandleResponsiveServer(Steamworks.Data.ServerInfo entry, Action<ServerInfo, ServerProvider> onServerDataReceived)
50  {
51  TaskPool.Add($"QueryServerRules (GetServers, {entry.Name}, {entry.Address})", entry.QueryRulesAsync(),
52  t =>
53  {
54  if (t.Status == TaskStatus.Faulted)
55  {
56  TaskPool.PrintTaskExceptions(t, $"Failed to retrieve rules for {entry.Name}", msg => DebugConsole.ThrowError(msg));
57  return;
58  }
59 
60  if (!t.TryGetResult(out Dictionary<string, string>? rules)) { return; }
61  if (rules is null) { return; }
62 
63  if (!InfoFromListEntry(entry).TryUnwrap(out var serverInfo)) { return; }
64  serverInfo.UpdateInfo(key =>
65  {
66  if (rules.TryGetValue(key, out var val)) { return val; }
67  return null;
68  });
69  serverInfo.Checked = true;
70 
71  onServerDataReceived(serverInfo, this);
72  });
73  }
74 
75  private void HandleUnresponsiveServer(Steamworks.Data.ServerInfo entry, Action<ServerInfo, ServerProvider> onServerDataReceived)
76  {
77  //TODO: do we still want to list unresponsive servers?
78  if (!InfoFromListEntry(entry).TryUnwrap(out var serverInfo)) { return; }
79  onServerDataReceived(serverInfo, this);
80  }
81 
82  private Steamworks.ServerList.Internet? serverQuery;
83  private CoroutineHandle? queryCoroutine;
84 
85  protected override void RetrieveServersImpl(Action<ServerInfo, ServerProvider> onServerDataReceived, Action onQueryCompleted)
86  {
87  if (!SteamManager.IsInitialized)
88  {
89  onQueryCompleted();
90  return;
91  }
92 
93  // All lambdas in here must only capture this call's
94  // query, not the provider's latest query
95  var selfServerQuery = new Steamworks.ServerList.Internet();
96  serverQuery = selfServerQuery;
97 
98  ConcurrentQueue<Steamworks.Data.ServerInfo> responsiveServers =
99  new ConcurrentQueue<Steamworks.Data.ServerInfo>();
100  ConcurrentQueue<Steamworks.Data.ServerInfo> unresponsiveServers =
101  new ConcurrentQueue<Steamworks.Data.ServerInfo>();
102 
103  selfServerQuery.OnResponsiveServer = responsiveServers.Enqueue;
104  selfServerQuery.OnUnresponsiveServer = unresponsiveServers.Enqueue;
105 
106  void dequeue(int? limit = null)
107  {
108  for (int i = 0; (!limit.HasValue || i < limit) && responsiveServers.TryDequeue(out var serverInfo); i++)
109  {
110  HandleResponsiveServer(serverInfo, onServerDataReceived);
111  }
112 
113  for (int i = 0; (!limit.HasValue || i < limit) && unresponsiveServers.TryDequeue(out var serverInfo); i++)
114  {
115  HandleUnresponsiveServer(serverInfo, onServerDataReceived);
116  }
117  }
118 
119  IEnumerable<CoroutineStatus> dequeueCoroutine()
120  {
121  while (true)
122  {
123  dequeue(limit: 20);
124  yield return new WaitForSeconds(0.1f, ignorePause: true);
125  }
126  }
127  var selfQueryCoroutine = CoroutineManager.StartCoroutine(dequeueCoroutine(),
128  $"{nameof(SteamDedicatedServerProvider)}.{nameof(RetrieveServers)}.{nameof(dequeueCoroutine)}");
129  queryCoroutine = selfQueryCoroutine;
130 
131  TaskPool.Add("RunServerQuery", selfServerQuery.RunQueryAsync(timeoutSeconds: 30f),
132  t =>
133  {
134  try
135  {
136  // Clear the callbacks because it's too late now, we want to get this over with
137  selfServerQuery.OnResponsiveServer = null;
138  selfServerQuery.OnUnresponsiveServer = null;
139 
140  CoroutineManager.StopCoroutines(selfQueryCoroutine);
141  dequeue();
142 
143  if (t.Status == TaskStatus.Faulted) { TaskPool.PrintTaskExceptions(t, "Failed to retrieve servers", msg => DebugConsole.ThrowError(msg)); }
144 
145  selfServerQuery.Dispose();
146  }
147  finally
148  {
149  onQueryCompleted();
150  }
151  });
152  }
153 
154  public override void Cancel()
155  {
156  if (queryCoroutine != null) { CoroutineManager.StopCoroutines(queryCoroutine); }
157  serverQuery?.Dispose();
158  serverQuery = null;
159  }
160  }
161 }
static new Option< DataSource > Parse(XElement element)
override void RetrieveServersImpl(Action< ServerInfo, ServerProvider > onServerDataReceived, Action onQueryCompleted)