#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; } typedef struct { int x, y, w, h; unsigned col; } Debug_Rect; #define max_debug_rects 128 Debug_Rect debug_rects[max_debug_rects]; int debug_rect_count; void ren_debug_rect( int x, int y, int w, int h, unsigned colour ) { Debug_Rect* r = &debug_rects[debug_rect_count++]; assert(debug_rect_count < max_debug_rects); r->x = x; r->y = y; r->w = w; r->h = h; r->col = colour; } #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 prog_init(Arena* m); extern int prog_update(void); extern int prog_deinit(void); int main(int argc, const char** argv) { Arena a; void* mem = malloc(memory_size); int r; (void)argc; (void)argv; if (!mem) { print_err("Out of memory.\n"); return error_out_of_memory; } init_timer(); init_arena( &a, mem, memory_size ); if ((r = prog_init(&a))) return r; for (r = 1; r; r = prog_update()); prog_deinit(); return error_none; } #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 0x63: return btn_special; 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 ); #ifdef DEBUG for (sj = 0; sj < debug_rect_count; sj++) { const Debug_Rect* dr = &debug_rects[sj]; XSetForeground(i->d, i->gc, dr->col); XFillRectangle( i->d, i->wi, i->gc, dr->x * s, dr->y * s, dr->w * s, dr->h * s ); } #endif debug_rect_count = 0; } 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 #ifdef plat_sdl2 #if defined(plat_ems) #include #endif #include #include #include void print(const char* fmt, ...) { va_list a; va_start(a, fmt); SDL_LogMessageV(0, SDL_LOG_PRIORITY_INFO, fmt, a); va_end(a); } void print_err(const char* fmt, ...) { va_list a; va_start(a, fmt); SDL_LogMessageV(0, SDL_LOG_PRIORITY_ERROR, fmt, a); va_end(a); } void print_war(const char* fmt, ...) { va_list a; va_start(a, fmt); SDL_LogMessageV(0, SDL_LOG_PRIORITY_WARN, fmt, a); va_end(a); } 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 pbreak(Error code) { (void)code; SDL_TriggerBreakpoint(); } void init_fps(FPS* f, int mpf) { f->now = SDL_GetTicks(); f->next = f->now; f->mpf = mpf; f->pt = 0; f->ct = -1; } void fps_begin(FPS* f) { f->now = SDL_GetTicks(); } void fps_end(FPS* f) { unsigned long ts = f->next - f->now; if (ts > 0) { SDL_Delay(ts); } } void fps_update(FPS* f) { f->next += f->mpf; f->pt = f->ct; f->ct = SDL_GetTicks(); f->fps = 1000 / (f->ct - f->pt); } extern int prog_init(Arena* m); extern int prog_update(void); extern void prog_deinit(void); static void main_loop(void) { #ifdef plat_ems prog_update(); #else int r; for (r = 1; r; r = prog_update()); SDL_Quit(); #endif } int main(int argc, const char** argv) { Arena a; void* mem = malloc(memory_size); int r; (void)argc; (void)argv; if (!mem) { print_err("Out of memory.\n"); return error_out_of_memory; } init_arena( &a, mem, memory_size ); if ((r = prog_init(&a))) return r; #ifdef plat_ems emscripten_set_main_loop(main_loop, 0, 1); #else main_loop(); #endif prog_deinit(); return 0; } Btn sdl_to_btn(const SDL_Event* e) { switch (e->key.keysym.sym) { case SDLK_z: case SDLK_RETURN: return btn_jump; case SDLK_x: return btn_shoot; case SDLK_c: return btn_special; case SDLK_LEFT: case SDLK_h: return btn_left; case SDLK_RIGHT: case SDLK_l: return btn_right; case SDLK_UP: case SDLK_k: return btn_up; case SDLK_DOWN: case SDLK_j: return btn_down; default: return btn_unknown; } } typedef struct { SDL_Window* wi; SDL_Renderer* re; SDL_Texture* bb; unsigned* bbp; int w, h; } App_Internal; void init_window(App* a, App_Internal* i, const char* n) { int flag = 0; #ifndef plat_ems flag = SDL_WINDOW_RESIZABLE; #endif SDL_CreateWindowAndRenderer( viewport_w * 2, viewport_h * 2, flag, &i->wi, &i->re ); i->w = viewport_w * 2; i->h = viewport_h * 2; SDL_SetWindowTitle(i->wi, n); i->bb = SDL_CreateTexture( i->re, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, viewport_w, viewport_h ); a->fb = heap_alloc( a->heap, (viewport_w * viewport_h + 32) / 32 * sizeof *a->fb ); } App* new_app(struct 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; init_window(a, i, n); a->o = 1; return a; } void deinit_app(App* a) { } void app_begin(App* a) { App_Internal* i = (App_Internal*)(&a[1]); SDL_Event e; int j; Btn btn; for (j = 0; j < btn_count; j++) a->btn_states[j] &= ~( btn_state_just_pressed | btn_state_just_released ); while (SDL_PollEvent(&e)) { switch (e.type) { case SDL_QUIT: a->o = 0; break; case SDL_KEYDOWN: btn = sdl_to_btn(&e); a->btn_states[btn] |= btn_state_pressed | btn_state_just_pressed; break; case SDL_KEYUP: btn = sdl_to_btn(&e); a->btn_states[btn] &= ~btn_state_pressed; a->btn_states[btn] |= btn_state_just_released; break; case SDL_WINDOWEVENT: switch (e.window.event) { case SDL_WINDOWEVENT_RESIZED: i->w = e.window.data1; i->h = e.window.data2; break; } break; default: break; } } } void rencpy(App* a, unsigned* t, int p) { const unsigned white = 0xffffffff; const unsigned black = 0x00000000; int x, y, i = 0; p /= 4; for (y = 0; y < viewport_h; y++) { for (x = 0; x < viewport_w; x++, i++) { int bit = 1 << (i & 0x1f); bit &= a->fb[i >> 5]; t[x + y * p] = bit? white: black; } } } void fit_dr(App_Internal* i, SDL_Rect* r) { int w = viewport_w, h = viewport_h, s; for (s = 1; w * s <= i->w && h * s <= i->h; s++); if (s > 1) s--; r->w = viewport_w * s; r->h = viewport_h * s; r->x = i->w / 2 - r->w / 2; r->y = i->h / 2 - r->h / 2; } void app_end(App* a) { App_Internal* i = (App_Internal*)(&a[1]); unsigned* target; SDL_Rect sr = { 0, 0, viewport_w, viewport_h }; SDL_Rect dr = sr; int pitch; fit_dr((App_Internal*)&a[1], &dr); SDL_LockTexture( i->bb, 0, (void**)&target, &pitch ); rencpy(a, target, pitch); SDL_UnlockTexture(i->bb); SDL_RenderClear(i->re); SDL_RenderCopy( i->re, i->bb, &sr, &dr ); SDL_RenderPresent(i->re); } static SDL_AudioSpec aud_spec; int audio_len, audio_time; void fill_audio(void *udata, Uint8 *stream, int len) { int i; int16_t* pcm = (int16_t*)stream; len /= 2; for ( i = 0; i < len && audio_len; i++, audio_len--, audio_time++ ) pcm[i] = (int16_t)(sin((double)audio_time / 0.05) * 16000.0); for (; i < len; i++) pcm[i] = 0; } void init_audio(void) { audio_len = 0; audio_time = 0; aud_spec.freq = audio_sample_rate; aud_spec.format = AUDIO_S16; aud_spec.channels = 1; #ifndef plat_ems aud_spec.samples = audio_buffer_size; #else /* websites are retarded */ aud_spec.samples = 1024; #endif aud_spec.callback = fill_audio; aud_spec.userdata = 0; SDL_OpenAudio(&aud_spec, 0); SDL_PauseAudio(0); } void deinit_audio(void) { SDL_CloseAudio(); } void play_sound(int len) { audio_len += len; } #endif