some optimization

This commit is contained in:
RandomityGuy 2024-02-02 01:08:10 +05:30
parent 99ae6ecf65
commit aa85fc2faa
6 changed files with 224 additions and 106 deletions

View file

@ -1062,7 +1062,7 @@ class Marble extends GameObject {
searchbox.addSpherePos(position.x + velocity.x * deltaT, position.y + velocity.y * deltaT, position.z + velocity.z * deltaT, _radius);
var foundObjs = this.collisionWorld.boundingSearch(searchbox);
foundObjs.push(this.collisionWorld.staticWorld);
// foundObjs.push(this.collisionWorld.staticWorld);
var finalT = deltaT;
var found = false;
@ -1151,7 +1151,7 @@ class Marble extends GameObject {
// var v = surface.points[surface.indices[i + 1]].transformed(obj.transform);
// var v2 = surface.points[surface.indices[i + 2]].transformed(obj.transform);
var triangleVerts = [v0, v, v2];
// var triangleVerts = [v0, v, v2];
var surfaceNormal = new Vector(verts.nx, verts.ny,
verts.nz); // surface.normals[surface.indices[i]].transformed3x3(obj.transform).normalized();
@ -1166,8 +1166,8 @@ class Marble extends GameObject {
}
testTriangles.push({
v: [v0, v, v2],
n: surfaceNormal,
v: [v0.clone(), v.clone(), v2.clone()],
n: surfaceNormal.clone(),
});
// Time until collision with the plane
@ -1190,7 +1190,9 @@ class Marble extends GameObject {
}
// We *might* be colliding with an edge
var lastVert = v2;
var triangleVerts = [v0.clone(), v.clone(), v2.clone()];
var lastVert = v2.clone();
var radSq = radius * radius;
for (iter in 0...3) {
@ -1211,7 +1213,7 @@ class Marble extends GameObject {
// If it's not quadratic or has no solution, ignore this edge.
if (a == 0.0 || discriminant < 0.0) {
lastVert = thisVert;
lastVert.load(thisVert);
continue;
}
@ -1231,7 +1233,7 @@ class Marble extends GameObject {
// If the collision doesn't happen on this time step, ignore this edge.
if (edgeCollisionTime2 <= 0.0001 || finalT <= edgeCollisionTime) {
lastVert = thisVert;
lastVert.load(thisVert);
continue;
}
@ -1245,7 +1247,7 @@ class Marble extends GameObject {
// If the collision happens outside the boundaries of the edge, ignore this edge.
if (-radius > distanceAlongEdge || edgeLen + radius < distanceAlongEdge) {
lastVert = thisVert;
lastVert.load(thisVert);
continue;
}
@ -1309,14 +1311,14 @@ class Marble extends GameObject {
// We still need to check the other corner ...
// Build one last quadratic equation to solve for the collision time
posVertDiff = position.sub(lastVert);
var posVertDiff = position.sub(lastVert);
b = 2 * posVertDiff.dot(relVel);
c = posVertDiff.lengthSq() - radSq;
discriminant = b * b - (4 * a * c);
// If it's not quadratic or has no solution, then skip this corner
if (a == 0.0 || discriminant < 0.0) {
lastVert = thisVert;
lastVert.load(thisVert);
continue;
}
@ -1335,7 +1337,7 @@ class Marble extends GameObject {
}
if (edgeCollisionTime2 <= 0.0001 || finalT <= edgeCollisionTime) {
lastVert = thisVert;
lastVert.load(thisVert);
continue;
}
@ -1343,7 +1345,7 @@ class Marble extends GameObject {
edgeCollisionTime = 0;
if (edgeCollisionTime < 0.000001) {
lastVert = thisVert;
lastVert.load(thisVert);
continue;
}
@ -1351,7 +1353,7 @@ class Marble extends GameObject {
currentFinalPos = position.add(relVel.multiply(finalT));
// Debug.drawSphere(currentFinalPos, radius);
lastVert = thisVert;
lastVert.load(thisVert);
found = true;
// iterationFound = true;
}
@ -1415,7 +1417,7 @@ class Marble extends GameObject {
// Nudge to the surface of the contact plane
Debug.drawTriangle(testTri.v[0], testTri.v[1], testTri.v[2]);
Debug.drawSphere(position, radius);
position = position.add(separatingDistance.multiply(radius - distToContactPlane - 0.005));
position.load(position.add(separatingDistance.multiply(radius - distToContactPlane - 0.005)));
resolved++;
}
}
@ -1446,7 +1448,7 @@ class Marble extends GameObject {
var dist = marblePosition.distance(position);
if (dist < radius + marble.radius + 0.001) {
var separatingDistance = position.sub(marblePosition).normalized();
position = position.add(separatingDistance.multiply(radius + marble.radius + 0.001 - dist));
position.load(position.add(separatingDistance.multiply(radius + marble.radius + 0.001 - dist)));
}
}
return position;

View file

@ -1,5 +1,7 @@
package src;
import net.MarblePredictionStore;
import net.MarblePredictionStore.MarblePrediction;
import net.MarbleUpdateQueue;
import haxe.Exception;
import net.NetPacket.MarbleUpdatePacket;
@ -103,6 +105,7 @@ import modes.NullMode;
import modes.GameMode.GameModeFactory;
import src.Renderer;
import src.Analytics;
import src.Debug;
class MarbleWorld extends Scheduler {
public var collisionWorld:CollisionWorld;
@ -204,6 +207,7 @@ class MarbleWorld extends Scheduler {
var maxPredictionTicks:Int = 16;
var clientMarbles:Map<GameConnection, Marble> = [];
var predictions:MarblePredictionStore;
public var lastMoves:MarbleUpdateQueue;
@ -246,6 +250,7 @@ class MarbleWorld extends Scheduler {
isRecording = false;
isWatching = false;
lastMoves = new MarbleUpdateQueue();
predictions = new MarblePredictionStore();
}
// Set the network RNG for hunt
@ -768,10 +773,10 @@ class MarbleWorld extends Scheduler {
var tmat = Matrix.T(interiorPosition.x, interiorPosition.y, interiorPosition.z);
mat.multiply(mat, tmat);
if (hasCollision)
this.collisionWorld.addStaticInterior(interior.collider, mat);
// if (hasCollision)
// this.collisionWorld.addStaticInterior(interior.collider, mat);
interior.isCollideable = false;
interior.isCollideable = hasCollision;
interior.setTransform(mat);
onFinish();
});
@ -890,7 +895,7 @@ class MarbleWorld extends Scheduler {
public function addInterior(obj:InteriorObject, onFinish:Void->Void) {
this.interiors.push(obj);
obj.init(cast this, () -> {
// this.collisionWorld.addEntity(obj.collider);
this.collisionWorld.addEntity(obj.collider);
if (obj.useInstancing)
this.instanceManager.addObject(obj);
else
@ -1033,9 +1038,12 @@ class MarbleWorld extends Scheduler {
}
public function applyReceivedMoves() {
var needsPrediction = 0;
if (!lastMoves.ourMoveApplied) {
var ourMove = lastMoves.myMarbleUpdate;
if (ourMove != null) {
var ourMoveStruct = Net.clientConnection.moveManager.acknowledgeMove(ourMove.move.id, timeState);
lastMoves.ourMoveApplied = true;
for (client => arr in lastMoves.otherMarbleUpdates) {
var lastMove = null;
while (arr.length > 0) {
@ -1047,27 +1055,61 @@ class MarbleWorld extends Scheduler {
}
}
if (lastMove != null) {
if (ourMove.serverTicks == lastMove.serverTicks)
// if (ourMove.serverTicks == lastMove.serverTicks) {
if (ourMoveStruct != null) {
var otherPred = predictions.retrieveState(clientMarbles[Net.clientIdMap[client]], ourMoveStruct.timeState.ticks);
if (otherPred != null) {
if (otherPred.getError(lastMove) > 0.001) {
// trace('Prediction error: ${otherPred.getError(lastMove)}');
trace('Desync for tick ${ourMoveStruct.timeState.ticks}');
clientMarbles[Net.clientIdMap[client]].unpackUpdate(lastMove);
needsPrediction |= 1 << client;
arr.insert(0, lastMove);
}
} else {
trace('Desync for tick ${ourMoveStruct.timeState.ticks}');
clientMarbles[Net.clientIdMap[client]].unpackUpdate(lastMove);
needsPrediction |= 1 << client;
arr.insert(0, lastMove);
}
} else {
trace('Desync in General');
clientMarbles[Net.clientIdMap[client]].unpackUpdate(lastMove);
arr.insert(0, lastMove);
needsPrediction |= 1 << client;
arr.insert(0, lastMove);
}
// }
}
}
marble.unpackUpdate(ourMove);
if (ourMoveStruct != null) {
var ourPred = predictions.retrieveState(marble, ourMoveStruct.timeState.ticks);
if (ourPred != null) {
if (ourPred.getError(ourMove) > 0.001) {
trace('Desync for tick ${ourMoveStruct.timeState.ticks}');
marble.unpackUpdate(ourMove);
needsPrediction |= 1 << Net.clientId;
}
} else {
trace('Desync for tick ${ourMoveStruct.timeState.ticks}');
marble.unpackUpdate(ourMove);
needsPrediction |= 1 << Net.clientId;
}
} else {
trace('Desync in General');
marble.unpackUpdate(ourMove);
needsPrediction |= 1 << Net.clientId;
}
}
}
return needsPrediction;
}
public function applyClientPrediction() {
public function applyClientPrediction(marbleNeedsPrediction:Int) {
// First acknowledge the marble's last move so we can get that over with
var ourLastMove = lastMoves.myMarbleUpdate;
if (ourLastMove == null)
return;
if (ourLastMove == null || marbleNeedsPrediction == 0)
return -1;
var ackLag = @:privateAccess Net.clientConnection.moveManager.queuedMoves.length;
var mvStored = null;
if (!lastMoves.ourMoveApplied)
mvStored = Net.clientConnection.moveManager.acknowledgeMove(ourLastMove.move.id, timeState);
else
return;
var ourLastMoveTime = ourLastMove.serverTicks;
@ -1076,97 +1118,87 @@ class MarbleWorld extends Scheduler {
var qm = ourQueuedMoves[0];
var advanceTimeState = qm != null ? qm.timeState.clone() : timeState.clone();
advanceTimeState.dt = 0.032;
advanceTimeState.ticks = ourLastMoveTime;
if (qm != null) {
var mvs = qm.powerupStates.copy();
for (pw in marble.level.powerUps) {
pw.lastPickUpTime = mvs.shift();
if (marbleNeedsPrediction & (1 << Net.clientId) > 0) {
if (qm != null) {
var mvs = qm.powerupStates.copy();
for (pw in marble.level.powerUps) {
pw.lastPickUpTime = mvs.shift();
}
@:privateAccess marble.helicopterEnableTime = qm.helicopterState;
@:privateAccess marble.megaMarbleEnableTime = qm.megaState;
marble.blastAmount = qm.blastAmt;
}
@:privateAccess marble.helicopterEnableTime = qm.helicopterState;
@:privateAccess marble.megaMarbleEnableTime = qm.megaState;
marble.blastAmount = qm.blastAmt;
}
ackLag = ourQueuedMoves.length;
// Tick the remaining moves (ours)
if (!lastMoves.ourMoveApplied) {
@:privateAccess this.marble.isNetUpdate = true;
var totalTicksToDo = ourQueuedMoves.length;
var endTick = ourLastMoveTime + totalTicksToDo;
var currentTick = ourLastMoveTime;
//- Std.int(ourLastMove.moveQueueSize - @:privateAccess Net.clientConnection.moveManager.ackRTT); // - Std.int((@:privateAccess Net.clientConnection.moveManager.ackRTT)) - offset;
@:privateAccess this.marble.isNetUpdate = true;
var totalTicksToDo = ourQueuedMoves.length;
var endTick = ourLastMoveTime + totalTicksToDo;
var currentTick = ourLastMoveTime;
//- Std.int(ourLastMove.moveQueueSize - @:privateAccess Net.clientConnection.moveManager.ackRTT); // - Std.int((@:privateAccess Net.clientConnection.moveManager.ackRTT)) - offset;
var marblesToTick = new Map();
var marblesToTick = new Map();
for (client => arr in lastMoves.otherMarbleUpdates) {
if (arr.length > 0) {
var m = arr[0];
if (m.serverTicks == ourLastMoveTime) {
var marbleToUpdate = clientMarbles[Net.clientIdMap[client]];
Debug.drawSphere(@:privateAccess marbleToUpdate.newPos, marbleToUpdate._radius);
m.calculationTicks = ourQueuedMoves.length; // ourQueuedMoves.length;
for (client => arr in lastMoves.otherMarbleUpdates) {
if (marbleNeedsPrediction & (1 << client) > 0 && arr.length > 0) {
var m = arr[0];
// if (m.serverTicks == ourLastMoveTime) {
var marbleToUpdate = clientMarbles[Net.clientIdMap[client]];
Debug.drawSphere(@:privateAccess marbleToUpdate.newPos, marbleToUpdate._radius);
if (marbleNeedsPrediction & (1 << Net.clientId) > 0)
m.calculationTicks = ourQueuedMoves.length; // ourQueuedMoves.length;
else
m.calculationTicks = ourQueuedMoves.length;
// - Std.int((@:privateAccess Net.clientConnection.moveManager.ackRTT - ourLastMove.moveQueueSize) / 2);
// - Std.int((@:privateAccess Net.clientConnection.moveManager.ackRTT - ourLastMove.moveQueueSize) / 2);
marblesToTick.set(client, m);
arr.shift();
}
}
marblesToTick.set(client, m);
arr.shift();
// }
}
}
Debug.drawSphere(@:privateAccess this.marble.newPos, this.marble._radius);
// var syncTickStates = new Map();
Debug.drawSphere(@:privateAccess this.marble.newPos, this.marble._radius);
// var syncTickStates = new Map();
for (move in ourQueuedMoves) {
var m = move.move;
// Debug.drawSphere(@:privateAccess this.marble.newPos, this.marble._radius);
for (move in ourQueuedMoves) {
var m = move.move;
// Debug.drawSphere(@:privateAccess this.marble.newPos, this.marble._radius);
if (marbleNeedsPrediction & (1 << Net.clientId) > 0) {
this.marble.heldPowerup = move.powerup;
@:privateAccess this.marble.moveMotionDir = move.motionDir;
@:privateAccess this.marble.advancePhysics(advanceTimeState, m, this.collisionWorld, this.pathedInteriors);
// var collidings = @:privateAccess this.marble.contactEntities.filter(x -> x is SphereCollisionEntity);
advanceTimeState.currentAttemptTime += 0.032;
for (client => m in marblesToTick) {
if (m.calculationTicks > 0) {
var marbleToUpdate = clientMarbles[Net.clientIdMap[client]];
// Debug.drawSphere(@:privateAccess marbleToUpdate.newPos, marbleToUpdate._radius);
var beforePos = @:privateAccess marbleToUpdate.newPos.clone();
var mv = m.move.move;
@:privateAccess marbleToUpdate.isNetUpdate = true;
@:privateAccess marbleToUpdate.moveMotionDir = m.move.motionDir;
@:privateAccess marbleToUpdate.advancePhysics(advanceTimeState, mv, this.collisionWorld, this.pathedInteriors);
@:privateAccess marbleToUpdate.isNetUpdate = false;
m.calculationTicks--;
var afterPos = @:privateAccess marbleToUpdate.newPos;
}
}
currentTick++;
this.predictions.storeState(this.marble, move.timeState.ticks);
}
// var collidings = @:privateAccess this.marble.contactEntities.filter(x -> x is SphereCollisionEntity);
// for (marble => state in syncTickStates) {
// marble.restoreState(state);
// }
for (client => m in marblesToTick) {
if (m.calculationTicks > 0) {
var marbleToUpdate = clientMarbles[Net.clientIdMap[client]];
// Debug.drawSphere(@:privateAccess marbleToUpdate.newPos, marbleToUpdate._radius);
// for (client => m in marblesToTick) {
// if (m.calculationTicks >= 0) {
// var marbleToUpdate = clientMarbles[Net.clientIdMap[client]];
// while (m.calculationTicks > 0) {
// var mv = m.move.move;
// @:privateAccess marbleToUpdate.isNetUpdate = true;
// @:privateAccess marbleToUpdate.moveMotionDir = m.move.motionDir;
// @:privateAccess marbleToUpdate.advancePhysics(advanceTimeState, mv, this.collisionWorld, this.pathedInteriors);
// @:privateAccess marbleToUpdate.isNetUpdate = false;
// m.calculationTicks--;
// }
// }
// }
lastMoves.ourMoveApplied = true;
@:privateAccess this.marble.isNetUpdate = false;
var mv = m.move.move;
@:privateAccess marbleToUpdate.isNetUpdate = true;
@:privateAccess marbleToUpdate.moveMotionDir = m.move.motionDir;
@:privateAccess marbleToUpdate.advancePhysics(advanceTimeState, mv, this.collisionWorld, this.pathedInteriors);
this.predictions.storeState(marbleToUpdate, move.timeState.ticks);
@:privateAccess marbleToUpdate.isNetUpdate = false;
m.calculationTicks--;
}
}
advanceTimeState.currentAttemptTime += 0.032;
advanceTimeState.ticks++;
currentTick++;
}
lastMoves.ourMoveApplied = true;
@:privateAccess this.marble.isNetUpdate = false;
return advanceTimeState.ticks;
return -1;
}
public function rollback(t:Float) {
@ -1355,10 +1387,11 @@ class MarbleWorld extends Scheduler {
tickAccumulator += timeState.dt;
while (tickAccumulator >= 0.032) {
// Apply the server side ticks
var lastPredTick = -1;
if (Net.isClient) {
applyReceivedMoves();
var marbleNeedsTicking = applyReceivedMoves();
// Catch up
applyClientPrediction();
lastPredTick = applyClientPrediction(marbleNeedsTicking);
}
// Do the clientside prediction sim
@ -1371,6 +1404,10 @@ class MarbleWorld extends Scheduler {
for (client => marble in clientMarbles) {
otherMoves.push(marble.updateServer(fixedDt, collisionWorld, pathedInteriors));
}
this.predictions.storeState(marble, myMove.timeState.ticks);
for (client => marble in clientMarbles) {
this.predictions.storeState(marble, myMove.timeState.ticks);
}
if (Net.isHost) {
for (client => othermarble in clientMarbles) { // Oh no!
var mv = otherMoves.shift();

View file

@ -53,7 +53,7 @@ class CollisionWorld {
}
}
contacts = contacts.concat(this.staticWorld.sphereIntersection(spherecollision, timeState));
// contacts = contacts.concat(this.staticWorld.sphereIntersection(spherecollision, timeState));
var dynSearch = dynamicOctree.boundingSearch(box).map(x -> cast(x, CollisionEntity));
for (obj in dynSearch) {

View file

@ -0,0 +1,62 @@
package net;
import net.NetPacket.MarbleUpdatePacket;
import net.NetPacket.MarbleMovePacket;
import src.TimeState;
import src.Marble;
import h3d.Vector;
@:publicFields
class MarblePrediction {
var tick:Int;
var position:Vector;
var velocity:Vector;
var omega:Vector;
public function new(marble:Marble, tick:Int) {
this.tick = tick;
position = @:privateAccess marble.newPos.clone();
velocity = @:privateAccess marble.velocity.clone();
omega = @:privateAccess marble.omega.clone();
}
public inline function getError(p:MarbleUpdatePacket) {
var subs = position.sub(p.position).lengthSq() + velocity.sub(p.velocity).lengthSq() + omega.sub(p.omega).lengthSq();
return subs;
}
}
class MarblePredictionStore {
var predictions:Map<Marble, Array<MarblePrediction>>;
public function new() {
predictions = [];
}
public function storeState(marble:Marble, tick:Int) {
var state = new MarblePrediction(marble, tick);
if (predictions.exists(marble)) {
var arr = predictions[marble];
while (arr.length != 0 && arr[0].tick >= tick)
arr.shift();
arr.push(state);
} else {
predictions.set(marble, [state]);
}
}
public function retrieveState(marble:Marble, tick:Int) {
if (predictions.exists(marble)) {
var arr = predictions[marble];
while (arr.length != 0 && arr[0].tick < tick)
arr.shift();
if (arr.length == 0)
return null;
var p = arr[0];
if (p.tick == tick)
return p;
return null;
}
return null;
}
}

View file

@ -1,5 +1,6 @@
package net;
import net.NetPacket.MarbleUpdatePacket;
import shapes.PowerUp;
import net.NetPacket.MarbleMovePacket;
import src.TimeState;
@ -199,4 +200,20 @@ class MoveManager {
lastAckMoveId = m;
return mv;
}
public function getMoveForTick(m:Int) {
if (m <= lastAckMoveId)
return null;
if (m == 65535 || m == -1)
return null;
if (queuedMoves.length == 0)
return null;
for (i in 0...queuedMoves.length) {
if (queuedMoves[i].id == m)
return queuedMoves[i];
if (queuedMoves[i].id > m)
return null;
}
return null;
}
}

View file

@ -50,7 +50,7 @@ class Net {
isHost = true;
isClient = false;
clientId = 0;
masterWs = new WebSocket("ws://localhost:8080");
masterWs = new WebSocket("ws://192.168.1.2:8080");
masterWs.onmessage = (m) -> {
switch (m) {
@ -97,7 +97,7 @@ class Net {
}
public static function joinServer(connectedCb:() -> Void) {
masterWs = new WebSocket("ws://localhost:8080");
masterWs = new WebSocket("ws://192.168.1.2:8080");
masterWs.onopen = () -> {
client = new RTCPeerConnection(["stun:stun.l.google.com:19302"], "0.0.0.0");
var candidates = [];