Split the network events to the net directory to keep the app code implementation clean.feature/data-structs
@@ -84,7 +84,7 @@ add_executable( | |||||
src/packages/game/util/IZ_midi.h | src/packages/game/util/IZ_midi.h | ||||
src/packages/game/net/core/IZ_websocket.h | src/packages/game/net/core/IZ_websocket.h | ||||
src/packages/game/net/core/IZ_websocket.c | src/packages/game/net/core/IZ_websocket.c | ||||
src/packages/game/net/IZ_net.c src/packages/game/net/IZ_net.h) | |||||
src/packages/game/net/IZ_net.c src/packages/game/net/IZ_net.h src/packages/game/net/IZ_app.c src/packages/game/net/IZ_app.h) | |||||
target_link_libraries( | target_link_libraries( | ||||
game | game | ||||
@@ -1,29 +1,23 @@ | |||||
#include "IZ_app.h" | #include "IZ_app.h" | ||||
IZ_ProcedureResult IZ_AppConnect(void* app_raw) { | |||||
IZ_App* app = app_raw; | |||||
if (IZ_WSClientInitialize(&app->net_state.ws, app->net_state.params)) { | |||||
return -1; | |||||
} | |||||
u64 IZ_AppGetTicks(struct IZ_App* app) { | |||||
return app->ticks; | |||||
} | |||||
i32 result = 0; | |||||
while (true) { | |||||
if (IZ_WSClientHandle(&app->net_state.ws)) { | |||||
result = -1; | |||||
break; | |||||
} | |||||
void IZ_AppBindConnection(struct IZ_App* app, struct lws* wsi) { | |||||
app->net_state.ws.connection = wsi; | |||||
} | |||||
if (app->net_state.ws.interrupted) { | |||||
break; | |||||
} | |||||
} | |||||
IZ_NetState* IZ_AppGetNetState(struct IZ_App* app) { | |||||
return &app->net_state; | |||||
} | |||||
IZ_WSClientTeardown(&app->net_state.ws); | |||||
return result; | |||||
IZ_InputState* IZ_AppGetInputState(struct IZ_App* app) { | |||||
return &app->input_state; | |||||
} | } | ||||
IZ_ProcedureResult IZ_AppInitialize(IZ_App* app, u8 argc, const char* argv[]) { | |||||
memset(app, 0, sizeof(IZ_App)); | |||||
IZ_ProcedureResult IZ_AppInitialize(struct IZ_App* app, u8 argc, const char* argv[]) { | |||||
memset(app, 0, sizeof(struct IZ_App)); | |||||
u32 flags = ( | u32 flags = ( | ||||
SDL_INIT_VIDEO | SDL_INIT_VIDEO | ||||
| SDL_INIT_GAMECONTROLLER | | SDL_INIT_GAMECONTROLLER | ||||
@@ -44,7 +38,7 @@ IZ_ProcedureResult IZ_AppInitialize(IZ_App* app, u8 argc, const char* argv[]) { | |||||
IZ_ConfigGetDefaultPath(config_path, 128); | IZ_ConfigGetDefaultPath(config_path, 128); | ||||
} | } | ||||
if (IZ_VideoInitialize(&app->video_state, config_path, argc, argv)) { | |||||
if (IZ_VideoInitialize(&app->video_state, app, config_path, argc, argv)) { | |||||
return IZ_APP_RUN_VIDEO_INIT_ERROR; | return IZ_APP_RUN_VIDEO_INIT_ERROR; | ||||
} | } | ||||
@@ -63,7 +57,7 @@ IZ_ProcedureResult IZ_AppInitialize(IZ_App* app, u8 argc, const char* argv[]) { | |||||
return IZ_APP_RUN_RESULT_OK; | return IZ_APP_RUN_RESULT_OK; | ||||
} | } | ||||
void IZ_AppTeardown(IZ_App* app) { | |||||
void IZ_AppTeardown(struct IZ_App* app) { | |||||
IZ_NetDisconnect(&app->net_state); | IZ_NetDisconnect(&app->net_state); | ||||
IZ_PoolTeardown(&app->pool); | IZ_PoolTeardown(&app->pool); | ||||
IZ_InputTeardown(&app->input_state); | IZ_InputTeardown(&app->input_state); | ||||
@@ -71,7 +65,7 @@ void IZ_AppTeardown(IZ_App* app) { | |||||
SDL_Quit(); | SDL_Quit(); | ||||
} | } | ||||
IZ_ProcedureResult IZ_AppHandleSDLEvents(IZ_App* app) { | |||||
IZ_ProcedureResult IZ_AppHandleSDLEvents(struct IZ_App* app) { | |||||
SDL_Event e; | SDL_Event e; | ||||
while (SDL_PollEvent(&e) != 0) { | while (SDL_PollEvent(&e) != 0) { | ||||
if (e.type == SDL_QUIT) { | if (e.type == SDL_QUIT) { | ||||
@@ -100,7 +94,7 @@ IZ_ProcedureResult IZ_AppHandleSDLEvents(IZ_App* app) { | |||||
return 0; | return 0; | ||||
} | } | ||||
void IZ_AppHandlePortMIDIEvents(IZ_App* app) { | |||||
void IZ_AppHandlePortMIDIEvents(struct IZ_App* app) { | |||||
u8 player_index; | u8 player_index; | ||||
i32* midi_events_count; | i32* midi_events_count; | ||||
u32 midi_event_index; | u32 midi_event_index; | ||||
@@ -130,21 +124,7 @@ void IZ_AppHandlePortMIDIEvents(IZ_App* app) { | |||||
} | } | ||||
} | } | ||||
void IZ_AppHandleNetworkingInboundBinaryEvents(IZ_App* app, void* binary_raw, size_t len) { | |||||
u8* binary = binary_raw; | |||||
size_t i; | |||||
printf("%llu: Binary", app->ticks); | |||||
for (i = 0; i < len; i += 1) { | |||||
printf("%c%02x", i == 0 ? '(' : ' ', binary[i]); | |||||
} | |||||
printf(")\n"); | |||||
} | |||||
void IZ_AppHandleNetworkingInboundTextEvents(IZ_App* app, const char* text, size_t len) { | |||||
printf("%llu: String(%s)\n", app->ticks, text); | |||||
} | |||||
IZ_ProcedureResult IZ_AppHandleInputEvents(IZ_App* app) { | |||||
IZ_ProcedureResult IZ_AppHandleInputEvents(struct IZ_App* app) { | |||||
i32 sdl_events_result = IZ_AppHandleSDLEvents(app); | i32 sdl_events_result = IZ_AppHandleSDLEvents(app); | ||||
if (sdl_events_result) { | if (sdl_events_result) { | ||||
return sdl_events_result; | return sdl_events_result; | ||||
@@ -154,252 +134,140 @@ IZ_ProcedureResult IZ_AppHandleInputEvents(IZ_App* app) { | |||||
return 0; | return 0; | ||||
} | } | ||||
IZ_ProcedureResult IZ_AppRun(IZ_App* app, u8 argc, const char* argv[]) { | |||||
IZ_ProcedureResult init_result = IZ_AppInitialize(app, argc, argv); | |||||
if (init_result) { | |||||
return init_result; | |||||
} | |||||
while (true) { | |||||
app->ticks = SDL_GetTicks64(); | |||||
// TODO do audio processing | |||||
// TODO do networking | |||||
if (IZ_AppHandleInputEvents(app)) { | |||||
break; | |||||
void IZ_VideoUpdateForDebugTicks(IZ_VideoState* video_state, uint64_t ticks) { | |||||
SDL_SetRenderDrawColor(video_state->renderer, 0x00, 0xff, 0xff, 0xff); | |||||
u64 the_ticks = ticks; | |||||
u8 column; | |||||
u8 row; | |||||
const u8 size = 4; | |||||
u8 i; | |||||
for (i = 0; i < 64; i += 1) { | |||||
column = i % 32; | |||||
row = i / 32; | |||||
if (the_ticks & 0x1) { | |||||
SDL_RenderFillRectF(video_state->renderer, &(SDL_FRect) { | |||||
(f32) (video_state->config.width - ((column + 1) * size)), | |||||
(f32) (video_state->config.height - ((row + 1) * size)), | |||||
size, | |||||
size | |||||
}); | |||||
} | } | ||||
the_ticks >>= 1; | |||||
} | |||||
} | |||||
// TODO update this | |||||
u8 player_index; | |||||
for (player_index = 0; player_index < IZ_PLAYERS; player_index += 1) { | |||||
if (app->net_state.action[player_index] != app->input_state.action[player_index]) { | |||||
IZ_NetSendBinaryMessage( | |||||
&app->net_state, | |||||
&(app->input_state.action[player_index]), | |||||
sizeof app->input_state.action[player_index] | |||||
); | |||||
void IZ_VideoUpdateForDebugInput(IZ_VideoState* video_state, IZ_InputState* input_state) { | |||||
SDL_SetRenderDrawColor(video_state->renderer, 0xff, 0xff, 0x00, 0xff); | |||||
const u8 size = 4; | |||||
u8 column; | |||||
u8 row; | |||||
u8 p; | |||||
u8 i; | |||||
for (p = 0; p < IZ_PLAYERS; p += 1) { | |||||
IZ_Action the_action = input_state->action[p]; | |||||
for (i = 0; i < CONTROLS; i += 1) { | |||||
column = (i % 4) + (p * 4); | |||||
row = i / 4; | |||||
if (the_action & 0x1) { | |||||
SDL_RenderFillRectF(video_state->renderer, &(SDL_FRect) { | |||||
(f32) (column * size), | |||||
(f32) (row * size), | |||||
size, | |||||
size | |||||
}); | |||||
} | } | ||||
app->net_state.action[player_index] = app->input_state.action[player_index]; | |||||
the_action >>= 1; | |||||
} | } | ||||
IZ_VideoUpdate(&app->video_state, app->ticks, &app->input_state); | |||||
} | } | ||||
IZ_AppTeardown(app); | |||||
return IZ_APP_RUN_RESULT_OK; | |||||
} | } | ||||
void IZ_WSClientAttemptConnect(struct lws_sorted_usec_list *sul) { | |||||
IZ_WSClientVHostData* vhd = lws_container_of(sul, IZ_WSClientVHostData, sul); | |||||
vhd->i.context = vhd->context; | |||||
vhd->i.port = *vhd->port; | |||||
vhd->i.address = vhd->address; | |||||
vhd->i.path = vhd->path; | |||||
vhd->i.host = vhd->i.address; | |||||
vhd->i.origin = vhd->i.address; | |||||
vhd->i.ssl_connection = 0; | |||||
vhd->i.protocol = NETWORK_PROTOCOL; | |||||
vhd->i.pwsi = &vhd->client_wsi; | |||||
if (lws_client_connect_via_info(&vhd->i)) { | |||||
void IZ_VideoUpdateForDebugNet(IZ_VideoState* video_state, IZ_NetState* net_state) { | |||||
if (!net_state->ws.connection) { | |||||
return; | return; | ||||
} | } | ||||
const u8 size = 4; | |||||
lws_sul_schedule( | |||||
vhd->context, | |||||
0, | |||||
&vhd->sul, | |||||
IZ_WSClientAttemptConnect, | |||||
10 * LWS_US_PER_SEC | |||||
); | |||||
} | |||||
IZ_ProcedureResult IZ_WSClientProtocolInitialize(struct lws* wsi, void* in) { | |||||
const struct lws_protocols* protocols = lws_get_protocol(wsi); | |||||
struct lws_vhost* vhost = lws_get_vhost(wsi); | |||||
IZ_WSClientVHostData* vhd_instance = (IZ_WSClientVHostData*) lws_protocol_vh_priv_get(vhost,protocols); | |||||
IZ_WSClientVHostData** vhd = &vhd_instance; | |||||
*vhd = lws_protocol_vh_priv_zalloc(vhost, protocols, sizeof(IZ_WSClientVHostData)); | |||||
(*vhd)->ring = lws_ring_create( | |||||
sizeof(IZ_WebsocketMessage), | |||||
RING_COUNT, | |||||
IZ_WebsocketDestroyMessage | |||||
); | |||||
if (!(*vhd)->ring) { | |||||
return -1; | |||||
} | |||||
(*vhd)->context = lws_get_context(wsi); | |||||
(*vhd)->protocol = protocols; | |||||
(*vhd)->vhost = vhost; | |||||
(*vhd)->port = (u16*) lws_pvo_search( | |||||
(const struct lws_protocol_vhost_options *)in, | |||||
"port" | |||||
)->value; | |||||
(*vhd)->address = lws_pvo_search( | |||||
(const struct lws_protocol_vhost_options *)in, | |||||
"address" | |||||
)->value; | |||||
(*vhd)->path = lws_pvo_search( | |||||
(const struct lws_protocol_vhost_options *)in, | |||||
"path" | |||||
)->value; | |||||
(*vhd)->app = lws_pvo_search( | |||||
(const struct lws_protocol_vhost_options *)in, | |||||
"app" | |||||
)->value; | |||||
IZ_WSClientAttemptConnect(&(*vhd)->sul); | |||||
return 0; | |||||
} | |||||
void IZ_WSClientProtocolTeardown(struct lws* wsi) { | |||||
IZ_WSClientVHostData* vhd = (IZ_WSClientVHostData*) lws_protocol_vh_priv_get( | |||||
lws_get_vhost(wsi), | |||||
lws_get_protocol(wsi) | |||||
); | |||||
if (vhd->ring) { | |||||
lws_ring_destroy(vhd->ring); | |||||
} | |||||
lws_sul_cancel(&vhd->sul); | |||||
} | |||||
void 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) | |||||
); | |||||
IZ_App* app = (IZ_App*) vhd->app; | |||||
app->net_state.ws.connection = NULL; | |||||
vhd->client_wsi = NULL; | |||||
lws_sul_schedule( | |||||
vhd->context, | |||||
SDL_SetRenderDrawColor(video_state->renderer, 0x00, 0xff, 0x00, 0xff); | |||||
SDL_RenderFillRectF(video_state->renderer, &(SDL_FRect) { | |||||
0, | 0, | ||||
&vhd->sul, | |||||
IZ_WSClientAttemptConnect, | |||||
LWS_US_PER_SEC | |||||
); | |||||
} | |||||
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; | |||||
(f32) (video_state->config.height - size), | |||||
size, | |||||
size, | |||||
}); | |||||
u8 column; | |||||
u8 row; | |||||
u8 p; | |||||
u8 i; | |||||
for (p = 0; p < IZ_PLAYERS; p += 1) { | |||||
IZ_Action the_action = net_state->action[p]; | |||||
for (i = 0; i < CONTROLS; i += 1) { | |||||
column = (i % 4) + (p * 4); | |||||
row = i / 4; | |||||
if (the_action & 0x1) { | |||||
SDL_RenderFillRectF(video_state->renderer, &(SDL_FRect) { | |||||
(f32) (column * size), | |||||
(f32) ((row * size) + (video_state->config.height - (size * 5))), | |||||
size, | |||||
size | |||||
}); | |||||
} | |||||
the_action >>= 1; | |||||
} | |||||
} | } | ||||
IZ_WSClientVHostData* vhd = (IZ_WSClientVHostData*) lws_protocol_vh_priv_get( | |||||
lws_get_vhost(wsi), | |||||
lws_get_protocol(wsi) | |||||
); | |||||
IZ_App* app = (IZ_App*) vhd->app; | |||||
app->net_state.ws.connection = wsi; | |||||
pss->tail = 0; | |||||
return 0; | |||||
} | } | ||||
void IZ_WSClientOnClose(struct lws* wsi) { | |||||
IZ_WSClientVHostData* vhd = (IZ_WSClientVHostData*) lws_protocol_vh_priv_get( | |||||
lws_get_vhost(wsi), | |||||
lws_get_protocol(wsi) | |||||
); | |||||
IZ_App* app = (IZ_App*) vhd->app; | |||||
app->net_state.ws.connection = NULL; | |||||
vhd->client_wsi = NULL; | |||||
lws_sul_schedule( | |||||
vhd->context, | |||||
0, | |||||
&vhd->sul, | |||||
IZ_WSClientAttemptConnect, | |||||
LWS_US_PER_SEC | |||||
); | |||||
void IZ_VideoUpdateForDebug(IZ_VideoState* video_state, u64 ticks, IZ_InputState* input_state, IZ_NetState* net_state) { | |||||
IZ_VideoUpdateForDebugTicks(video_state, ticks); | |||||
IZ_VideoUpdateForDebugInput(video_state, input_state); | |||||
IZ_VideoUpdateForDebugNet(video_state, net_state); | |||||
} | } | ||||
IZ_ProcedureResult IZ_WSClientWritable(struct lws* wsi) { | |||||
IZ_WSClientVHostData* vhd = (IZ_WSClientVHostData*) lws_protocol_vh_priv_get( | |||||
lws_get_vhost(wsi), | |||||
lws_get_protocol(wsi) | |||||
); | |||||
const IZ_WebsocketMessage* pmsg = lws_ring_get_element(vhd->ring, &vhd->tail); | |||||
if (!pmsg) { | |||||
return 0; | |||||
} | |||||
/* notice we allowed for LWS_PRE in the payload already */ | |||||
i32 m = lws_write( | |||||
wsi, | |||||
((unsigned char*) pmsg->payload) + LWS_PRE, | |||||
pmsg->len, | |||||
pmsg->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT | |||||
); | |||||
if (m < (i32)pmsg->len) { | |||||
lwsl_err("ERROR %d writing to ws socket\n", m); | |||||
return -1; | |||||
} | |||||
lws_ring_consume_single_tail(vhd->ring, &vhd->tail, 1); | |||||
/* more to do for us? */ | |||||
if (lws_ring_get_element(vhd->ring, &vhd->tail)) { | |||||
/* come back as soon as we can write more */ | |||||
lws_callback_on_writable(wsi); | |||||
void IZ_VideoUpdate(IZ_VideoState* video_state) { | |||||
struct IZ_App* app = video_state->user_data; | |||||
if (app->ticks - video_state->last_update_at > 1000 / video_state->config.max_fps) { | |||||
// Update window | |||||
SDL_SetRenderDrawColor(video_state->renderer, 0x00, 0x00, 0x00, 0xff); | |||||
SDL_RenderClear(video_state->renderer); | |||||
for (u8 i = 0; i < MAX_ACTIVE_SPRITES; i += 1) { | |||||
if (!video_state->active_sprites[i]) { | |||||
continue; | |||||
} | |||||
// TODO draw sprites | |||||
} | |||||
IZ_VideoUpdateForDebug(video_state, app->ticks, &app->input_state, &app->net_state); | |||||
SDL_RenderPresent(video_state->renderer); | |||||
video_state->last_update_at = app->ticks; | |||||
} | } | ||||
return 0; | |||||
} | } | ||||
void IZ_WSClientOnReceive(struct lws* wsi, IZ_WSClientSessionData* pss, void* in, size_t len) { | |||||
i32 n = (i32) lws_ring_get_count_free_elements(pss->ring); | |||||
if (!n) { | |||||
lwsl_user("dropping!\n"); | |||||
return; | |||||
IZ_ProcedureResult IZ_AppRun(struct IZ_App* app, u8 argc, const char* argv[]) { | |||||
IZ_ProcedureResult init_result = IZ_AppInitialize(app, argc, argv); | |||||
if (init_result) { | |||||
return init_result; | |||||
} | } | ||||
lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE: %4d (rpp %5d, first %d, last %d, bin %d)\n", | |||||
(int)len, (int)lws_remaining_packet_payload(wsi), | |||||
lws_is_first_fragment(wsi), | |||||
lws_is_final_fragment(wsi), | |||||
lws_frame_is_binary(wsi)); | |||||
// lwsl_hexdump_notice(in, len); | |||||
IZ_WebsocketMessage amsg; | |||||
i32 result = ( | |||||
lws_frame_is_binary(wsi) | |||||
? IZ_WebsocketCreateBinaryMessage(wsi, &amsg, in, len) | |||||
: IZ_WebsocketCreateTextMessage(wsi, &amsg, in, len) | |||||
); | |||||
if (result < 0) { | |||||
lwsl_user("OOM: dropping\n"); | |||||
return; | |||||
} | |||||
while (true) { | |||||
app->ticks = SDL_GetTicks64(); | |||||
IZ_WSClientVHostData* vhd = (IZ_WSClientVHostData*) lws_protocol_vh_priv_get( | |||||
lws_get_vhost(wsi), | |||||
lws_get_protocol(wsi) | |||||
); | |||||
// TODO do audio processing | |||||
// TODO do networking | |||||
IZ_App* app = (IZ_App*) vhd->app; | |||||
if (amsg.binary) { | |||||
IZ_AppHandleNetworkingInboundBinaryEvents(app, in, len); | |||||
} else { | |||||
IZ_AppHandleNetworkingInboundTextEvents(app, in, len); | |||||
} | |||||
if (IZ_AppHandleInputEvents(app)) { | |||||
break; | |||||
} | |||||
if (!lws_ring_insert(pss->ring, &amsg, 1)) { | |||||
IZ_WebsocketDestroyMessage(&amsg); | |||||
lwsl_user("dropping!\n"); | |||||
return; | |||||
IZ_AppHandleOutboundNetworking(app); | |||||
IZ_VideoUpdate(&app->video_state); | |||||
} | } | ||||
lws_callback_on_writable(wsi); | |||||
if (!pss->flow_controlled && n < 3) { | |||||
pss->flow_controlled = true; | |||||
lws_rx_flow_control(wsi, 0); | |||||
} | |||||
IZ_AppTeardown(app); | |||||
return IZ_APP_RUN_RESULT_OK; | |||||
} | } |
@@ -6,8 +6,7 @@ | |||||
#include "input/IZ_input.h" | #include "input/IZ_input.h" | ||||
#include "output/IZ_video.h" | #include "output/IZ_video.h" | ||||
#include "memory/IZ_pool.h" | #include "memory/IZ_pool.h" | ||||
#include "net/svc/IZ_wsclient.h" | |||||
#include "net/IZ_net.h" | |||||
#include "net/IZ_app.h" | |||||
typedef enum { | typedef enum { | ||||
IZ_APP_RUN_RESULT_OK, | IZ_APP_RUN_RESULT_OK, | ||||
@@ -18,7 +17,7 @@ typedef enum { | |||||
IZ_APP_RUN_NETWORKING_ERROR, | IZ_APP_RUN_NETWORKING_ERROR, | ||||
} IZ_AppRunResult; | } IZ_AppRunResult; | ||||
typedef struct { | |||||
typedef struct IZ_App { | |||||
IZ_InputState input_state; | IZ_InputState input_state; | ||||
IZ_VideoState video_state; | IZ_VideoState video_state; | ||||
IZ_Pool pool; | IZ_Pool pool; | ||||
@@ -27,46 +26,8 @@ typedef struct { | |||||
IZ_NetState net_state; | IZ_NetState net_state; | ||||
} IZ_App; | } IZ_App; | ||||
typedef struct { | |||||
u8 player_index: 3; | |||||
u8 player_state: 5; | |||||
u16 action_set; | |||||
} IZ_AppPlayerActionSyncMessage; | |||||
IZ_ProcedureResult IZ_AppRun(struct IZ_App*, u8, const char**); | |||||
typedef struct { | |||||
u8 player_index: 3; | |||||
f32 x; | |||||
f32 y; | |||||
f32 right; | |||||
f32 up; | |||||
} IZ_AppPlayerState; | |||||
typedef enum { | |||||
IZ_MESSAGE_KIND_ACTION_SYNC = 0, | |||||
IZ_MESSAGE_KIND_STATE_SYNC = 1, | |||||
} IZ_MessageKind; | |||||
typedef struct { | |||||
u8 message_kind; // player | |||||
u64 client_elapsed_time; // for synchronization | |||||
} IZ_AppMessageHeader; | |||||
typedef struct { | |||||
u8 player_actions_count; | |||||
IZ_AppPlayerActionSyncMessage player_actions[]; | |||||
} IZ_AppPlayerActionSection; | |||||
typedef struct { | |||||
IZ_AppMessageHeader header; | |||||
IZ_AppPlayerActionSection player_actions; | |||||
} IZ_AppActionSyncMessage; | |||||
typedef struct { | |||||
IZ_AppMessageHeader header; | |||||
IZ_AppPlayerState player_state[IZ_PLAYERS]; | |||||
IZ_AppPlayerActionSection player_actions; | |||||
} IZ_AppStateSyncMessage; | |||||
IZ_ProcedureResult IZ_AppRun(IZ_App*, u8, const char**); | |||||
IZ_ProcedureResult IZ_AppConnect(void*); | |||||
#endif | #endif |
@@ -0,0 +1,284 @@ | |||||
#include "IZ_app.h" | |||||
void IZ_AppHandleNetworkingInboundBinaryEvents(struct IZ_App* app, void* binary_raw, size_t len) { | |||||
u8* binary = binary_raw; | |||||
size_t i; | |||||
printf("%llu: Binary", IZ_AppGetTicks(app)); | |||||
for (i = 0; i < len; i += 1) { | |||||
printf("%c%02x", i == 0 ? '(' : ' ', binary[i]); | |||||
} | |||||
printf(")\n"); | |||||
} | |||||
void IZ_AppHandleNetworkingInboundTextEvents(struct IZ_App* app, const char* text, size_t len) { | |||||
printf("%llu: String(%s)\n", IZ_AppGetTicks(app), text); | |||||
} | |||||
void IZ_AppHandleOutboundNetworking(struct IZ_App* app) { | |||||
// TODO implement queueing of messages | |||||
IZ_NetState* net_state = IZ_AppGetNetState(app); | |||||
IZ_InputState* input_state = IZ_AppGetInputState(app); | |||||
u8 player_index; | |||||
for (player_index = 0; player_index < IZ_PLAYERS; player_index += 1) { | |||||
if (net_state->action[player_index] != input_state->action[player_index]) { | |||||
u8 player_actions_count = 1; | |||||
IZ_AppActionSyncMessage* msg = SDL_malloc( | |||||
sizeof(IZ_AppMessageHeader) | |||||
+ sizeof(u8) | |||||
+ (sizeof(IZ_AppPlayerActionSyncMessage) * player_actions_count) | |||||
); | |||||
msg->header.client_elapsed_time = IZ_AppGetTicks(app); | |||||
msg->header.message_kind = IZ_MESSAGE_KIND_ACTION_SYNC; | |||||
msg->action.player_actions_count = player_actions_count; | |||||
msg->action.player[0].index = player_index; | |||||
msg->action.player[0].value = input_state->action[player_index]; | |||||
msg->action.player[0].state = 0; | |||||
IZ_NetSendBinaryMessage( | |||||
net_state, | |||||
msg, | |||||
sizeof(*msg) | |||||
); | |||||
SDL_free(msg); | |||||
} | |||||
net_state->action[player_index] = input_state->action[player_index]; | |||||
} | |||||
} | |||||
void IZ_WSClientAttemptConnect(struct lws_sorted_usec_list *sul) { | |||||
IZ_WSClientVHostData* vhd = lws_container_of(sul, IZ_WSClientVHostData, sul); | |||||
vhd->i.context = vhd->context; | |||||
vhd->i.port = *vhd->port; | |||||
vhd->i.address = vhd->address; | |||||
vhd->i.path = vhd->path; | |||||
vhd->i.host = vhd->i.address; | |||||
vhd->i.origin = vhd->i.address; | |||||
vhd->i.ssl_connection = 0; | |||||
vhd->i.protocol = NETWORK_PROTOCOL; | |||||
vhd->i.pwsi = &vhd->client_wsi; | |||||
if (lws_client_connect_via_info(&vhd->i)) { | |||||
return; | |||||
} | |||||
lws_sul_schedule( | |||||
vhd->context, | |||||
0, | |||||
&vhd->sul, | |||||
IZ_WSClientAttemptConnect, | |||||
10 * LWS_US_PER_SEC | |||||
); | |||||
} | |||||
IZ_ProcedureResult IZ_WSClientProtocolInitialize(struct lws* wsi, void* in) { | |||||
const struct lws_protocols* protocols = lws_get_protocol(wsi); | |||||
struct lws_vhost* vhost = lws_get_vhost(wsi); | |||||
IZ_WSClientVHostData* vhd_instance = (IZ_WSClientVHostData*) lws_protocol_vh_priv_get(vhost,protocols); | |||||
IZ_WSClientVHostData** vhd = &vhd_instance; | |||||
*vhd = lws_protocol_vh_priv_zalloc(vhost, protocols, sizeof(IZ_WSClientVHostData)); | |||||
(*vhd)->ring = lws_ring_create( | |||||
sizeof(IZ_WebsocketMessage), | |||||
RING_COUNT, | |||||
IZ_WebsocketDestroyMessage | |||||
); | |||||
if (!(*vhd)->ring) { | |||||
return -1; | |||||
} | |||||
(*vhd)->context = lws_get_context(wsi); | |||||
(*vhd)->protocol = protocols; | |||||
(*vhd)->vhost = vhost; | |||||
(*vhd)->port = (u16*) lws_pvo_search( | |||||
(const struct lws_protocol_vhost_options *)in, | |||||
"port" | |||||
)->value; | |||||
(*vhd)->address = lws_pvo_search( | |||||
(const struct lws_protocol_vhost_options *)in, | |||||
"address" | |||||
)->value; | |||||
(*vhd)->path = lws_pvo_search( | |||||
(const struct lws_protocol_vhost_options *)in, | |||||
"path" | |||||
)->value; | |||||
(*vhd)->app = lws_pvo_search( | |||||
(const struct lws_protocol_vhost_options *)in, | |||||
"app" | |||||
)->value; | |||||
IZ_WSClientAttemptConnect(&(*vhd)->sul); | |||||
return 0; | |||||
} | |||||
void IZ_WSClientProtocolTeardown(struct lws* wsi) { | |||||
IZ_WSClientVHostData* vhd = (IZ_WSClientVHostData*) lws_protocol_vh_priv_get( | |||||
lws_get_vhost(wsi), | |||||
lws_get_protocol(wsi) | |||||
); | |||||
if (vhd->ring) { | |||||
lws_ring_destroy(vhd->ring); | |||||
} | |||||
lws_sul_cancel(&vhd->sul); | |||||
} | |||||
void 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) | |||||
); | |||||
struct IZ_App* app = (struct IZ_App*) vhd->app; | |||||
IZ_AppBindConnection(app, NULL); | |||||
vhd->client_wsi = NULL; | |||||
lws_sul_schedule( | |||||
vhd->context, | |||||
0, | |||||
&vhd->sul, | |||||
IZ_WSClientAttemptConnect, | |||||
LWS_US_PER_SEC | |||||
); | |||||
} | |||||
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_AppBindConnection(app, wsi); | |||||
pss->tail = 0; | |||||
return 0; | |||||
} | |||||
void IZ_WSClientOnClose(struct lws* wsi) { | |||||
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_AppBindConnection(app, NULL); | |||||
vhd->client_wsi = NULL; | |||||
lws_sul_schedule( | |||||
vhd->context, | |||||
0, | |||||
&vhd->sul, | |||||
IZ_WSClientAttemptConnect, | |||||
LWS_US_PER_SEC | |||||
); | |||||
} | |||||
IZ_ProcedureResult IZ_WSClientWritable(struct lws* wsi) { | |||||
IZ_WSClientVHostData* vhd = (IZ_WSClientVHostData*) lws_protocol_vh_priv_get( | |||||
lws_get_vhost(wsi), | |||||
lws_get_protocol(wsi) | |||||
); | |||||
const IZ_WebsocketMessage* pmsg = lws_ring_get_element(vhd->ring, &vhd->tail); | |||||
if (!pmsg) { | |||||
return 0; | |||||
} | |||||
/* notice we allowed for LWS_PRE in the payload already */ | |||||
i32 m = lws_write( | |||||
wsi, | |||||
((unsigned char*) pmsg->payload) + LWS_PRE, | |||||
pmsg->len, | |||||
pmsg->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT | |||||
); | |||||
if (m < (i32)pmsg->len) { | |||||
lwsl_err("ERROR %d writing to ws socket\n", m); | |||||
return -1; | |||||
} | |||||
lws_ring_consume_single_tail(vhd->ring, &vhd->tail, 1); | |||||
/* more to do for us? */ | |||||
if (lws_ring_get_element(vhd->ring, &vhd->tail)) { | |||||
/* come back as soon as we can write more */ | |||||
lws_callback_on_writable(wsi); | |||||
} | |||||
return 0; | |||||
} | |||||
void IZ_WSClientOnReceive(struct lws* wsi, IZ_WSClientSessionData* pss, void* in, size_t len) { | |||||
i32 n = (i32) lws_ring_get_count_free_elements(pss->ring); | |||||
if (!n) { | |||||
lwsl_user("dropping!\n"); | |||||
return; | |||||
} | |||||
lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE: %4d (rpp %5d, first %d, last %d, bin %d)\n", | |||||
(int)len, (int)lws_remaining_packet_payload(wsi), | |||||
lws_is_first_fragment(wsi), | |||||
lws_is_final_fragment(wsi), | |||||
lws_frame_is_binary(wsi)); | |||||
// lwsl_hexdump_notice(in, len); | |||||
IZ_WebsocketMessage amsg; | |||||
i32 result = ( | |||||
lws_frame_is_binary(wsi) | |||||
? IZ_WebsocketCreateBinaryMessage(wsi, &amsg, in, len) | |||||
: IZ_WebsocketCreateTextMessage(wsi, &amsg, in, len) | |||||
); | |||||
if (result < 0) { | |||||
lwsl_user("OOM: dropping\n"); | |||||
return; | |||||
} | |||||
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; | |||||
if (amsg.binary) { | |||||
IZ_AppHandleNetworkingInboundBinaryEvents(app, in, len); | |||||
} else { | |||||
IZ_AppHandleNetworkingInboundTextEvents(app, in, len); | |||||
} | |||||
if (!lws_ring_insert(pss->ring, &amsg, 1)) { | |||||
IZ_WebsocketDestroyMessage(&amsg); | |||||
lwsl_user("dropping!\n"); | |||||
return; | |||||
} | |||||
lws_callback_on_writable(wsi); | |||||
if (!pss->flow_controlled && n < 3) { | |||||
pss->flow_controlled = true; | |||||
lws_rx_flow_control(wsi, 0); | |||||
} | |||||
} | |||||
IZ_ProcedureResult IZ_AppConnect(void* app_raw) { | |||||
struct IZ_App* app = app_raw; | |||||
IZ_NetState* net_state = IZ_AppGetNetState(app); | |||||
if (IZ_WSClientInitialize(&net_state->ws, net_state->params)) { | |||||
return -1; | |||||
} | |||||
i32 result = 0; | |||||
while (true) { | |||||
if (IZ_WSClientHandle(&net_state->ws)) { | |||||
result = -1; | |||||
break; | |||||
} | |||||
if (net_state->ws.interrupted) { | |||||
break; | |||||
} | |||||
} | |||||
IZ_WSClientTeardown(&net_state->ws); | |||||
return result; | |||||
} |
@@ -0,0 +1,60 @@ | |||||
#ifndef IZ_NET_APP_H | |||||
#define IZ_NET_APP_H | |||||
#include "../IZ_common.h" | |||||
#include "../input/IZ_input.h" | |||||
#include "IZ_net.h" | |||||
typedef enum { | |||||
IZ_MESSAGE_KIND_ACTION_SYNC = 0, | |||||
IZ_MESSAGE_KIND_STATE_SYNC = 1, | |||||
} IZ_MessageKind; | |||||
typedef struct { | |||||
u8 index: 3; | |||||
u8 state: 5; | |||||
u16 value; | |||||
} IZ_AppPlayerActionSyncMessage; | |||||
typedef struct { | |||||
u8 player_index: 3; | |||||
f32 x; | |||||
f32 y; | |||||
f32 right; | |||||
f32 up; | |||||
} IZ_AppPlayerState; | |||||
typedef struct { | |||||
u8 message_kind; // player | |||||
u64 client_elapsed_time; // for synchronization | |||||
} IZ_AppMessageHeader; | |||||
typedef struct { | |||||
u8 player_actions_count; | |||||
IZ_AppPlayerActionSyncMessage player[]; | |||||
} IZ_AppPlayerActionSection; | |||||
typedef struct { | |||||
IZ_AppMessageHeader header; | |||||
IZ_AppPlayerActionSection action; | |||||
} IZ_AppActionSyncMessage; | |||||
typedef struct { | |||||
IZ_AppMessageHeader header; | |||||
IZ_AppPlayerState player_state[IZ_PLAYERS]; | |||||
IZ_AppPlayerActionSection player_actions; | |||||
} IZ_AppStateSyncMessage; | |||||
struct IZ_App; | |||||
u64 IZ_AppGetTicks(struct IZ_App*); | |||||
void IZ_AppBindConnection(struct IZ_App*, struct lws*); | |||||
IZ_NetState* IZ_AppGetNetState(struct IZ_App*); | |||||
IZ_InputState* IZ_AppGetInputState(struct IZ_App*); | |||||
void IZ_AppHandleOutboundNetworking(struct IZ_App*); | |||||
#endif |
@@ -27,7 +27,7 @@ void IZ_VideoOverrideConfig(IZ_VideoState* state, u8 argc, const char* argv[]) { | |||||
} | } | ||||
} | } | ||||
IZ_ProcedureResult IZ_VideoInitialize(IZ_VideoState* state, const char* config_path, u8 argc, const char* argv[]) { | |||||
IZ_ProcedureResult IZ_VideoInitialize(IZ_VideoState* state, void* user_data, const char* config_path, u8 argc, const char* argv[]) { | |||||
SDL_memcpy(state, &IZ_DEFAULT_VIDEO_STATE, sizeof(IZ_VideoState)); | SDL_memcpy(state, &IZ_DEFAULT_VIDEO_STATE, sizeof(IZ_VideoState)); | ||||
IZ_VideoLoadConfig(state, config_path); | IZ_VideoLoadConfig(state, config_path); | ||||
@@ -36,6 +36,7 @@ IZ_ProcedureResult IZ_VideoInitialize(IZ_VideoState* state, const char* config_p | |||||
} | } | ||||
IZ_VideoOverrideConfig(state, argc, argv); | IZ_VideoOverrideConfig(state, argc, argv); | ||||
state->last_update_at = 0u; | state->last_update_at = 0u; | ||||
state->user_data = user_data; | |||||
SDL_Window* window = SDL_CreateWindow( | SDL_Window* window = SDL_CreateWindow( | ||||
IZ_APP_NAME, | IZ_APP_NAME, | ||||
@@ -55,79 +56,6 @@ IZ_ProcedureResult IZ_VideoInitialize(IZ_VideoState* state, const char* config_p | |||||
return 0; | return 0; | ||||
} | } | ||||
void IZ_VideoUpdateForDebugTicks(IZ_VideoState* video_state, uint64_t ticks) { | |||||
SDL_SetRenderDrawColor(video_state->renderer, 0x00, 0xff, 0xff, 0xff); | |||||
u64 the_ticks = ticks; | |||||
u8 column; | |||||
u8 row; | |||||
const u8 size = 4; | |||||
u8 i; | |||||
for (i = 0; i < 64; i += 1) { | |||||
column = i % 32; | |||||
row = i / 32; | |||||
if (the_ticks & 0x1) { | |||||
SDL_RenderFillRectF(video_state->renderer, &(SDL_FRect) { | |||||
(f32) (video_state->config.width - ((column + 1) * size)), | |||||
(f32) (video_state->config.height - ((row + 1) * size)), | |||||
size, | |||||
size | |||||
}); | |||||
} | |||||
the_ticks >>= 1; | |||||
} | |||||
} | |||||
void IZ_VideoUpdateForDebugInput(IZ_VideoState* video_state, IZ_InputState* input_state) { | |||||
SDL_SetRenderDrawColor(video_state->renderer, 0xff, 0xff, 0x00, 0xff); | |||||
u8 column; | |||||
u8 row; | |||||
const u8 size = 4; | |||||
u8 p; | |||||
u8 i; | |||||
for (p = 0; p < IZ_PLAYERS; p += 1) { | |||||
IZ_Action the_action = input_state->action[p]; | |||||
for (i = 0; i < CONTROLS; i += 1) { | |||||
column = (i % 4) + (p * 4); | |||||
row = i / 4; | |||||
if (the_action & 0x1) { | |||||
SDL_RenderFillRectF(video_state->renderer, &(SDL_FRect) { | |||||
(f32) (column * size), | |||||
(f32) (row * size), | |||||
size, | |||||
size | |||||
}); | |||||
} | |||||
the_action >>= 1; | |||||
} | |||||
} | |||||
} | |||||
void IZ_VideoUpdateForDebug(IZ_VideoState* video_state, IZ_InputState* input_state, u64 ticks) { | |||||
IZ_VideoUpdateForDebugTicks(video_state, ticks); | |||||
IZ_VideoUpdateForDebugInput(video_state, input_state); | |||||
} | |||||
void IZ_VideoUpdate(IZ_VideoState* video_state, u64 ticks, IZ_InputState* input_state) { | |||||
if (ticks - video_state->last_update_at > 1000 / video_state->config.max_fps) { | |||||
// Update window | |||||
SDL_SetRenderDrawColor(video_state->renderer, 0x00, 0x00, 0x00, 0xff); | |||||
SDL_RenderClear(video_state->renderer); | |||||
for (u8 i = 0; i < MAX_ACTIVE_SPRITES; i += 1) { | |||||
if (!video_state->active_sprites[i]) { | |||||
continue; | |||||
} | |||||
// TODO draw sprites | |||||
} | |||||
IZ_VideoUpdateForDebug(video_state, input_state, ticks); | |||||
SDL_RenderPresent(video_state->renderer); | |||||
video_state->last_update_at = ticks; | |||||
} | |||||
} | |||||
void IZ_VideoTeardown(IZ_VideoState* state) { | void IZ_VideoTeardown(IZ_VideoState* state) { | ||||
SDL_DestroyWindow(state->window); | SDL_DestroyWindow(state->window); | ||||
} | } |
@@ -6,6 +6,7 @@ | |||||
#include <SDL_render.h> | #include <SDL_render.h> | ||||
#include "../input/IZ_input.h" | #include "../input/IZ_input.h" | ||||
#include "../net/IZ_net.h" | |||||
#include "../IZ_common.h" | #include "../IZ_common.h" | ||||
#include "../IZ_config.h" | #include "../IZ_config.h" | ||||
@@ -21,6 +22,7 @@ typedef struct { | |||||
} IZ_VideoConfig; | } IZ_VideoConfig; | ||||
typedef struct { | typedef struct { | ||||
void* user_data; | |||||
IZ_VideoConfig config; | IZ_VideoConfig config; | ||||
u64 last_update_at; | u64 last_update_at; | ||||
SDL_Window* window; | SDL_Window* window; | ||||
@@ -29,6 +31,7 @@ typedef struct { | |||||
} IZ_VideoState; | } IZ_VideoState; | ||||
static const IZ_VideoState IZ_DEFAULT_VIDEO_STATE = { | static const IZ_VideoState IZ_DEFAULT_VIDEO_STATE = { | ||||
.user_data = NULL, | |||||
.config = { | .config = { | ||||
.width = 320u, | .width = 320u, | ||||
.height = 240u, | .height = 240u, | ||||
@@ -40,11 +43,11 @@ static const IZ_VideoState IZ_DEFAULT_VIDEO_STATE = { | |||||
.active_sprites = {}, | .active_sprites = {}, | ||||
}; | }; | ||||
IZ_ProcedureResult IZ_VideoInitialize(IZ_VideoState*, const char*, u8, const char**); | |||||
IZ_ProcedureResult IZ_VideoInitialize(IZ_VideoState*, void*, const char*, u8, const char**); | |||||
IZ_ProcedureResult IZ_VideoSaveConfig(IZ_VideoState*, const char*); | IZ_ProcedureResult IZ_VideoSaveConfig(IZ_VideoState*, const char*); | ||||
void IZ_VideoUpdate(IZ_VideoState*, u64, IZ_InputState*); | |||||
void IZ_VideoUpdate(IZ_VideoState*); | |||||
void IZ_VideoTeardown(IZ_VideoState*); | void IZ_VideoTeardown(IZ_VideoState*); | ||||