UdpNetworkSession.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using Microsoft.Xna.Framework;
  5. namespace Microsoft.Xna.Framework.Net
  6. {
  7. /// <summary>
  8. /// Adapter that wraps the existing NetworkSession to implement INetworkSession.
  9. /// This allows NetworkSession to be used transparently through the INetworkSession interface,
  10. /// enabling future alternative implementations (e.g., Steam) to be swapped in via dependency injection.
  11. /// </summary>
  12. internal class UdpNetworkSession : INetworkSession
  13. {
  14. private readonly NetworkSession innerSession;
  15. private ILocalNetworkGamer localGamerAdapter;
  16. private Dictionary<string, INetworkGamer> gamerAdapters;
  17. private bool disposed;
  18. /// <summary>
  19. /// Initializes a new instance of UdpNetworkSession with an underlying NetworkSession.
  20. /// </summary>
  21. internal UdpNetworkSession(NetworkSession underlyingSession)
  22. {
  23. this.innerSession = underlyingSession ?? throw new ArgumentNullException(nameof(underlyingSession));
  24. this.gamerAdapters = new Dictionary<string, INetworkGamer>();
  25. this.disposed = false;
  26. }
  27. /// <summary>
  28. /// Initializes a new instance of UdpNetworkSession (default constructor for factory).
  29. /// </summary>
  30. internal UdpNetworkSession() : this(null)
  31. {
  32. }
  33. /// <summary>
  34. /// Gets all gamers (local and remote) in the session.
  35. /// </summary>
  36. public IReadOnlyList<INetworkGamer> AllGamers
  37. {
  38. get
  39. {
  40. if (innerSession == null) return new List<INetworkGamer>();
  41. return innerSession.AllGamers.Select(AdaptGamer).ToList();
  42. }
  43. }
  44. /// <summary>
  45. /// Gets the local player in the session.
  46. /// </summary>
  47. public ILocalNetworkGamer LocalGamer
  48. {
  49. get
  50. {
  51. if (innerSession?.Host == null) return null;
  52. if (localGamerAdapter == null)
  53. {
  54. localGamerAdapter = new LocalNetworkGamerAdapter(innerSession.Host as LocalNetworkGamer);
  55. }
  56. return localGamerAdapter;
  57. }
  58. }
  59. /// <summary>
  60. /// Gets the current state of the session.
  61. /// </summary>
  62. public NetworkSessionState State
  63. {
  64. get
  65. {
  66. if (innerSession == null) return NetworkSessionState.Creating;
  67. return innerSession.SessionState;
  68. }
  69. }
  70. /// <summary>
  71. /// Gets the unique identifier for this session.
  72. /// </summary>
  73. public string SessionId
  74. {
  75. get
  76. {
  77. if (innerSession == null) return null;
  78. return innerSession.sessionId;
  79. }
  80. }
  81. /// <summary>
  82. /// Occurs when a message is received from a remote gamer.
  83. /// </summary>
  84. public event EventHandler<MessageReceivedEventArgs> MessageReceived
  85. {
  86. add
  87. {
  88. if (innerSession != null)
  89. innerSession.MessageReceived += value;
  90. }
  91. remove
  92. {
  93. if (innerSession != null)
  94. innerSession.MessageReceived -= value;
  95. }
  96. }
  97. /// <summary>
  98. /// Occurs when a remote gamer joins the session.
  99. /// </summary>
  100. public event EventHandler<GamerJoinedEventArgs> GamerJoined
  101. {
  102. add
  103. {
  104. if (innerSession != null)
  105. innerSession.GamerJoined += value;
  106. }
  107. remove
  108. {
  109. if (innerSession != null)
  110. innerSession.GamerJoined -= value;
  111. }
  112. }
  113. /// <summary>
  114. /// Occurs when a remote gamer leaves the session.
  115. /// </summary>
  116. public event EventHandler<GamerLeftEventArgs> GamerLeft
  117. {
  118. add
  119. {
  120. if (innerSession != null)
  121. innerSession.GamerLeft += value;
  122. }
  123. remove
  124. {
  125. if (innerSession != null)
  126. innerSession.GamerLeft -= value;
  127. }
  128. }
  129. /// <summary>
  130. /// Occurs when the game is ready to start.
  131. /// </summary>
  132. public event EventHandler<GameStartedEventArgs> GameStarted
  133. {
  134. add
  135. {
  136. if (innerSession != null)
  137. innerSession.GameStarted += value;
  138. }
  139. remove
  140. {
  141. if (innerSession != null)
  142. innerSession.GameStarted -= value;
  143. }
  144. }
  145. /// <summary>
  146. /// Occurs when the game has ended.
  147. /// </summary>
  148. public event EventHandler<GameEndedEventArgs> GameEnded
  149. {
  150. add
  151. {
  152. if (innerSession != null)
  153. innerSession.GameEnded += value;
  154. }
  155. remove
  156. {
  157. if (innerSession != null)
  158. innerSession.GameEnded -= value;
  159. }
  160. }
  161. /// <summary>
  162. /// Occurs when the session ends.
  163. /// </summary>
  164. public event EventHandler<NetworkSessionEndedEventArgs> SessionEnded
  165. {
  166. add
  167. {
  168. if (innerSession != null)
  169. innerSession.SessionEnded += value;
  170. }
  171. remove
  172. {
  173. if (innerSession != null)
  174. innerSession.SessionEnded -= value;
  175. }
  176. }
  177. /// <summary>
  178. /// Creates a new session as host.
  179. /// </summary>
  180. public async System.Threading.Tasks.Task CreateAsync(NetworkSessionType sessionType, int maxGamers, int privateGamerSlots)
  181. {
  182. if (disposed)
  183. throw new ObjectDisposedException(nameof(UdpNetworkSession));
  184. var newSession = await NetworkSession.CreateAsync(
  185. sessionType,
  186. maxLocalGamers: 1,
  187. maxGamers: maxGamers,
  188. privateGamerSlots: privateGamerSlots,
  189. sessionProperties: null
  190. );
  191. // Replace inner session
  192. ReplaceInnerSession(newSession);
  193. }
  194. /// <summary>
  195. /// Joins an existing session.
  196. /// </summary>
  197. public async System.Threading.Tasks.Task JoinAsync(string hostAddress)
  198. {
  199. if (disposed)
  200. throw new ObjectDisposedException(nameof(UdpNetworkSession));
  201. // Parse hostAddress to find the session
  202. var availableSessions = await NetworkSession.FindAsync(
  203. NetworkSessionType.SystemLink,
  204. maxLocalGamers: 1,
  205. sessionProperties: null
  206. );
  207. var session = availableSessions.FirstOrDefault(s => s.HostEndpoint?.ToString() == hostAddress);
  208. if (session != null)
  209. {
  210. var newSession = await NetworkSession.JoinAsync(session);
  211. ReplaceInnerSession(newSession);
  212. }
  213. else
  214. {
  215. throw new InvalidOperationException($"Session not found: {hostAddress}");
  216. }
  217. }
  218. /// <summary>
  219. /// Sends a message to a specific gamer.
  220. /// </summary>
  221. public void SendMessage(INetworkMessage message, INetworkGamer recipient)
  222. {
  223. if (disposed)
  224. throw new ObjectDisposedException(nameof(UdpNetworkSession));
  225. if (innerSession == null)
  226. throw new InvalidOperationException("Session not initialized");
  227. // Find the underlying NetworkGamer and use session's SendDataToGamer
  228. var networkGamer = innerSession.AllGamers.FirstOrDefault(g => g.Id == recipient.Id);
  229. if (networkGamer != null)
  230. {
  231. var writer = new PacketWriter();
  232. message.Serialize(writer);
  233. innerSession.SendDataToGamer(networkGamer, writer, SendDataOptions.Reliable);
  234. }
  235. }
  236. /// <summary>
  237. /// Broadcasts a message to all remote gamers.
  238. /// </summary>
  239. public void BroadcastMessage(INetworkMessage message)
  240. {
  241. if (disposed)
  242. throw new ObjectDisposedException(nameof(UdpNetworkSession));
  243. if (innerSession == null)
  244. throw new InvalidOperationException("Session not initialized");
  245. // Broadcast to all remote gamers using SendToAll
  246. var writer = new PacketWriter();
  247. message.Serialize(writer);
  248. innerSession.SendToAll(writer, SendDataOptions.Reliable);
  249. }
  250. /// <summary>
  251. /// Updates the session (processes incoming messages, etc.).
  252. /// </summary>
  253. public void Update(GameTime gameTime)
  254. {
  255. if (disposed || innerSession == null)
  256. return;
  257. // The underlying NetworkSession handles updates internally
  258. // This is a pass-through for interface compliance
  259. }
  260. /// <summary>
  261. /// Closes the session and disconnects.
  262. /// </summary>
  263. public async System.Threading.Tasks.Task CloseAsync()
  264. {
  265. if (disposed || innerSession == null)
  266. return;
  267. await innerSession.DisposeAsync();
  268. }
  269. /// <summary>
  270. /// Disposes the session.
  271. /// </summary>
  272. public void Dispose()
  273. {
  274. if (disposed)
  275. return;
  276. innerSession?.Dispose();
  277. gamerAdapters.Clear();
  278. disposed = true;
  279. }
  280. // Helper methods
  281. private INetworkGamer AdaptGamer(NetworkGamer gamer)
  282. {
  283. if (gamer == null) return null;
  284. if (!gamerAdapters.TryGetValue(gamer.Id, out var adapter))
  285. {
  286. adapter = gamer.IsLocal
  287. ? (INetworkGamer)new LocalNetworkGamerAdapter(gamer as LocalNetworkGamer)
  288. : new NetworkGamerAdapter(gamer);
  289. gamerAdapters[gamer.Id] = adapter;
  290. }
  291. return adapter;
  292. }
  293. private void ReplaceInnerSession(NetworkSession newSession)
  294. {
  295. if (innerSession != null)
  296. {
  297. innerSession.Dispose();
  298. }
  299. // Create a new adapter with the new session
  300. // Note: This is a workaround due to the private constructor
  301. typeof(UdpNetworkSession)
  302. .GetField("innerSession", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
  303. ?.SetValue(this, newSession);
  304. gamerAdapters.Clear();
  305. localGamerAdapter = null;
  306. }
  307. }
  308. /// <summary>
  309. /// Adapter for NetworkGamer to implement INetworkGamer.
  310. /// </summary>
  311. internal class NetworkGamerAdapter : INetworkGamer
  312. {
  313. private readonly NetworkGamer innerGamer;
  314. internal NetworkGamerAdapter(NetworkGamer gamer)
  315. {
  316. this.innerGamer = gamer ?? throw new ArgumentNullException(nameof(gamer));
  317. }
  318. public string Id => innerGamer.Id;
  319. public string Gamertag => innerGamer.Gamertag;
  320. public bool IsLocal => innerGamer.IsLocal;
  321. public bool IsHost => innerGamer.IsHost;
  322. public bool IsReady
  323. {
  324. get => innerGamer.IsReady;
  325. set => innerGamer.IsReady = value;
  326. }
  327. public TimeSpan RoundtripTime => innerGamer.RoundtripTime;
  328. public object Tag
  329. {
  330. get => innerGamer.Tag;
  331. set => innerGamer.Tag = value;
  332. }
  333. }
  334. /// <summary>
  335. /// Adapter for LocalNetworkGamer to implement ILocalNetworkGamer.
  336. /// </summary>
  337. internal class LocalNetworkGamerAdapter : NetworkGamerAdapter, ILocalNetworkGamer
  338. {
  339. private readonly LocalNetworkGamer innerLocalGamer;
  340. internal LocalNetworkGamerAdapter(LocalNetworkGamer gamer)
  341. : base(gamer)
  342. {
  343. this.innerLocalGamer = gamer ?? throw new ArgumentNullException(nameof(gamer));
  344. }
  345. bool INetworkGamer.IsHost => innerLocalGamer.IsHost;
  346. bool INetworkGamer.IsReady
  347. {
  348. get => innerLocalGamer.IsReady;
  349. set => innerLocalGamer.IsReady = value;
  350. }
  351. bool ILocalNetworkGamer.IsHost
  352. {
  353. get => innerLocalGamer.IsHost;
  354. set
  355. {
  356. // IsHost is readonly in NetworkGamer - it's set during construction
  357. // This setter exists for interface compatibility but cannot modify the value
  358. if (value != innerLocalGamer.IsHost)
  359. {
  360. throw new InvalidOperationException("Cannot change IsHost after session creation. Host status is determined at session creation time.");
  361. }
  362. }
  363. }
  364. bool ILocalNetworkGamer.IsReady
  365. {
  366. get => innerLocalGamer.IsReady;
  367. set => innerLocalGamer.IsReady = value;
  368. }
  369. }
  370. }