From 187775b44b26d31a5aee2f716202f3d27b969040 Mon Sep 17 00:00:00 2001 From: Ian McFarlane Date: Tue, 6 May 2025 15:15:06 -0400 Subject: add unfinished forkman game and syscalls --- user/forkman.c | 363 ++++++++++++++++++++++++++++++++++++++++++++++++++ user/include/unistd.h | 40 ++++++ user/lib/syscall.S | 3 + 3 files changed, 406 insertions(+) create mode 100644 user/forkman.c (limited to 'user') diff --git a/user/forkman.c b/user/forkman.c new file mode 100644 index 0000000..538992b --- /dev/null +++ b/user/forkman.c @@ -0,0 +1,363 @@ +/* + * FORKMAN: a platformer game with IPC + * + * This executable will fork. One process is responsible for writing to the + * screen and getting user input, the other is responsible for game logic. + * + */ + +#include +#include +#include +#include +#include "../kernel/include/comus/keycodes.h" + +#define DBG + +#define GAME_WIDTH 480 +#define GAME_HEIGHT 360 +#define SHARED_PAGES 10 +#define PAGE_SIZE 4096 +#define TILE_WIDTH 16 +#define TILE_HEIGHT 16 +#define GAME_WIDTH_TILES (GAME_WIDTH / TILE_WIDTH) +#define GAME_HEIGHT_TILES (GAME_HEIGHT / TILE_HEIGHT) +#define PLAYER_WIDTH 10 +#define PLAYER_HEIGHT 10 + +typedef struct { + double x; + double y; +} vec; + +typedef struct { + uint32_t *mapped_memory; + int width; + int height; + int bpp; + int scale; + size_t size; +} framebuffer; + +enum tile_type { + TILE_AIR, + TILE_SOLID, + TILE_GRATE, +}; + +typedef struct { + enum tile_type type; +} tile; + +#define CLIENT_BIT (1) +#define SERVER_BIT (2) +#define CLIENT_WAITING_BIT (1) +#define SERVER_WAITING_BIT (2) + +typedef struct { + volatile uint8_t inner; +} spinlock; + +enum key_state { + KEY_STATE_PRESSED, + KEY_STATE_UNPRESSED, + // TODO: JUST_PRESSED and JUST_RELEASED +}; + +typedef struct { + volatile size_t frame; + volatile size_t dummy_counter; + volatile uint8_t client_barrier; + volatile uint8_t server_barrier; + volatile enum key_state key_status[255]; + spinlock player_lock; + volatile vec player_pos; + volatile vec player_vel; + volatile tile tiles[GAME_HEIGHT_TILES * GAME_WIDTH_TILES]; + volatile uint8_t mem[PAGE_SIZE * (SHARED_PAGES - 1)]; +} sharedmem; + +static int display_server_entry(sharedmem *); +static int client_entry(sharedmem *); +static void barrier_wait(sharedmem *, int isclient); +static volatile tile *tile_at(sharedmem *, size_t x, size_t y); +static int is_inbounds(size_t x, size_t y); +static void spinlock_lock(volatile spinlock *lock, int isclient); +static void spinlock_unlock(volatile spinlock *lock, int isclient); + +int main(void) +{ + // static_assert where are you :( + if (SHARED_PAGES * 4096 < sizeof(sharedmem)) { + fprintf(stderr, "bad memory configuration"); + return 1; + } + + int child = fork(); + if (child < 0) { + fprintf(stderr, "fork failed!\n"); + return 1; + } + + if (child) { + sharedmem *shared = allocshared(SHARED_PAGES, child); + if (!shared) { + fprintf(stderr, "memory share failure\n"); + return 1; + } + + return display_server_entry(shared); + } else { + sharedmem *shared; + + while (!(shared = popsharedmem())) + sleep(1); + + return client_entry(shared); + } +} + +static void set_pixel(framebuffer *fb, size_t x, size_t y, + int state) // state is 0 or 1 +{ + const size_t idx = x + y * fb->width; +#ifdef DBG + if (idx > fb->size) { + printf("overflow?\n"); + exit(0); + } +#endif + fb->mapped_memory[idx] = state * (uint32_t)-1; +} + +static void draw_filled(framebuffer *fb, const size_t x, const size_t y, + int state) +{ + for (size_t rx = x * TILE_WIDTH; rx < (x + 1) * TILE_WIDTH; ++rx) { + for (size_t ry = y * TILE_HEIGHT; ry < (y + 1) * TILE_HEIGHT; ++ry) { + set_pixel(fb, rx, ry, state); + } + } +} + +static void draw_grate(framebuffer *fb, const size_t x, const size_t y) +{ + for (size_t rx = x * TILE_WIDTH; rx < (x + 1) * TILE_WIDTH; ++rx) { + for (size_t ry = y * TILE_HEIGHT; ry < (y + 1) * TILE_HEIGHT; ++ry) { + int state; + if (x == y) { + state = 1; + } else { + state = 0; + } + set_pixel(fb, rx, ry, state); + } + } +} + +static void draw_tiles(sharedmem *shared, framebuffer *fb) +{ + for (size_t x = 0; x < GAME_WIDTH_TILES; ++x) { + for (size_t y = 0; y < GAME_HEIGHT_TILES; ++y) { + volatile tile *tile = tile_at(shared, x, y); + switch (tile->type) { + case TILE_AIR: + draw_filled(fb, x, y, 0); + break; + case TILE_SOLID: + draw_filled(fb, x, y, 1); + break; + case TILE_GRATE: + draw_grate(fb, x, y); + break; + } + } + } +} + +static void draw_player(framebuffer *fb, vec pos) +{ + for (size_t x = pos.x; x < ((size_t)pos.x + PLAYER_WIDTH); ++x) { + for (size_t y = pos.y; y < ((size_t)pos.y + PLAYER_WIDTH); ++y) { + set_pixel(fb, x, y, 1); + } + } +} + +static void init_level(sharedmem *shared) +{ + for (size_t i = 0; i < GAME_WIDTH_TILES; ++i) { + tile_at(shared, i, 10)->type = TILE_GRATE; + } + + shared->player_pos = (vec){ .x = 5 * TILE_WIDTH, .y = 5 * TILE_HEIGHT }; +} + +static size_t get_total_time(size_t tick_start) // arbitrary units +{ + // 60 is arbitrary pretend fps + return ((ticks() - tick_start) / (1000 / 60)); +} + +static int display_server_entry(sharedmem *shared) +{ + framebuffer fb; + if (drm((void **)&fb.mapped_memory, &fb.width, &fb.height, &fb.bpp)) { + fprintf(stderr, "Unable to map framebuffer, display server failing\n"); + return 1; + } + + fb.size = (fb.width * fb.height * fb.bpp) / 8; + + barrier_wait(shared, 0); + + while (1) { + struct keycode keycode; + + if (keypoll(&keycode)) { + if (keycode.flags & KC_FLAG_KEY_DOWN) { + shared->key_status[(uint8_t)keycode.key] = KEY_STATE_PRESSED; + } + if (keycode.flags & KC_FLAG_KEY_UP) { + shared->key_status[(uint8_t)keycode.key] = KEY_STATE_UNPRESSED; + } + } + + draw_tiles(shared, &fb); + + spinlock_lock(&shared->player_lock, 0); + draw_player(&fb, shared->player_pos); + spinlock_unlock(&shared->player_lock, 0); + } + + return 0; +} + +static int client_entry(sharedmem *shared) +{ + init_level(shared); + + size_t start_ticks = ticks(); + + double last_time = get_total_time(start_ticks); + + barrier_wait(shared, 1); + do { + double time = get_total_time(start_ticks); + double delta_time = time - last_time; + spinlock_lock(&shared->player_lock, 1); + + shared->player_vel.y -= 9.8 * delta_time; + + // framerate dependent... + const vec drag = { + .x = shared->player_vel.x * 0.1, + .y = shared->player_vel.y * 0.1, + }; + shared->player_vel.x -= drag.x; + shared->player_vel.y -= drag.y; + + for (size_t i = 0; i < shared->player_vel.x; ++i) { + size_t x = shared->player_pos.x + 1; + size_t y = shared->player_pos.y + 1; + // boundscheck to consider outside tiles to be solid + if (is_inbounds(x, y) && tile_at(shared, x, y)->type != TILE_AIR) { + break; + } + shared->player_pos.x += 1; + shared->player_pos.y += 1; + } + + if (shared->key_status[KEY_SPACE] == KEY_STATE_PRESSED) { + shared->player_vel.y = 10; + } else if (shared->key_status[KEY_B] == KEY_STATE_PRESSED) { + shared->player_vel.y = -10; + } + spinlock_unlock(&shared->player_lock, 1); + + } while (1); + + return 0; +} + +static volatile tile *tile_at(sharedmem *shared, size_t x, size_t y) +{ +#ifdef DBG + if (!is_inbounds(x, y)) { + printf("out of bounds"); + exit(0); + } +#endif + const size_t idx = x + (y * GAME_WIDTH_TILES); + return shared->tiles + idx; +} + +static int is_inbounds(size_t x, size_t y) +{ + const size_t idx = x + (y * GAME_WIDTH_TILES); + return idx < (GAME_WIDTH_TILES * GAME_HEIGHT_TILES); +} + +static void spinlock_lock(volatile spinlock *lock, int isclient) +{ + const uint8_t bit = isclient ? CLIENT_BIT : SERVER_BIT; + const uint8_t otherbit = isclient ? SERVER_BIT : CLIENT_BIT; + + // wait for us to be the only waiter + while (1) { + if (lock->inner == 0) { + lock->inner |= bit; + // recover from the possibility that something happened between that + // if statement and the |= operation. check for other bits being set + if ((lock->inner ^ bit) != 0) { + // okay, somebody messed with something, undo what we did and + // keep waiting + lock->inner ^= bit; + continue; + } + + // okay all good, we are the exclusive owner + break; + } + } + +#ifdef DBG + // when we own the lock, only our bit should be active and the other proc's + // bit should not be active + if (lock->inner & otherbit || !(lock->inner & bit)) { + printf("SPINLOCK BAD\n"); + exit(1); + } +#endif +} + +static void spinlock_unlock(volatile spinlock *lock, int isclient) +{ + const uint8_t bit = isclient ? CLIENT_BIT : SERVER_BIT; + + // assume our thread of execution (process) is the only one changing our + // bit. this can be screwed over if the other process lies about whether + // it is server or client + lock->inner ^= bit; +} + +static void barrier_wait(sharedmem *shared, int isclient) +{ + if (isclient) { + if (shared->server_barrier) { + shared->server_barrier = 0; + } else { + shared->client_barrier = 1; + while (shared->client_barrier) + ; + } + } else { + if (shared->client_barrier) { + shared->client_barrier = 0; + } else { + shared->server_barrier = 1; + while (shared->server_barrier) + ; + } + } +} diff --git a/user/include/unistd.h b/user/include/unistd.h index 4f582d2..c54cd20 100644 --- a/user/include/unistd.h +++ b/user/include/unistd.h @@ -15,6 +15,14 @@ /* System Call Definitions */ +// NOTE: needs to match kernel input.h +struct keycode { + char key; + char flags; +}; + +typedef unsigned short pid_t; + enum { S_SET = 0, S_CUR = 1, @@ -184,6 +192,38 @@ extern void *brk(const void *addr); */ extern void *sbrk(intptr_t increment); +/** + * Allocate a number of pages shared with another PID. Does not map the pages + * into the other process's vtable until the other process calls popsharedmem(). + * + * @param num_pages number of pages to allocate + * @param other_pid pid of other process + * @return pointer to the virtual address which will be accessible by both, + * after popsharedmem() is called. + */ +extern void *allocshared(size_t num_pages, int other_pid); + +/** + * Checks if another process has tried to share memory with us, and return it. + * No size information is returned, it is only guaranteed that there is at least + * one page in the shared allocation. To get around this, the sharer can write + * a size number to the start of the first page. + * + * @return page aligned pointer to the start of the shared pages, or NULL if no + * process has tried to share with us, or NULL if we the shared virtual address + * space is already occupied in the caller's pagetable. + */ +extern void *popsharedmem(void); + +/** + * Get the most recent key event, if there is one. + * + * @param poll the keycode to write out to + * @return 0 if there was no key event, in which case `poll` was not changed, or + * 1 if there was an event and the caller should read from `poll`. + */ +extern int keypoll(struct keycode *poll); + /** * Poweroff the system. * diff --git a/user/lib/syscall.S b/user/lib/syscall.S index 9f7025e..c45f641 100644 --- a/user/lib/syscall.S +++ b/user/lib/syscall.S @@ -30,3 +30,6 @@ SYSCALL poweroff SYS_poweroff SYSCALL drm SYS_drm SYSCALL ticks SYS_ticks SYSCALL seek SYS_seek +SYSCALL allocshared SYS_allocshared +SYSCALL popsharedmem SYS_popsharedmem +SYSCALL keypoll SYS_keypoll -- cgit v1.2.3-freya