use fs; use gl; use gl::*; use glm; use glw; use math; use net; use os; use sdl2; use sdl2::*; use time; use trace; use types::c; type Window = struct { timestamp: time::instant, scale: u16, gui_proj: glm::m4, mouse_x: f32, mouse_y: f32, held_keys: []Key, held_mouse_buttons: [5]bool, input_bottom: size, window: *SDL_Window, }; let WINDOW: (Window | void) = void; // XXX: hack to get around a temporary limitation of tagged unions. // this will actually stop working once that's resolved... fn get_WINDOW() *Window = { WINDOW as Window; return (&WINDOW: uintptr + offset(struct { tag: int = 0, w: Window = Window { window = null: *SDL_Window, ... }, }.w): uintptr): *Window; }; fn gl_get_proc_address(procname: *const c::char) *opaque = SDL_GL_GetProcAddress(procname); fn window_run(addr: str, username: str) void = { match (SDL_Init(SDL_INIT_VIDEO)) { case void => void; case let err: sdl2::error => die("failed to initialize sdl: {}", err); }; defer SDL_Quit(); trace::info(&trace::root, "using video {}", SDL_GetCurrentVideoDriver()); match (window_init_gl_attrs()) { case void => void; case let err: sdl2::error => die("failed to set gl attributes: {}", err); }; const window = match (SDL_CreateWindow( "hacraft", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, SDL_WindowFlags::RESIZABLE | SDL_WindowFlags::ALLOW_HIGHDPI | SDL_WindowFlags::OPENGL)) { case let window: *SDL_Window => yield window; case let err: sdl2::error => die("failed to create window: {}", err); }; defer SDL_DestroyWindow(window); match (SDL_GL_CreateContext(window)) { case *SDL_GLContext => void; case let err: sdl2::error => die("failed to create gl context: {}", err); }; gl::load_with_fn(&gl_get_proc_address); trace::info(&trace::root, "using opengl {} / glsl {} / on {} {}", glw::get_string(GL_VERSION), glw::get_string(GL_SHADING_LANGUAGE_VERSION), glw::get_string(GL_VENDOR), glw::get_string(GL_RENDERER)); glw::init_debug_logging(); match (SDL_SetRelativeMouseMode(true)) { case void => void; case let err: sdl2::error => trace::error(&trace::root, "Failed to enable relative mouse: {}", err): void; }; match (SDL_GL_SetSwapInterval(0)) { case void => void; case let err: sdl2::error => trace::error(&trace::root, "Failed to set swap interval: {}", err): void; }; WINDOW = Window { timestamp = time::now(time::clock::MONOTONIC), scale = 1, gui_proj = glm::m4_new_ident(), mouse_x = 0.0, mouse_y = 0.0, held_keys = [], held_mouse_buttons = [false...], input_bottom = 0, window = window, }; defer WINDOW = void; register_blocks(); defer destroy_blocks(); init_bstates(); defer destroy_bstates(); const assets = os::diropen("resources")!; defer fs::close(assets); const assets = Pack { fs = assets, kind = "assets", }; render_init(&assets); defer render_destroy(); client_connect(addr, username); defer client_destroy(); for (window_frame()) void; }; fn window_init_gl_attrs() (void | sdl2::error) = { SDL_GL_SetAttribute(SDL_GLattr::GL_CONTEXT_PROFILE_MASK, SDL_GLprofile::GL_CONTEXT_PROFILE_ES)?; SDL_GL_SetAttribute(SDL_GLattr::GL_CONTEXT_MAJOR_VERSION, 3)?; SDL_GL_SetAttribute(SDL_GLattr::GL_CONTEXT_MINOR_VERSION, 2)?; SDL_GL_SetAttribute(SDL_GLattr::GL_DEPTH_SIZE, 24)?; SDL_GL_SetAttribute(SDL_GLattr::GL_CONTEXT_FLAGS, SDL_GLcontextFlag::GL_CONTEXT_DEBUG_FLAG)?; SDL_GL_SetAttribute(SDL_GLattr::GL_FRAMEBUFFER_SRGB_CAPABLE, 1)?; }; fn drawable_size() (u16, u16) = { const WINDOW = get_WINDOW(); let width = 0, height = 0; SDL_GL_GetDrawableSize(WINDOW.window, &width, &height); assert(width == width: u16: int && height == height: u16: int, "the fuck?"); return (width: u16, height: u16); }; fn frame_timestamp() time::instant = { const WINDOW = get_WINDOW(); return WINDOW.timestamp; }; fn gui_scale() u16 = { const WINDOW = get_WINDOW(); return WINDOW.scale; }; fn gui_proj() *glm::m4 = { const WINDOW = get_WINDOW(); return &WINDOW.gui_proj; }; fn window_frame() bool = { const WINDOW = get_WINDOW(); window_update_layer_input(); for (true) { let event = sdl2::event { ... }; match (SDL_PollEvent(&event)) { case let pending: int => if (pending == 0) break; case let err: sdl2::error => trace::error(&trace::root, "SDL error: {}", err): void; return false; }; if (event.event_type == SDL_EventType::QUIT) { return false; }; window_event(&event); }; const (width, height) = drawable_size(); const xscale = width / 320; const yscale = height / 240; WINDOW.scale = if (xscale < yscale) xscale else yscale; if (WINDOW.scale == 0) { WINDOW.scale = 1; }; glm::m4_set_ident(&WINDOW.gui_proj); glm::scale(&WINDOW.gui_proj, &[gui_scale(): f32, gui_scale(): f32, 1.0]); glm::scale(&WINDOW.gui_proj, &[2.0 / width: f32, -2.0 / height: f32, 1.0]); glm::translate(&WINDOW.gui_proj, &[-1.0f32, 1.0, 0.0]); client_frame(); layers_render(); SDL_GL_SwapWindow(WINDOW.window); const then = WINDOW.timestamp; WINDOW.timestamp = time::now(time::clock::MONOTONIC); const dt = time::diff(then, WINDOW.timestamp); const dt = (dt: f64 / 1000000000.0): f32; const (sec, subsec) = math::modfracf32(dt); // trace::debug(&trace::root, "frame took {}.{:03} s / fps {}", // sec, (subsec * 1000.0): i32, 1.0 / dt); return true; }; fn window_event(event: *sdl2::event) void = { const WINDOW = get_WINDOW(); switch (event.event_type) { case SDL_EventType::KEYDOWN => const key_event = KeyEvent { status = ButtonStatus::DOWN, key = event.key.keysym.scancode: Key, }; if (event.key.repeat == 0) { append(WINDOW.held_keys, key_event.key); }; layers_input(key_event, len(LAYERS), false); window_update_layer_input(); case SDL_EventType::KEYUP => let key_event = KeyEvent { status = ButtonStatus::UP, key = event.key.keysym.scancode: Key, }; for (let i = 0z; i < len(WINDOW.held_keys)) { if (WINDOW.held_keys[i] != key_event.key) { i += 1; continue; }; delete(WINDOW.held_keys[i]); }; layers_input(key_event, len(LAYERS), false); window_update_layer_input(); case SDL_EventType::MOUSEBUTTONDOWN => WINDOW.held_mouse_buttons[event.button.button] = true; layers_input(MouseButtonEvent { status = ButtonStatus::DOWN, button = event.button.button: MouseButton, }, len(LAYERS), false); window_update_layer_input(); case SDL_EventType::MOUSEBUTTONUP => WINDOW.held_mouse_buttons[event.button.button] = false; layers_input(MouseButtonEvent { status = ButtonStatus::UP, button = event.button.button: MouseButton, }, len(LAYERS), false); window_update_layer_input(); case SDL_EventType::MOUSEMOTION => WINDOW.mouse_x = event.motion.x: f32 / gui_scale(): f32; WINDOW.mouse_y = event.motion.y: f32 / gui_scale(): f32; window_update_mouse_over(); case => void; }; }; fn window_update_layer_input() void = { const WINDOW = get_WINDOW(); let bottom = len(LAYERS); for (bottom != 0) { bottom -= 1; if (LAYERS[bottom].blocks_input()) { break; }; }; if (bottom > WINDOW.input_bottom) { layers_input(CancelEvent, bottom, true); WINDOW.input_bottom = bottom; } else if (bottom < WINDOW.input_bottom) { const old_bottom = WINDOW.input_bottom; WINDOW.input_bottom = bottom; window_uncancel(old_bottom); }; window_update_mouse_over(); }; fn window_uncancel(top: size) void = { const WINDOW = get_WINDOW(); window_update_mouse_over(); for (let i = 0z; i < len(WINDOW.held_keys); i += 1) { layers_input(KeyEvent { status = ButtonStatus::HELD, key = WINDOW.held_keys[i], }, top, true); }; for (let i = 0z; i < len(WINDOW.held_mouse_buttons); i += 1) { if (!WINDOW.held_mouse_buttons[i]) { continue; }; layers_input(MouseButtonEvent { status = ButtonStatus::HELD, button = i: MouseButton, }, top, true); }; }; fn window_update_mouse_over() void = { const i = layers_input(MouseOverEvent { covered = false }, len(LAYERS), false); layers_input(MouseOverEvent { covered = true }, i, true); };