#include "config.h" #include "maths.h" #include "memory.h" #include "plat.h" int btn_pressed(const App* a, Btn btn) { return a->btn_states[btn] & btn_state_pressed; } int btn_just_pressed(const App* a, Btn btn) { return a->btn_states[btn] & btn_state_just_pressed; } int btn_just_released(const App* a, Btn btn) { return a->btn_states[btn] & btn_state_just_released; } #ifdef plat_posix #define _POSIX_SOURCE #define _GNU_SOURCE #include #include #include #include #include #include extern int isatty(int); extern int fileno(FILE*); int imp_assert( int val, const char* expr, const char* file, int line ) { if (!val) { print_err( "%d:%s: Assertion failed: %s.\n", line, file, expr ); pbreak(error_assertion_failed); return 0; } return 1; } void print(const char* fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stdout, fmt, args); va_end(args); } void print_err(const char* fmt, ...) { va_list args; va_start(args, fmt); if (isatty(fileno(stderr))) { fprintf(stderr, "\033[31;31m"); } vfprintf(stderr, fmt, args); if (isatty(fileno(stderr))) { fprintf(stderr, "\033[0m"); } va_end(args); } void print_war(const char* fmt, ...) { va_list args; va_start(args, fmt); if (isatty(fileno(stderr))) { fprintf(stderr, "\033[31;35m"); } vfprintf(stderr, fmt, args); if (isatty(fileno(stderr))) { fprintf(stderr, "\033[0m"); } va_end(args); } void pbreak(Error code) { #if defined(DEBUG) && defined(plat_x86) __asm__("int3;"); (void)code; #else exit(code); #endif } static clockid_t global_clock; static unsigned long global_freq; void init_timer(void) { struct timespec ts; global_clock = CLOCK_REALTIME; global_freq = 1000000000; #if defined(_POSIX_MONOTONIC_CLOCK) if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { global_clock = CLOCK_MONOTONIC; } #else (void)ts; #endif } unsigned long get_timer(void) { struct timespec ts; clock_gettime(global_clock, &ts); return (unsigned long)ts.tv_sec * global_freq + (unsigned long)ts.tv_nsec; } void sleep_ns(unsigned long ns) { struct timespec t = { 0 }; t.tv_nsec = ns; nanosleep(&t, &t); } void init_fps(FPS* f, int mpf) { f->now = get_timer(); f->next = f->now; f->mpf = mpf; f->pt = 0; f->ct = -1; } void fps_begin(FPS* f) { f->now = get_timer(); } void fps_end(FPS* f) { unsigned long ts = f->next - f->now; if (ts > 0) { sleep_ns(ts); } } void fps_update(FPS* f) { f->next += f->mpf * 1000000; f->pt = f->ct; f->ct = get_timer(); f->fps = 1000000000 / (f->ct - f->pt); } extern int entrypoint(int, const char**, Arena*); int main(int argc, const char** argv) { Arena a; void* mem = malloc(memory_size); if (!mem) { print_err("Out of memory.\n"); return error_out_of_memory; } init_timer(); init_arena( &a, mem, memory_size ); return entrypoint(argc, argv, &a); } #endif #ifdef plat_x11 #include #include typedef struct { unsigned char r, g, b, a; } Colour; typedef struct { Display* d; Window wi; GC gc; int w, h; int rmx, rmy; int omx, omy; int ms; unsigned long begin, end; XImage* bb; Colour* bbp; Atom wm_delete; } App_Internal; void init_rendering( App* a, App_Internal* i, XWindowAttributes* wa ) { Colour* p; int w = max_pc_window_w; int h = max_pc_window_h; a->fb = heap_alloc( a->heap, (viewport_w * viewport_h + 32) / 32 * sizeof *a->fb ); p = malloc(sizeof *p * w * h); if (!p || !a->fb) { print_err("Out of memory.\n"); pbreak(error_out_of_memory); } i->bb = XCreateImage( i->d, wa->visual, wa->depth, ZPixmap, 0, (char*)p, w, h, 32, w * sizeof *p ); if (!i->bb) { print_err("Failed to create X11 backbuffer.\n"); pbreak(error_platform_error); } i->bbp = p; } void init_window(App* a, App_Internal* i, const char* n) { Window root, w; Display* d; XWindowAttributes wa; unsigned rm, bm; i->bb = 0; i->bbp = 0; d = i->d; root = DefaultRootWindow(d); w = XCreateSimpleWindow( d, root, 0, 0, viewport_w * default_scale, viewport_h * default_scale, 0, WhitePixel(d, 0), BlackPixel(d, 0) ); i->wi = w; i->wm_delete = XInternAtom( d, "WM_DELETE_WINDOW", 0 ); XSetWMProtocols(d, w, &i->wm_delete, 1); XStoreName(d, w, n); XSelectInput( d, w, ExposureMask | KeyPressMask | KeyReleaseMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask ); XMapRaised(d, w); i->gc = XCreateGC(d, w, 0, 0); if (!i->gc) { print_err("Failed to create X graphics context.\n"); pbreak(error_platform_error); } XGetWindowAttributes(d, w, &wa); if (wa.depth != 24 && wa.depth != 32) { print_err("Only true colour displays are supported.\n"); pbreak(error_platform_error); } rm = wa.visual->red_mask & 0x1000000; bm = wa.visual->blue_mask & 0xffffff; if ((rm == 0xff && bm == 0xff0000)) { print_war("Detected BGR. Colours will look fucked.\n"); } init_rendering(a, i, &wa); i->w = wa.width; i->h = wa.height; } Btn btn_from_xkey(unsigned key) { switch (key) { case XK_Up: return btn_up; case XK_Down: return btn_down; case XK_Left: return btn_left; case XK_Right: return btn_right; case 0x78: return btn_shoot; case 0x7a: return btn_jump; case XK_Return: return btn_start; default: return btn_unknown; } } App* new_app(Heap* mem, const char* n) { App* a; App_Internal* i; a = heap_alloc(mem, sizeof *a + sizeof *i); i = (App_Internal*)(&a[1]); a->heap = mem; a->s = 2; a->o = 0; a->err = error_none; i->d = XOpenDisplay(0); i->omx = i->omy = 0; i->ms = 1; if (!i->d) { print_err("Failed to open X11 display.\n"); pbreak(error_platform_error); } init_window(a, i, n); a->o = 1; return a; } void deinit_app(App* a) { App_Internal* i = (App_Internal*)(&a[1]); XDestroyImage(i->bb); XFreeGC(i->d, i->gc); XDestroyWindow(i->d, i->wi); XCloseDisplay(i->d); heap_free(a->heap, a); } void app_begin(App* a) { App_Internal* i = (App_Internal*)(&a[1]); Display* d = i->d; Window w = i->wi; int j; i->begin = get_timer(); for (j = 0; j < btn_count; j++) a->btn_states[j] &= ~( btn_state_just_pressed | btn_state_just_released ); while (XPending(d)) { XEvent e; XWindowAttributes wa; KeySym sym; Btn btn; XNextEvent(d, &e); switch (e.type) { case ClientMessage: if ( (Atom)e.xclient.data.l[0] == i->wm_delete ) { a->o = 0; } break; case Expose: XGetWindowAttributes(d, w, &wa); i->w = mini(wa.width, max_pc_window_w); i->h = mini(wa.height, max_pc_window_h); break; case KeyPress: sym = XLookupKeysym(&e.xkey, 0); btn = btn_from_xkey(sym); a->btn_states[btn] |= btn_state_pressed | btn_state_just_pressed; break; case KeyRelease: sym = XLookupKeysym(&e.xkey, 0); btn = btn_from_xkey(sym); a->btn_states[btn] &= ~btn_state_pressed; a->btn_states[btn] |= btn_state_just_released; break; default: break; } } } void app_rencpy( App* a, App_Internal* i, int sx, int sy, int s ) { const Colour white = { 0xff, 0xff, 0xff, 0xff }; const Colour black = { 0x00, 0x00, 0x00, 0xff }; int x, y, sj = 0; int w = viewport_w * s; int h = viewport_h * s; int ex = sx + w; int ey = sy + h; for (y = sy; y < ey; y++, sj++) { int sy = sj / s; int si = 0; for (x = sx; x < ex; x++, si++) { int sx = si / s; int idx = sx + sy * viewport_w; int bit = 1 << (idx & 0x1f); bit &= a->fb[idx >> 5]; i->bbp[x + y * max_pc_window_w] = bit? white: black; } } XPutImage( i->d, i->wi, i->gc, i->bb, 0, 0, sx, sy, w, h ); } void app_end(App* a) { int s = 1; int m1, m2; App_Internal* i = (App_Internal*)(&a[1]); if (i->w < i->h) { m1 = i->w; m2 = viewport_w; } else { m1 = i->h; m2 = viewport_h; } while (m2 * s < m1) s++; /* lol */ s = maxi(1, s - 1); app_rencpy(a, i, 0, 0, s); i->end = get_timer(); a->fps = 1000000000 / (i->end - i->begin); } #endif #ifdef plat_pulse #include "maths.h" #define inline /* erm sir we are using C90 here... */ #include #include #include #include struct { short* buf; pa_simple* dev; int r, play, time; pthread_t worker; pthread_mutex_t mutex; } audio; void* audio_worker(void* arg) { const int s = audio_buffer_size; int i, c = 1; short* buf = audio.buf; pa_simple* dev = audio.dev; (void)arg; while (c) { pthread_mutex_lock(&audio.mutex); if (!audio.r) c = 0; for ( i = 0; i < s && audio.play; i++, audio.play--, audio.time++ ) buf[i] = (short)(sin((double)audio.time / 0.05) * 16000.0); pthread_mutex_unlock(&audio.mutex); for (; i < s; i++) { buf[i] = 0; } pa_simple_write(dev, buf, s * sizeof(short), 0); } return 0; } void init_audio(void) { pa_sample_spec sp = { 0 }; sp.format = PA_SAMPLE_S16LE; sp.channels = 1; sp.rate = audio_sample_rate; audio.dev = pa_simple_new( 0, game_name, PA_STREAM_PLAYBACK, 0, "Game Audio", &sp, 0, 0, 0 ); audio.r = 0; audio.play = 0; if (!audio.dev) { print_err("Failed to create audio device.\n"); print_war("Running without audio.\n"); return; } audio.r = 1; audio.buf = malloc( audio_buffer_size * sizeof *audio.buf ); pthread_mutex_init(&audio.mutex, 0); pthread_create( &audio.worker, 0, audio_worker, 0 ); } void deinit_audio(void) { if (audio.dev) { pthread_mutex_lock(&audio.mutex); audio.r = 0; pthread_mutex_unlock(&audio.mutex); pthread_mutex_destroy(&audio.mutex); pthread_join(audio.worker, 0); pa_simple_drain(audio.dev, 0); pa_simple_free(audio.dev); free(audio.buf); } } void play_sound(int len) { pthread_mutex_lock(&audio.mutex); audio.play += len; pthread_mutex_unlock(&audio.mutex); } #endif