From: Charles Date: Sun, 21 Mar 2010 03:04:44 +0000 (+0000) Subject: Switched to using the asynchronous NetworkSession calls per request. X-Git-Url: https://git.dogcows.com/gitweb?a=commitdiff_plain;h=fc7104b36d66d536d38ba38a4afae588c437f6c6;p=chaz%2Fcarfire Switched to using the asynchronous NetworkSession calls per request. - There are some new states and callback methods in LobbyGUI that need to be implemented correctly. Documented the NetworkGame class. Started work on the network protocol implementation. - It's currently non-functional; no chatting yet! git-svn-id: https://bd85.net/svn/cs3505_group@27 92bb83a3-7c8f-8a45-bc97-515c4e399668 --- diff --git a/Project06/CS 3505 Project 06/CS 3505 Project 06/CS 3505 Project 06.csproj b/Project06/CS 3505 Project 06/CS 3505 Project 06/CS 3505 Project 06.csproj index d0949de..99e7d27 100644 --- a/Project06/CS 3505 Project 06/CS 3505 Project 06/CS 3505 Project 06.csproj +++ b/Project06/CS 3505 Project 06/CS 3505 Project 06/CS 3505 Project 06.csproj @@ -84,6 +84,7 @@ + diff --git a/Project06/CS 3505 Project 06/CS 3505 Project 06/Game06.cs b/Project06/CS 3505 Project 06/CS 3505 Project 06/Game06.cs index c6f21f6..348655e 100644 --- a/Project06/CS 3505 Project 06/CS 3505 Project 06/Game06.cs +++ b/Project06/CS 3505 Project 06/CS 3505 Project 06/Game06.cs @@ -35,12 +35,11 @@ namespace CS_3505_Project_06 graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; - // Make the game object. The game is currently called 'testHarness'. + Components.Add(new GamerServicesComponent(this)); lobby = new lobbyGUI(); deterministicGame = new TestHarness(); networkGame = new NetworkGame(lobby, deterministicGame); - Components.Add(new GamerServicesComponent(this)); } /// @@ -56,8 +55,7 @@ namespace CS_3505_Project_06 IsFixedTimeStep = true; TargetElapsedTime = networkGame.TargetTimeSpan; - // For debugging - reset the mouse position to the center of the window. - + // DEBUG: This is for the test harness. Mouse.SetPosition(400, 300); // Allow the base class to initialize. @@ -75,8 +73,6 @@ namespace CS_3505_Project_06 spriteBatch = new SpriteBatch(GraphicsDevice); - networkGame.font = Content.Load("InstructionFont"); - lobby.LoadContent(Content, graphics); deterministicGame.LoadContent(Content); } @@ -110,7 +106,8 @@ namespace CS_3505_Project_06 /// Provides a snapshot of timing values. protected override void Draw(GameTime gameTime) { - GraphicsDevice.Clear(new Color(16, 16, 16, 255)); // Needed by the test harness, should be removed for the real game. + // DEBUG: This is for the test harness. + GraphicsDevice.Clear(new Color(16, 16, 16, 255)); spriteBatch.Begin(); diff --git a/Project06/CS 3505 Project 06/CS 3505 Project 06/LobbyGUI.cs b/Project06/CS 3505 Project 06/CS 3505 Project 06/LobbyGUI.cs index b898fcc..3c157dc 100644 --- a/Project06/CS 3505 Project 06/CS 3505 Project 06/LobbyGUI.cs +++ b/Project06/CS 3505 Project 06/CS 3505 Project 06/LobbyGUI.cs @@ -66,6 +66,8 @@ namespace CS_3505_Project_06 Welcome, CreateGame, FindGame, + FindingGames, // TODO: New state. + JoiningGame, // TODO: New state. Connected } @@ -127,13 +129,49 @@ namespace CS_3505_Project_06 } + // TODO: New method. + void JoinedSession(NetworkSession session, NetworkGame networkGame) + { + if (session != null) + { + currentState = lobbyState.Connected; + } + else + { + // TODO: This should do something more than just throw the player back to the welcome screen. + currentState = lobbyState.Welcome; + Console.WriteLine("Couldn't create/join the session."); + } + } + + // TODO: New method. + void FoundSessions(AvailableNetworkSessionCollection sessions, NetworkGame networkGame) + { + availableSessions = sessions; + + if (availableSessions != null && availableSessions.Count > 0) + { + networkGame.JoinSession(availableSessions[0], JoinedSession); + currentState = lobbyState.JoiningGame; + + availableSessions.Dispose(); + availableSessions = null; + } + else + { + // TODO: This should do something more than just throw the player back to the welcome screen. + currentState = lobbyState.Welcome; + Console.WriteLine("No sessions to join!"); + } + } + public long Update(GameTime gameTime, NetworkGame networkGame) { UpdateSpotLight(gameTime); currentKeyboardState = Keyboard.GetState(); - if (networkGame.sessionExists()) + if (networkGame.HasActiveSession) { players = networkGame.NetworkGamers; } @@ -163,7 +201,7 @@ namespace CS_3505_Project_06 { currentState = lobbyState.Welcome; ready = false; - if (networkGame.sessionExists()) + if (networkGame.HasActiveSession) { players = null; networkGame.LeaveSession(); @@ -171,8 +209,8 @@ namespace CS_3505_Project_06 } if (currentKeyboardState.IsKeyDown(Keys.Y) && previousKeyboardState.IsKeyUp(Keys.Y)) { - currentState = lobbyState.Connected; - networkGame.CreateSession(4); + currentState = lobbyState.JoiningGame; + networkGame.CreateSession(JoinedSession); } break; @@ -182,22 +220,15 @@ namespace CS_3505_Project_06 currentState = lobbyState.Welcome; ready = false; } - availableSessions = networkGame.FindSessions(); - if (availableSessions != null) - { - networkGame.JoinSession(availableSessions[0]); - currentState = lobbyState.Connected; - - availableSessions.Dispose(); - availableSessions = null; - } + networkGame.FindSessions(FoundSessions); + currentState = lobbyState.FindingGames; break; case lobbyState.Connected: if (currentKeyboardState.IsKeyDown(Keys.X) && previousKeyboardState.IsKeyUp(Keys.X)) { ready = false; - if (networkGame.sessionExists()) + if (networkGame.HasActiveSession) { players = null; networkGame.LeaveSession(); @@ -207,7 +238,7 @@ namespace CS_3505_Project_06 if (currentKeyboardState.IsKeyDown(Keys.R) && previousKeyboardState.IsKeyUp(Keys.R)) networkGame.LocalGamer.IsReady = true; - if (networkGame.sessionExists()) + if (networkGame.HasActiveSession) { localPlayer = networkGame.LocalGamer; players = networkGame.NetworkGamers; @@ -273,7 +304,7 @@ namespace CS_3505_Project_06 break; - case lobbyState.FindGame: + case lobbyState.FindingGames: spriteBatch.Draw(selectGameScreen, backgroundPos, null, Color.White, 0, zero, scale, SpriteEffects.None, 0); if(availableSessions == null) spriteBatch.DrawString(menuFont, "searching for available games ....", new Vector2(150, 100), Color.Gray, 0f, zero, .7f, SpriteEffects.None, 0.5f); diff --git a/Project06/CS 3505 Project 06/CS 3505 Project 06/NetworkGame.cs b/Project06/CS 3505 Project 06/CS 3505 Project 06/NetworkGame.cs index 3d46d56..95d843e 100644 --- a/Project06/CS 3505 Project 06/CS 3505 Project 06/NetworkGame.cs +++ b/Project06/CS 3505 Project 06/CS 3505 Project 06/NetworkGame.cs @@ -11,12 +11,30 @@ using Microsoft.Xna.Framework.Input; namespace CS_3505_Project_06 { + /// + /// A manager class to handle network interactions between peers and + /// lobby/game switching. + /// public class NetworkGame { + // Private class variable members + #region Instance Variables + NetworkSession mNetworkSession; + JoinedSessionDelegate mJoinedSessionDelegate; + FoundSessionsDelegate mFoundSessionsDelegate; + ILobby mLobby; IDeterministicGame mGame; + + List mLastPressedKeys = new List(); + bool mLastButtonPressed; + + int mLatency; + long mNextLatencyAdjustmentFrame; + int mStallCount; + int mAverageOwd; TimeSpan mTargetTimeSpan = new TimeSpan(166666); public TimeSpan TargetTimeSpan @@ -27,48 +45,53 @@ namespace CS_3505_Project_06 } } - List lastPressedKeys; - bool lastButtonPressed; - - Object[] playerIdentifiers = { "One", "Two", "Three", "Four" }; // Any objects will do, strings are easy to debug. + #endregion - // For debugging - Object activePlayer; - bool paused; - long lastAutoPause; + /// + /// Called when a session has been created or joined using CreateSession() or JoinSession(). + /// + /// The new session that was created or joined. + /// The NetworkGame that joined the session. + public delegate void JoinedSessionDelegate(NetworkSession session, NetworkGame networkGame); - public SpriteFont font; + /// + /// Called when sessions are found as a result of calling FindSessions(). + /// + /// A container of the available sessions. + /// The NetworkGame that searched for the sessions. + public delegate void FoundSessionsDelegate(AvailableNetworkSessionCollection sessions, NetworkGame networkGame); + /// + /// Construct a NetworkGame with a lobby and a game. + /// + /// Provides an associated lobby to update and draw. + /// Provides a game object to be played over the network. public NetworkGame(ILobby lobby, IDeterministicGame game) { Debug.Assert(lobby != null && game != null); mLobby = lobby; mGame = game; - - // Begin: Test harness stuff - lastPressedKeys = new List(); - activePlayer = playerIdentifiers[0]; - paused = false; - - // Reset the game - indicate that player #1 (player 0) owns this instance of the game. - - mGame.ResetGame(playerIdentifiers, playerIdentifiers[0]); } + /// + /// Get the Gamer object for the local player. + /// public LocalNetworkGamer LocalGamer { get { + // TODO: Is this the correct way to get the single local gamer? return mNetworkSession.LocalGamers[0]; } } - // I added this as I needed a way to display all gamers not just the first gamer - // -Brady + /// + /// Get all the gamers associated with the active network session. + /// public GamerCollection NetworkGamers { get @@ -78,53 +101,98 @@ namespace CS_3505_Project_06 } - public NetworkSession CreateSession() + /// + /// Begin a new network session with the local gamer as the host. You must not + /// call this method or use JoinSession without first using LeaveSession. + /// + /// The delegate/method to call when the session is created. + public void CreateSession(JoinedSessionDelegate callback) { - return CreateSession(mGame.MaximumSupportedPlayers); + CreateSession(mGame.MaximumSupportedPlayers, callback); } - public NetworkSession CreateSession(int maxGamers) + /// + /// Begin a new network session with the local gamer as the host. You must not + /// call this method or use JoinSession without first using LeaveSession. + /// + /// Provide the maximum number of players allowed to connect. + /// The delegate/method to call when the session is created. + public void CreateSession(int maxGamers, JoinedSessionDelegate callback) { - Debug.Assert(mNetworkSession == null); + Debug.Assert(mNetworkSession == null); - mNetworkSession = NetworkSession.Create(NetworkSessionType.SystemLink, 1, maxGamers); + mJoinedSessionDelegate = callback; + NetworkSession.BeginCreate(NetworkSessionType.SystemLink, 1, maxGamers, CreateSessionEnd, null); + } + private void CreateSessionEnd(IAsyncResult result) + { + Debug.Assert(mNetworkSession == null); + + mNetworkSession = NetworkSession.EndCreate(result); mNetworkSession.AllowHostMigration = true; mNetworkSession.AllowJoinInProgress = false; - return mNetworkSession; + mJoinedSessionDelegate(mNetworkSession, this); } - // added so I can test if sessionExists and thus be able to call things on NetworkGame safely - // -Brady - public bool sessionExists() + /// + /// Determine whether or not the network game object is associated with any network session. + /// + /// True if there exists a NetworkSession; false otherwise. + public bool HasActiveSession { - return mNetworkSession != null; + get + { + return mNetworkSession != null; + } } - public AvailableNetworkSessionCollection FindSessions() + + /// + /// Find available sessions to join. + /// + /// The delegate/method to call when the search finishes. + public void FindSessions(FoundSessionsDelegate callback) { - return NetworkSession.Find(NetworkSessionType.SystemLink, 1, new NetworkSessionProperties()); + Debug.Assert(mNetworkSession == null); + + mFoundSessionsDelegate = callback; + NetworkSession.BeginFind(NetworkSessionType.SystemLink, 1, null, new AsyncCallback(FindSessionsEnd), null); + } + private void FindSessionsEnd(IAsyncResult result) + { + AvailableNetworkSessionCollection sessions = NetworkSession.EndFind(result); + mFoundSessionsDelegate(sessions, this); } - public NetworkSession JoinSession(AvailableNetworkSession availableSession) + /// + /// Join a network session found using FindSessions(). This is for joining a game that + /// somebody else has already started hosting. + /// + /// Pass the session object to try to join. + /// The delegate/method to call when the search finishes. + public void JoinSession(AvailableNetworkSession availableSession, JoinedSessionDelegate callback) { Debug.Assert(mNetworkSession == null); - mNetworkSession = NetworkSession.Join(availableSession); - - return mNetworkSession; + mJoinedSessionDelegate = callback; + NetworkSession.BeginJoin(availableSession, JoinSessionEnd, null); } - - // added to begin the game. I made the LobbyGUI make sure that only the host will call it when everyone is ready. - // This is already taken care of in the update method below. But it may be nice to allow the host to signal the start - // rather then having it start automatically. Just a suggestion. - // -Brady - public void StartGame() + private void JoinSessionEnd(IAsyncResult result) { - mNetworkSession.StartGame(); - mNetworkSession.ResetReady(); + Debug.Assert(mNetworkSession == null); + + mNetworkSession = NetworkSession.EndJoin(result); + + mJoinedSessionDelegate(mNetworkSession, this); + mJoinedSessionDelegate = null; } + + /// + /// Leave and dispose of any currently associated network session. You will find yourself + /// back in the lobby. + /// public void LeaveSession() { Debug.Assert(mNetworkSession != null); @@ -134,6 +202,9 @@ namespace CS_3505_Project_06 } + /// + /// Set up the network session to simulate 200ms latency and 10% packet loss. + /// public void SimulateBadNetwork() { Debug.Assert(mNetworkSession != null); @@ -143,6 +214,23 @@ namespace CS_3505_Project_06 } + /// + /// Indicate that the game should begin (moving players from the lobby to the game). + /// You must call CreateSession() before calling this. + /// + public void StartGame() + { + Debug.Assert(mNetworkSession != null && mNetworkSession.IsHost); + + mNetworkSession.StartGame(); + mNetworkSession.ResetReady(); + } + + + /// + /// Manages the network session and allows either the lobby or game to update. + /// + /// Pass the time away. public void Update(GameTime gameTime) { if (mNetworkSession == null) @@ -152,6 +240,7 @@ namespace CS_3505_Project_06 else { mNetworkSession.Update(); + ReadPackets(); if (mNetworkSession.SessionState == NetworkSessionState.Lobby) { @@ -169,123 +258,227 @@ namespace CS_3505_Project_06 } else if (mNetworkSession.SessionState == NetworkSessionState.Playing) { - // TODO: in-game update stuff - UpdateTestHarness(gameTime); - - mGame.Update(mTargetTimeSpan); + if (HaveNeededEvents) + { + if (IsLatencyAdjustmentFrame) AdjustLatency(); + mStallCount = 0; + SendLocalEvents(); + ApplyEvents(); + mGame.Update(mTargetTimeSpan); + } + else // Stall! + { + } } } } + /// + /// Allows either the lobby or the game to draw, depending on the state + /// of the network connection and whether or not a game is in progress. + /// + /// Pass the time away. + /// The sprite batch. public void Draw(GameTime gameTime, SpriteBatch spriteBatch) { - if (mNetworkSession != null) + if (mNetworkSession == null) { - if (mNetworkSession.SessionState == NetworkSessionState.Playing) - DrawTestHarness(gameTime, spriteBatch); - else - mLobby.Draw(spriteBatch); + mLobby.Draw(spriteBatch); } else - mLobby.Draw(spriteBatch); - + { + if (mNetworkSession.SessionState == NetworkSessionState.Lobby) + { + mLobby.Draw(spriteBatch); + } + else if (mNetworkSession.SessionState == NetworkSessionState.Playing) + { + mLobby.Draw(spriteBatch); + } + } } + // Private implementation methods of the network protocol + #region Private Implementation Methods + + /// + /// Reinitialize the private variables in preparation for new game to start. + /// + private void Reset() + { + mLatency = 1; + mNextLatencyAdjustmentFrame = 1; + mStallCount = 0; + mAverageOwd = AverageOneWayDelay; + // TODO: The game object needs to be reset, too. + //mGame.ResetGame(playerIdentifiers, playerIdentifiers[0]); + } - void UpdateTestHarness(GameTime gameTime) + + /// + /// Allows either the lobby or the game to draw, depending on the state + /// of the network connection and whether or not a game is in progress. + /// + /// Pass the time away. + /// The sprite batch. + private void ReadPackets() { - // Get user's input state. + PacketReader packetReader = new PacketReader(); - KeyboardState keyState = Keyboard.GetState(); - MouseState mouseState = Mouse.GetState(); + foreach (LocalNetworkGamer gamer in mNetworkSession.LocalGamers) + { + while (gamer.IsDataAvailable) + { + NetworkGamer sender; - // Make a list of the keys pressed or released this frame. + gamer.ReceiveData(packetReader, out sender); + byte packetId = packetReader.ReadByte(); - List pressedKeys = new List(); - List releasedKeys = new List(); + switch (packetId) + { + // Chat Packet + case 1: + short messageLength = packetReader.ReadInt16(); + char[] message = packetReader.ReadChars(messageLength); + + ChatPacket chatPacket; + chatPacket.sender = sender; + chatPacket.message = new String(message); + break; + + // Event Packet + case 2: + short stallCount = packetReader.ReadInt16(); + short averageOwd = packetReader.ReadInt16(); + int frameNumber = packetReader.ReadInt32(); + byte numEvents = packetReader.ReadByte(); + + for (byte i = 0; i < numEvents; ++i) + { + ReadEvent(packetReader, sender); + } + + break; + + // Stall Packet + case 3: + byte numStalledPeers = packetReader.ReadByte(); + byte[] stalledPeers = packetReader.ReadBytes(numStalledPeers); + + break; + } + } + } + } - Keys[] pressedKeysArray = keyState.GetPressedKeys(); - foreach (Keys k in pressedKeysArray) - if (!lastPressedKeys.Contains(k)) - pressedKeys.Add(k); - else - lastPressedKeys.Remove(k); + private void ReadEvent(PacketReader packetReader, NetworkGamer sender) + { + byte eventId = packetReader.ReadByte(); + long applicationFrame = packetReader.ReadInt32(); - releasedKeys = lastPressedKeys; - lastPressedKeys = new List(pressedKeysArray); + switch (eventId) + { + // Key Down + case 1: + int keyCode1 = packetReader.ReadInt32(); - // Get mouse button state. + break; - bool buttonPressed = mouseState.LeftButton == ButtonState.Pressed; + // Key Up + case 2: + int keyCode2 = packetReader.ReadInt32(); - /***** Begining of game logic. *****/ + break; - // Debug - allow user on this machine to direct input to any player's state in the game. + // Mouse Down + case 3: + byte buttonId1 = packetReader.ReadByte(); - if (pressedKeys.Contains(Keys.F1)) activePlayer = playerIdentifiers[0]; - if (pressedKeys.Contains(Keys.F2)) activePlayer = playerIdentifiers[1]; - if (pressedKeys.Contains(Keys.F3)) activePlayer = playerIdentifiers[2]; - if (pressedKeys.Contains(Keys.F4)) activePlayer = playerIdentifiers[3]; + break; - // Debug - allow user on this machine to pause/resume game state advances. + // Mouse Up + case 4: + byte buttonId2 = packetReader.ReadByte(); - if (pressedKeys.Contains(Keys.F12) || - pressedKeys.Contains(Keys.P) && (keyState.IsKeyDown(Keys.LeftControl) || keyState.IsKeyDown(Keys.RightControl))) - { - paused = !paused; - return; // Don't update on pause start or stop + break; + + // Mouse Move + case 5: + short x = packetReader.ReadInt16(); + short y = packetReader.ReadInt16(); + + break; } + } - // Debug - automatically pause every 1000 frames. - if (mGame.CurrentFrameNumber % 1000 == 0 && mGame.CurrentFrameNumber != lastAutoPause) + private bool IsLatencyAdjustmentFrame + { + get { - paused = true; - lastAutoPause = mGame.CurrentFrameNumber; + // TODO + return false; } + } + + private void AdjustLatency() + { + // TODO + } - //if (pressedKeys.Contains(Keys.Escape)) - // this.Exit(); + private void SendLocalEvents() + { + // TODO: Not finished. - // Game update + KeyboardState keyState = Keyboard.GetState(); + MouseState mouseState = Mouse.GetState(); - // Direct inputs to the game engine - only report changes. + // Make a list of the keys pressed or released this frame. - foreach (Keys k in pressedKeys) - mGame.ApplyKeyInput(activePlayer, k, true); + List pressedKeys = new List(); + List releasedKeys = new List(); - foreach (Keys k in releasedKeys) - mGame.ApplyKeyInput(activePlayer, k, false); + Keys[] pressedKeysArray = keyState.GetPressedKeys(); + foreach (Keys k in pressedKeysArray) + if (!mLastPressedKeys.Contains(k)) + pressedKeys.Add(k); + else + mLastPressedKeys.Remove(k); - mGame.ApplyMouseLocationInput(activePlayer, mouseState.X, mouseState.Y); + releasedKeys = mLastPressedKeys; + mLastPressedKeys = new List(pressedKeysArray); - if (lastButtonPressed != buttonPressed) - mGame.ApplyMouseButtonInput(activePlayer, buttonPressed); + bool buttonPressed = mouseState.LeftButton == ButtonState.Pressed; + } - lastButtonPressed = buttonPressed; - if (!paused) + private bool HaveNeededEvents + { + get { - // Advance the game engine. - - mGame.Update(mTargetTimeSpan); + // TODO + return true; } } - void DrawTestHarness(GameTime gameTime, SpriteBatch spriteBatch) + private void ApplyEvents() { + // TODO + } - // BEGIN: Test harness stuff. - if (paused && gameTime.TotalRealTime.Milliseconds < 500) - spriteBatch.DrawString(font, "-=> Paused <=-", new Vector2(10, 130), Color.White); - - spriteBatch.DrawString(font, "Press [F1]...[F4] to simulate input for each player. Click X's to end game or terminate player.", new Vector2(10, 540), Color.White); - spriteBatch.DrawString(font, "Press [ESC] to exit and [F12] to pause/unpause. Game auto-pauses every 1000 frames.", new Vector2(10, 570), Color.White); - //END: Test harness stuff. + private int AverageOneWayDelay + { + get + { + // TODO + return 12; + } } + + #endregion } }