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 {