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 (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;

View File

@ -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;
}

View File

@ -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;

View File

@ -10,8 +10,8 @@ namespace ClassicalSharp.Events {
public void RaiseOnNewMap() { Raise(OnNewMap); }
/// <summary> Raised when a portion of the world is read and decompressed, or generated. </summary>
public event EventHandler<MapLoadingEventArgs> MapLoading;
public void RaiseMapLoading(float progress) { loadingArgs.Progress = progress; Raise(MapLoading, loadingArgs); }
public event EventHandler<LoadingEventArgs> Loading;
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>
public event EventHandler OnNewMapLoaded;
@ -21,11 +21,11 @@ namespace ClassicalSharp.Events {
public event EventHandler<EnvVarEventArgs> 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 {
/// <summary> Percentage of the map that has been fully decompressed, or generated. </summary>
public float Progress;

View File

@ -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() {

View File

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

View File

@ -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);
}
@ -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");
}
}

View File

@ -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() {

View File

@ -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;

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 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. */

View File

@ -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;

View File

@ -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) {

View File

@ -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);

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

View File

@ -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 {