diff --git a/ClassicalSharp/2D/Screens/ChatScreen.cs b/ClassicalSharp/2D/Screens/ChatScreen.cs index 54a9fbfe2..b858fe75c 100644 --- a/ClassicalSharp/2D/Screens/ChatScreen.cs +++ b/ClassicalSharp/2D/Screens/ChatScreen.cs @@ -347,7 +347,8 @@ namespace ClassicalSharp.Gui.Screens { if (HandlesAllInput) { // text input bar if (key == game.Mapping(KeyBind.SendChat) || key == Key.KeypadEnter || key == game.Mapping(KeyBind.PauseOrExit)) { SetHandlesAllInput(false); - game.CursorVisible = false; + // when underlying screen is HUD, user is interacting with the world normally + game.CursorVisible = game.Gui.UnderlyingScreen != game.Gui.hudScreen; game.Camera.RegrabMouse(); game.Keyboard.KeyRepeat = false; diff --git a/ClassicalSharp/2D/Screens/DisconnectScreen.cs b/ClassicalSharp/2D/Screens/DisconnectScreen.cs index ab1c09943..5d6cc6a5d 100644 --- a/ClassicalSharp/2D/Screens/DisconnectScreen.cs +++ b/ClassicalSharp/2D/Screens/DisconnectScreen.cs @@ -74,8 +74,8 @@ namespace ClassicalSharp.Gui.Screens { if (!reconnect.Disabled && reconnect.Contains(mouseX, mouseY)) { string connect = "Connecting to " + game.IPAddress + ":" + game.Port + ".."; - game.Gui.SetNewScreen(new LoadingMapScreen(game, connect, "")); - game.Server.Connect(game.IPAddress, game.Port); + game.Gui.SetNewScreen(new LoadingScreen(game, connect, "")); + game.Server.BeginConnect(); } return true; } diff --git a/ClassicalSharp/2D/Screens/LoadingMapScreen.cs b/ClassicalSharp/2D/Screens/LoadingMapScreen.cs index 3f75e8e08..2cc93b236 100644 --- a/ClassicalSharp/2D/Screens/LoadingMapScreen.cs +++ b/ClassicalSharp/2D/Screens/LoadingMapScreen.cs @@ -10,10 +10,10 @@ using ClassicalSharp.Textures; using OpenTK.Input; namespace ClassicalSharp.Gui.Screens { - public class LoadingMapScreen : Screen { + public class LoadingScreen : Screen { readonly Font font; - public LoadingMapScreen(Game game, string title, string message) : base(game) { + public LoadingScreen(Game game, string title, string message) : base(game) { this.title = title; this.message = message; font = new Font(game.FontName, 16); @@ -34,7 +34,7 @@ namespace ClassicalSharp.Gui.Screens { game.Graphics.Fog = false; ContextRecreated(); - game.WorldEvents.MapLoading += MapLoading; + game.WorldEvents.Loading += Loading; game.Graphics.ContextLost += ContextLost; game.Graphics.ContextRecreated += ContextRecreated; } @@ -63,7 +63,7 @@ namespace ClassicalSharp.Gui.Screens { font.Dispose(); ContextLost(); - game.WorldEvents.MapLoading -= MapLoading; + game.WorldEvents.Loading -= Loading; game.Graphics.ContextLost -= ContextLost; game.Graphics.ContextRecreated -= ContextRecreated; } @@ -73,7 +73,7 @@ namespace ClassicalSharp.Gui.Screens { titleWidget.Reposition(); } - void MapLoading(object sender, MapLoadingEventArgs e) { + void Loading(object sender, LoadingEventArgs e) { progress = e.Progress; } @@ -160,7 +160,7 @@ namespace ClassicalSharp.Gui.Screens { } } - public class GeneratingMapScreen : LoadingMapScreen { + public class GeneratingMapScreen : LoadingScreen { string lastState; IMapGenerator gen; diff --git a/ClassicalSharp/Events/WorldEvents.cs b/ClassicalSharp/Events/WorldEvents.cs index 86f30e4ec..6b40af213 100644 --- a/ClassicalSharp/Events/WorldEvents.cs +++ b/ClassicalSharp/Events/WorldEvents.cs @@ -10,8 +10,8 @@ namespace ClassicalSharp.Events { public void RaiseOnNewMap() { Raise(OnNewMap); } /// Raised when a portion of the world is read and decompressed, or generated. - public event EventHandler MapLoading; - public void RaiseMapLoading(float progress) { loadingArgs.Progress = progress; Raise(MapLoading, loadingArgs); } + public event EventHandler Loading; + public void RaiseLoading(float progress) { loadingArgs.Progress = progress; Raise(Loading, loadingArgs); } /// Raised when new world has finished loading and the player can now interact with it. public event EventHandler OnNewMapLoaded; @@ -21,11 +21,11 @@ namespace ClassicalSharp.Events { public event EventHandler EnvVariableChanged; public void RaiseEnvVariableChanged(EnvVar envVar) { envArgs.Var = envVar; Raise(EnvVariableChanged, envArgs); } - MapLoadingEventArgs loadingArgs = new MapLoadingEventArgs(); + LoadingEventArgs loadingArgs = new LoadingEventArgs(); EnvVarEventArgs envArgs = new EnvVarEventArgs(); } - public sealed class MapLoadingEventArgs : EventArgs { + public sealed class LoadingEventArgs : EventArgs { /// Percentage of the map that has been fully decompressed, or generated. public float Progress; diff --git a/ClassicalSharp/Game/Game.Init.cs b/ClassicalSharp/Game/Game.Init.cs index 6fe1fbc9d..29b854f12 100644 --- a/ClassicalSharp/Game/Game.Init.cs +++ b/ClassicalSharp/Game/Game.Init.cs @@ -162,8 +162,8 @@ namespace ClassicalSharp { MapBordersRenderer.UseLegacyMode(true); EnvRenderer.UseLegacyMode(true); } - Gui.SetNewScreen(new LoadingMapScreen(this, connectString, "")); - Server.Connect(IPAddress, Port); + Gui.SetNewScreen(new LoadingScreen(this, connectString, "")); + Server.BeginConnect(); } void ExtractInitialTexturePack() { diff --git a/ClassicalSharp/Network/IServerConnection.cs b/ClassicalSharp/Network/IServerConnection.cs index f6d9ef58b..c8ea08a31 100644 --- a/ClassicalSharp/Network/IServerConnection.cs +++ b/ClassicalSharp/Network/IServerConnection.cs @@ -21,8 +21,8 @@ namespace ClassicalSharp { public bool IsSinglePlayer; - /// Opens a connection to the given IP address and port, and prepares the initial state of the client. - public abstract void Connect(IPAddress address, int port); + /// Opens a connection to the server, and prepares the initial state of the client. + public abstract void BeginConnect(); public abstract void SendChat(string text); diff --git a/ClassicalSharp/Network/NetworkProcessor.cs b/ClassicalSharp/Network/NetworkProcessor.cs index 120549194..1477eeecc 100644 --- a/ClassicalSharp/Network/NetworkProcessor.cs +++ b/ClassicalSharp/Network/NetworkProcessor.cs @@ -31,27 +31,52 @@ namespace ClassicalSharp.Network { public NetReader reader; public NetWriter writer; + bool connecting = false; + DateTime connectTimeout; + const int timeoutSecs = 15; + internal ClassicProtocol classic; internal CPEProtocol cpe; internal CPEProtocolBlockDefs cpeBlockDefs; internal WoMProtocol wom; internal CPESupport cpeData; - public override void Connect(IPAddress address, int port) { - socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + public override void BeginConnect() { + socket = new Socket(game.IPAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); game.UserEvents.BlockChanged += BlockChanged; Disconnected = false; - try { - socket.Connect(address, port); - } catch (SocketException ex) { - ErrorHandler.LogError("connecting to server", ex); - game.Disconnect("Failed to connect to " + address + ":" + port, - "You failed to connect to the server. It's probably down!"); - Dispose(); - return; - } + socket.Blocking = false; + connecting = true; + connectTimeout = DateTime.UtcNow.AddSeconds(timeoutSecs); + try { + socket.Connect(game.IPAddress, game.Port); + } catch (SocketException ex) { + SocketError err = ex.SocketErrorCode; + if (err == SocketError.InProgress || err == SocketError.WouldBlock) return; + + ErrorHandler.LogError("connecting to server", ex); + FailConnect(); + } + } + + void TickConnect() { + DateTime now = DateTime.UtcNow; + if (socket.Connected) { + socket.Blocking = true; + FinishConnect(); + } else if (now > connectTimeout) { + FailConnect(); + } else { + double leftSecs = (connectTimeout - now).TotalSeconds; + game.WorldEvents.RaiseLoading((float)leftSecs / timeoutSecs); + } + } + + void FinishConnect() { + connecting = false; + game.WorldEvents.RaiseLoading(0); reader = new NetReader(socket); writer = new NetWriter(socket); @@ -66,8 +91,15 @@ namespace ClassicalSharp.Network { lastPacket = DateTime.UtcNow; } + void FailConnect() { + connecting = false; + game.Disconnect("Failed to connect to " + game.IPAddress + ":" + game.Port, + "You failed to connect to the server. It's probably down!"); + Dispose(); + } + public override void SendChat(string text) { - if (String.IsNullOrEmpty(text)) return; + if (String.IsNullOrEmpty(text) || connecting) return; while (text.Length > Utils.StringLength) { classic.WriteChat(text.Substring(0, Utils.StringLength), true); @@ -97,6 +129,8 @@ namespace ClassicalSharp.Network { public override void Tick(ScheduledTask task) { if (Disconnected) return; + if (connecting) { TickConnect(); return; } + if ((DateTime.UtcNow - lastPacket).TotalSeconds >= 30) { CheckDisconnection(task.Interval); } @@ -188,14 +222,14 @@ namespace ClassicalSharp.Network { handlers[i] = null; packetSizes[i] = 0; } - - BlockInfo.SetMaxUsed(255); - ResetState(); + + BlockInfo.SetMaxUsed(255); + ResetState(); Dispose(); } void ResetState() { - if (classic == null) return; // null if no successful connection ever made before + if (classic == null) return; // null if no successful connection ever made before cpeData.Reset(); classic.Reset(); @@ -237,7 +271,7 @@ namespace ClassicalSharp.Network { if (testAcc < 1) return; testAcc = 0; - if (!socket.Connected || (socket.Poll(1000, SelectMode.SelectRead) && socket.Available == 0)) { + if (!socket.Connected || (socket.Available == 0 && socket.Poll(0, SelectMode.SelectRead))) { game.Disconnect("Disconnected!", "You've lost connection to the server"); } } diff --git a/ClassicalSharp/Network/Protocols/Classic.cs b/ClassicalSharp/Network/Protocols/Classic.cs index c55c6bcef..e60f33f12 100644 --- a/ClassicalSharp/Network/Protocols/Classic.cs +++ b/ClassicalSharp/Network/Protocols/Classic.cs @@ -95,11 +95,11 @@ namespace ClassicalSharp.Network.Protocols { game.WorldEvents.RaiseOnNewMap(); prevScreen = game.Gui.activeScreen; - if (prevScreen is LoadingMapScreen) + if (prevScreen is LoadingScreen) prevScreen = null; prevCursorVisible = game.CursorVisible; - game.Gui.SetNewScreen(new LoadingMapScreen(game, net.ServerName, net.ServerMotd), false); + game.Gui.SetNewScreen(new LoadingScreen(game, net.ServerName, net.ServerMotd), false); net.wom.CheckMotd(); receivedFirstPosition = false; gzipHeader = new GZipHeaderReader(); @@ -171,7 +171,7 @@ namespace ClassicalSharp.Network.Protocols { } float progress = map == null ? 0 : (float)mapIndex / map.Length; - game.WorldEvents.RaiseMapLoading(progress); + game.WorldEvents.RaiseLoading(progress); } void HandleLevelFinalise() { diff --git a/ClassicalSharp/Singleplayer/Server.cs b/ClassicalSharp/Singleplayer/Server.cs index 980c3ff1d..6ce506799 100644 --- a/ClassicalSharp/Singleplayer/Server.cs +++ b/ClassicalSharp/Singleplayer/Server.cs @@ -23,7 +23,7 @@ namespace ClassicalSharp.Singleplayer { IsSinglePlayer = true; } - public override void Connect(IPAddress address, int port) { + public override void BeginConnect() { game.Chat.SetLogName("Singleplayer"); game.SupportsCPEBlocks = game.UseCPE; int max = game.SupportsCPEBlocks ? Block.MaxCpeBlock : Block.MaxOriginalBlock; diff --git a/src/Client/Event.h b/src/Client/Event.h index 3f267b8d8..debe52665 100644 --- a/src/Client/Event.h +++ b/src/Client/Event.h @@ -104,7 +104,7 @@ Event_Void BlockEvents_PermissionsChanged; /* Block permissions (can place/delet Event_Void BlockEvents_BlockDefChanged; /* Block definition is changed or removed. */ Event_Void WorldEvents_NewMap; /* Player begins loading a new world. */ -Event_Real WorldEvents_MapLoading; /* Portion of world is decompressed/generated. (Arg is progress from 0-1) */ +Event_Real WorldEvents_Loading; /* Portion of world is decompressed/generated. (Arg is progress from 0-1) */ Event_Void WorldEvents_MapLoaded; /* New world has finished loading, player can now interact with it. */ Event_Int WorldEvents_EnvVarChanged; /* World environment variable changed by player/CPE/WoM config. */ diff --git a/src/Client/Game.c b/src/Client/Game.c index 4034cc87c..85d16b4b2 100644 --- a/src/Client/Game.c +++ b/src/Client/Game.c @@ -536,7 +536,7 @@ void Game_Load(void) { Gui_FreeActive(); Gui_SetActive(LoadingScreen_MakeInstance(&loadTitle, &loadMsg)); - ServerConnection_Connect(&Game_IPAddress, Game_Port); + ServerConnection_BeginConnect(); } Stopwatch game_frameTimer; diff --git a/src/Client/PacketHandlers.c b/src/Client/PacketHandlers.c index 07d66bddd..37e1966dd 100644 --- a/src/Client/PacketHandlers.c +++ b/src/Client/PacketHandlers.c @@ -446,7 +446,7 @@ static void Classic_LevelDataChunk(Stream* stream) { } Real32 progress = map == NULL ? 0.0f : (Real32)mapIndex / mapVolume; - Event_RaiseReal(&WorldEvents_MapLoading, progress); + Event_RaiseReal(&WorldEvents_Loading, progress); } static void Classic_LevelFinalise(Stream* stream) { diff --git a/src/Client/Platform.h b/src/Client/Platform.h index 680f2b011..f08703cbc 100644 --- a/src/Client/Platform.h +++ b/src/Client/Platform.h @@ -15,6 +15,8 @@ extern UInt8 Platform_DirectorySeparator; extern ReturnCode ReturnCode_FileShareViolation; extern ReturnCode ReturnCode_FileNotFound; extern ReturnCode ReturnCode_NotSupported; +extern ReturnCode ReturnCode_SocketInProgess; +extern ReturnCode ReturnCode_SocketWouldBlock; void Platform_Init(void); void Platform_Free(void); @@ -83,12 +85,13 @@ Size2D Platform_TextDraw(DrawTextArgs* args, Int32 x, Int32 y, PackedCol col); void Platform_ReleaseBitmap(void); void Platform_SocketCreate(void** socket); +ReturnCode Platform_SocketSetBlocking(void* socket, bool blocking); ReturnCode Platform_SocketConnect(void* socket, STRING_PURE String* ip, Int32 port); ReturnCode Platform_SocketRead(void* socket, UInt8* buffer, UInt32 count, UInt32* modified); ReturnCode Platform_SocketWrite(void* socket, UInt8* buffer, UInt32 count, UInt32* modified); ReturnCode Platform_SocketClose(void* socket); ReturnCode Platform_SocketAvailable(void* socket, UInt32* available); -ReturnCode Platform_SocketSelectRead(void* socket, Int32 microseconds, bool* success); +ReturnCode Platform_SocketSelect(void* socket, bool selectRead, bool* success); void Platform_HttpInit(void); ReturnCode Platform_HttpMakeRequest(AsyncRequest* request, void** handle); diff --git a/src/Client/Screens.c b/src/Client/Screens.c index 1c9234e22..d984dc845 100644 --- a/src/Client/Screens.c +++ b/src/Client/Screens.c @@ -549,7 +549,7 @@ static void LoadingScreen_Init(GuiElement* elem) { Gfx_SetFog(false); LoadingScreen_ContextRecreated(screen); - Event_RegisterReal(&WorldEvents_MapLoading, screen, LoadingScreen_MapLoading); + Event_RegisterReal(&WorldEvents_Loading, screen, LoadingScreen_MapLoading); Event_RegisterVoid(&GfxEvents_ContextLost, screen, LoadingScreen_ContextLost); Event_RegisterVoid(&GfxEvents_ContextRecreated, screen, LoadingScreen_ContextRecreated); } @@ -579,7 +579,7 @@ static void LoadingScreen_Free(GuiElement* elem) { Platform_FontFree(&screen->Font); LoadingScreen_ContextLost(screen); - Event_UnregisterReal(&WorldEvents_MapLoading, screen, LoadingScreen_MapLoading); + Event_UnregisterReal(&WorldEvents_Loading, screen, LoadingScreen_MapLoading); Event_UnregisterVoid(&GfxEvents_ContextLost, screen, LoadingScreen_ContextLost); Event_UnregisterVoid(&GfxEvents_ContextRecreated, screen, LoadingScreen_ContextRecreated); } @@ -865,7 +865,8 @@ static bool ChatScreen_HandlesKeyDown(GuiElement* elem, Key key) { if (screen->HandlesAllInput) { /* text input bar */ if (key == KeyBind_Get(KeyBind_SendChat) || key == Key_KeypadEnter || key == KeyBind_Get(KeyBind_PauseOrExit)) { ChatScreen_SetHandlesAllInput(screen, false); - Game_SetCursorVisible(false); + /* when underlying screen is HUD, user is interacting with the world normally */ + Game_SetCursorVisible(Gui_GetUnderlyingScreen() != Gui_HUD); Camera_Active->RegrabMouse(); Key_KeyRepeat = false; @@ -1512,10 +1513,10 @@ static bool DisconnectScreen_HandlesMouseDown(GuiElement* elem, Int32 x, Int32 y UInt8 connectBuffer[String_BufferSize(STRING_SIZE)]; String connect = String_InitAndClearArray(connectBuffer); String empty = String_MakeNull(); - String_Format2(&connect, "Connecting to %s: %i..", &Game_IPAddress, &Game_Port); + String_Format2(&connect, "Connecting to %s:%i..", &Game_IPAddress, &Game_Port); Gui_ReplaceActive(LoadingScreen_MakeInstance(&connect, &empty)); - ServerConnection_Connect(&Game_IPAddress, Game_Port); + ServerConnection_BeginConnect(); } return true; } diff --git a/src/Client/ServerConnection.c b/src/Client/ServerConnection.c index 0c385909f..456b8bf49 100644 --- a/src/Client/ServerConnection.c +++ b/src/Client/ServerConnection.c @@ -207,7 +207,7 @@ Int32 PingList_AveragePingMs(void) { /*########################################################################################################################* *-------------------------------------------------Singleplayer connection-------------------------------------------------* *#########################################################################################################################*/ -static void SPConnection_Connect(STRING_PURE String* ip, Int32 port) { +static void SPConnection_BeginConnect(void) { String logName = String_FromConst("Singleplayer"); Chat_SetLogName(&logName); Game_UseCPEBlocks = Game_UseCPE; @@ -280,7 +280,7 @@ void ServerConnection_InitSingleplayer(void) { ServerConnection_SupportsPartialMessages = true; ServerConnection_IsSinglePlayer = true; - ServerConnection_Connect = SPConnection_Connect; + ServerConnection_BeginConnect = SPConnection_BeginConnect; ServerConnection_SendChat = SPConnection_SendChat; ServerConnection_SendPosition = SPConnection_SendPosition; ServerConnection_SendPlayerClick = SPConnection_SendPlayerClick; @@ -295,17 +295,22 @@ void ServerConnection_InitSingleplayer(void) { *--------------------------------------------------Multiplayer connection-------------------------------------------------* *#########################################################################################################################*/ void* net_socket; -DateTime net_lastPacket; -UInt8 net_lastOpcode; Stream net_readStream; Stream net_writeStream; UInt8 net_readBuffer[4096 * 5]; UInt8 net_writeBuffer[131]; + Int32 net_maxHandledPacket; bool net_writeFailed; Int32 net_ticks; +DateTime net_lastPacket; +UInt8 net_lastOpcode; Real64 net_discAccumulator; +bool net_connecting; +Int64 net_connectTimeout; +#define NET_TIMEOUT_MS (15 * 1000) + static void MPConnection_BlockChanged(void* obj, Vector3I coords, BlockID oldBlock, BlockID block) { Vector3I p = coords; if (block == BLOCK_AIR) { @@ -318,26 +323,9 @@ static void MPConnection_BlockChanged(void* obj, Vector3I coords, BlockID oldBlo } static void ServerConnection_Free(void); -static void MPConnection_Connect(STRING_PURE String* ip, Int32 port) { - Platform_SocketCreate(&net_socket); - Event_RegisterBlock(&UserEvents_BlockChanged, NULL, MPConnection_BlockChanged); - ServerConnection_Disconnected = false; - - ReturnCode result = Platform_SocketConnect(net_socket, &Game_IPAddress, Game_Port); - if (result != 0) { - UInt8 msgBuffer[String_BufferSize(STRING_SIZE * 2)]; - String msg = String_InitAndClearArray(msgBuffer); - String_Format3(&msg, "Error connecting to %s:%i: %i", ip, &port, &result); - ErrorHandler_Log(&msg); - - String_Clear(&msg); - String_Format2(&msg, "Failed to connect to %s:%i", ip, &port); - String reason = String_FromConst("You failed to connect to the server. It's probably down!"); - Game_Disconnect(&msg, &reason); - - ServerConnection_Free(); - return; - } +static void MPConnection_FinishConnect(void) { + net_connecting = false; + Event_RaiseReal(&WorldEvents_Loading, 0.0f); String streamName = String_FromConst("network socket"); Stream_ReadonlyMemory(&net_readStream, net_readBuffer, sizeof(net_readBuffer), &streamName); @@ -352,8 +340,63 @@ static void MPConnection_Connect(STRING_PURE String* ip, Int32 port) { Platform_CurrentUTCTime(&net_lastPacket); } +static void MPConnection_FailConnect(ReturnCode result) { + net_connecting = false; + UInt8 msgBuffer[String_BufferSize(STRING_SIZE * 2)]; + String msg = String_InitAndClearArray(msgBuffer); + + if (result != 0) { + String_Format3(&msg, "Error connecting to %s:%i: %i", &Game_IPAddress, &Game_Port, &result); + ErrorHandler_Log(&msg); + String_Clear(&msg); + } + + String_Format2(&msg, "Failed to connect to %s:%i", &Game_IPAddress, &Game_Port); + String reason = String_FromConst("You failed to connect to the server. It's probably down!"); + Game_Disconnect(&msg, &reason); + + ServerConnection_Free(); +} + +static void MPConnection_TickConnect(void) { + DateTime now; Platform_CurrentUTCTime(&now); + Int64 nowMS = DateTime_TotalMs(&now); + + bool poll_success = false; + ReturnCode result = Platform_SocketSelect(net_socket, false, &poll_success); + + if (result != 0) { + MPConnection_FailConnect(result); + } else if (poll_success) { + Platform_SocketSetBlocking(net_socket, true); + MPConnection_FinishConnect(); + } else if (nowMS > net_connectTimeout) { + MPConnection_FailConnect(0); + } else { + Int64 leftMS = net_connectTimeout - nowMS; + Event_RaiseReal(&WorldEvents_Loading, (Real32)leftMS / NET_TIMEOUT_MS); + } +} + +static void MPConnection_BeginConnect(void) { + Platform_SocketCreate(&net_socket); + Event_RegisterBlock(&UserEvents_BlockChanged, NULL, MPConnection_BlockChanged); + ServerConnection_Disconnected = false; + + Platform_SocketSetBlocking(net_socket, false); + net_connecting = true; + DateTime now; Platform_CurrentUTCTime(&now); + net_connectTimeout = DateTime_TotalMs(&now) + NET_TIMEOUT_MS; + + ReturnCode result = Platform_SocketConnect(net_socket, &Game_IPAddress, Game_Port); + if (result == 0) return; + if (result != ReturnCode_SocketInProgess && result != ReturnCode_SocketWouldBlock) { + MPConnection_FailConnect(result); + } +} + static void MPConnection_SendChat(STRING_PURE String* text) { - if (text->length == 0) return; + if (text->length == 0 || net_connecting) return; String remaining = *text; while (remaining.length > STRING_SIZE) { @@ -382,11 +425,11 @@ static void MPConnection_CheckDisconnection(Real64 delta) { if (net_discAccumulator < 1.0) return; net_discAccumulator = 0.0; - UInt32 available = 0; bool selected = false; + UInt32 available = 0; bool poll_success = false; ReturnCode availResult = Platform_SocketAvailable(net_socket, &available); - ReturnCode selectResult = Platform_SocketSelectRead(net_socket, 1000, &selected); + ReturnCode selectResult = Platform_SocketSelect(net_socket, true, &poll_success); - if (net_writeFailed || availResult != 0 || selectResult != 0 || (selected && available == 0)) { + if (net_writeFailed || availResult != 0 || selectResult != 0 || (available == 0 && poll_success)) { String title = String_FromConst("Disconnected!"); String reason = String_FromConst("You've lost connection to the server"); Game_Disconnect(&title, &reason); @@ -395,6 +438,8 @@ static void MPConnection_CheckDisconnection(Real64 delta) { static void MPConnection_Tick(ScheduledTask* task) { if (ServerConnection_Disconnected) return; + if (net_connecting) { MPConnection_TickConnect(); return; } + DateTime now; Platform_CurrentUTCTime(&now); if (DateTime_MsBetween(&net_lastPacket, &now) >= 30 * 1000) { MPConnection_CheckDisconnection(task->Interval); @@ -496,7 +541,7 @@ void ServerConnection_InitMultiplayer(void) { ServerConnection_ResetState(); ServerConnection_IsSinglePlayer = false; - ServerConnection_Connect = MPConnection_Connect; + ServerConnection_BeginConnect = MPConnection_BeginConnect; ServerConnection_SendChat = MPConnection_SendChat; ServerConnection_SendPosition = MPConnection_SendPosition; ServerConnection_SendPlayerClick = MPConnection_SendPlayerClick; diff --git a/src/Client/ServerConnection.h b/src/Client/ServerConnection.h index b4a39a921..1bf5e2587 100644 --- a/src/Client/ServerConnection.h +++ b/src/Client/ServerConnection.h @@ -68,7 +68,7 @@ extern String ServerConnection_ServerName; extern String ServerConnection_ServerMOTD; extern String ServerConnection_AppName; -void (*ServerConnection_Connect)(STRING_PURE String* ip, Int32 port); +void (*ServerConnection_BeginConnect)(void); void (*ServerConnection_SendChat)(STRING_PURE String* text); void (*ServerConnection_SendPosition)(Vector3 pos, Real32 rotY, Real32 headX); void (*ServerConnection_SendPlayerClick)(MouseButton button, bool isDown, EntityID targetId, PickedPos* pos); diff --git a/src/Client/WinPlatform.c b/src/Client/WinPlatform.c index 010950938..9e9ea9128 100644 --- a/src/Client/WinPlatform.c +++ b/src/Client/WinPlatform.c @@ -25,6 +25,8 @@ UInt8 Platform_DirectorySeparator = '\\'; ReturnCode ReturnCode_FileShareViolation = ERROR_SHARING_VIOLATION; ReturnCode ReturnCode_FileNotFound = ERROR_FILE_NOT_FOUND; ReturnCode ReturnCode_NotSupported = ERROR_NOT_SUPPORTED; +ReturnCode ReturnCode_SocketInProgess = WSAEINPROGRESS; +ReturnCode ReturnCode_SocketWouldBlock = WSAEWOULDBLOCK; void Platform_Init(void) { heap = GetProcessHeap(); /* TODO: HeapCreate instead? probably not */ @@ -533,6 +535,11 @@ void Platform_SocketCreate(void** socketResult) { } } +ReturnCode Platform_SocketSetBlocking(void* socket, bool blocking) { + Int32 blocking_raw = blocking ? 0 : -1; + return ioctlsocket(socket, FIONBIO, &blocking_raw); +} + ReturnCode Platform_SocketConnect(void* socket, STRING_PURE String* ip, Int32 port) { struct sockaddr_in addr; addr.sin_family = AF_INET; @@ -575,16 +582,17 @@ ReturnCode Platform_SocketAvailable(void* socket, UInt32* available) { return ioctlsocket(socket, FIONREAD, available); } -ReturnCode Platform_SocketSelectRead(void* socket, Int32 microseconds, bool* success) { - void* args[2]; - args[0] = (void*)1; - args[1] = socket; +ReturnCode Platform_SocketSelect(void* socket, bool selectRead, bool* success) { + void* args[2]; args[0] = (void*)1; args[1] = socket; + TIMEVAL time = { 0 }; + Int32 selectCount; - TIMEVAL time; - time.tv_usec = microseconds % (1000 * 1000); - time.tv_sec = microseconds / (1000 * 1000); + if (selectRead) { + selectCount = select(1, &args, NULL, NULL, &time); + } else { + selectCount = select(1, NULL, &args, NULL, &time); + } - Int32 selectCount = select(1, &args, NULL, NULL, &time); if (selectCount == SOCKET_ERROR) { *success = false; return WSAGetLastError(); } else {