diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/main.ha | 142 | ||||
-rw-r--r-- | server/save_load.ha | 4 |
2 files changed, 108 insertions, 38 deletions
diff --git a/server/main.ha b/server/main.ha index 1dffbb5..19ecc51 100644 --- a/server/main.ha +++ b/server/main.ha @@ -15,15 +15,15 @@ use time; use unix::poll; use drawing; -use drawing::{pos}; +use drawing::{pos,CHUNKSIZE}; use packet_reader; def PORT: u16 = 41460; -def CHUNKSIZE = 512; type server_state = struct { connections: []connection, + // nb we use 'picture' and 'chunk' interchangeably pictures: []drawing::picture, listener: net::socket, running: bool @@ -39,20 +39,13 @@ type connection = struct { def save_interval = 20 * time::SECOND; export fn main() void = { - // create 4 pictures. later we will have more pictures - // but for now there are just 4, and they are at fixed positions - const offs: [_]pos = [ - (0,0), (0,CHUNKSIZE), (CHUNKSIZE,0), (CHUNKSIZE,CHUNKSIZE), - ]; - - let pictures: []drawing::picture = alloc([],len(offs)); - 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"); - }); - }; + // 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) @@ -65,7 +58,7 @@ export fn main() void = { let state = server_state { connections = [], - pictures = pictures, + pictures = [], listener = listener, running = true, }; @@ -75,7 +68,7 @@ export fn main() void = { for (state.running) { const timeout = time::diff(now, next); - fmt::println("running loop, timeout {}",timeout)!; + fmt::printfln("running loop, timeout {} sec",timeout/time::SECOND)!; loop(&state, timeout); now = time::now(time::clock::MONOTONIC); @@ -87,11 +80,9 @@ export fn main() void = { case void => yield; }; next = time::add(now, save_interval); + unload_distant_chunks(&state); }; }; - - - }; fn loop(state: *server_state, timeout: time::duration) void = { @@ -123,7 +114,14 @@ fn loop(state: *server_state, timeout: time::duration) void = { }); fmt::printfln("there are now {} connections", len(state.connections))!; - greet_connection(state, 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) @@ -131,7 +129,8 @@ fn loop(state: *server_state, timeout: time::duration) void = { // 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; - fmt::println("checking conn {} with pollfd {}",conn_idx, pollfd_idx)!; + if (state.connections[conn_idx].should_delete) continue; + fmt::printfln("checking conn {} with pollfd {}",conn_idx, pollfd_idx)!; if (0 != pollfds[conn_idx+1].revents & poll::event::POLLIN) { read_from_connection(state, conn_idx); }; @@ -141,6 +140,7 @@ fn loop(state: *server_state, timeout: time::duration) void = { }; +// 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; @@ -152,7 +152,9 @@ fn perform_deletions(state: *server_state) void = { }; }; -fn greet_connection(state: *server_state, conn_idx: size) void = void; +fn greet_connection(state: *server_state, conn_idx: size) +(void|io::error) = void; + fn read_from_connection(state: *server_state, conn_idx: size) void = { @@ -166,10 +168,17 @@ fn read_from_connection(state: *server_state, conn_idx: size) void = { conn.should_delete = true; case void => for (true) match (packet_reader::next(&conn.pr)) { - // foreach loop seems to break match exhaustivity here + // xxx foreach loop seems to break match exhaustivity here + // investigate that at some point case done => break; case let p: packet_reader::packet => - handle_packet(state, conn_idx, p); + 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; @@ -181,11 +190,13 @@ fn handle_packet( state: *server_state, conn_idx: size, packet: packet_reader::packet -) void = { +) (void|io::error) = { + const conn = &state.connections[conn_idx]; match (packet) { case let op: packet_reader::packet_drawop => const opc = op as drawing::op_circle; - fmt::printfln("#{}: drawop ({:+6},{:+6})",conn_idx,opc.0,opc.1)!; + fmt::printfln("#{}: drawop ({:+6},{:+6})", + conn_idx,opc.0,opc.1)!; drawing::perform(state.pictures, opc); for (let other_idx = 0z; other_idx < len(state.connections); @@ -203,19 +214,78 @@ fn handle_packet( 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)!; + fmt::printfln("shouldn't be getting other packets from #{}", + conn_idx)!; state.connections[conn_idx].should_delete = true; }; }; -fn send_world(conn: io::file, pictures: []drawing::picture) (void | io::error) = { - for (const pic &.. pictures) { - fmt::printfln(" sending {},{}",pic.world_pos.0, pic.world_pos.1)!; - packet_reader::send(conn, packet_reader::packet_sendchunk { - world_pos = pic.world_pos, - chunk_data = pic.d[..pic.w*pic.h], - })?; +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); + 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 + + 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 (0 <= dcx && dcx <= 2*CHUNKSIZE && 0 <= 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]); + }; + }; +}; + + + diff --git a/server/save_load.ha b/server/save_load.ha index cf82a0d..db5a18a 100644 --- a/server/save_load.ha +++ b/server/save_load.ha @@ -7,14 +7,13 @@ use os; use strings; use drawing; -use drawing::{pos}; +use drawing::{pos,CHUNKSIZE}; // caller should free return value fn filename_from_world_pos(pos: pos) str = fmt::asprintf("./c.{}.{}.ppm",pos.0,pos.1); fn save_world(state: *server_state) (void | fs::error) = { - fmt::printfln("saving world!")!; for (const pic &.. state.pictures) { const filename = filename_from_world_pos(pic.world_pos); fmt::printfln("\t-> {}",filename)!; @@ -35,6 +34,7 @@ fn save_world(state: *server_state) (void | fs::error) = { buf[3*i+2] = ((px) &0xff): u8; }; io::writeall(file, buf)?; + }; }; |