From 187d6d32e71aface08c1c486874901ecc9b160c1 Mon Sep 17 00:00:00 2001 From: ubq323 Date: Tue, 16 Apr 2024 12:34:18 +0100 Subject: fix chunkloading and unloading on client --- client/main.ha | 151 +++++------------------------------------------------ client/pictures.ha | 145 ++++++++++++++++++++++++++++++++++++++++++++++++++ drawing/drawing.ha | 23 ++++---- server/main.ha | 2 +- todo | 1 - 5 files changed, 172 insertions(+), 150 deletions(-) create mode 100644 client/pictures.ha diff --git a/client/main.ha b/client/main.ha index 9b8f92b..0f4b1b8 100644 --- a/client/main.ha +++ b/client/main.ha @@ -7,12 +7,12 @@ use net::dial; use unix::poll; use drawing; -use drawing::{pos,CHUNKSIZE}; +use drawing::{pos,CHUNKSIZE,picture}; use client::paintui; use packet_reader; use packet_reader::{VERSION}; -def NCHUNKS = 16; +def NSURFS = 16; def WIN_H: i32 = 480; def WIN_W: i32 = 640; @@ -27,22 +27,7 @@ export fn main() void = { const wsurf = sdl2::SDL_GetWindowSurface(win)!; - let offs: []pos = []; - for (let x = -1; x < 3; x+=1) for (let y = -1; y < 3; y+=1) - append(offs, (1024*1024+x*CHUNKSIZE,y*CHUNKSIZE)); - - let pictures: []drawing::picture = alloc([],NCHUNKS); - let picture_surfaces: []*sdl2::SDL_Surface = alloc([], NCHUNKS); - for (let i = 0z; i < NCHUNKS; i +=1){ - const surf = sdl2::SDL_CreateRGBSurface(0, - CHUNKSIZE, CHUNKSIZE, 32, 0xff0000, 0xff00, 0xff, 0)!; - append(picture_surfaces, surf); - append(pictures, picture_from_surface(surf, offs[i])); - }; - for (const p &.. pictures) { - drawing::clear_picture(p, 0x00ff00); - drawing::outline_picture(p); - }; + let pmgr = picture_mgr { ... }; // connect to server const conn = match(dial::dial("tcp","localhost","41460")) { @@ -51,6 +36,7 @@ export fn main() void = { fmt::fatal("couldn't connect to server:",net::strerror(err)); }; + // version check let byte = [0u8]; match (io::read(conn, byte)) { case let e: io::error => @@ -60,7 +46,6 @@ export fn main() void = { byte[0], VERSION); }; - const pollfd: [1]poll::pollfd = [ poll::pollfd { fd = conn, events=poll::event::POLLIN, revents = 0 }]; @@ -74,16 +59,14 @@ export fn main() void = { let n = 0; let lasttime = sdl2::SDL_GetTicks(); - let requested_chunks: []pos = []; - // in SCREEN coords let mouse_pos: pos = (0,0); let mouse_down = false; - request_visible_chunks(pictures, conn, camera_pos, &requested_chunks); - const win_pic = picture_from_surface(wsurf, (9,9)); + process_chunk_loadedness(&pmgr, conn, camera_pos); + const win_pic = picture_from_surface(wsurf, (9,9)); for (!quit) { const did_move = do_movement(&camera_pos); @@ -124,13 +107,13 @@ export fn main() void = { match (paintui::tick(&pstate, mouse_pos_world, mouse_down)) { case void => yield; case let op: drawing::op => - drawing::perform(pictures, op); + perform_drawop(&pmgr, op); packet_reader::send(conn, op: packet_reader::packet_drawop)!; }; if (did_move) { packet_reader::send(conn, camera_pos: packet_reader::packet_position)!; - request_visible_chunks(pictures, conn, camera_pos, &requested_chunks); + process_chunk_loadedness(&pmgr, conn, camera_pos); }; const n = poll::poll(pollfd, poll::NONBLOCK)!; @@ -139,36 +122,17 @@ export fn main() void = { for (const packet => packet_reader::next(&packet_reader)!) { match (packet) { case let op: packet_reader::packet_drawop => - drawing::perform(pictures, op); + perform_drawop(&pmgr, op); case let packet: packet_reader::packet_sendchunk => - assert(packet.world_pos.0 % CHUNKSIZE == 0 - && packet.world_pos.1 % CHUNKSIZE == 0, "bad chunk world pos"); - const pic = find_picture_for_chunkdata(camera_pos, packet.world_pos, pictures); - pic.world_pos = packet.world_pos; - pic.d[..len(packet.chunk_data)] = packet.chunk_data[..]; - - for (let i = 0z; i < len(requested_chunks); i += 1) { - const rc = requested_chunks[i]; - if (rc.0 == packet.world_pos.0 && rc.1 == packet.world_pos.1) { - delete(requested_chunks[i]); - fmt::printfln("fulfilling chunk request {},{}", - packet.world_pos.0, packet.world_pos.1)!; - break; - }; - }; - + enact_chunkdata(&pmgr, packet, camera_pos); }; }; }; - drawing::clear_picture(&win_pic, 0xdddddd); - for (let i = 0z; i < len(pictures); i += 1) { - const psurf = picture_surfaces[i]; - const pic = &pictures[i]; - render_picture(pic, psurf, wsurf, camera_pos); - }; + for (const pic &.. pmgr.pictures) + render_picture(pic, wsurf, camera_pos); drawing::circle_hollow(&win_pic, mouse_pos, paintui::sizes[pstate.size_idx]: i32, pstate.color); @@ -185,8 +149,8 @@ fn picture_from_surface(surf: *sdl2::SDL_Surface, world_pos: pos) drawing::pictu world_pos = world_pos, }; -fn render_picture(pic: *drawing::picture, surf: *sdl2::SDL_Surface, winsurf: *sdl2::SDL_Surface, camera_pos: pos) void = { - sdl2::SDL_BlitSurface(surf, null, winsurf, &sdl2::SDL_Rect{ +fn render_picture(pic: *picture_c, winsurf: *sdl2::SDL_Surface, camera_pos: pos) void = { + sdl2::SDL_BlitSurface(pic.surface, null, winsurf, &sdl2::SDL_Rect{ x = pic.world_pos.0 - camera_pos.0, y = pic.world_pos.1 - camera_pos.1, ... })!; }; @@ -216,91 +180,4 @@ fn do_movement(pos: *pos) bool = { return did_move; }; -fn is_picture_visible(camera_pos: pos, pic_pos: pos) bool = { - const s_min: pos = camera_pos; - const s_max: pos = (camera_pos.0 + WIN_W, camera_pos.1 + WIN_H); - const p_min: pos = pic_pos; - const p_max: pos = (pic_pos.0 + CHUNKSIZE: i32, pic_pos.1 + CHUNKSIZE: i32); - return (s_min.0 <= p_max.0 && s_max.0 >= p_min.0) - && (s_min.1 <= p_max.1 && s_max.1 >= p_min.1); -}; - -fn find_picture_for_chunkdata(camera_pos: pos, world_pos: pos, pictures: []drawing::picture) *drawing::picture = { - // if we have one with that exact worldpos, return that - // otherwise find one that isn't visible - - let invisible: nullable *drawing::picture = null; - - for (const pic &.. pictures) { - if (pic.world_pos.0 == world_pos.0 && pic.world_pos.1 == world_pos.1) { - // fmt::printfln("found already pic at {},{}",pic.world_pos.0, pic.world_pos.1)!; - return pic; - }; - if (invisible == null && !is_picture_visible(camera_pos, pic.world_pos)) { - // fmt::printfln("can recycle pic at {},{}",pic.world_pos.0,pic.world_pos.1)!; - invisible = pic; - }; - }; - - match (invisible) { - case null => abort("couldn't find offscreen picture???"); - case let p: *drawing::picture => return p; - }; - -}; - - - -fn floor_div(a: i32, b: i32) i32 = { - if (a < 0) return -1-floor_div(-(a+1),b); - return a / b; -}; -fn find_chunk(pictures: []drawing::picture, world_pos: pos) - nullable *drawing::picture = { - for (const pic &.. pictures) { - if (pic.world_pos.0 == world_pos.0 - && pic.world_pos.1 == world_pos.1) { - return pic; - }; - }; - return null; -}; - -fn request_visible_chunks( - pictures: []drawing::picture, - conn: net::socket, - camera_pos: pos, - requested_chunks: *[]pos, -) void = { - const x0 = CHUNKSIZE*floor_div(camera_pos.0,CHUNKSIZE); - const y0 = CHUNKSIZE*floor_div(camera_pos.1,CHUNKSIZE); - - for (let dx = 0i32; dx < 3; dx += 1) for (let dy = 0i32; dy < 3; dy += 1) { - let world_pos = (x0+CHUNKSIZE*dx, y0+CHUNKSIZE*dy): pos; - - for (const rc .. requested_chunks) { - if (rc.0 == world_pos.0 && rc.1 == world_pos.1) { - fmt::printfln("not requesting {},{} again", rc.0, rc.1)!; - return; - }; - }; - - - if (!is_picture_visible(camera_pos, world_pos)) { - // fmt::printfln("invisible {},{}", world_pos.0, world_pos.1)!; - continue; - }; - // fmt::printfln("visible {},{}, checking availability", world_pos.0, world_pos.1)!; - match (find_chunk(pictures,world_pos)) { - case *drawing::picture => - // fmt::printfln("\talready got that one")!; - yield; - case null => - fmt::printfln("\trequesting {},{}", world_pos.0, world_pos.1)!; - packet_reader::send(conn, world_pos: packet_reader::packet_reqchunk)!; - append(requested_chunks, world_pos); - }; - }; -}; - diff --git a/client/pictures.ha b/client/pictures.ha new file mode 100644 index 0000000..1db3954 --- /dev/null +++ b/client/pictures.ha @@ -0,0 +1,145 @@ +use fmt; +use net; + +use sdl2; + +use drawing::{picture,pos,CHUNKSIZE}; +use drawing; +use packet_reader; + +type picture_mgr = struct { + pictures: []picture_c, + requests: []pos, +}; + +type picture_c = struct { + picture, + surface: *sdl2::SDL_Surface, +}; + + +fn is_picture_visible(camera_pos: pos, pic_pos: pos) bool = { + const s_min: pos = camera_pos; + const s_max: pos = (camera_pos.0 + WIN_W, camera_pos.1 + WIN_H); + const p_min: pos = pic_pos; + const p_max: pos = (pic_pos.0 + CHUNKSIZE: i32, pic_pos.1 + CHUNKSIZE: i32); + return (s_min.0 <= p_max.0 && s_max.0 >= p_min.0) + && (s_min.1 <= p_max.1 && s_max.1 >= p_min.1); +}; + +fn find_picture_by_pos(pmgr: *picture_mgr, world_pos: pos) + nullable *picture = { + for (const pic &.. pmgr.pictures) { + if (pic.world_pos.0 == world_pos.0 + && pic.world_pos.1 == world_pos.1) { + return pic; + }; + }; + return null; +}; + +// called when a new chunkdata packet comes in +fn enact_chunkdata(pmgr: *picture_mgr, + packet: packet_reader::packet_sendchunk, + camera_pos: pos, +) void = { + assert(packet.world_pos.0 % CHUNKSIZE == 0 + && packet.world_pos.1 % CHUNKSIZE == 0, + "bad chunk world pos"); + assert(len(packet.chunk_data) == CHUNKSIZE*CHUNKSIZE, + "bad chunk data size"); + + const surf = sdl2::SDL_CreateRGBSurface(0, + CHUNKSIZE, CHUNKSIZE, 32, 0xff0000, 0xff00, 0xff, 0)!; + append(pmgr.pictures, picture_c { + w = CHUNKSIZE, + h = CHUNKSIZE, + d = (surf.pixels as *opaque: *[*]u32), + world_pos = packet.world_pos, + surface = surf, + }); + + const p = &pmgr.pictures[len(pmgr.pictures)-1]; + p.d[..p.w*p.h] = packet.chunk_data[..]; + + // delete extant request + for (let i = 0z; i < len(pmgr.requests); i += 1) { + const rc = pmgr.requests[i]; + if (rc.0 == packet.world_pos.0 && rc.1 == packet.world_pos.1) { + delete(pmgr.requests[i]); + break; + }; + }; +}; + +fn floor_div(a: i32, b: i32) i32 = { + if (a < 0) return -1-floor_div(-(a+1),b); + return a / b; +}; + + +fn is_request_inflight(pmgr: *picture_mgr, world_pos: pos) bool = { + for (const rc .. pmgr.requests) + if (rc.0 == world_pos.0 && rc.1 == world_pos.1) + return true; + return false; +}; + +// called on startup and on camera movement +// for each loaded chunk not visible, unload it +// for each chunk that should be visible that isn't loaded +// and which hasn't already been requested, +// request it. +fn process_chunk_loadedness( + pmgr: *picture_mgr, + conn: net::socket, + camera_pos: pos, +) void = { + + // unload all invisible chunks + for (let i1 = len(pmgr.pictures); i1 > 0; i1 -= 1) { + const i = i1 - 1; + const pic = pmgr.pictures[i]; + if (!is_picture_visible(camera_pos, pic.world_pos)) { + fmt::printfln("## unloading {},{}", pic.world_pos.0, pic.world_pos.1)!; + delete(pmgr.pictures[i]); + }; + }; + + + const x0 = CHUNKSIZE*floor_div(camera_pos.0,CHUNKSIZE); + const y0 = CHUNKSIZE*floor_div(camera_pos.1,CHUNKSIZE); + + // use a 3x3 window for 'chunks that might need to be loaded' + for (let dx = 0i32; dx < 3; dx += 1) { + for (let dy = 0i32; dy < 3; dy += 1) { + const world_pos = (x0 + CHUNKSIZE*dx, y0 + CHUNKSIZE*dy): pos; + const visible = is_picture_visible(camera_pos, world_pos); + const loaded = !(find_picture_by_pos(pmgr, world_pos) is null); + const requested = is_request_inflight(pmgr, world_pos); + + if (!visible) { + fmt::printfln("\tnot visible {},{}", world_pos.0, world_pos.1)!; + continue; + }; + if (loaded) { + fmt::printfln("\talready loaded {},{}", world_pos.0, world_pos.1)!; + continue; + }; + if (requested) { + fmt::printfln("\tnot requesting again {},{}", world_pos.0, world_pos.1)!; + continue; + }; + + fmt::printfln("!! requesting {},{}", world_pos.0, world_pos.1)!; + packet_reader::send(conn, world_pos: packet_reader::packet_reqchunk)!; + append(pmgr.requests, world_pos); + }; + }; +}; + + +fn perform_drawop(pmgr: *picture_mgr, op: drawing::op) void = { + for (const pic &.. pmgr.pictures) + drawing::perform(pic, op); +}; diff --git a/drawing/drawing.ha b/drawing/drawing.ha index 54116a3..ae36501 100644 --- a/drawing/drawing.ha +++ b/drawing/drawing.ha @@ -127,23 +127,24 @@ export fn stroke(picture: *picture, c0: pos, c1: pos, r: i32, color: u32) void = }; // ehh should check bounding box instead of doing all pictures maybe -export fn perform(pictures: []picture, op: op) void = { +export fn perform(pic: *picture, op: op) void = { match (op) { case let o: op_circle => const x = o.pos.0, y=o.pos.1; const r = o.radius; const c = o.color & 0xffffff; - for (const pic &.. pictures) { - let pos_within_pic = - (x - pic.world_pos.0, y - pic.world_pos.1): pos; - circle(pic, pos_within_pic, r: i32, c); - }; + const pos_within_pic: pos = + (x - pic.world_pos.0, y - pic.world_pos.1); + circle(pic, pos_within_pic, r: i32, c); case let s: op_stroke => const col = s.color & 0xffffff; - for (const pic &.. pictures) { - const c0 = (s.pos0.0 - pic.world_pos.0, s.pos0.1 - pic.world_pos.1); - const c1 = (s.pos1.0 - pic.world_pos.0, s.pos1.1 - pic.world_pos.1); - stroke(pic, c0, c1, s.radius: i32, col); - }; + const c0 = (s.pos0.0 - pic.world_pos.0, s.pos0.1 - pic.world_pos.1); + const c1 = (s.pos1.0 - pic.world_pos.0, s.pos1.1 - pic.world_pos.1); + stroke(pic, c0, c1, s.radius: i32, col); }; }; + +export fn perform_multi(pictures: []picture, op: op) void = { + for (const pic &.. pictures) + perform(pic, op); +}; diff --git a/server/main.ha b/server/main.ha index 2043a1d..41e6b6a 100644 --- a/server/main.ha +++ b/server/main.ha @@ -199,7 +199,7 @@ fn handle_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); + drawing::perform_multi(state.pictures, op); for (let other_idx = 0z; other_idx < len(state.connections); other_idx += 1) diff --git a/todo b/todo index 105a3ca..2c2c176 100644 --- a/todo +++ b/todo @@ -1,3 +1,2 @@ -smooth brush strokes rle compression more elegant solution to big packets than just having a giant buffer -- cgit v1.2.3