You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

340 lines
10 KiB
C

#include <chipmunk.h> // initializing bodies
#include "types.h"
#include "sokol_time.h"
#include <enet/enet.h>
#include <stdio.h>
#include <inttypes.h> // int64 printing
#include <stdlib.h>
#include <string.h> // error string
#include <errno.h>
#include "minilzo.h"
#ifdef __unix
#define fopen_s(pFile,filename,mode) ((*(pFile))=fopen((filename),(mode)))==NULL
#endif
// started in a thread from host
void server(void* world_save_name)
{
stm_setup();
struct GameState gs = { 0 };
size_t entities_size = (sizeof(Entity) * MAX_ENTITIES);
Entity* entity_data = malloc(entities_size);
initialize(&gs, entity_data, entities_size);
Log("Allocated %zu bytes for entities\n", entities_size);
if (world_save_name != NULL)
{
size_t read_game_data_buffer_size = entities_size;
char* read_game_data = malloc(read_game_data_buffer_size);
FILE* file = NULL;
fopen_s(&file, (const char*)world_save_name, "rb");
if (file == NULL)
{
Log("Could not read from data file %s: errno %d\n", (const char*)world_save_name, errno);
}
else
{
size_t actual_length = fread(read_game_data, sizeof(char), entities_size, file);
if (actual_length <= 1)
{
Log("Could only read %zu bytes, error: errno %d\n", actual_length, errno);
exit(-1);
}
Log("Read %zu bytes from save file\n", actual_length);
ServerToClient msg = (ServerToClient){
.cur_gs = &gs,
};
from_bytes(&msg, read_game_data, actual_length, true, true);
fclose(file);
}
free(read_game_data);
}
#define BOX_AT_TYPE(grid, pos, type) { Entity* box = new_entity(&gs); box_create(&gs, box, grid, pos); box->box_type = type; }
#define BOX_AT(grid, pos) BOX_AT_TYPE(grid, pos, BoxHullpiece)
// one box policy
if (false)
{
Entity* grid = new_entity(&gs);
grid_create(&gs, grid);
entity_set_pos(grid, (V2) { -BOX_SIZE * 2, 0.0f });
Entity* box = new_entity(&gs);
box_create(&gs, box, grid, (V2) { 0 });
}
// rotation test
if (false)
{
Entity* grid = new_entity(&gs);
grid_create(&gs, grid);
entity_set_pos(grid, (V2) { -BOX_SIZE * 2, 0.0f });
entity_set_rotation(grid, PI / 1.7f);
cpBodySetVelocity(grid->body, cpv(-0.1, 0.0));
cpBodySetAngularVelocity(grid->body, 1.0f);
BOX_AT(grid, ((V2) { 0 }));
BOX_AT(grid, ((V2) { BOX_SIZE, 0 }));
BOX_AT(grid, ((V2) { 2.0 * BOX_SIZE, 0 }));
BOX_AT(grid, ((V2) { 2.0 * BOX_SIZE, BOX_SIZE }));
BOX_AT(grid, ((V2) { 0.0 * BOX_SIZE, -BOX_SIZE }));
}
if (enet_initialize() != 0)
{
fprintf(stderr, "An error occurred while initializing ENet.\n");
exit(-1);
}
ENetAddress address;
ENetHost* server;
int sethost = enet_address_set_host_ip(&address, LOCAL_SERVER_ADDRESS);
if (sethost != 0)
{
Log("Fishy return value from set host: %d\n", sethost);
}
/* Bind the server to port 1234. */
address.port = SERVER_PORT;
server = enet_host_create(&address /* the address to bind the server host to */,
32 /* allow up to 32 clients and/or outgoing connections */,
2 /* allow up to 2 channels to be used, 0 and 1 */,
0 /* assume any amount of incoming bandwidth */,
0 /* assume any amount of outgoing bandwidth */);
if (server == NULL)
{
fprintf(stderr,
"An error occurred while trying to create an ENet server host.\n");
exit(-1);
}
Log("Serving on port %d...\n", SERVER_PORT);
ENetEvent event;
uint64_t last_processed_time = stm_now();
uint64_t last_saved_world_time = stm_now();
float total_time = 0.0f;
size_t player_to_latest_id_processed[MAX_PLAYERS] = { 0 };
char* world_save_buffer = malloc(entities_size);
while (true)
{
// @Speed handle enet messages and simulate gamestate in parallel, then sync... must clone gamestate for this
while (true)
{
int ret = enet_host_service(server, &event, 0);
if (ret == 0)
break;
if (ret < 0)
{
fprintf(stderr, "Enet host service error %d\n", ret);
}
if (ret > 0)
{
switch (event.type)
{
case ENET_EVENT_TYPE_CONNECT:
{
Log("A new client connected from %x:%u.\n",
event.peer->address.host,
event.peer->address.port);
int64_t player_slot = -1;
for (int i = 0; i < MAX_PLAYERS; i++)
{
if (!gs.players[i].connected)
{
player_slot = i;
break;
}
}
if (player_slot == -1)
{
enet_peer_disconnect_now(event.peer, 69);
}
else
{
event.peer->data = (void*)player_slot;
gs.players[player_slot] = (struct Player){ 0 };
gs.players[player_slot].connected = true;
player_to_latest_id_processed[player_slot] = 0;
#ifdef UNLOCK_ALL
gs.players[player_slot].unlocked_bombs = true;
#endif
}
break;
}
case ENET_EVENT_TYPE_RECEIVE:
{
//Log("A packet of length %zu was received on channel %u.\n",
// event.packet->dataLength,
//event.channelID);
size_t length = event.packet->dataLength;
if (length != sizeof(struct ClientToServer))
{
Log("Length did not match up...\n");
}
else
{
struct ClientToServer received = { 0 };
memcpy(&received, event.packet->data, length);
int64_t player_slot = (int64_t)event.peer->data;
size_t latest_id = player_to_latest_id_processed[player_slot];
if (received.inputs[0].id > latest_id)
{
for (int i = INPUT_BUFFER - 1; i >= 0; i--)
{
if (received.inputs[i].tick == 0) // empty input
continue;
if (received.inputs[i].id <= latest_id)
continue; // don't reprocess inputs already processed
InputFrame cur_input = received.inputs[i];
gs.players[player_slot].input.movement = cur_input.movement;
gs.players[player_slot].input.hand_pos = cur_input.hand_pos;
// for these "event" inputs, only modify the current input if the event is true.
// while processing the gamestate, will mark it as false once processed. This
// prevents setting the event input to false before it's been processed.
if (cur_input.seat_action)
{
gs.players[player_slot].input.seat_action = cur_input.seat_action;
gs.players[player_slot].input.grid_hand_pos_local_to = cur_input.grid_hand_pos_local_to;
}
if (cur_input.dobuild)
{
gs.players[player_slot].input.grid_hand_pos_local_to = cur_input.grid_hand_pos_local_to;
gs.players[player_slot].input.dobuild = cur_input.dobuild;
gs.players[player_slot].input.build_type = cur_input.build_type;
gs.players[player_slot].input.build_rotation = cur_input.build_rotation;
}
}
player_to_latest_id_processed[player_slot] = received.inputs[0].id;
}
}
/* Clean up the packet now that we're done using it. */
enet_packet_destroy(event.packet);
break;
}
case ENET_EVENT_TYPE_DISCONNECT:
{
int player_index = (int)(int64_t)event.peer->data;
Log("%" PRId64 " disconnected player index %d.\n", (int64_t)event.peer->data, player_index);
Entity* player_body = get_entity(&gs, gs.players[player_index].entity);
if (player_body != NULL)
{
entity_destroy(&gs, player_body);
}
gs.players[player_index].connected = false;
// box_destroy(&gs.players[player_index].box);
event.peer->data = NULL;
break;
}
}
}
}
total_time += (float)stm_sec(stm_diff(stm_now(), last_processed_time));
last_processed_time = stm_now();
// @Robost @BeforeShip if can't process quick enough will be stuck being lagged behind, think of a solution for this...
bool processed = false;
while (total_time > TIMESTEP)
{
processed = true;
process(&gs, TIMESTEP);
total_time -= TIMESTEP;
}
if (world_save_name != NULL && (stm_sec(stm_diff(stm_now(), last_saved_world_time))) > TIME_BETWEEN_WORLD_SAVE)
{
last_saved_world_time = stm_now();
ServerToClient msg = (ServerToClient){
.cur_gs = &gs,
};
size_t out_len = 0;
into_bytes(&msg, world_save_buffer, &out_len, entities_size, NULL, true);
FILE* save_file = NULL;
fopen_s(&save_file, (const char*)world_save_name, "wb");
if (save_file == NULL)
{
Log("Could not open save file: errno %d\n", errno);
}
else {
size_t data_written = fwrite(world_save_buffer, sizeof(*world_save_buffer), out_len, save_file);
if (data_written != out_len)
{
Log("Failed to save world data, wanted to write %zu but could only write %zu\n", out_len, data_written);
}
else {
Log("Saved game world to %s\n", (const char*)world_save_name);
}
fclose(save_file);
}
}
if (processed)
{
static char lzo_working_mem[LZO1X_1_MEM_COMPRESS] = { 0 };
for (int i = 0; i < server->peerCount; i++)
{
if (server->peers[i].state != ENET_PEER_STATE_CONNECTED)
{
continue;
}
int this_player_index = (int)(int64_t)server->peers[i].data;
Entity* this_player_entity = get_entity(&gs, gs.players[this_player_index].entity);
if (this_player_entity == NULL) continue;
// @Speed don't recreate the packet for every peer, gets expensive copying gamestate over and over again
char* bytes_buffer = malloc(sizeof * bytes_buffer * MAX_BYTES_SIZE);
char* compressed_buffer = malloc(sizeof * compressed_buffer * MAX_BYTES_SIZE);
struct ServerToClient to_send;
to_send.cur_gs = &gs;
to_send.your_player = this_player_index;
size_t len = 0;
into_bytes(&to_send, bytes_buffer, &len, MAX_BYTES_SIZE, this_player_entity, false);
if (len > MAX_BYTES_SIZE - 8)
{
Log("Too much data quitting!\n");
exit(-1);
}
size_t compressed_len = 0;
lzo1x_1_compress(bytes_buffer, len, compressed_buffer, &compressed_len, (void*)lzo_working_mem);
#ifdef LOG_GAMESTATE_SIZE
Log("Size of gamestate packet before comrpession: %zu | After: %zu\n", len, compressed_len);
#endif
//ENetPacket* gamestate_packet = enet_packet_create((void*)bytes_buffer, len, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
ENetPacket* gamestate_packet = enet_packet_create((void*)compressed_buffer, compressed_len, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
// @Robust error check this
int err = enet_peer_send(&server->peers[i], 0, gamestate_packet);
if (err < 0)
{
Log("Enet failed to send packet error %d\n", err);
enet_packet_destroy(gamestate_packet);
}
free(bytes_buffer);
free(compressed_buffer);
}
}
}
free(world_save_buffer);
destroy(&gs);
free(entity_data);
enet_host_destroy(server);
enet_deinitialize();
}