Program Listing for File server_lifecycle.c

Return to documentation for file (server/server_lifecycle.c)

#define _GNU_SOURCE

#include <arpa/inet.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/poll.h>
#include <sys/resource.h>
#include <unistd.h>

#include "client/client.h"
#include "game_events/names.h"
#include "utils/debug.h"
#include "utils/resizable_array.h"

#include "event.h"
#include "server.h"
#include "server_args_parser.h"

static
void signal_handler(int signum, siginfo_t *info, void *context)
{
    static server_t *server = nullptr;

    if (info == nullptr && !signum) {
        server = (server_t *)context;
        return;
    }
    if (signum == SIGINT || signum == SIGTERM) {
        DEBUG_MSG("Received signal, shutting down server");
        server->is_running = false;
    }
}

static
int socket_open(struct sockaddr_in *srv_sa)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);

    if (fd < 0)
        return -1;
    if (
        setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0
        || bind(fd, (struct sockaddr *)srv_sa, sizeof *srv_sa) < 0
    ) {
        close(fd);
        return -1;
    }
    return fd;
}

static
bool setup_teams(server_t *srv, params_t *p, uint64_t timestamp)
{
    size_t t_counter = 0;

    for (; p->teams[t_counter] != nullptr; t_counter++);
    if (!sized_struct_ensure_capacity((resizable_array_t *)&srv->eggs,
        t_counter * p->team_capacity, sizeof(egg_t)))
        return perror("Allocation for start's egg fail"), false;
    for (size_t t_idx = 0; t_idx < t_counter; t_idx++) {
        srv->team_names[t_idx] = p->teams[t_idx];
        for (size_t t_egg_id = 0; t_egg_id < p->team_capacity; t_egg_id++) {
            srv->eggs.buff[(t_idx * p->team_capacity) + t_egg_id] = (egg_t){
                .hatch = timestamp, .team_id = t_idx, .id = srv->last_egg_id,
                .x = rand() % p->map_width, .y = rand() % p->map_height};
            srv->last_egg_id++;
        }
    }
    srv->eggs.nmemb = t_counter * p->team_capacity;
    return true;
}

static
bool server_boot(server_t *srv, params_t *p)
{
    static constexpr const int BACKLOG = 32;
    struct sockaddr_in default_sa = {
        .sin_family = AF_INET, .sin_port = htons(p->port),
        .sin_addr.s_addr = INADDR_ANY};
    event_t meteor = { .timestamp = srv->start_time,
        .client_idx = 0, .client_id = 0, .command = { METEOR }};

    srv->self_fd = socket_open(&default_sa);
    if (srv->self_fd < 0 || listen(srv->self_fd, BACKLOG) < 0)
        return perror("Can't open server socket"), false;
    srv->map_height = p->map_height;
    srv->map_width = p->map_width;
    srv->start_time = get_timestamp();
    srv->frequency = p->frequency;
    srv->cm.server_pfds[0].fd = srv->self_fd;
    srv->cm.clients[0].fd = srv->self_fd;
    meteor.timestamp = srv->start_time;
    meteor.client_idx = 0;
    return event_heap_push(&srv->events, &meteor);
}

static
bool server_allocate(server_t *srv, params_t *p, uint64_t timestamp)
{
    struct sigaction sa = {
        .sa_flags = SA_SIGINFO,
        .sa_sigaction = signal_handler
    };

    if (sigaction(SIGINT, &sa, nullptr) < 0
        || sigaction(SIGTERM, &sa, nullptr) < 0)
        return perror("Can't set signal handler"), false;
    if (!setup_teams(srv, p, timestamp))
        return false;
    if (!client_manager_init(&srv->cm))
        return perror("Can't initialize client manager"), false;
    if (!event_heap_init(&srv->events))
        return perror("Can't initialize event priority queue"), false;
    signal_handler(0, nullptr, srv);
    return true;
}

static
void server_destroy(server_t *srv)
{
    size_t fd_count = srv->cm.count;

    for (size_t i = 1; i < fd_count; i++) {
        if (srv->cm.server_pfds[i].fd == srv->self_fd)
            close(srv->self_fd);
        if (srv->cm.server_pfds[i].fd != srv->self_fd)
            remove_client(srv, i);
    }
    free(srv->eggs.buff);
    free(srv->cm.server_pfds);
    free(srv->cm.clients);
    event_heap_free(&srv->events);
    srv->is_running = false;
}

bool server_run(params_t *p, uint64_t timestamp)
{
    server_t srv = {.self_fd = -1, .is_running = true};

    if (!server_allocate(&srv, p, timestamp) || !server_boot(&srv, p))
        return server_destroy(&srv), false;
    for (int32_t to; srv.is_running;) {
        server_handle_events(&srv);
        to = compute_timeout(&srv);
        if (UNLIKELY(to > 0)) {
            handle_poll(&srv, to);
            handle_fds_revents(&srv);
            process_clients_buff(&srv);
            handle_client_disconnection(&srv);
            continue;
        }
        if (UNLIKELY(to < 0))
            fprintf(stderr, "WANRING: Server can't keep up with the events, "
                "timeout is negative (%d ms), skipping tick\n", to);
    }
    server_destroy(&srv);
    return true;
}