use gl::*; use glm; use io; use mcproto; use net; use net::dial; use net::ip; use net::tcp; use strings; use time; use trace; use unix::poll; use rt; def TCP_NODELAY: int = 1; // XXX: linux def PROTO_VER = 761i32; // TODO type ClientState = enum u8 { IDLE, CONNECT, LOGIN, JOIN, GAME, }; let CLIENT_STATE = ClientState::IDLE; let CLIENT_ADDR = ""; let CLIENT_USERNAME = ""; let CLIENT_SOCK: net::socket = -1; // TODO: use tagged union once possible let CLIENT_RECV: nullable *Receiver = null; let CLIENT_RBUF: [0x1fffff]u8 = [0...]; fn client_connect(addr: str, username: str) void = { trace::info(&trace::root, "connecting to {}...", addr); // TODO: async dns? let (addrs, port) = match (dial::resolve("tcp", addr, "minecraft")) { case let res: ([]ip::addr, u16) => yield res; case let err: dial::error => die("resolve {}: {}", addr, dial::strerror(err)); }; // TODO: try secondary addresses trace::info(&trace::root, "trying {}:{}...", ip::string(addrs[0]), port); match (tcp::connect(addrs[0], port, net::sockflag::NONBLOCK)) { case let sock: net::socket => // TODO: error handling; // maybe don't use rt module (if there was an option) rt::setsockopt(sock, rt::IPPROTO_TCP, TCP_NODELAY, &1i, size(int): u32)!; CLIENT_STATE = ClientState::CONNECT; CLIENT_SOCK = sock; CLIENT_ADDR = strings::dup(addr); CLIENT_USERNAME = strings::dup(username); case let err: net::error => die("connect {}: {}", addr, net::strerror(err)); }; }; fn client_destroy() void = { match (io::close(CLIENT_SOCK)) { case void => void; case let err: io::error => trace::error(&trace::root, "failed to close socket: {}", io::strerror(err)): void; }; match (CLIENT_RECV) { case let recv: *Receiver => recv_finish(CLIENT_RECV as *Receiver); free(CLIENT_RECV); CLIENT_RECV = null; case null => void; }; if (CLIENT_STATE == ClientState::GAME) { game_destroy(); }; free(CLIENT_ADDR); CLIENT_ADDR = ""; free(CLIENT_USERNAME); CLIENT_USERNAME = ""; CLIENT_STATE = ClientState::IDLE; }; fn client_frame() void = { const t0 = time::now(time::clock::MONOTONIC); client_update_connection(); const t1 = time::now(time::clock::MONOTONIC); //trace::debug(&trace::root, "client_update_connection took {} µs", // time::diff(t0, t1) / 1000); if (CLIENT_STATE == ClientState::GAME) { game_frame(); }; }; fn client_update_connection() void = { for (true) switch (CLIENT_STATE) { case ClientState::IDLE => abort(); case ClientState::CONNECT => let fds = [poll::pollfd { fd = CLIENT_SOCK, events = poll::event::POLLOUT, ... }]; match (poll::poll(fds, 0)) { case let n: uint => if (n == 0) { return; }; case let err: poll::error => die("poll: {}", poll::strerror(err)); }; let err: int = 0; rt::getsockopt(CLIENT_SOCK, rt::SOL_SOCKET, rt::SO_ERROR, &err, &(size(int): u32))!; if (err != 0) { die("connect {}: {}", CLIENT_ADDR, rt::strerror(err)); }; trace::info(&trace::root, "connection established"); CLIENT_RECV = alloc(newrecv(0x200000)); CLIENT_STATE = ClientState::LOGIN; login_start(); case ClientState::LOGIN, ClientState::JOIN, ClientState::GAME => const recv = CLIENT_RECV as *Receiver; match (recv_poll(recv, CLIENT_SOCK, CLIENT_RBUF)) { case let length: size => let payload = CLIENT_RBUF[..length]; match (client_handle_packet(payload)) { case void => void; case trace::failed => die("invalid packet"); }; case void => return; }; }; }; fn client_handle_packet(payload: []u8) (void | trace::failed) = { const tr = trace::ctx(&trace::root, "handle packet"); const dec = mcproto::Decoder { input = payload, pos = 0, tracer = &tr, }; const ctx = mcproto::root(&dec); const ctx_ = mcproto::context(&ctx, "packet id"); const packet_id = mcproto::decode_varint(&ctx_)?; switch (CLIENT_STATE) { case ClientState::IDLE, ClientState::CONNECT => abort(); case ClientState::LOGIN => login_handle_packet(&ctx, packet_id)?; case ClientState::JOIN, ClientState::GAME => switch (packet_id) { case 0x1f => const ctx_ = mcproto::context(&ctx, "0x1f keep alive"); handle_keep_alive(&ctx_)?; case 0x24 => if (CLIENT_STATE == ClientState::GAME) { game_destroy(); CLIENT_STATE = ClientState::JOIN; }; const ctx_ = mcproto::context(&ctx, "0x24 login"); handle_login(&ctx_)?; CLIENT_STATE = ClientState::GAME; case => if (CLIENT_STATE == ClientState::JOIN) { return mcproto::error(&ctx, "Login packet expected"); }; game_handle_packet(&ctx, packet_id)?; }; }; }; fn network_send(packet_id: i32, payload: []u8) void = { match (mcproto::write_packet(CLIENT_SOCK, packet_id, payload)) { case void => void; case let err: io::error => die("network: error: write: {}", io::strerror(err)); }; }; const LAYER_CLIENT_WAITING = Layer { blocks_input = &layer_client_waiting_blocks_input, input = &layer_client_waiting_input, is_opaque = &layer_client_waiting_is_opaque, render = &layer_client_waiting_render, }; fn client_waiting_status() (str | void) = { switch (CLIENT_STATE) { case ClientState::CONNECT => return "Connecting to the server..."; case ClientState::LOGIN => return "Logging in..."; case ClientState::JOIN => return "Joining world..."; case ClientState::IDLE, ClientState::GAME => void; }; }; fn layer_client_waiting_blocks_input() bool = { return client_waiting_status() is str; }; fn layer_client_waiting_input(event: InputEvent) bool = { return false; }; fn layer_client_waiting_is_opaque() bool = { return client_waiting_status() is str; }; fn layer_client_waiting_render() void = { match (client_waiting_status()) { case let status: str => render_wait_screen(status); case void => void; }; };