Skip to content

Commit

Permalink
v0.3.8 netcode bugfix (#109)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexanderclarktx authored Apr 2, 2024
1 parent 4f53ae0 commit cb74fd7
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 109 deletions.
18 changes: 11 additions & 7 deletions core/src/contrib/systems/net/delay/DelayClientSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export const DelayClientSystem: SystemBuilder<"DelayClientSystem"> = ({
// const wsClient = new WebSocket(servers.staging);
// const wsClient = new WebSocket(servers.dev);

let lastLatency = 0;
let serverMessageBuffer: DelayTickData[] = [];
let lastLatency = 0;

setInterval(() => {
(lastMessageTick && ((world.tick - lastMessageTick) < 500)) ? world.isConnected = true : world.isConnected = false;
Expand All @@ -35,8 +35,10 @@ export const DelayClientSystem: SystemBuilder<"DelayClientSystem"> = ({

// record latency
lastLatency = Date.now() - message.timestamp;

if (message.latency) world.ms = (lastLatency + message.latency) / 2;

// set flag to green
world.tickFlag = "green";
}

const onTick = (_: Entity[]) => {
Expand Down Expand Up @@ -64,10 +66,7 @@ export const DelayClientSystem: SystemBuilder<"DelayClientSystem"> = ({

const handleLatestMessage = () => {

if (serverMessageBuffer.length === 0) {
world.skipNextTick = true;
return;
}
if (serverMessageBuffer.length === 0) return;

if (serverMessageBuffer.length > 10) {
serverMessageBuffer = [];
Expand Down Expand Up @@ -118,7 +117,7 @@ export const DelayClientSystem: SystemBuilder<"DelayClientSystem"> = ({
rollback = true;
}

if ((message.tick - 1) !== world.tick) mustRollback("old tick");
if ((message.tick - 1) !== world.tick) mustRollback(`old tick world=${world.tick} msg=${message.tick}`);

const sre: Record<string, SerializedEntity> = {}
for (const entityId in world.entities) {
Expand Down Expand Up @@ -176,6 +175,11 @@ export const DelayClientSystem: SystemBuilder<"DelayClientSystem"> = ({
world.chatHistory.set(world.tick, playerId, messages);
});
}

// if message buffer is empty, set flag to red
if (serverMessageBuffer.length === 0) {
world.tickFlag = "red";
}
}

return {
Expand Down
143 changes: 72 additions & 71 deletions core/src/contrib/systems/net/rollback/RollbackClientSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const RollbackClientSystem: SystemBuilder<"RollbackClientSystem"> = ({
// const wsClient = new WebSocket(servers.staging);
// const wsClient = new WebSocket(servers.dev);

let ticksAhead = 0;
let lastLatency = 0;

setInterval(() => {
Expand All @@ -36,11 +37,11 @@ export const RollbackClientSystem: SystemBuilder<"RollbackClientSystem"> = ({
if (entityId.startsWith("skelly") && entityId !== `skelly-${clientPlayerId}`) {
const actionsCopy = [...actions];

if (!message.actions[tick + world.framesAhead + 1]) {
message.actions[tick + world.framesAhead + 1] = {};
if (!message.actions[tick + ticksAhead + 1]) {
message.actions[tick + ticksAhead + 1] = {};
}

message.actions[tick + world.framesAhead + 1][entityId] = actionsCopy;
message.actions[tick + ticksAhead + 1][entityId] = actionsCopy;
delete message.actions[tick][entityId];
}
}
Expand All @@ -61,6 +62,74 @@ export const RollbackClientSystem: SystemBuilder<"RollbackClientSystem"> = ({
sendMessage(world);
}

const rollback = (world: World, td: RollbackTickData) => {
const now = Date.now();

// determine how many ticks to increment
ticksAhead = Math.ceil((((world.ms) / world.tickrate) * 2) + 1);
if (Math.abs(ticksAhead - (world.tick - td.tick)) <= 1) {
ticksAhead = world.tick - td.tick;
}

console.log(`ms:${world.ms} msgFrame:${td.tick} clientFrame:${world.tick} targetFrame:${td.tick + ticksAhead}`);

// set tick
world.tick = td.tick - 1;

// remove old local entities
Object.keys(world.entities).forEach((entityId) => {
if (world.entities[entityId].components.networked) {

if (!td.serializedEntities[entityId]) {
// delete if not present in rollback frame
console.log("DELETE ENTITY", entityId, td.serializedEntities);
world.removeEntity(entityId);
}
}
});

// add new entities if not present locally
Object.keys(td.serializedEntities).forEach((entityId) => {
if (!world.entities[entityId]) {
if (entityId.startsWith("zombie")) {
world.addEntity(Zombie({ id: entityId }));
} else if (entityId.startsWith("ball")) {
world.addEntity(Ball({ id: entityId }));
} else if (entityId.startsWith("noob")) {
world.addEntity(Noob({ id: entityId }))
} else if (entityId.startsWith("skelly")) {
world.addEntity(Skelly(entityId));
} else {
console.error("UNKNOWN ENTITY ON SERVER", entityId);
}
}
});

// deserialize everything
Object.keys(td.serializedEntities).forEach((entityId) => {
if (world.entities[entityId]) {
world.entities[entityId].deserialize(td.serializedEntities[entityId]);
}
});

// update local action buffer
Object.keys(td.actions).map(Number).forEach((tick) => {
Object.keys(td.actions[tick]).forEach((entityId) => {
// skip future actions for controlled entities
if (tick > td.tick && world.entities[entityId]?.components.controlled?.data.entityId === world.clientPlayerId) return;

world.actionBuffer.set(tick, entityId, td.actions[tick][entityId]);
});
});

Object.values(world.systems).forEach((system) => system.onRollback ? system.onRollback() : null);

// run system updates
for (let i = 0; i < ticksAhead + 1; i++) world.onTick({ isRollback: true });

console.log(`rollback took ${Date.now() - now}ms`);
}

const sendMessage = (world: World) => {

// prepare actions from recent frames for the client entity
Expand Down Expand Up @@ -198,71 +267,3 @@ export const RollbackClientSystem: SystemBuilder<"RollbackClientSystem"> = ({
}
}
});

const rollback = (world: World, td: RollbackTickData) => {
const now = Date.now();

// determine how many ticks to increment
world.framesAhead = Math.ceil((((world.ms) / world.tickrate) * 2) + 1);
if (Math.abs(world.framesAhead - (world.tick - td.tick)) <= 1) {
world.framesAhead = world.tick - td.tick;
}

console.log(`ms:${world.ms} msgFrame:${td.tick} clientFrame:${world.tick} targetFrame:${td.tick + world.framesAhead}`);

// set tick
world.tick = td.tick - 1;

// remove old local entities
Object.keys(world.entities).forEach((entityId) => {
if (world.entities[entityId].components.networked) {

if (!td.serializedEntities[entityId]) {
// delete if not present in rollback frame
console.log("DELETE ENTITY", entityId, td.serializedEntities);
world.removeEntity(entityId);
}
}
});

// add new entities if not present locally
Object.keys(td.serializedEntities).forEach((entityId) => {
if (!world.entities[entityId]) {
if (entityId.startsWith("zombie")) {
world.addEntity(Zombie({ id: entityId }));
} else if (entityId.startsWith("ball")) {
world.addEntity(Ball({ id: entityId }));
} else if (entityId.startsWith("noob")) {
world.addEntity(Noob({ id: entityId }))
} else if (entityId.startsWith("skelly")) {
world.addEntity(Skelly(entityId));
} else {
console.error("UNKNOWN ENTITY ON SERVER", entityId);
}
}
});

// deserialize everything
Object.keys(td.serializedEntities).forEach((entityId) => {
if (world.entities[entityId]) {
world.entities[entityId].deserialize(td.serializedEntities[entityId]);
}
});

// update local action buffer
Object.keys(td.actions).map(Number).forEach((tick) => {
Object.keys(td.actions[tick]).forEach((entityId) => {
// skip future actions for controlled entities
if (tick > td.tick && world.entities[entityId]?.components.controlled?.data.entityId === world.clientPlayerId) return;

world.actionBuffer.set(tick, entityId, td.actions[tick][entityId]);
});
});

Object.values(world.systems).forEach((system) => system.onRollback ? system.onRollback() : null);

// run system updates
for (let i = 0; i < world.framesAhead + 1; i++) world.onTick({ isRollback: true });

console.log(`rollback took ${Date.now() - now}ms`);
}
57 changes: 28 additions & 29 deletions core/src/runtime/World.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {
Command, Entity, Game, GameBuilder,
InvokedAction, Renderer, SerializedEntity,
StateBuffer, System,
SystemBuilder, SystemEntity
StateBuffer, System, SystemBuilder, SystemEntity
} from "@piggo-gg/core";

export type WorldProps = {
Expand All @@ -29,14 +28,13 @@ export type World = {
isConnected: boolean
lastTick: DOMHighResTimeStamp
ms: number
framesAhead: number
renderer: Renderer | undefined
renderMode: "cartesian" | "isometric"
runtimeMode: "client" | "server"
systems: Record<string, System>
skipNextTick: boolean
tickFaster: boolean
tick: number
tickFaster: boolean
tickFlag: "green" | "red"
tickrate: number
addEntities: (entities: Entity[]) => void
addEntity: (entity: Entity) => string
Expand Down Expand Up @@ -67,23 +65,22 @@ export const World = ({ clientPlayerId, commands, games, renderer, renderMode, r
actionBuffer: StateBuffer(),
chatHistory: StateBuffer(),
clientPlayerId,
currentGame: { id: "", entities: [], systems: [] },
commands: {},
currentGame: { id: "", entities: [], systems: [] },
debug: false,
entities: {},
entitiesAtTick: {},
games: {},
isConnected: false,
lastTick: 0,
ms: 0,
framesAhead: 0,
renderer,
renderMode,
runtimeMode,
systems: {},
skipNextTick: false,
tickFaster: false,
tick: 0,
tickFaster: false,
tickFlag: "green",
tickrate: 25,
addEntity: (entity: Entity) => {
const oldEntity = world.entities[entity.id];
Expand Down Expand Up @@ -144,6 +141,12 @@ export const World = ({ clientPlayerId, commands, games, renderer, renderMode, r
return;
}

if (world.tickFlag === "red") {
console.log("defering tick");
scheduleOnTick();
return;
}

// update lastTick
if (!isRollback && !world.tickFaster) {
if ((now - world.tickrate - world.tickrate) > world.lastTick) {
Expand All @@ -155,28 +158,24 @@ export const World = ({ clientPlayerId, commands, games, renderer, renderMode, r
}
}

if (world.skipNextTick) {
world.skipNextTick = false;
} else {
// increment tick
world.tick += 1;

// store serialized entities before systems run
const serializedEntities: Record<string, SerializedEntity> = {}
for (const entityId in world.entities) {
if (world.entities[entityId].components.networked) {
serializedEntities[entityId] = world.entities[entityId].serialize();
}
// increment tick
world.tick += 1;

// store serialized entities before systems run
const serializedEntities: Record<string, SerializedEntity> = {}
for (const entityId in world.entities) {
if (world.entities[entityId].components.networked) {
serializedEntities[entityId] = world.entities[entityId].serialize();
}
world.entitiesAtTick[world.tick] = serializedEntities;

// run system updates
Object.values(world.systems).forEach((system) => {
if (!isRollback || (isRollback && !system.skipOnRollback)) {
system.query ? system.onTick(filterEntities(system.query, Object.values(world.entities)), isRollback) : system.onTick([], isRollback);
}
});
}
world.entitiesAtTick[world.tick] = serializedEntities;

// run system updates
Object.values(world.systems).forEach((system) => {
if (!isRollback || (isRollback && !system.skipOnRollback)) {
system.query ? system.onTick(filterEntities(system.query, Object.values(world.entities)), isRollback) : system.onTick([], isRollback);
}
});

// schedule onTick
if (!isRollback) scheduleOnTick();
Expand Down
2 changes: 1 addition & 1 deletion docs/piggo-gg-min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion web/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const Header = ({ world, netState, setNetState }: HeaderProps) => {
</h1>
<div style={{ position: 'absolute', right: 0, bottom: 0 }}>
<span style={{ fontFamily: "sans-serif", fontSize: 14, marginRight: 5, verticalAlign: "-70%" }}>
v<b>0.3.7</b>
v<b>0.3.8</b>
</span>
<a style={{ margin: 0, color: "inherit", textDecoration: "none" }} target="_blank" href="https://discord.gg/VfFG9XqDpJ">
<FaDiscord size={20} style={{ color: "white", verticalAlign: "-80%" }}></FaDiscord>
Expand Down

0 comments on commit cb74fd7

Please # to comment.