summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--game.js34
-rw-r--r--server/src/main.rs38
2 files changed, 56 insertions, 16 deletions
diff --git a/game.js b/game.js
index 27c0971..811f23c 100644
--- a/game.js
+++ b/game.js
@@ -17,6 +17,7 @@ window.addEventListener('load', function () {
let nibSize = 12;
let pieceWidth = 70;
let pieceHeight;
+ let receivedAck = true;
document.body.style.setProperty('--image', `url("${imageUrl}")`);// TODO : escaping
const image = new Image();
const draggingPieceLastPos = Object.preventExtensions({x: null, y: null});
@@ -177,6 +178,7 @@ window.addEventListener('load', function () {
element;
nibTypes;
connectedComponent;
+ upToDateWithServer;
getClipPath() {
const nibTypes = this.nibTypes;
let shoulderWidth = (pieceWidth - nibSize) / 2;
@@ -211,6 +213,7 @@ window.addEventListener('load', function () {
this.y = y;
this.u = u;
this.v = v;
+ this.upToDateWithServer = true;
this.connectedComponent = [this];
const element = this.element = document.createElement('div');
element.classList.add('piece');
@@ -264,11 +267,6 @@ window.addEventListener('load', function () {
if (draggingPiece) {
let anyConnected = false;
for (const piece of draggingPiece.connectedComponent) {
- const canonicalPos = screenPosToCanonical({
- x: piece.x,
- y: piece.y,
- });
- socket.send(`move ${piece.id} ${canonicalPos.x} ${canonicalPos.y}`);
if (solved) break;
const col = piece.col();
const row = piece.row();
@@ -312,6 +310,7 @@ window.addEventListener('load', function () {
piece.x += dx;
piece.y += dy;
piece.updatePosition();
+ piece.upToDateWithServer = false;
}
draggingPieceLastPos.x = e.clientX;
draggingPieceLastPos.y = e.clientY;
@@ -403,6 +402,7 @@ window.addEventListener('load', function () {
// only udpate the position of one piece per equivalence class mod is-connected-to
if (connectivity[i] !== i) continue;
const piece = pieces[i];
+ if (!piece.upToDateWithServer) continue;
if (draggingPiece && draggingPiece.connectedComponent === piece.connectedComponent) continue;
const newPos = canonicalToScreenPos({x: piecePositions[2*i], y: piecePositions[2*i+1]});
const diff = [newPos.x - piece.x, newPos.y - piece.y];
@@ -413,7 +413,23 @@ window.addEventListener('load', function () {
piece.updatePosition();
}
}
-
+ function sendServerUpdate() {
+ // send update to server
+ if (!receivedAck) return; // last update hasn't been acknowledged yet
+ const motions = [];
+ for (const piece of pieces) {
+ if (piece.upToDateWithServer) continue;
+ const canonicalPos = screenPosToCanonical({
+ x: piece.x,
+ y: piece.y,
+ });
+ motions.push(`move ${piece.id} ${canonicalPos.x} ${canonicalPos.y}`);
+ }
+ if (motions.length) {
+ receivedAck = false;
+ socket.send(motions.join('\n'));
+ }
+ }
socket.addEventListener('open', () => {
if (joinPuzzle) {
socket.send(`join ${joinPuzzle}`);
@@ -421,12 +437,18 @@ window.addEventListener('load', function () {
socket.send(`new ${puzzleWidth} ${puzzleHeight} ${imageUrl}`);
}
setInterval(() => socket.send('poll'), 1000);
+ setInterval(sendServerUpdate, 1000);
});
socket.addEventListener('message', (e) => {
if (typeof e.data === 'string') {
if (e.data.startsWith('id: ')) {
let puzzleID = e.data.split(' ')[1];
console.log('ID:', puzzleID);
+ } else if (e.data === 'ack') {
+ for (const piece of pieces) {
+ piece.upToDateWithServer = true;
+ }
+ receivedAck = true;
}
} else {
const opcode = new Uint8Array(e.data, 0, 1)[0];
diff --git a/server/src/main.rs b/server/src/main.rs
index 1fac1e5..32220a9 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -116,25 +116,43 @@ async fn handle_connection(database: &Database, conn: &mut tokio::net::TcpStream
puzzle_id = Some(id);
let info = get_puzzle_info(&database, &id)?;
ws.send(Message::Binary(info)).await?;
- } else if let Some(data) = text.strip_prefix("move ") {
- let mut parts = data.split(' ');
+ } else if text.starts_with("move ") {
let puzzle_id = puzzle_id.ok_or_else(|| anyhow!("move without puzzle ID"))?;
- let piece: usize = parts.next().ok_or_else(|| anyhow!("bad syntax"))?.parse()?;
- let x: f32 = parts.next().ok_or_else(|| anyhow!("bad syntax"))?.parse()?;
- let y: f32 = parts.next().ok_or_else(|| anyhow!("bad syntax"))?.parse()?;
+ #[derive(Clone, Copy)]
+ struct Motion {
+ piece: usize,
+ x: f32,
+ y: f32,
+ }
+ let mut motions = vec![];
+ for line in text.split('\n') {
+ let mut parts = line.split(' ');
+ parts.next(); // skip "move"
+ let piece: usize = parts.next().ok_or_else(|| anyhow!("bad syntax"))?.parse()?;
+ let x: f32 = parts.next().ok_or_else(|| anyhow!("bad syntax"))?.parse()?;
+ let y: f32 = parts.next().ok_or_else(|| anyhow!("bad syntax"))?.parse()?;
+ motions.push(Motion {
+ piece,
+ x,
+ y,
+ });
+ }
loop {
let curr_pieces = database.pieces.get(&puzzle_id)?
.ok_or_else(|| anyhow!("bad puzzle ID"))?;
let mut new_pieces = curr_pieces.to_vec();
- new_pieces.get_mut(8 * piece..8 * piece + 4).ok_or_else(|| anyhow!("bad piece ID"))?
- .copy_from_slice(&x.to_le_bytes());
- new_pieces.get_mut(8 * piece + 4..8 * piece + 8).ok_or_else(|| anyhow!("bad piece ID"))?
- .copy_from_slice(&y.to_le_bytes());
+ for Motion {piece, x, y} in motions.iter().copied() {
+ new_pieces.get_mut(8 * piece..8 * piece + 4).ok_or_else(|| anyhow!("bad piece ID"))?
+ .copy_from_slice(&x.to_le_bytes());
+ new_pieces.get_mut(8 * piece + 4..8 * piece + 8).ok_or_else(|| anyhow!("bad piece ID"))?
+ .copy_from_slice(&y.to_le_bytes());
+ }
if database.pieces.compare_and_swap(&puzzle_id, Some(curr_pieces), Some(new_pieces))?.is_ok() {
break;
}
- tokio::time::sleep(std::time::Duration::from_millis(10)).await;
+ tokio::time::sleep(std::time::Duration::from_millis(1)).await; // yield maybe (don't let contention hog resources)
}
+ ws.send(Message::Text("ack".to_string())).await?;
} else if text == "poll" {
let puzzle_id = puzzle_id.ok_or_else(|| anyhow!("poll without puzzle ID"))?;
let pieces = database.pieces.get(&puzzle_id)?.ok_or_else(|| anyhow!("bad puzzle ID"))?;