use bufio; use bytes; use errors; use fmt; use fs; use io; use net::ip; use net::tcp; use net; use os; use strings; use time::chrono; use time::date; use time; use unix::poll; use drawing; use drawing::{pos,CHUNKSIZE}; use packet_reader; use packet_reader::{VERSION}; def PORT: u16 = 41460; type server_state = struct { connections: []connection, // nb we use 'picture' and 'chunk' interchangeably pictures: []drawing::picture, listener: net::socket, running: bool }; type connection = struct { pr: packet_reader::packet_reader, sock: net::socket, pos: pos, should_delete: bool, }; def save_interval = 20 * time::SECOND; export fn main() void = { // for (let i = 0z; i < len(offs); i +=1){ // append(pictures, match (load_picture_from_file(offs[i])) { // case let pic: drawing::picture => yield pic; // case let err: fs::error => fmt::fatal(fs::strerror(err)); // case bad_header => fmt::fatal("bad ppm header"); // }); // }; const listener = match ( tcp::listen(ip::ANY_V4, PORT, tcp::reuseaddr) ) { case let err: net::error => fmt::fatalf(net::strerror(err)); case let sock: net::socket => yield sock; }; let state = server_state { connections = [], pictures = [], listener = listener, running = true, }; let now = time::now(time::clock::MONOTONIC); let next = time::add(now, save_interval); for (state.running) { const timeout = time::diff(now, next); fmt::printfln("running loop, timeout {} sec",timeout/time::SECOND)!; loop(&state, timeout); now = time::now(time::clock::MONOTONIC); if (time::compare(now, next) >= 0) { fmt::println("saving world!")!; match (save_world(&state)) { case let e: fs::error => fmt::fatalf("couldn't save world: {}",fs::strerror(e)); case void => yield; }; next = time::add(now, save_interval); unload_distant_chunks(&state); }; }; }; fn loop(state: *server_state, timeout: time::duration) void = { // do poll. let pollfds: []poll::pollfd = []; append(pollfds, poll::pollfd { fd = state.listener, events = poll::event::POLLIN, ... }); for (const conn &.. state.connections) { append(pollfds, poll::pollfd { fd = conn.sock, events = poll::event::POLLIN, ... }); }; poll::poll(pollfds, timeout)!; // if listener: add connection if (0 != pollfds[0].revents & poll::event::POLLIN) { const new_conn = tcp::accept(pollfds[0].fd)!; append(state.connections, connection { sock = new_conn, pr = packet_reader::new(), pos = (0,0), should_delete = false, }); fmt::printfln("there are now {} connections", len(state.connections))!; const conn_idx = len(state.connections) - 1; match (greet_connection(state, conn_idx)) { case void => yield; case let e: io::error => fmt::printfln("error greeting {}: {}",conn_idx, io::strerror(e))!; delete(state.connections[conn_idx]); }; }; // for each connection, handle packet(s) // loop thru pollfds not state.connections so we don't // try to look at the nonexistent pollfd for a connection we just accepted for (let pollfd_idx = 1z: size; pollfd_idx < len(pollfds); pollfd_idx += 1) { const conn_idx = pollfd_idx - 1; if (state.connections[conn_idx].should_delete) continue; if (0 != pollfds[conn_idx+1].revents & poll::event::POLLIN) { read_from_connection(state, conn_idx); }; }; perform_deletions(state); }; // kicks connections in an error state fn perform_deletions(state: *server_state) void = { for (let i1 = len(state.connections); i1 > 0; i1 -= 1) { const i = i1 - 1; const conn = state.connections[i]; if (conn.should_delete) { fmt::printfln("deleting conn {}",i)!; delete(state.connections[i]); }; }; }; fn greet_connection(state: *server_state, conn_idx: size) (void|io::error) = { const conn = state.connections[conn_idx]; io::write(conn.sock, [VERSION])?; }; fn read_from_connection(state: *server_state, conn_idx: size) void = { const conn = &state.connections[conn_idx]; match (packet_reader::read(&conn.pr, conn.sock)) { case let err: io::error => fmt::printfln("#{} error: {}", conn_idx, io::strerror(err))!; conn.should_delete = true; case io::EOF => fmt::printfln("#{} disconnect", conn_idx)!; conn.should_delete = true; case void => for (true) match (packet_reader::next(&conn.pr)) { // xxx foreach loop seems to break match exhaustivity here // investigate that at some point case done => break; case let p: packet_reader::packet => match (handle_packet(state, conn_idx, p)) { case void => yield; case let e: io::error => fmt::printfln("handle packet error. #{}. {}", conn_idx, io::strerror(e))!; conn.should_delete = true; }; case let e: packet_reader::error => fmt::printfln("packet error {}",e)!; conn.should_delete = true; }; }; }; fn handle_packet( state: *server_state, conn_idx: size, packet: packet_reader::packet ) (void|io::error) = { const conn = &state.connections[conn_idx]; match (packet) { case let op: packet_reader::packet_drawop => // fmt::printfln("#{}: drawop ({:+6},{:+6}) r{}", // conn_idx,opc.pos.0,opc.pos.1, opc.radius)!; drawing::perform(state.pictures, op); for (let other_idx = 0z; other_idx < len(state.connections); other_idx += 1) { if (other_idx == conn_idx) continue; const other_conn = &state.connections[other_idx]; if (other_conn.should_delete) continue; // fmt::printfln("\t -> #{}",other_idx)!; match (packet_reader::send(other_conn.sock, packet)) { case void => yield; case let e: io::error => fmt::printfln("couldn't send to #{}: {}", other_idx, io::strerror(e))!; other_conn.should_delete = true; }; }; case let pos: packet_reader::packet_position => // fmt::printfln("pos of #{} is now {},{}", // conn_idx, pos.0, pos.1)!; state.connections[conn_idx].pos = pos; case let world_pos: packet_reader::packet_reqchunk => // xxx it is probably easy to ddos the server by sending lots of these fmt::printfln("#{} requests chunk {},{}", conn_idx, world_pos.0, world_pos.1)!; const pic = ensure_chunk(state, world_pos); packet_reader::send(conn.sock, packet_reader::packet_sendchunk { world_pos = world_pos, chunk_data = pic.d[..pic.w*pic.h], })?; case => fmt::printfln("shouldn't be getting other packets from #{}", conn_idx)!; state.connections[conn_idx].should_delete = true; }; }; fn ensure_chunk(state: *server_state, world_pos: pos) *drawing::picture = { // if that chunk is already loaded, do nothing // if not, load it from disk, or create it newly if not on disk // in either case, return it for (const pic &.. state.pictures) { if (pic.world_pos.0 == world_pos.0 && pic.world_pos.1 == world_pos.1) { return pic; }; }; const pic = load_picture_from_file(world_pos)!; append(state.pictures, pic); return &state.pictures[len(state.pictures)-1]; }; fn floor_div(a: i32, b: i32) i32 = { if (a < 0) return -1-floor_div(-(a+1),b); return a / b; }; fn unload_distant_chunks(state: *server_state) void = { const n_pics = len(state.pictures); if (n_pics == 0) return; let should_unload = alloc([true...], n_pics); defer free(should_unload); for (const conn &.. state.connections) { const chunk_in_x = floor_div(conn.pos.0, CHUNKSIZE)*CHUNKSIZE; const chunk_in_y = floor_div(conn.pos.1, CHUNKSIZE)*CHUNKSIZE; // pic_chunk_pos - player_chunk_pos must be either 0 or 512 or 1024 // can't be less than 0 or more than 1024 // // -CHUNKSIZE because i suspect this still unloads chunks when // it should't, somehow for (let i = 0z; i < n_pics; i += 1) { const pic = &state.pictures[i]; const dcx = pic.world_pos.0 - chunk_in_x; const dcy = pic.world_pos.1 - chunk_in_y; if (-CHUNKSIZE <= dcx && dcx <= 2*CHUNKSIZE && -CHUNKSIZE <= dcy && dcy <= 2*CHUNKSIZE) should_unload[i] = false; }; }; for (let i1 = n_pics; i1 > 0; i1 -= 1) { const i = i1 - 1; if (should_unload[i]) { fmt::printfln("unloading {},{}", state.pictures[i].world_pos.0, state.pictures[i].world_pos.1)!; delete(state.pictures[i]); }; }; };