diff options
Diffstat (limited to 'server.c')
-rwxr-xr-x | server.c | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/server.c b/server.c new file mode 100755 index 0000000..7b16f33 --- /dev/null +++ b/server.c @@ -0,0 +1,272 @@ +#if 0 +exec cc -Wall -Wextra -pedantic $@ -o server $0 +#endif + +#include <sys/types.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <sys/event.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/un.h> +#include <sysexits.h> +#include <unistd.h> + +#include "torus.h" + +static struct Tile *tiles; + +static void tilesMap(void) { + int fd = open("torus.dat", O_CREAT | O_RDWR, 0644); + if (fd < 0) err(EX_IOERR, "torus.dat"); + + int error = ftruncate(fd, TILES_SIZE); + if (error) err(EX_IOERR, "ftruncate"); + + tiles = mmap(NULL, TILES_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (tiles == MAP_FAILED) err(EX_OSERR, "mmap"); +} + +static struct Tile *tileGet(uint32_t tileX, uint32_t tileY) { + struct Tile *tile = &tiles[tileY * TILE_ROWS + tileX]; + if (!tile->present) { + memset(tile->cells, ' ', CELLS_SIZE); + memset(tile->colors, COLOR_WHITE, CELLS_SIZE); + tile->present = true; + } + return tile; +} + +static struct Client { + int fd; + + uint32_t tileX; + uint32_t tileY; + uint8_t cellX; + uint8_t cellY; + uint8_t color; + + struct Client *prev; + struct Client *next; +} *clientHead; + +static struct Client *clientAdd(int fd) { + struct Client *client = malloc(sizeof(*client)); + if (!client) err(EX_OSERR, "malloc"); + + client->fd = fd; + client->tileX = TILE_INIT_X; + client->tileY = TILE_INIT_Y; + client->cellX = CELL_INIT_X; + client->cellY = CELL_INIT_Y; + client->color = COLOR_WHITE; + + client->prev = NULL; + if (clientHead) { + clientHead->prev = client; + client->next = clientHead; + } else { + client->next = NULL; + } + clientHead = client; + + return client; +} + +static void clientRemove(struct Client *client) { + if (client->prev) client->prev->next = client->next; + if (client->next) client->next->prev = client->prev; + if (clientHead == client) clientHead = client->next; + close(client->fd); + free(client); +} + +static bool clientSend(struct Client *client, const struct ServerMessage *msg) { + ssize_t len = send(client->fd, msg, sizeof(*msg), 0); + if (len < 0) { + clientRemove(client); + return false; + } + + if (msg->type == SERVER_TILE) { + struct Tile *tile = tileGet(client->tileX, client->tileY); + len = send(client->fd, tile, sizeof(*tile), 0); + if (len < 0) { + clientRemove(client); + return false; + } + } + + return true; +} + +static bool clientCast(struct Client *origin, const struct ServerMessage *msg) { + uint32_t tileX = origin->tileX; + uint32_t tileY = origin->tileY; + + bool success = clientSend(origin, msg); + + for (struct Client *client = clientHead; client; client = client->next) { + if (client == origin) continue; + if (client->tileX != tileX) continue; + if (client->tileY != tileY) continue; + + struct Client *next = client->next; + if (!clientSend(client, msg)) { + client = next; + if (!client) break; + } + } + + return success; +} + +static bool clientMove(struct Client *client, int8_t dx, uint8_t dy) { + struct Client old = *client; + + client->cellX += dx; + client->cellY += dy; + + // TODO: Handle moves greater than 1 in either direction. + if (client->cellX == CELL_COLS) { client->tileX++; client->cellX = 0; } + if (client->cellX == UINT8_MAX) { client->tileX--; client->cellX = CELL_COLS - 1; } + if (client->cellY == CELL_ROWS) { client->tileY++; client->cellY = 0; } + if (client->cellY == UINT8_MAX) { client->tileY--; client->cellY = CELL_ROWS - 1; } + + if (client->tileX == TILE_COLS) client->tileX = 0; + if (client->tileX == UINT32_MAX) client->tileX = TILE_COLS - 1; + if (client->tileY == TILE_ROWS) client->tileY = 0; + if (client->tileY == UINT32_MAX) client->tileY = TILE_ROWS - 1; + + struct ServerMessage msg = { .type = SERVER_MOVE }; + msg.data.m.cellX = client->cellX; + msg.data.m.cellY = client->cellY; + if (!clientSend(client, &msg)) return false; + + if (client->tileX != old.tileX || client->tileY != old.tileY) { + msg.type = SERVER_TILE; + return clientSend(client, &msg); + } + + return true; +} + +static bool clientPut(struct Client *client, char cell) { + struct Tile *tile = tileGet(client->tileX, client->tileY); + tile->colors[client->cellY][client->cellX] = client->color; + tile->cells[client->cellY][client->cellX] = cell; + + struct ServerMessage msg = { .type = SERVER_PUT }; + msg.data.p.cellX = client->cellX; + msg.data.p.cellY = client->cellY; + msg.data.p.color = client->color; + msg.data.p.cell = cell; + return clientCast(client, &msg); +} + +int main() { + int error; + + signal(SIGPIPE, SIG_IGN); + + tilesMap(); + + int server = socket(PF_LOCAL, SOCK_STREAM, 0); + if (server < 0) err(EX_OSERR, "socket"); + + error = unlink("torus.sock"); + if (error && errno != ENOENT) err(EX_IOERR, "torus.sock"); + + struct sockaddr_un addr = { + .sun_family = AF_LOCAL, + .sun_path = "torus.sock", + }; + error = bind(server, (struct sockaddr *)&addr, sizeof(addr)); + if (error) err(EX_IOERR, "torus.sock"); + + error = listen(server, 0); + if (error) err(EX_OSERR, "listen"); + + int kq = kqueue(); + if (kq < 0) err(EX_OSERR, "kqueue"); + + struct kevent event = { + .ident = server, + .filter = EVFILT_READ, + .flags = EV_ADD, + .fflags = 0, + .data = 0, + .udata = NULL, + }; + int nevents = kevent(kq, &event, 1, NULL, 0, NULL); + if (nevents < 0) err(EX_OSERR, "kevent"); + + for (;;) { + nevents = kevent(kq, NULL, 0, &event, 1, NULL); + if (nevents < 0) err(EX_IOERR, "kevent"); + if (!nevents) continue; + + if (!event.udata) { + int fd = accept(server, NULL, NULL); + if (fd < 0) err(EX_IOERR, "accept"); + fcntl(fd, F_SETFL, O_NONBLOCK); + + struct Client *client = clientAdd(fd); + + struct kevent event = { + .ident = fd, + .filter = EVFILT_READ, + .flags = EV_ADD, + .fflags = 0, + .data = 0, + .udata = client, + }; + nevents = kevent(kq, &event, 1, NULL, 0, NULL); + if (nevents < 0) err(EX_OSERR, "kevent"); + + struct ServerMessage msg = { .type = SERVER_TILE }; + if (!clientSend(client, &msg)) continue; + clientMove(client, 0, 0); + + continue; + } + + struct Client *client = event.udata; + if (event.flags & EV_EOF) { + clientRemove(client); + continue; + } + + struct ClientMessage msg; + ssize_t len = recv(client->fd, &msg, sizeof(msg), 0); + if (len != sizeof(msg)) { + clientRemove(client); + continue; + } + + switch (msg.type) { + case CLIENT_MOVE: + clientMove(client, msg.data.m.dx, msg.data.m.dy); + break; + + case CLIENT_COLOR: + client->color = msg.data.c; + break; + + case CLIENT_PUT: + clientPut(client, msg.data.p); + break; + + default: + clientRemove(client); + } + } +} |