#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);
}
}
}