diff --git a/src/packages/game/net/IZ_app.c b/src/packages/game/net/IZ_app.c index dc1e8a0..ec6af69 100644 --- a/src/packages/game/net/IZ_app.c +++ b/src/packages/game/net/IZ_app.c @@ -59,6 +59,9 @@ void IZ_WSClientAttemptConnect(struct lws_sorted_usec_list *sul) { vhd->i.protocol = NETWORK_PROTOCOL; vhd->i.pwsi = &vhd->client_wsi; + struct IZ_App* app = (struct IZ_App*) vhd->app; + IZ_NetState* net_state = IZ_AppGetNetState(app); + net_state->status = IZ_NET_STATUS_CONNECTING; if (lws_client_connect_via_info(&vhd->i)) { return; } @@ -120,38 +123,60 @@ void IZ_WSClientProtocolTeardown(struct lws* wsi) { } lws_sul_cancel(&vhd->sul); + struct IZ_App* app = (struct IZ_App*) vhd->app; + IZ_NetState* net_state = IZ_AppGetNetState(app); + net_state->status = IZ_NET_STATUS_PRISTINE; } -void IZ_WSClientConnectionError(struct lws* wsi, void* in) { +IZ_ProcedureResult IZ_WSClientConnectionError(struct lws* wsi, void* in) { lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", in ? (char *)in : "(null)"); IZ_WSClientVHostData* vhd = (IZ_WSClientVHostData*) lws_protocol_vh_priv_get( lws_get_vhost(wsi), lws_get_protocol(wsi) ); + + if (!vhd) { + return -1; + } + struct IZ_App* app = (struct IZ_App*) vhd->app; IZ_AppBindConnection(app, NULL); vhd->client_wsi = NULL; + IZ_NetState* net_state = IZ_AppGetNetState(app); + if (net_state->retries == net_state->config.max_reconnect_retries) { + lwsl_err("Max number of retries reached!\n"); + net_state->status = IZ_NET_STATUS_PRISTINE; + return -1; + } + net_state->status = IZ_NET_STATUS_ERROR; + net_state->retries += 1; lws_sul_schedule( vhd->context, 0, &vhd->sul, IZ_WSClientAttemptConnect, - LWS_US_PER_SEC + net_state->config.reconnect_interval_secs * LWS_US_PER_SEC ); + + return 0; } IZ_ProcedureResult IZ_WSClientOnOpen(struct lws* wsi, IZ_WSClientSessionData* pss) { - pss->ring = lws_ring_create(sizeof(IZ_WebsocketMessage), RING_COUNT,IZ_WebsocketDestroyMessage); - if (!pss->ring) { - return -1; - } - IZ_WSClientVHostData* vhd = (IZ_WSClientVHostData*) lws_protocol_vh_priv_get( lws_get_vhost(wsi), lws_get_protocol(wsi) ); struct IZ_App* app = (struct IZ_App*) vhd->app; + IZ_NetState* net_state = IZ_AppGetNetState(app); + pss->ring = lws_ring_create(sizeof(IZ_WebsocketMessage), RING_COUNT,IZ_WebsocketDestroyMessage); + if (!pss->ring) { + net_state->status = IZ_NET_STATUS_ERROR; + return -1; + } + IZ_AppBindConnection(app, wsi); + net_state->status = IZ_NET_STATUS_CONNECTED; + net_state->retries = 0; pss->tail = 0; return 0; } @@ -251,6 +276,8 @@ void IZ_WSClientOnReceive(struct lws* wsi, IZ_WSClientSessionData* pss, void* in lwsl_user("dropping!\n"); return; } + + lws_ring_consume_single_tail(pss->ring, &pss->tail, 1); lws_callback_on_writable(wsi); if (!pss->flow_controlled && n < 3) { diff --git a/src/packages/game/net/IZ_net.c b/src/packages/game/net/IZ_net.c index e48e3a9..e12f4be 100644 --- a/src/packages/game/net/IZ_net.c +++ b/src/packages/game/net/IZ_net.c @@ -1,25 +1,25 @@ #include "IZ_net.h" -bool IZ_NetIsConnected(IZ_NetState* state) { - if (state->ws.interrupted) { - return false; - } - if (!state->ws.context) { - return false; - } - if (!state->ws.connection) { - return false; - } - return true; -} - void IZ_NetLoadConfig(IZ_NetState* state, const char* config_path) { char buffer[32]; ini_gets("Network", "Username", IZ_NET_DEFAULT_STATE.config.username, buffer, 32, config_path); memcpy_s(state->config.username, 32, buffer, 32); - state->config.interval = ini_getl("Network", "Interval", IZ_NET_DEFAULT_STATE.config.interval, config_path); + state->config.packet_interval_ms = ini_getl("Network", "PacketIntervalMs", IZ_NET_DEFAULT_STATE.config.packet_interval_ms, config_path); + if (!(100 <= state->config.packet_interval_ms && state->config.packet_interval_ms <= 500)) { + state->config.packet_interval_ms = IZ_NET_DEFAULT_STATE.config.packet_interval_ms; + } + + state->config.max_reconnect_retries = ini_getl("Network", "MaxReconnectRetries", IZ_NET_DEFAULT_STATE.config.max_reconnect_retries, config_path); + if (!(0 <= state->config.max_reconnect_retries && state->config.max_reconnect_retries <= 8)) { + state->config.max_reconnect_retries = IZ_NET_DEFAULT_STATE.config.max_reconnect_retries; + } + + state->config.reconnect_interval_secs = ini_getl("Network", "ReconnectIntervalSeconds", IZ_NET_DEFAULT_STATE.config.reconnect_interval_secs, config_path); + if (!(3 <= state->config.reconnect_interval_secs && state->config.reconnect_interval_secs <= 10)) { + state->config.reconnect_interval_secs = IZ_NET_DEFAULT_STATE.config.reconnect_interval_secs; + } } IZ_ProcedureResult IZ_NetSaveConfig(IZ_NetState* state, const char* config_path) { @@ -27,7 +27,15 @@ IZ_ProcedureResult IZ_NetSaveConfig(IZ_NetState* state, const char* config_path) return -1; } - if (!ini_putl("Network", "Interval", state->config.interval, config_path)) { + if (!ini_putl("Network", "PacketIntervalMs", state->config.packet_interval_ms, config_path)) { + return -1; + } + + if (!ini_putl("Network", "MaxReconnectRetries", state->config.max_reconnect_retries, config_path)) { + return -1; + } + + if (!ini_putl("Network", "ReconnectIntervalSeconds", state->config.reconnect_interval_secs, config_path)) { return -1; } @@ -37,7 +45,7 @@ IZ_ProcedureResult IZ_NetSaveConfig(IZ_NetState* state, const char* config_path) void IZ_NetOverrideConfig(IZ_NetState* state, u8 argc, const char* argv[]) { const char* cmdline_buffer; if ((cmdline_buffer = IZ_ConfigGetCommandlineOption(argc, argv, "-i"))) { - state->config.interval = atoi(cmdline_buffer); + state->config.packet_interval_ms = atoi(cmdline_buffer); } } @@ -60,6 +68,7 @@ IZ_ProcedureResult IZ_NetInitialize( } state->ws.user_data = user_data; state->callback = callback; + state->retries = state->config.max_reconnect_retries; u8 player_index; for (player_index = 0; player_index < IZ_PLAYERS; player_index += 1) { state->action[player_index] = 0; @@ -69,28 +78,47 @@ IZ_ProcedureResult IZ_NetInitialize( } void IZ_NetConnect(IZ_NetState* state, IZ_WSClientInitializeParams params) { - if (IZ_NetIsConnected(state)) { + if (!state->callback) { return; } - if (!state->callback) { + if (state->status == IZ_NET_STATUS_CONNECTED) { + return; + } + + if (state->status == IZ_NET_STATUS_CONNECTING) { + return; + } + + if (state->retries < state->config.max_reconnect_retries) { return; } + state->retries = 0; state->params = params; state->client_thread = SDL_CreateThread(state->callback, "networking", state->ws.user_data); SDL_DetachThread(state->client_thread); } void IZ_NetDisconnect(IZ_NetState* state) { - if (!IZ_NetIsConnected(state)) { + if (state->status == IZ_NET_STATUS_PRISTINE) { return; } + if (state->ws.connection) { + IZ_WSClientVHostData *vhd = (IZ_WSClientVHostData *) lws_protocol_vh_priv_get( + lws_get_vhost(state->ws.connection), + lws_get_protocol(state->ws.connection) + ); + if (vhd) { + lws_sul_cancel(&vhd->sul); + } + } + state->retries = state->config.max_reconnect_retries; IZ_WSClientCancelService(&state->ws); } void IZ_NetSendBinaryMessage(IZ_NetState* state, void* in, size_t len) { - if (!IZ_NetIsConnected(state)) { + if (state->status != IZ_NET_STATUS_CONNECTED) { return; } @@ -112,7 +140,7 @@ void IZ_NetSendBinaryMessage(IZ_NetState* state, void* in, size_t len) { } void IZ_NetSendTextMessage(IZ_NetState* state, char* in, size_t len) { - if (!IZ_NetIsConnected(state)) { + if (state->status != IZ_NET_STATUS_CONNECTED) { return; } diff --git a/src/packages/game/net/IZ_net.h b/src/packages/game/net/IZ_net.h index b49c5fc..51d9e15 100644 --- a/src/packages/game/net/IZ_net.h +++ b/src/packages/game/net/IZ_net.h @@ -9,8 +9,17 @@ #include "core/IZ_websocket.h" #include "svc/IZ_wsclient.h" +typedef enum { + IZ_NET_STATUS_PRISTINE, + IZ_NET_STATUS_CONNECTING, + IZ_NET_STATUS_ERROR, + IZ_NET_STATUS_CONNECTED, +} IZ_NetStatus; + typedef struct { - u16 interval; + u16 packet_interval_ms; + u8 max_reconnect_retries; + u8 reconnect_interval_secs; char username[32]; } IZ_NetConfig; @@ -21,13 +30,17 @@ typedef struct { IZ_WSClientInitializeParams params; void* callback; IZ_Action action[IZ_PLAYERS]; + u8 retries; + IZ_NetStatus status; // TODO add message queue } IZ_NetState; static IZ_NetState IZ_NET_DEFAULT_STATE = { .client_thread = NULL, .config = { - .interval = 200, + .packet_interval_ms = 200, + .max_reconnect_retries = 3, + .reconnect_interval_secs = 3, .username = "Player", }, .ws = { @@ -43,6 +56,8 @@ static IZ_NetState IZ_NET_DEFAULT_STATE = { }, .callback = NULL, .action = {}, + .retries = 3, + .status = IZ_NET_STATUS_PRISTINE, }; IZ_ProcedureResult IZ_NetInitialize(IZ_NetState*, void*, void*, const char*, u8, const char**); diff --git a/src/packages/game/net/svc/IZ_wsclient.c b/src/packages/game/net/svc/IZ_wsclient.c index 913f915..0dd6d7a 100644 --- a/src/packages/game/net/svc/IZ_wsclient.c +++ b/src/packages/game/net/svc/IZ_wsclient.c @@ -14,8 +14,7 @@ IZ_ProcedureResult IZ_WSClientCallback( IZ_WSClientProtocolTeardown(wsi); break; case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: - IZ_WSClientConnectionError(wsi, in); - break; + return IZ_WSClientConnectionError(wsi, in); case LWS_CALLBACK_CLIENT_ESTABLISHED: return IZ_WSClientOnOpen(wsi, user); case LWS_CALLBACK_CLIENT_CLOSED: diff --git a/src/packages/game/net/svc/IZ_wsclient.h b/src/packages/game/net/svc/IZ_wsclient.h index 5fa79d7..c5a5868 100644 --- a/src/packages/game/net/svc/IZ_wsclient.h +++ b/src/packages/game/net/svc/IZ_wsclient.h @@ -47,7 +47,7 @@ IZ_ProcedureResult IZ_WSClientProtocolInitialize(struct lws*, void*); void IZ_WSClientProtocolTeardown(struct lws*); -void IZ_WSClientConnectionError(struct lws*, void*); +IZ_ProcedureResult IZ_WSClientConnectionError(struct lws*, void*); IZ_ProcedureResult IZ_WSClientOnOpen(struct lws*, IZ_WSClientSessionData*); diff --git a/src/packages/game/output/video/IZ_app.c b/src/packages/game/output/video/IZ_app.c index 8e694f3..6f3aae3 100644 --- a/src/packages/game/output/video/IZ_app.c +++ b/src/packages/game/output/video/IZ_app.c @@ -53,12 +53,22 @@ void IZ_VideoUpdateForDebugInput(IZ_VideoState* video_state, IZ_InputState* inpu } void IZ_VideoUpdateForDebugNet(IZ_VideoState* video_state, IZ_NetState* net_state) { - if (!net_state->ws.connection) { - return; - } const u8 size = 4; - SDL_SetRenderDrawColor(video_state->renderer, 0x00, 0xff, 0x00, 0xff); + switch (net_state->status) { + default: + return; + case IZ_NET_STATUS_ERROR: + SDL_SetRenderDrawColor(video_state->renderer, 0xff, 0x00, 0x00, 0xff); + break; + case IZ_NET_STATUS_CONNECTING: + SDL_SetRenderDrawColor(video_state->renderer, 0xff, 0xff, 0x00, 0xff); + break; + case IZ_NET_STATUS_CONNECTED: + SDL_SetRenderDrawColor(video_state->renderer, 0x00, 0xff, 0x00, 0xff); + break; + } + SDL_RenderFillRectF(video_state->renderer, &(SDL_FRect) { 0, (f32) (video_state->config.height - size), @@ -66,6 +76,10 @@ void IZ_VideoUpdateForDebugNet(IZ_VideoState* video_state, IZ_NetState* net_stat size, }); + if (!net_state->ws.connection) { + return; + } + u8 column; u8 row;