aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorubq323 <ubq323@ubq323.website>2024-04-14 00:35:19 +0100
committerubq323 <ubq323@ubq323.website>2024-04-14 00:35:19 +0100
commit6cff8d620cd92f9ddc63c6891b3e669b5294619e (patch)
treed3632c81992fdbf0f8435fa225920122bee29776
parentc95e947d90a02091aef4e63693ce3205fdfba3b6 (diff)
multiple chunks on server and unload not near players. just need client to req chunks now
-rw-r--r--client/main.ha25
-rw-r--r--drawing/drawing.ha2
-rw-r--r--packet_reader/packet_reader.ha65
-rw-r--r--server/main.ha142
-rw-r--r--server/save_load.ha4
-rw-r--r--todo2
6 files changed, 179 insertions, 61 deletions
diff --git a/client/main.ha b/client/main.ha
index e720b65..de6b2fd 100644
--- a/client/main.ha
+++ b/client/main.ha
@@ -4,12 +4,11 @@ use math;
use net;
use net::dial;
use drawing;
-use drawing::{pos};
+use drawing::{pos,CHUNKSIZE};
use client::paintui;
use unix::poll;
use packet_reader;
-def CHUNKSIZE = 512;
def NCHUNKS = 16;
def WIN_H: i32 = 480;
@@ -42,13 +41,19 @@ export fn main() void = {
drawing::outline_picture(p);
};
-
// connect to server
const conn = match(dial::dial("tcp","localhost","41460")) {
case let c: net::socket => yield c;
case let err: net::error =>
fmt::fatal("couldn't connect to server:",net::strerror(err));
};
+
+ for (let i = 0; i < 3; i += 1) {
+ packet_reader::send(conn, (
+ i*CHUNKSIZE,i*CHUNKSIZE
+ ):packet_reader::packet_reqchunk)!;
+ };
+
const pollfd: [1]poll::pollfd = [ poll::pollfd {
fd = conn, events=poll::event::POLLIN, revents = 0
}];
@@ -66,7 +71,7 @@ export fn main() void = {
let mouse_pos: pos = (0,0);
let mouse_down = false;
for (!quit) {
- do_movement(&camera_pos);
+ const did_move = do_movement(&camera_pos);
// let nvis = 0;
// for (const pic &.. pictures) {
@@ -95,7 +100,6 @@ export fn main() void = {
case => void;
};
-
match (paintui::tick(&pstate, mouse_pos, mouse_down)) {
case void => yield;
case let op: drawing::op =>
@@ -103,6 +107,10 @@ export fn main() void = {
packet_reader::send(conn, op: packet_reader::packet_drawop)!;
};
+ if (did_move) {
+ packet_reader::send(conn, camera_pos: packet_reader::packet_position)!;
+ };
+
const n = poll::poll(pollfd, poll::NONBLOCK)!;
if (n > 0) {
fmt::println("data available")!;
@@ -155,7 +163,8 @@ fn render_picture(pic: *drawing::picture, surf: *sdl2::SDL_Surface, winsurf: *sd
def SPEED = 17;
def DIAG_SPEED = 12; // thereabouts
-fn do_movement(pos: *pos) void = {
+// returned whether movement happened
+fn do_movement(pos: *pos) bool = {
const kb = sdl2::SDL_GetKeyboardState();
let dx = 0;
let dy = 0;
@@ -164,11 +173,15 @@ fn do_movement(pos: *pos) void = {
if (kb[sdl2::SDL_Scancode::A]) dx -= 1;
if (kb[sdl2::SDL_Scancode::D]) dx += 1;
+ const did_move = dx!=0 || dy!=0;
+
let speed = SPEED;
if (dx != 0 && dy != 0) speed = DIAG_SPEED;
pos.0 += dx * speed;
pos.1 += dy * speed;
+
+ return did_move;
};
fn is_picture_visible(camera_pos: pos, pic_pos: pos) bool = {
diff --git a/drawing/drawing.ha b/drawing/drawing.ha
index 70bd7af..fc59e1b 100644
--- a/drawing/drawing.ha
+++ b/drawing/drawing.ha
@@ -4,6 +4,8 @@ use io;
use endian;
use math::random;
+export def CHUNKSIZE = 512;
+
export type pos = (i32, i32);
export type picture = struct {
diff --git a/packet_reader/packet_reader.ha b/packet_reader/packet_reader.ha
index 30911df..df4d417 100644
--- a/packet_reader/packet_reader.ha
+++ b/packet_reader/packet_reader.ha
@@ -2,6 +2,7 @@ use io;
use fmt;
use endian;
use drawing;
+use drawing::{pos,CHUNKSIZE};
export type error = !str;
@@ -27,15 +28,23 @@ fn cast_u8s_to_u32s(in: []u8) []u32 =
export type packet_type = enum u8 {
DRAW_OP,
SEND_CHUNK,
+ POSITION,
+ REQ_CHUNK
};
export type packet_drawop = drawing::op;
export type packet_sendchunk = struct {
- world_pos: drawing::pos,
+ world_pos: pos,
chunk_data: []u32,
};
+export type packet_position = pos;
+export type packet_reqchunk = pos;
-export type packet = (packet_drawop | packet_sendchunk);
+export type packet = (
+ packet_drawop
+ | packet_sendchunk
+ | packet_position
+ | packet_reqchunk);
// call when input is ready. could block otherwise
export fn read(pr: *packet_reader, sock: io::handle) (void | io::error | io::EOF) = {
@@ -43,7 +52,7 @@ export fn read(pr: *packet_reader, sock: io::handle) (void | io::error | io::EOF
const read_pos = if (remaining_amt > 0) {
// still some unconsumed content in the buffer
// move unconsumed stuff to start of buffer
- fmt::printfln("moving {} remaining bytes",remaining_amt)!;
+ // fmt::printfln("moving {} remaining bytes",remaining_amt)!;
pr.buf[0..remaining_amt] = pr.good;
yield remaining_amt;
} else 0z;
@@ -51,10 +60,10 @@ export fn read(pr: *packet_reader, sock: io::handle) (void | io::error | io::EOF
case io::EOF => return io::EOF;
case let n: size => yield n;
};
- fmt::printfln("read {} bytes",nread)!;
+ // fmt::printfln("read {} bytes",nread)!;
const total_amt = read_pos + nread;
pr.good = pr.buf[0..total_amt];
- fmt::printfln("now {} bytes in buffer",total_amt)!;
+ // fmt::printfln("now {} bytes in buffer",total_amt)!;
};
// packet format:
@@ -69,34 +78,50 @@ export fn next(pr: *packet_reader) (packet | done | error) = {
// and return the packet,
// or, ascertain there is no full packet, and return done
if (len(pr.good) < size(u32)) return done;
- fmt::println("a")!;
const packet_len = endian::legetu32(pr.good[0..4]);
if (packet_len < 8) return "packet size field too small": error;
- fmt::println("b")!;
if (len(pr.good) < packet_len) return done;
- fmt::println("c")!;
const packet_bytes = pr.good[..packet_len];
pr.good = pr.good[packet_len..];
const ty = endian::legetu32(packet_bytes[4..8]): packet_type;
+ const payload = packet_bytes[8..];
switch (ty) {
case packet_type::DRAW_OP =>
- const op = drawing::deser_op(packet_bytes[8..]);
- fmt::println("d")!;
+ const op = drawing::deser_op(payload);
return op: packet_drawop;
case packet_type::SEND_CHUNK =>
// return value is BORROWED from the BUFFER
- const pos_bytes = packet_bytes[8..16];
- const chunk_data_bytes = packet_bytes[16..];
+ const pos_bytes = payload[0..8];
+ const chunk_data_bytes = payload[8..];
const pos = (
endian::legetu32(pos_bytes[0..4]): i32,
endian::legetu32(pos_bytes[4..8]): i32
- ): drawing::pos;
+ ): pos;
const d = cast_u8s_to_u32s(chunk_data_bytes);
- assert(len(d) == 512*512,"wrong chunk size??");
- fmt::println("e")!;
+ if (len(d) != CHUNKSIZE*CHUNKSIZE)
+ return "wrong chunk size??": error;
return packet_sendchunk { world_pos = pos, chunk_data = d };
+ case packet_type::POSITION =>
+ if (len(payload) != 8)
+ return "position packet wrong size": error;
+ const pos = (
+ endian::legetu32(payload[0..4]): i32,
+ endian::legetu32(payload[4..8]): i32
+ ): pos;
+ return pos: packet_position;
+ case packet_type::REQ_CHUNK =>
+ if (len(payload) != 8)
+ return "reqchunk packet wrong size": error;
+ const world_pos = (
+ endian::legetu32(payload[0..4]): i32,
+ endian::legetu32(payload[4..8]): i32
+ ): pos;
+ if (world_pos.0 % CHUNKSIZE != 0 || world_pos.1 % CHUNKSIZE != 0) {
+ return "invalid world_pos": error;
+ };
+ return world_pos: packet_reqchunk;
};
};
@@ -124,5 +149,15 @@ export fn send(sock: io::file, packet: packet) (void | io::error) = {
endian::leputu32(pos_buf[4..8],packet.world_pos.1: u32);
const chunk_data_bytes = cast_u32s_to_u8s(packet.chunk_data);
send_raw(sock, packet_type::SEND_CHUNK, pos_buf, chunk_data_bytes)?;
+ case let pos: packet_position =>
+ const pos_buf: [8]u8 = [0...];
+ endian::leputu32(pos_buf[0..4], pos.0: u32);
+ endian::leputu32(pos_buf[4..8], pos.1: u32);
+ send_raw(sock, packet_type::POSITION, pos_buf)?;
+ case let world_pos: packet_reqchunk =>
+ const pos_buf: [8]u8 = [0...];
+ endian::leputu32(pos_buf[0..4], world_pos.0: u32);
+ endian::leputu32(pos_buf[4..8], world_pos.1: u32);
+ send_raw(sock, packet_type::REQ_CHUNK, pos_buf)?;
};
};
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)?;
+
};
};
diff --git a/todo b/todo
index 70c70e8..58de6a7 100644
--- a/todo
+++ b/todo
@@ -1,4 +1,2 @@
multiple chunks on server
-client send position to server
-server list of clients, properly
server periodically unloads chunks not near any player