martinbwg / Pixel-Tanks

1 stars 3 forks source link

Lag Update Progress #420

Closed cs6413110 closed 6 months ago

cs6413110 commented 6 months ago

Progress on the lag update.

cs6413110 commented 6 months ago

Figuring out what I want to do for determining if the tank has changed cell position(chunk loading).

Timeline as of now is something like this:

1.) Chunk loading detection on Tank and Engine for multiplayer 2.) Chunk loading formula for Multiplayer 3.) Implement new chunk loading over old 4.) Implement updating events for each entity (Tank, Damage, AI, Shot, Block) 5.) Code update streaming for Multiplayer 6.) Bugfixes

cs6413110 commented 6 months ago

Beta chunkloading coords

  chunkload(t, x, y) {
    return;
    const w = ?, h = ?;
    const ocx = Math.floor(t.x/100), ocy = Math.floor(t.y/100), ncx = Math.floor(x/100), ncy = Math.floor(y/100);
    const xd = ocx-ncx, yd = ocy-ncy, yda = yd < 0 ? -1 : 1, xda = xd < 0 ? -1 : 1;
    // New Loaded Rect Coords
    const nhrx = ncx-w/2*xda, nhry = ncy-h/2*yda, nhrw = w*xda, nhrh = Math.min(h, Math.abs(yd))*yda;
    const nvrx = nhrx, nvry = nhry+nhrh, nvrw = Math.min(w, Math.abs(xd))*xda, nvrh = (h-Math.min(h, Math.abs(yd)))*yda;
    const ohrx = ocx+w/2*xda, ohry = ncy-h/2*yda, ohrw = -nhrw, ohrh = -nhrh;
    const ovrx = ohrx, ovry = ohry+ohrw, ovrw = -nvrw, ovrh = -nvrh;
  }
cs6413110 commented 6 months ago

Chunk loading simulation

<canvas id='canvas' width='500', height='500'></canvas>
<script>
const canvas = document.getElementById('canvas'), draw = canvas.getContext('2d');
canvas.style = 'border: 1px solid black';
let t = {x: 0, y: 0}, x = 0, y = 0;
(a = (t, x, y) => {
  const s = 20;
  t.x *= 100;
  t.y *= 100;
  x *= 100;
  y *= 100;

  const w = 7, h = 5; // must be odd ig?

  const ocx = Math.floor(t.x/100)+.5, ocy = Math.floor(t.y/100)+.5, ncx = Math.floor(x/100)+.5, ncy = Math.floor(y/100)+.5;
  const xd = ocx-ncx, yd = ocy-ncy, yda = yd < 0 ? -1 : 1, xda = xd < 0 ? -1 : 1;
  // New Loaded Rect Coords
  const nhrx = ncx-w/2*xda, nhry = ncy-h/2*yda, nhrw = w*xda, nhrh = Math.min(h, Math.abs(yd))*yda;
  const nvrx = nhrx, nvry = nhry+nhrh, nvrw = Math.min(w, Math.abs(xd))*xda, nvrh = (h-Math.min(h, Math.abs(yd)))*yda;
  const ohrx = ocx+w/2*xda, ohry = ocy+h/2*yda, ohrw = -nhrw, ohrh = -nhrh;
  const ovrx = ohrx, ovry = ohry+ohrh, ovrw = -nvrw, ovrh = -nvrh;

  t.x /= 100;
  t.y /= 100;
  x /= 100;
  y /= 100;

  // GUI
  draw.clearRect(0, 0, 500, 500);
  draw.lineWidth = 1;
  draw.strokeStyle = '#000000';
  for (let i = 1; i < Math.floor(500/s); i++) {
    draw.moveTo(i*s, 0);
    draw.lineTo(i*s, 500);
    draw.moveTo(0, i*s);
    draw.lineTo(500, i*s);
  }
  draw.stroke();
  draw.fillStyle = '#ff0000';
  draw.fillRect(ocx*s-2, ocy*s-2, 4, 4);
  draw.fillStyle = '#00ff00';
  draw.fillRect(ncx*s-2, ncy*s-2, 4, 4);

  draw.lineWidth = 5;
  draw.strokeStyle = '#ff0000';
  draw.strokeRect((ocx-w/2)*s, (ocy-h/2)*s, w*s, h*s); 
  draw.strokeStyle = '#00ff00';
  draw.strokeRect((ncx-w/2)*s, (ncy-h/2)*s, w*s, h*s);

  draw.lineWidth = 3;
  draw.strokeStyle = '#00ffff';
  draw.strokeRect(nvrx*s, nvry*s, nvrw*s, nvrh*s);
  draw.strokeRect(nhrx*s, nhry*s, nhrw*s, nhrh*s);
  draw.strokeStyle = '#ffff00';
  draw.strokeRect(ohrx*s, ohry*s, ohrw*s, ohrh*s);
  draw.strokeRect(ovrx*s, ovry*s, ovrw*s, ovrh*s);
})(t, x, y);

document.addEventListener('keydown', e => {
  e.preventDefault();
  if (e.keyCode == 39) x++;
  if (e.keyCode == 37) x--;
  if (e.keyCode == 38) y--;
  if (e.keyCode == 40) y++;
  if (e.keyCode == 68) t.x++;
  if (e.keyCode == 65) t.x--;
  if (e.keyCode == 87) t.y--;
  if (e.keyCode == 83) t.y++;
  a(t, x, y);
});
</script>

The math works :D WASD to move old viewport, Arrow keys to move new viewport.

cs6413110 commented 6 months ago

Implementing as a for loop

cs6413110 commented 6 months ago

Converted to for loop :) Now time to optimize/simplify

<canvas id='canvas' width='500', height='500'></canvas>
<script>
const canvas = document.getElementById('canvas'), draw = canvas.getContext('2d');
canvas.style = 'border: 1px solid black';
let t = {x: 0, y: 0}, x = 0, y = 0;
(a = (t, x, y) => {
  const s = 20;
  t.x *= 100;
  t.y *= 100;
  x *= 100;
  y *= 100;

  const w = 7, h = 5; // must be odd ig?

  let ocx = Math.floor(t.x/100)+.5, ocy = Math.floor(t.y/100)+.5, ncx = Math.floor(x/100)+.5, ncy = Math.floor(y/100)+.5;
  let xd = ocx-ncx, yd = ocy-ncy, yda = yd < 0 ? -1 : 1, xda = xd < 0 ? -1 : 1;
  // New Loaded Rect Coords
  let nhrx = ncx-w/2*xda, nhry = ncy-h/2*yda, nhrw = w*xda, nhrh = Math.min(h, Math.abs(yd))*yda;
  let nvrx = nhrx, nvry = nhry+nhrh, nvrw = Math.min(w, Math.abs(xd))*xda, nvrh = (h-Math.min(h, Math.abs(yd)))*yda;
  let ohrx = ocx+w/2*xda, ohry = ocy+h/2*yda, ohrw = -nhrw, ohrh = -nhrh;
  let ovrx = ohrx, ovry = ohry+ohrh, ovrw = -nvrw, ovrh = -nvrh;

  t.x /= 100;
  t.y /= 100;
  x /= 100;
  y /= 100;

  // GUI
  draw.clearRect(0, 0, 500, 500);

  draw.fillStyle = '#0000ff';
  let nys = yda > 0 ? nhry : nhry-1;
  let nxs = xda > 0 ? nhrx : nhrx-1;
  for (let yl = false, y = nys; y != nys+h*yda; y += yda) {
    if (y === nys+nhrh) yl = true;
    for (let x = nxs; x != nxs+(yl ? nvrw : w*xda); x += xda) { 
      draw.fillRect(x*s+1, y*s+1, s-2, s-2);
    }
  }
  draw.fillStyle = '#ff0000';
  let oys = yda > 0 ? ohry-1 : ohry;
  let oxs = xda > 0 ? ohrx-1 : ohrx;
  let iterations = 0;
  for (let yl = false, y = oys; y != oys-h*yda; y -= yda) {
    if (y === oys+ohrh) yl = true;
    for (let x = oxs; x != oxs-(yl ? Math.abs(ovrw) : w)*xda; x -= xda) {
      iterations++;
      if (iterations > 300) {
        alert(x+' '+(-xda)+' '+(oxs-(yl ? ovrw : w)*xda));
        break;
      }
      draw.fillRect(x*s+1, y*s+1, s-2, s-2);
    }
  }

  draw.lineWidth = 1;
  draw.strokeStyle = '#000000';
  for (let i = 1; i < Math.floor(500/s); i++) {
    draw.moveTo(i*s, 0);
    draw.lineTo(i*s, 500);
    draw.moveTo(0, i*s);
    draw.lineTo(500, i*s);
  }
  draw.stroke();
  draw.fillStyle = '#ff0000';
  draw.fillRect(ocx*s-2, ocy*s-2, 4, 4);
  draw.fillStyle = '#00ff00';
  draw.fillRect(ncx*s-2, ncy*s-2, 4, 4);

  draw.lineWidth = 5;
  draw.strokeStyle = '#ff0000';
  draw.strokeRect((ocx-w/2)*s, (ocy-h/2)*s, w*s, h*s); 
  draw.strokeStyle = '#00ff00';
  draw.strokeRect((ncx-w/2)*s, (ncy-h/2)*s, w*s, h*s);

  draw.lineWidth = 3;
  draw.strokeStyle = '#00ffff';
  draw.strokeRect(nvrx*s, nvry*s, nvrw*s, nvrh*s);
  draw.strokeRect(nhrx*s, nhry*s, nhrw*s, nhrh*s);
  draw.strokeStyle = '#ffff00';
  draw.strokeRect(ohrx*s, ohry*s, ohrw*s, ohrh*s);
  draw.strokeRect(ovrx*s, ovry*s, ovrw*s, ovrh*s);
})(t, x, y);

document.addEventListener('keydown', e => {
  e.preventDefault();
  if (e.keyCode == 39) x++;
  if (e.keyCode == 37) x--;
  if (e.keyCode == 38) y--;
  if (e.keyCode == 40) y++;
  if (e.keyCode == 68) t.x++;
  if (e.keyCode == 65) t.x--;
  if (e.keyCode == 87) t.y--;
  if (e.keyCode == 83) t.y++;
  a(t, x, y);
});
</script>
cs6413110 commented 6 months ago

Ok, done with testing. Final code ready for actual beta cases.

chunkload(t, x, y) {
    const w = 21, h = 15;
    const ocx = Math.floor(t.x/100)+.5, ocy = Math.floor(t.y/100)+.5, ncx = Math.floor(x/100)+.5, ncy = Math.floor(y/100)+.5;
    let xd = ocx-ncx, yd = ocy-ncy, yda = yd < 0 ? -1 : 1, xda = xd < 0 ? -1 : 1;
    for (let yl = false, nys = (yda > 0 ? 0 : -1)+ncy-h/2*yda, y = nys; y != nys+h*yda; y += yda) {
      if (y === nys+Math.min(h, Math.abs(yd))*yda) yl = true;
      for (let nxs = (xda > 0 ? 0 : -1)+ncx-w/2*xda, x = nxs; x != nxs+(yl ? Math.min(w, Math.abs(xd))*xda : w*xda); x += xda) {
        draw.fillRect(x*s+1, y*s+1, s-2, s-2);
      }
    }
    for (let yl = false, oys = (yda > 0 ? -1 : 0)+ocy+h/2*yda, y = oys; y != oys-h*yda; y -= yda) {
      if (y === oys-Math.min(h, Math.abs(yd))*yda) yl = true;
      for (let oxs = (xda > 0 ? -1 : 0)+ocx+w/2*xda, x = oxs; x != oxs-(yl ? Math.abs(Math.min(w, Math.abs(xd))*xda) : w)*xda; x -= xda) {
        draw.fillRect(x*s+1, y*s+1, s-2, s-2);
      }
    }
  }
cs6413110 commented 6 months ago

Did some brainstorming about how to actually link up the chunk loading, event sending, etc.

Also did you know the game technically waits up to 15ms to send updates to clients instead of instantly. I'm going to change this so that it does it instantly, but if updates are too fast it will rate limit to 60/s.

cs6413110 commented 6 months ago

I don't like this so upgrading....

A.createTemplate('message', class {b = []; pt = []; ai = []; s = []; d = []; event = 'hostupdate'; delete = {b: [], pt: [], ai: [], s: [], d: []}}, m => {
  for (const property of ['b', 'pt', 'ai', 's', 'd']) {
    m[property].length = 0;
    m.delete[property].length = 0;
  }

Thinking somthing more like:

{
  event: 'hostupdate',
  b: [],
  pt: [],
  ai: [],
  s: [],
  d: [],
  delete: [],
}

Because ids don't need entity data

CelestialKnight7 commented 6 months ago

all 3 of my remaining brain cells are too small to understand this

cs6413110 commented 6 months ago

Beta Server for testing is on port 443 instead of port 8080

cs6413110 commented 6 months ago

Good?

Replace the :8080 at the end of the multiplayer join link to :443 to join.

cs6413110 commented 6 months ago

Recoding on a seperate branch so its more organized. Recoding AI and turrets to use memory efficent + update based code. Working on figuring out optimizing entity creation/cached updating. Maybe an update multiple thing.

cs6413110 commented 6 months ago

Nope. I actually haven't implemented anything yet. All I've been doing is preparation. So the recent lag spikes aren't caused by this at all.

cs6413110 commented 6 months ago

I have coded stuff, but its a complete upheaval of entire multiplayer system so its slow

cs6413110 commented 6 months ago

Lag Update self-reminder: Chunkload only triggers on client position updating as of now. Trigger it also on grapple pos change and initial chunkload of Engine.add()

cs6413110 commented 6 months ago

Making progress on debugging chunk loading bug. I know why and am just working on solving infinite loop bug.

cs6413110 commented 6 months ago

demo i used for debugging for future reference:

<canvas id='canvas' width='300' height='300' style='border: 1px solid green'></canvas>
<canvas id='canvas2' width='300' height='300' style='border: 1px solid red'></canvas>
WASD to move red rect, Arrows for green rect. Green bordered is the correct algorithm, red is incorrect. The current working solution is redundant. For the most part they are the same but certain chunks won't load when both reds and partially out of the rendering border limit.
<div id='output'></div>
<button onclick='test()'>adf</button> 
<script>
window.onerror = alert;
const canvas = document.getElementById('canvas'), draw = canvas.getContext('2d');
const canvas2 = document.getElementById('canvas2'), draw2 = canvas2.getContext('2d');

draw.setTransform(.05, 0, 0, .05, 75, 75);
draw2.setTransform(.05, 0, 0, .05, 75, 75);
let ox = 1500, oy = 1500, nx = 1500, ny = 1500;
document.addEventListener('keydown', e => {
  e.preventDefault();
  let k = e.keyCode;
  if (k == 87) oy -= 100;
  if (k == 83) oy += 100;
  if (k == 65) ox -= 100;
  if (k == 68) ox += 100;
  if (k == 38) ny -= 100;
  if (k == 40) ny += 100;
  if (k == 37) nx -= 100;
  if (k == 39) nx += 100;
  draw.clearRect(-1500, -1500, 6000, 6000);
  draw2.clearRect(-1500, -1500, 6000, 6000);
  chunkloadERRORED({x: ox, y: oy}, nx, ny);
  chunkloadFORCED({x: ox, y: oy}, nx, ny);
});

function test() {
  for (let x1 = 0; x1 < 3000; x1 += 100) {
    for (let y1 = 0; y1 < 3000; y1 += 100) {
      for (let x2 = 0; x2 < 3000; x2 += 100) {
        for (let y2 = 0; y2 < 3000; y2 += 100) {
          draw.clearRect(-1500, -1500, 6000, 6000);
          draw2.clearRect(-1500, -1500, 6000, 6000);
          chunkloadERRORED({x: x1, y: y1}, x2, y2);
          chunkloadFORCED({x: x1, y: y1}, x2, y2);
          if (canvas.toDataURL() !== canvas2.toDataURL()) document.getElementById('output').innerHTML += 'fail';
        }
      }
    }
  }
}

const m = o => Math.max(0, Math.min(29, o));
const m2 = o => Math.max(-1, Math.min(30, o));

function chunkloadERRORED(t, x, y) {
  const w = 21, h = 15;
  const ocx = Math.floor(t.x/100)+.5, ocy = Math.floor(t.y/100)+.5, ncx = Math.floor(x/100)+.5, ncy = Math.floor(y/100)+.5;
  const xd = ocx-ncx, yd = ocy-ncy, yda = yd < 0 ? -1 : 1, xda = xd < 0 ? -1 : 1, yl = Math.min(h, Math.abs(yd))*yda;
  for (let nys = (yda > 0 ? 0 : -1)+ncy-h/2*yda, y = m(nys), l = false; (yda > 0 ? (y < m2(nys+h*yda)) : (y > m2(nys+h*yda))); y += yda) {
    if (yda < 0 ? y <= nys+yl : y >= nys+yl) l = true;
    for (let nxs = (xda > 0 ? 0 : -1)+ncx-w/2*xda, x = m(nxs); (xda > 0 ? (x < m2(nxs+(l ? Math.min(w, Math.abs(xd)) : w)*xda)) : (x > m2(nxs+(l ? Math.min(w, Math.abs(xd)) : w)*xda))); x += xda) {
      draw2.fillStyle='#00ff00';
      draw2.fillRect(x*100, y*100, 100, 100); 
    }
  }
  for (let oys = (yda > 0 ? -1 : 0)+ocy+h/2*yda, y = m(oys), l = false; (yda < 0 ? (y < m2(oys-h*yda)) : (y > m2(oys-h*yda))); y -= yda) {
    if (yda > 0 ? y <= oys-yl : y >= oys-yl) l = true;
    for (let oxs = (xda > 0 ? -1 : 0)+ocx+w/2*xda, x = m(oxs); (xda < 0 ? (x < m2(oxs-(l ? Math.min(w, Math.abs(xd)) : w)*xda)) : (x > m2(oxs-(l ? Math.min(w, Math.abs(xd)) : w)*xda))); x -= xda) {
      draw2.fillStyle = '#ff0000';
      draw2.fillRect(x*100, y*100, 100, 100);
    }
  }
}
function chunkloadFORCED(t, x, y) {
  const w = 21, h = 15;
  const ocx = Math.floor(t.x/100)+.5, ocy = Math.floor(t.y/100)+.5, ncx = Math.floor(x/100)+.5, ncy = Math.floor(y/100)+.5;
  const xd = ocx-ncx, yd = ocy-ncy, yda = yd < 0 ? -1 : 1, xda = xd < 0 ? -1 : 1, yl = Math.min(h, Math.abs(yd))*yda;
  for (let nys = (yda > 0 ? 0 : -1)+ncy-h/2*yda, y = nys, l = false; y != nys+h*yda; y += yda) {
    if (y === nys+yl) l = true;
    for (let nxs = (xda > 0 ? 0 : -1)+ncx-w/2*xda, x = nxs; x != nxs+(l ? Math.min(w, Math.abs(xd)) : w)*xda; x += xda) {
      if (x >= 0 && x <= 29 && y >= 0 && y <= 29) {
        draw.fillStyle='#00ff00';
        draw.fillRect(x*100, y*100, 100, 100);
      }
    }
  }
  for (let oys = (yda > 0 ? -1 : 0)+ocy+h/2*yda, y = oys, l = false; y != oys-h*yda; y -= yda) {
    if (y === oys-yl) l = true;
    for (let oxs = (xda > 0 ? -1 : 0)+ocx+w/2*xda, x = oxs; x != oxs-(l ? Math.min(w, Math.abs(xd)) : w)*xda; x -= xda) {
      if (x >= 0 && x <= 29 && y >= 0 && y <= 29) {
        draw.fillStyle = '#ff0000';
        draw.fillRect(x*100, y*100, 100, 100);
      }
    }
  }
}
</script>
cs6413110 commented 6 months ago

Notes for future self:

cs6413110 commented 6 months ago

Progress Update:

CelestialKnight7 commented 6 months ago

when lag ded

CelestialKnight7 commented 6 months ago

or in a week

cs6413110 commented 6 months ago

Ok so it partially works

cs6413110 commented 6 months ago

Didn't account for entities that can move out of your render distance so I have to add a thing to track that.

cs6413110 commented 6 months ago

Also chunkloaded exploded for no reason

cs6413110 commented 6 months ago

Imma test with low render distance to see what's wrong

cs6413110 commented 6 months ago

Just debugging now