refactor: predictor next to client, smoothing

This commit is contained in:
cha0s 2024-09-08 17:19:59 -05:00
parent 336ec04f66
commit 04a0230248
4 changed files with 38 additions and 28 deletions

View File

@ -17,7 +17,13 @@ export default class LocalClient extends Client {
{type: 'module'}, {type: 'module'},
); );
this.interpolator.addEventListener('message', (event) => { this.interpolator.addEventListener('message', (event) => {
this.accept(event.data); const packet = event.data;
if (CLIENT_PREDICTION) {
this.predictor.postMessage([1, packet]);
}
else {
this.accept(packet);
}
}); });
} }
if (CLIENT_PREDICTION) { if (CLIENT_PREDICTION) {
@ -35,12 +41,7 @@ export default class LocalClient extends Client {
break; break;
} }
case 1: { case 1: {
if (CLIENT_INTERPOLATION) { this.accept(packet);
this.interpolator.postMessage(packet);
}
else {
this.accept(packet);
}
break; break;
} }
} }
@ -54,12 +55,12 @@ export default class LocalClient extends Client {
} }
this.throughput.$$down += event.data.byteLength; this.throughput.$$down += event.data.byteLength;
const packet = decode(event.data); const packet = decode(event.data);
if (CLIENT_PREDICTION) { if (CLIENT_INTERPOLATION) {
this.predictor.postMessage([1, packet]);
}
else if (CLIENT_INTERPOLATION) {
this.interpolator.postMessage(packet); this.interpolator.postMessage(packet);
} }
else if (CLIENT_PREDICTION) {
this.predictor.postMessage([1, packet]);
}
else { else {
this.accept(packet); this.accept(packet);
} }

View File

@ -27,17 +27,8 @@ const Flow = {
DOWN: 1, DOWN: 1,
}; };
const Stage = {
UNACK: 0,
ACK: 1,
FINISHING: 2,
FINISHED: 3,
};
const actions = new Map(); const actions = new Map();
let ecs = new PredictionEcs({Components, Systems}); let ecs = new PredictionEcs({Components, Systems});
let mainEntityId = 0; let mainEntityId = 0;
function applyClientActions(elapsed) { function applyClientActions(elapsed) {
@ -46,7 +37,7 @@ function applyClientActions(elapsed) {
const {Controlled} = main; const {Controlled} = main;
const finished = []; const finished = [];
for (const [id, action] of actions) { for (const [id, action] of actions) {
if (Stage.UNACK === action.stage) { if (0 === action.finished && !action.ack) {
if (!Controlled.locked) { if (!Controlled.locked) {
switch (action.action.type) { switch (action.action.type) {
case 'moveUp': case 'moveUp':
@ -60,7 +51,7 @@ function applyClientActions(elapsed) {
} }
action.steps.push(elapsed); action.steps.push(elapsed);
} }
if (Stage.FINISHING === action.stage) { if (1 === action.finished) {
if (!Controlled.locked) { if (!Controlled.locked) {
switch (action.action.type) { switch (action.action.type) {
case 'moveUp': case 'moveUp':
@ -72,9 +63,9 @@ function applyClientActions(elapsed) {
} }
} }
} }
action.stage = Stage.FINISHED; action.finished = 2;
} }
if (Stage.FINISHED === action.stage) { if (action.ack && 2 === action.finished) {
action.steps.shift(); action.steps.shift();
if (0 === action.steps.length) { if (0 === action.steps.length) {
finished.push(id); finished.push(id);
@ -113,13 +104,13 @@ onmessage = async (event) => {
if (0 === packet.payload.value) { if (0 === packet.payload.value) {
const ack = pending.get(packet.payload.type); const ack = pending.get(packet.payload.type);
const action = actions.get(ack); const action = actions.get(ack);
action.stage = Stage.FINISHING; action.finished = 1;
pending.delete(packet.payload.type); pending.delete(packet.payload.type);
} }
else { else {
const tx = { const tx = {
action: packet.payload, action: packet.payload,
stage: Stage.UNACK, ack: false,finished: 0,
steps: [], steps: [],
}; };
packet.payload.ack = Math.random(); packet.payload.ack = Math.random();
@ -139,11 +130,12 @@ onmessage = async (event) => {
switch (packet.type) { switch (packet.type) {
case 'ActionAck': { case 'ActionAck': {
const action = actions.get(packet.payload.ack); const action = actions.get(packet.payload.ack);
action.stage = Stage.ACK; action.ack = true;
return; return;
} }
case 'EcsChange': { case 'EcsChange': {
ecs = new PredictionEcs({Components, Systems}); ecs = new PredictionEcs({Components, Systems});
mainEntityId = 0;
break; break;
} }
case 'Tick': { case 'Tick': {

View File

@ -27,6 +27,7 @@ const textEncoder = new TextEncoder();
export default class Engine { export default class Engine {
ackingActions = new Map();
connectedPlayers = new Map(); connectedPlayers = new Map();
ecses = {}; ecses = {};
frame = 0; frame = 0;
@ -250,6 +251,15 @@ export default class Engine {
} }
} }
if (payload.ack) { if (payload.ack) {
if (!this.ackingActions.has(connection)) {
this.ackingActions.set(connection, []);
}
this.ackingActions.get(connection).push({
type: 'ActionAck',
payload: {
ack: payload.ack,
},
})
this.server.send( this.server.send(
connection, connection,
{ {
@ -303,6 +313,7 @@ export default class Engine {
if (!connectedPlayer) { if (!connectedPlayer) {
return; return;
} }
this.ackingActions.delete(connection);
this.connectedPlayers.delete(connection); this.connectedPlayers.delete(connection);
this.incomingActions.delete(connection); this.incomingActions.delete(connection);
const {entity, heartbeat, id} = connectedPlayer; const {entity, heartbeat, id} = connectedPlayer;
@ -445,6 +456,12 @@ export default class Engine {
update(elapsed) { update(elapsed) {
for (const [connection, {entity}] of this.connectedPlayers) { for (const [connection, {entity}] of this.connectedPlayers) {
if (this.ackingActions.has(connection)) {
for (const ack of this.ackingActions.get(connection)) {
this.server.send(connection, ack);
}
this.ackingActions.delete(connection);
}
if (!entity) { if (!entity) {
continue; continue;
} }

View File

@ -17,4 +17,4 @@ export const SERVER_LATENCY = 0;
export const TPS = 60; export const TPS = 60;
export const UPS = 15; export const UPS = 30;