Make connect non-blocking, partially addresses #521

This commit is contained in:
UnknownShadow200 2018-06-03 12:43:33 +10:00
parent 1102ec3a10
commit e6a044ce22
17 changed files with 177 additions and 85 deletions

View File

@ -347,7 +347,8 @@ namespace ClassicalSharp.Gui.Screens {
if (HandlesAllInput) { // text input bar if (HandlesAllInput) { // text input bar
if (key == game.Mapping(KeyBind.SendChat) || key == Key.KeypadEnter || key == game.Mapping(KeyBind.PauseOrExit)) { if (key == game.Mapping(KeyBind.SendChat) || key == Key.KeypadEnter || key == game.Mapping(KeyBind.PauseOrExit)) {
SetHandlesAllInput(false); 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.Camera.RegrabMouse();
game.Keyboard.KeyRepeat = false; game.Keyboard.KeyRepeat = false;

View File

@ -74,8 +74,8 @@ namespace ClassicalSharp.Gui.Screens {
if (!reconnect.Disabled && reconnect.Contains(mouseX, mouseY)) { if (!reconnect.Disabled && reconnect.Contains(mouseX, mouseY)) {
string connect = "Connecting to " + game.IPAddress + ":" + game.Port + ".."; string connect = "Connecting to " + game.IPAddress + ":" + game.Port + "..";
game.Gui.SetNewScreen(new LoadingMapScreen(game, connect, "")); game.Gui.SetNewScreen(new LoadingScreen(game, connect, ""));
game.Server.Connect(game.IPAddress, game.Port); game.Server.BeginConnect();
} }
return true; return true;
} }

View File

@ -10,10 +10,10 @@ using ClassicalSharp.Textures;
using OpenTK.Input; using OpenTK.Input;
namespace ClassicalSharp.Gui.Screens { namespace ClassicalSharp.Gui.Screens {
public class LoadingMapScreen : Screen { public class LoadingScreen : Screen {
readonly Font font; 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.title = title;
this.message = message; this.message = message;
font = new Font(game.FontName, 16); font = new Font(game.FontName, 16);
@ -34,7 +34,7 @@ namespace ClassicalSharp.Gui.Screens {
game.Graphics.Fog = false; game.Graphics.Fog = false;
ContextRecreated(); ContextRecreated();
game.WorldEvents.MapLoading += MapLoading; game.WorldEvents.Loading += Loading;
game.Graphics.ContextLost += ContextLost; game.Graphics.ContextLost += ContextLost;
game.Graphics.ContextRecreated += ContextRecreated; game.Graphics.ContextRecreated += ContextRecreated;
} }
@ -63,7 +63,7 @@ namespace ClassicalSharp.Gui.Screens {
font.Dispose(); font.Dispose();
ContextLost(); ContextLost();
game.WorldEvents.MapLoading -= MapLoading; game.WorldEvents.Loading -= Loading;
game.Graphics.ContextLost -= ContextLost; game.Graphics.ContextLost -= ContextLost;
game.Graphics.ContextRecreated -= ContextRecreated; game.Graphics.ContextRecreated -= ContextRecreated;
} }
@ -73,7 +73,7 @@ namespace ClassicalSharp.Gui.Screens {
titleWidget.Reposition(); titleWidget.Reposition();
} }
void MapLoading(object sender, MapLoadingEventArgs e) { void Loading(object sender, LoadingEventArgs e) {
progress = e.Progress; progress = e.Progress;
} }
@ -160,7 +160,7 @@ namespace ClassicalSharp.Gui.Screens {
} }
} }
public class GeneratingMapScreen : LoadingMapScreen { public class GeneratingMapScreen : LoadingScreen {
string lastState; string lastState;
IMapGenerator gen; IMapGenerator gen;

View File

@ -10,8 +10,8 @@ namespace ClassicalSharp.Events {
public void RaiseOnNewMap() { Raise(OnNewMap); } public void RaiseOnNewMap() { Raise(OnNewMap); }
/// <summary> Raised when a portion of the world is read and decompressed, or generated. </summary> /// <summary> Raised when a portion of the world is read and decompressed, or generated. </summary>
public event EventHandler<MapLoadingEventArgs> MapLoading; public event EventHandler<LoadingEventArgs> Loading;
public void RaiseMapLoading(float progress) { loadingArgs.Progress = progress; Raise(MapLoading, loadingArgs); } public void RaiseLoading(float progress) { loadingArgs.Progress = progress; Raise(Loading, loadingArgs); }
/// <summary> Raised when new world has finished loading and the player can now interact with it. </summary> /// <summary> Raised when new world has finished loading and the player can now interact with it. </summary>
public event EventHandler OnNewMapLoaded; public event EventHandler OnNewMapLoaded;
@ -21,11 +21,11 @@ namespace ClassicalSharp.Events {
public event EventHandler<EnvVarEventArgs> EnvVariableChanged; public event EventHandler<EnvVarEventArgs> EnvVariableChanged;
public void RaiseEnvVariableChanged(EnvVar envVar) { envArgs.Var = envVar; Raise(EnvVariableChanged, envArgs); } public void RaiseEnvVariableChanged(EnvVar envVar) { envArgs.Var = envVar; Raise(EnvVariableChanged, envArgs); }
MapLoadingEventArgs loadingArgs = new MapLoadingEventArgs(); LoadingEventArgs loadingArgs = new LoadingEventArgs();
EnvVarEventArgs envArgs = new EnvVarEventArgs(); EnvVarEventArgs envArgs = new EnvVarEventArgs();
} }
public sealed class MapLoadingEventArgs : EventArgs { public sealed class LoadingEventArgs : EventArgs {
/// <summary> Percentage of the map that has been fully decompressed, or generated. </summary> /// <summary> Percentage of the map that has been fully decompressed, or generated. </summary>
public float Progress; public float Progress;

View File

@ -162,8 +162,8 @@ namespace ClassicalSharp {
MapBordersRenderer.UseLegacyMode(true); MapBordersRenderer.UseLegacyMode(true);
EnvRenderer.UseLegacyMode(true); EnvRenderer.UseLegacyMode(true);
} }
Gui.SetNewScreen(new LoadingMapScreen(this, connectString, "")); Gui.SetNewScreen(new LoadingScreen(this, connectString, ""));
Server.Connect(IPAddress, Port); Server.BeginConnect();
} }
void ExtractInitialTexturePack() { void ExtractInitialTexturePack() {

View File

@ -21,8 +21,8 @@ namespace ClassicalSharp {
public bool IsSinglePlayer; public bool IsSinglePlayer;
/// <summary> Opens a connection to the given IP address and port, and prepares the initial state of the client. </summary> /// <summary> Opens a connection to the server, and prepares the initial state of the client. </summary>
public abstract void Connect(IPAddress address, int port); public abstract void BeginConnect();
public abstract void SendChat(string text); public abstract void SendChat(string text);

View File

@ -31,27 +31,52 @@ namespace ClassicalSharp.Network {
public NetReader reader; public NetReader reader;
public NetWriter writer; public NetWriter writer;
bool connecting = false;
DateTime connectTimeout;
const int timeoutSecs = 15;
internal ClassicProtocol classic; internal ClassicProtocol classic;
internal CPEProtocol cpe; internal CPEProtocol cpe;
internal CPEProtocolBlockDefs cpeBlockDefs; internal CPEProtocolBlockDefs cpeBlockDefs;
internal WoMProtocol wom; internal WoMProtocol wom;
internal CPESupport cpeData; internal CPESupport cpeData;
public override void Connect(IPAddress address, int port) { public override void BeginConnect() {
socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); socket = new Socket(game.IPAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
game.UserEvents.BlockChanged += BlockChanged; game.UserEvents.BlockChanged += BlockChanged;
Disconnected = false; Disconnected = false;
try { socket.Blocking = false;
socket.Connect(address, port); connecting = true;
} catch (SocketException ex) { connectTimeout = DateTime.UtcNow.AddSeconds(timeoutSecs);
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;
}
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); reader = new NetReader(socket);
writer = new NetWriter(socket); writer = new NetWriter(socket);
@ -66,8 +91,15 @@ namespace ClassicalSharp.Network {
lastPacket = DateTime.UtcNow; 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) { public override void SendChat(string text) {
if (String.IsNullOrEmpty(text)) return; if (String.IsNullOrEmpty(text) || connecting) return;
while (text.Length > Utils.StringLength) { while (text.Length > Utils.StringLength) {
classic.WriteChat(text.Substring(0, Utils.StringLength), true); classic.WriteChat(text.Substring(0, Utils.StringLength), true);
@ -97,6 +129,8 @@ namespace ClassicalSharp.Network {
public override void Tick(ScheduledTask task) { public override void Tick(ScheduledTask task) {
if (Disconnected) return; if (Disconnected) return;
if (connecting) { TickConnect(); return; }
if ((DateTime.UtcNow - lastPacket).TotalSeconds >= 30) { if ((DateTime.UtcNow - lastPacket).TotalSeconds >= 30) {
CheckDisconnection(task.Interval); CheckDisconnection(task.Interval);
} }
@ -237,7 +271,7 @@ namespace ClassicalSharp.Network {
if (testAcc < 1) return; if (testAcc < 1) return;
testAcc = 0; 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"); game.Disconnect("Disconnected!", "You've lost connection to the server");
} }
} }

View File

@ -95,11 +95,11 @@ namespace ClassicalSharp.Network.Protocols {
game.WorldEvents.RaiseOnNewMap(); game.WorldEvents.RaiseOnNewMap();
prevScreen = game.Gui.activeScreen; prevScreen = game.Gui.activeScreen;
if (prevScreen is LoadingMapScreen) if (prevScreen is LoadingScreen)
prevScreen = null; prevScreen = null;
prevCursorVisible = game.CursorVisible; 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(); net.wom.CheckMotd();
receivedFirstPosition = false; receivedFirstPosition = false;
gzipHeader = new GZipHeaderReader(); gzipHeader = new GZipHeaderReader();
@ -171,7 +171,7 @@ namespace ClassicalSharp.Network.Protocols {
} }
float progress = map == null ? 0 : (float)mapIndex / map.Length; float progress = map == null ? 0 : (float)mapIndex / map.Length;
game.WorldEvents.RaiseMapLoading(progress); game.WorldEvents.RaiseLoading(progress);
} }
void HandleLevelFinalise() { void HandleLevelFinalise() {

View File

@ -23,7 +23,7 @@ namespace ClassicalSharp.Singleplayer {
IsSinglePlayer = true; IsSinglePlayer = true;
} }
public override void Connect(IPAddress address, int port) { public override void BeginConnect() {
game.Chat.SetLogName("Singleplayer"); game.Chat.SetLogName("Singleplayer");
game.SupportsCPEBlocks = game.UseCPE; game.SupportsCPEBlocks = game.UseCPE;
int max = game.SupportsCPEBlocks ? Block.MaxCpeBlock : Block.MaxOriginalBlock; int max = game.SupportsCPEBlocks ? Block.MaxCpeBlock : Block.MaxOriginalBlock;

View File

@ -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 BlockEvents_BlockDefChanged; /* Block definition is changed or removed. */
Event_Void WorldEvents_NewMap; /* Player begins loading a new world. */ 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_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. */ Event_Int WorldEvents_EnvVarChanged; /* World environment variable changed by player/CPE/WoM config. */

View File

@ -536,7 +536,7 @@ void Game_Load(void) {
Gui_FreeActive(); Gui_FreeActive();
Gui_SetActive(LoadingScreen_MakeInstance(&loadTitle, &loadMsg)); Gui_SetActive(LoadingScreen_MakeInstance(&loadTitle, &loadMsg));
ServerConnection_Connect(&Game_IPAddress, Game_Port); ServerConnection_BeginConnect();
} }
Stopwatch game_frameTimer; Stopwatch game_frameTimer;

View File

@ -446,7 +446,7 @@ static void Classic_LevelDataChunk(Stream* stream) {
} }
Real32 progress = map == NULL ? 0.0f : (Real32)mapIndex / mapVolume; 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) { static void Classic_LevelFinalise(Stream* stream) {

View File

@ -15,6 +15,8 @@ extern UInt8 Platform_DirectorySeparator;
extern ReturnCode ReturnCode_FileShareViolation; extern ReturnCode ReturnCode_FileShareViolation;
extern ReturnCode ReturnCode_FileNotFound; extern ReturnCode ReturnCode_FileNotFound;
extern ReturnCode ReturnCode_NotSupported; extern ReturnCode ReturnCode_NotSupported;
extern ReturnCode ReturnCode_SocketInProgess;
extern ReturnCode ReturnCode_SocketWouldBlock;
void Platform_Init(void); void Platform_Init(void);
void Platform_Free(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_ReleaseBitmap(void);
void Platform_SocketCreate(void** socket); 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_SocketConnect(void* socket, STRING_PURE String* ip, Int32 port);
ReturnCode Platform_SocketRead(void* socket, UInt8* buffer, UInt32 count, UInt32* modified); ReturnCode Platform_SocketRead(void* socket, UInt8* buffer, UInt32 count, UInt32* modified);
ReturnCode Platform_SocketWrite(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_SocketClose(void* socket);
ReturnCode Platform_SocketAvailable(void* socket, UInt32* available); 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); void Platform_HttpInit(void);
ReturnCode Platform_HttpMakeRequest(AsyncRequest* request, void** handle); ReturnCode Platform_HttpMakeRequest(AsyncRequest* request, void** handle);

View File

@ -549,7 +549,7 @@ static void LoadingScreen_Init(GuiElement* elem) {
Gfx_SetFog(false); Gfx_SetFog(false);
LoadingScreen_ContextRecreated(screen); 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_ContextLost, screen, LoadingScreen_ContextLost);
Event_RegisterVoid(&GfxEvents_ContextRecreated, screen, LoadingScreen_ContextRecreated); Event_RegisterVoid(&GfxEvents_ContextRecreated, screen, LoadingScreen_ContextRecreated);
} }
@ -579,7 +579,7 @@ static void LoadingScreen_Free(GuiElement* elem) {
Platform_FontFree(&screen->Font); Platform_FontFree(&screen->Font);
LoadingScreen_ContextLost(screen); 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_ContextLost, screen, LoadingScreen_ContextLost);
Event_UnregisterVoid(&GfxEvents_ContextRecreated, screen, LoadingScreen_ContextRecreated); 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 (screen->HandlesAllInput) { /* text input bar */
if (key == KeyBind_Get(KeyBind_SendChat) || key == Key_KeypadEnter || key == KeyBind_Get(KeyBind_PauseOrExit)) { if (key == KeyBind_Get(KeyBind_SendChat) || key == Key_KeypadEnter || key == KeyBind_Get(KeyBind_PauseOrExit)) {
ChatScreen_SetHandlesAllInput(screen, false); 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(); Camera_Active->RegrabMouse();
Key_KeyRepeat = false; Key_KeyRepeat = false;
@ -1512,10 +1513,10 @@ static bool DisconnectScreen_HandlesMouseDown(GuiElement* elem, Int32 x, Int32 y
UInt8 connectBuffer[String_BufferSize(STRING_SIZE)]; UInt8 connectBuffer[String_BufferSize(STRING_SIZE)];
String connect = String_InitAndClearArray(connectBuffer); String connect = String_InitAndClearArray(connectBuffer);
String empty = String_MakeNull(); 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)); Gui_ReplaceActive(LoadingScreen_MakeInstance(&connect, &empty));
ServerConnection_Connect(&Game_IPAddress, Game_Port); ServerConnection_BeginConnect();
} }
return true; return true;
} }

View File

@ -207,7 +207,7 @@ Int32 PingList_AveragePingMs(void) {
/*########################################################################################################################* /*########################################################################################################################*
*-------------------------------------------------Singleplayer connection-------------------------------------------------* *-------------------------------------------------Singleplayer connection-------------------------------------------------*
*#########################################################################################################################*/ *#########################################################################################################################*/
static void SPConnection_Connect(STRING_PURE String* ip, Int32 port) { static void SPConnection_BeginConnect(void) {
String logName = String_FromConst("Singleplayer"); String logName = String_FromConst("Singleplayer");
Chat_SetLogName(&logName); Chat_SetLogName(&logName);
Game_UseCPEBlocks = Game_UseCPE; Game_UseCPEBlocks = Game_UseCPE;
@ -280,7 +280,7 @@ void ServerConnection_InitSingleplayer(void) {
ServerConnection_SupportsPartialMessages = true; ServerConnection_SupportsPartialMessages = true;
ServerConnection_IsSinglePlayer = true; ServerConnection_IsSinglePlayer = true;
ServerConnection_Connect = SPConnection_Connect; ServerConnection_BeginConnect = SPConnection_BeginConnect;
ServerConnection_SendChat = SPConnection_SendChat; ServerConnection_SendChat = SPConnection_SendChat;
ServerConnection_SendPosition = SPConnection_SendPosition; ServerConnection_SendPosition = SPConnection_SendPosition;
ServerConnection_SendPlayerClick = SPConnection_SendPlayerClick; ServerConnection_SendPlayerClick = SPConnection_SendPlayerClick;
@ -295,17 +295,22 @@ void ServerConnection_InitSingleplayer(void) {
*--------------------------------------------------Multiplayer connection-------------------------------------------------* *--------------------------------------------------Multiplayer connection-------------------------------------------------*
*#########################################################################################################################*/ *#########################################################################################################################*/
void* net_socket; void* net_socket;
DateTime net_lastPacket;
UInt8 net_lastOpcode;
Stream net_readStream; Stream net_readStream;
Stream net_writeStream; Stream net_writeStream;
UInt8 net_readBuffer[4096 * 5]; UInt8 net_readBuffer[4096 * 5];
UInt8 net_writeBuffer[131]; UInt8 net_writeBuffer[131];
Int32 net_maxHandledPacket; Int32 net_maxHandledPacket;
bool net_writeFailed; bool net_writeFailed;
Int32 net_ticks; Int32 net_ticks;
DateTime net_lastPacket;
UInt8 net_lastOpcode;
Real64 net_discAccumulator; 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) { static void MPConnection_BlockChanged(void* obj, Vector3I coords, BlockID oldBlock, BlockID block) {
Vector3I p = coords; Vector3I p = coords;
if (block == BLOCK_AIR) { 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 ServerConnection_Free(void);
static void MPConnection_Connect(STRING_PURE String* ip, Int32 port) { static void MPConnection_FinishConnect(void) {
Platform_SocketCreate(&net_socket); net_connecting = false;
Event_RegisterBlock(&UserEvents_BlockChanged, NULL, MPConnection_BlockChanged); Event_RaiseReal(&WorldEvents_Loading, 0.0f);
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;
}
String streamName = String_FromConst("network socket"); String streamName = String_FromConst("network socket");
Stream_ReadonlyMemory(&net_readStream, net_readBuffer, sizeof(net_readBuffer), &streamName); 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); 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) { static void MPConnection_SendChat(STRING_PURE String* text) {
if (text->length == 0) return; if (text->length == 0 || net_connecting) return;
String remaining = *text; String remaining = *text;
while (remaining.length > STRING_SIZE) { while (remaining.length > STRING_SIZE) {
@ -382,11 +425,11 @@ static void MPConnection_CheckDisconnection(Real64 delta) {
if (net_discAccumulator < 1.0) return; if (net_discAccumulator < 1.0) return;
net_discAccumulator = 0.0; 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 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 title = String_FromConst("Disconnected!");
String reason = String_FromConst("You've lost connection to the server"); String reason = String_FromConst("You've lost connection to the server");
Game_Disconnect(&title, &reason); Game_Disconnect(&title, &reason);
@ -395,6 +438,8 @@ static void MPConnection_CheckDisconnection(Real64 delta) {
static void MPConnection_Tick(ScheduledTask* task) { static void MPConnection_Tick(ScheduledTask* task) {
if (ServerConnection_Disconnected) return; if (ServerConnection_Disconnected) return;
if (net_connecting) { MPConnection_TickConnect(); return; }
DateTime now; Platform_CurrentUTCTime(&now); DateTime now; Platform_CurrentUTCTime(&now);
if (DateTime_MsBetween(&net_lastPacket, &now) >= 30 * 1000) { if (DateTime_MsBetween(&net_lastPacket, &now) >= 30 * 1000) {
MPConnection_CheckDisconnection(task->Interval); MPConnection_CheckDisconnection(task->Interval);
@ -496,7 +541,7 @@ void ServerConnection_InitMultiplayer(void) {
ServerConnection_ResetState(); ServerConnection_ResetState();
ServerConnection_IsSinglePlayer = false; ServerConnection_IsSinglePlayer = false;
ServerConnection_Connect = MPConnection_Connect; ServerConnection_BeginConnect = MPConnection_BeginConnect;
ServerConnection_SendChat = MPConnection_SendChat; ServerConnection_SendChat = MPConnection_SendChat;
ServerConnection_SendPosition = MPConnection_SendPosition; ServerConnection_SendPosition = MPConnection_SendPosition;
ServerConnection_SendPlayerClick = MPConnection_SendPlayerClick; ServerConnection_SendPlayerClick = MPConnection_SendPlayerClick;

View File

@ -68,7 +68,7 @@ extern String ServerConnection_ServerName;
extern String ServerConnection_ServerMOTD; extern String ServerConnection_ServerMOTD;
extern String ServerConnection_AppName; 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_SendChat)(STRING_PURE String* text);
void (*ServerConnection_SendPosition)(Vector3 pos, Real32 rotY, Real32 headX); void (*ServerConnection_SendPosition)(Vector3 pos, Real32 rotY, Real32 headX);
void (*ServerConnection_SendPlayerClick)(MouseButton button, bool isDown, EntityID targetId, PickedPos* pos); void (*ServerConnection_SendPlayerClick)(MouseButton button, bool isDown, EntityID targetId, PickedPos* pos);

View File

@ -25,6 +25,8 @@ UInt8 Platform_DirectorySeparator = '\\';
ReturnCode ReturnCode_FileShareViolation = ERROR_SHARING_VIOLATION; ReturnCode ReturnCode_FileShareViolation = ERROR_SHARING_VIOLATION;
ReturnCode ReturnCode_FileNotFound = ERROR_FILE_NOT_FOUND; ReturnCode ReturnCode_FileNotFound = ERROR_FILE_NOT_FOUND;
ReturnCode ReturnCode_NotSupported = ERROR_NOT_SUPPORTED; ReturnCode ReturnCode_NotSupported = ERROR_NOT_SUPPORTED;
ReturnCode ReturnCode_SocketInProgess = WSAEINPROGRESS;
ReturnCode ReturnCode_SocketWouldBlock = WSAEWOULDBLOCK;
void Platform_Init(void) { void Platform_Init(void) {
heap = GetProcessHeap(); /* TODO: HeapCreate instead? probably not */ 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) { ReturnCode Platform_SocketConnect(void* socket, STRING_PURE String* ip, Int32 port) {
struct sockaddr_in addr; struct sockaddr_in addr;
addr.sin_family = AF_INET; addr.sin_family = AF_INET;
@ -575,16 +582,17 @@ ReturnCode Platform_SocketAvailable(void* socket, UInt32* available) {
return ioctlsocket(socket, FIONREAD, available); return ioctlsocket(socket, FIONREAD, available);
} }
ReturnCode Platform_SocketSelectRead(void* socket, Int32 microseconds, bool* success) { ReturnCode Platform_SocketSelect(void* socket, bool selectRead, bool* success) {
void* args[2]; void* args[2]; args[0] = (void*)1; args[1] = socket;
args[0] = (void*)1; TIMEVAL time = { 0 };
args[1] = socket; Int32 selectCount;
TIMEVAL time; if (selectRead) {
time.tv_usec = microseconds % (1000 * 1000); selectCount = select(1, &args, NULL, NULL, &time);
time.tv_sec = microseconds / (1000 * 1000); } else {
selectCount = select(1, NULL, &args, NULL, &time);
}
Int32 selectCount = select(1, &args, NULL, NULL, &time);
if (selectCount == SOCKET_ERROR) { if (selectCount == SOCKET_ERROR) {
*success = false; return WSAGetLastError(); *success = false; return WSAGetLastError();
} else { } else {