attempt sync

This commit is contained in:
RandomityGuy 2024-01-28 13:13:14 +05:30
parent 086553d95a
commit b1ec446360
7 changed files with 275 additions and 42 deletions

View file

@ -53,6 +53,7 @@ class Debug {
debugSphere.setScale(sph.radius);
debugSphere.emitInstance();
}
_spheres = [];
} else {
if (debugSphere != null) {
debugSphere.remove();

View file

@ -702,6 +702,8 @@ class Marble extends GameObject {
normP.scale(1 + bounce);
velocity.load(velocity.sub(normP.multiply(ourMass)));
otherMarble.velocity.load(otherMarble.velocity.add(normP.multiply(1 / theirMass)));
contacts[i].velocity.load(otherMarble.velocity);
} else {
@ -1048,7 +1050,8 @@ class Marble extends GameObject {
found: false,
foundContacts: [],
lastContactPos: null,
lastContactNormal: null
lastContactNormal: null,
foundMarbles: [],
};
}
var searchbox = new Bounds();
@ -1065,6 +1068,34 @@ class Marble extends GameObject {
var testTriangles = [];
var finalContacts = [];
var foundMarbles = [];
// Marble-Marble
var nextPos = position.add(velocity.multiply(deltaT));
for (marble in this.collisionWorld.marbleEntities) {
if (marble == this.collider)
continue;
var otherPosition = marble.transform.getPosition();
var isec = Collision.capsuleSphereNearestOverlap(position, nextPos, _radius, otherPosition, marble.radius);
if (isec.result) {
foundMarbles.push(marble);
isec.t *= deltaT;
if (isec.t >= finalT) {
var vel = position.add(velocity.multiply(finalT)).sub(otherPosition);
vel.normalize();
var newVelLen = this.velocity.sub(marble.velocity).dot(vel);
if (newVelLen < 0.0) {
finalT = isec.t;
var posDiff = nextPos.sub(position).multiply(isec.t);
var p = posDiff.add(position);
lastContactNormal = p.sub(otherPosition);
lastContactNormal.normalize();
lastContactPos = p.sub(lastContactNormal.multiply(_radius));
}
}
}
}
// for (iter in 0...10) {
// var iterationFound = false;
@ -1379,13 +1410,14 @@ class Marble extends GameObject {
foundContacts: testTriangles,
lastContactPos: lastContactPos,
lastContactNormal: position.sub(lastContactPos).normalized(),
foundMarbles: foundMarbles
};
}
function nudgeToContacts(position:Vector, radius:Float, foundContacts:Array<{
v:Array<Vector>,
n:Vector
}>) {
}>, foundMarbles:Array<SphereCollisionEntity>) {
var it = 0;
var concernedContacts = foundContacts; // PathedInteriors have their own nudge logic
var prevResolved = 0;
@ -1444,6 +1476,14 @@ class Marble extends GameObject {
prevResolved = resolved;
it++;
} while (true && it < 10);
for (marble in foundMarbles) {
var marblePosition = marble.transform.getPosition();
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));
}
}
return position;
}
@ -1547,7 +1587,7 @@ class Marble extends GameObject {
}
var expectedPos = finalPosData.position;
// var newPos = expectedPos;
var newPos = nudgeToContacts(expectedPos, _radius, finalPosData.foundContacts);
var newPos = nudgeToContacts(expectedPos, _radius, finalPosData.foundContacts, finalPosData.foundMarbles);
if (this.velocity.lengthSq() > 1e-8) {
var posDiff = newPos.sub(expectedPos);
@ -1648,11 +1688,19 @@ class Marble extends GameObject {
public function unpackUpdate(p:MarbleUpdatePacket) {
// Assume packet header is already read
// Check if we aren't colliding with a marble
// for (marble in this.level.collisionWorld.marbleEntities) {
// if (marble != this.collider && marble.transform.getPosition().distance(p.position) < marble.radius + this._radius) {
// Console.log("Marble updated inside another one!");
// return false;
// }
// }
this.oldPos = this.newPos;
this.newPos = p.position;
this.collider.transform.setPosition(this.newPos);
this.velocity = p.velocity;
this.omega = p.omega;
return true;
}
public function updateServer(timeState:TimeState, collisionWorld:CollisionWorld, pathedInteriors:Array<PathedInterior>, packets:Array<haxe.io.Bytes>) {

View file

@ -1,5 +1,6 @@
package src;
import haxe.Exception;
import net.NetPacket.MarbleUpdatePacket;
import net.NetPacket.MarbleMovePacket;
import net.MoveManager;
@ -1022,48 +1023,178 @@ class MarbleWorld extends Scheduler {
}
public function applyReceivedMoves() {
if (!lastMoves[Net.clientId].applied) {
var allApplied = false;
for (client => lastMove in lastMoves) {
if (lastMove.applied)
continue;
if (lastMove.clientId == Net.clientId)
marble.unpackUpdate(lastMove);
else
clientMarbles[Net.clientIdMap[client]].unpackUpdate(lastMove);
if (lastMove.applied) {
allApplied = true;
break;
}
}
if (!allApplied) {
for (client => lastMove in lastMoves) {
var isApplied = lastMove.clientId == Net.clientId ? marble.unpackUpdate(lastMove) : clientMarbles[Net.clientIdMap[client]].unpackUpdate(lastMove);
}
}
}
// for (client => lastMove in lastMoves) {
// if (lastMove.applied)
// continue;
// var isApplied = lastMove.clientId == Net.clientId ? marble.unpackUpdate(lastMove) : clientMarbles[Net.clientIdMap[client]].unpackUpdate(lastMove);
// if (!isApplied)
// lastMove.applied = true;
// }
}
public function applyClientPrediction() {
// First acknowledge the marble's last move so we can get that over with
var ourLastMove = lastMoves[Net.clientId];
if (ourLastMove == null)
return;
var ackLag = -1;
if (!ourLastMove.applied)
ackLag = Net.clientConnection.moveManager.acknowledgeMove(ourLastMove.move.id);
else
return;
var ourLastMoveTime = ourLastMove.serverTicks;
// Then find the minimum tick from which we need to begin our predictions from
var tickStart = timeState.ticks;
var ourQueuedMoves = @:privateAccess Net.clientConnection.moveManager.queuedMoves.copy();
if (ourQueuedMoves.length > 0 && ourQueuedMoves[0].timeState.ticks < tickStart)
tickStart = ourQueuedMoves[0].timeState.ticks;
var advanceTimeState = timeState.clone();
advanceTimeState.dt = 0.032;
/*
for (client => lastMove in lastMoves) {
if (lastMove.applied)
continue;
var marbleToUpdate = lastMove.clientId == Net.clientId ? marble : clientMarbles[Net.clientIdMap[client]];
if (lastMove.serverTicks < tickStart)
tickStart = lastMove.serverTicks;
}
// Now actually do the sim, tick by tick
for (tick in tickStart...timeState.ticks) {
for (client => lastMove in lastMoves) {
if (lastMove.applied || (tick < lastMove.serverTicks && lastMove.clientId != Net.clientId))
continue;
var marbleToUpdate = lastMove.clientId == Net.clientId ? marble : clientMarbles[Net.clientIdMap[client]];
@:privateAccess marbleToUpdate.isNetUpdate = true;
if (marbleToUpdate == marble) {
var moveManager = @:privateAccess Net.clientConnection.moveManager;
var catchUpTickCount = 0;
Net.clientConnection.moveManager.acknowledgeMove(lastMove.move.id);
var advanceTimeState = timeState.clone();
advanceTimeState.dt = 0.032;
for (move in @:privateAccess moveManager.queuedMoves) {
if (ourQueuedMoves.length > 0) {
if (ourQueuedMoves[0].timeState.ticks <= tick) {
var move = ourQueuedMoves.shift();
Debug.drawSphere(@:privateAccess marbleToUpdate.newPos, marbleToUpdate._radius);
@:privateAccess marbleToUpdate.moveMotionDir = move.motionDir;
@:privateAccess marbleToUpdate.advancePhysics(advanceTimeState, move.move, this.collisionWorld, this.pathedInteriors);
}
} else {
var tickDiff = timeState.ticks - lastMove.serverTicks;
if (tickDiff > this.maxPredictionTicks)
tickDiff = this.maxPredictionTicks;
if (tickDiff > 0) {
var advanceTimeState = timeState.clone();
advanceTimeState.dt = 0.032;
lastMove.applied = true;
}
} else {
var m = lastMove.move.move;
Debug.drawSphere(@:privateAccess marbleToUpdate.newPos, marbleToUpdate._radius);
@:privateAccess marbleToUpdate.moveMotionDir = lastMove.move.motionDir;
for (o in 0...(tickDiff + 1)) {
@:privateAccess marbleToUpdate.advancePhysics(advanceTimeState, m, this.collisionWorld, this.pathedInteriors);
}
}
}
@:privateAccess marbleToUpdate.isNetUpdate = false;
}
}
*/
// Tick the remaining moves (ours)
if (!ourLastMove.applied) {
@:privateAccess this.marble.isNetUpdate = true;
var totalTicksToDo = ourQueuedMoves.length;
var endTick = ourLastMoveTime + totalTicksToDo;
for (move in ourQueuedMoves) {
var m = move.move;
Debug.drawSphere(@:privateAccess this.marble.newPos, this.marble._radius);
@:privateAccess this.marble.moveMotionDir = move.motionDir;
@:privateAccess this.marble.advancePhysics(advanceTimeState, m, this.collisionWorld, this.pathedInteriors);
// for (client => lastMove in lastMoves) {
// if (lastMove.clientId == Net.clientId || lastMove.calculationTicks >= endTick)
// continue;
// trace('tick diff: ${lastMove.serverTicks - ourLastMoveTime}');
// lastMove.calculationTicks++;
// // lastMove.serverTicks++;
// var marbleToUpdate = clientMarbles[Net.clientIdMap[client]];
// @:privateAccess marbleToUpdate.isNetUpdate = true;
// var m = lastMove.move.move;
// @:privateAccess marbleToUpdate.moveMotionDir = lastMove.move.motionDir;
// @:privateAccess marbleToUpdate.advancePhysics(advanceTimeState, m, this.collisionWorld, this.pathedInteriors);
// @:privateAccess marbleToUpdate.isNetUpdate = false;
// }
}
for (client => lastMove in lastMoves) {
if (lastMove.clientId == Net.clientId)
continue;
// lastMove.calculationTicks++;
// lastMove.serverTicks++;
var tickDiff = ackLag + 1; // - (lastMove.serverTicks - ourLastMoveTime) + 1;
while (tickDiff > 0) {
var marbleToUpdate = clientMarbles[Net.clientIdMap[client]];
@:privateAccess marbleToUpdate.isNetUpdate = true;
var m = lastMove.move.move;
@:privateAccess marbleToUpdate.moveMotionDir = lastMove.move.motionDir;
@:privateAccess marbleToUpdate.advancePhysics(advanceTimeState, m, this.collisionWorld, this.pathedInteriors);
@:privateAccess marbleToUpdate.isNetUpdate = false;
tickDiff--;
}
}
// if (ourQueuedMoves.length >= 2) {
// trace('Move queue tick diff: ${ourQueuedMoves[ourQueuedMoves.length - 1].timeState.ticks - ourQueuedMoves[0].timeState.ticks}');
// }
ourLastMove.applied = true;
@:privateAccess this.marble.isNetUpdate = false;
}
// for (client => lastMove in lastMoves) {
// if (lastMove.applied)
// continue;
// if (lastMove.serverTicks > timeState.ticks) {
// trace('Marble ticked ahead ${lastMove.serverTicks - timeState.ticks} ticks');
// lastMove.serverTicks = timeState.ticks;
// }
// if (lastMove.serverTicks < tickStart)
// tickStart = lastMove.serverTicks;
// }
// for (tick in tickStart...timeState.ticks) {
// for (client => lastMove in lastMoves) {
// if (lastMove.applied || tick < lastMove.serverTicks)
// continue;
// // if (lastMove.calculationTicks > 0) {
// // lastMove.calculationTicks--;
// // continue;
// // }
// var marbleToUpdate = clientMarbles[Net.clientIdMap[client]];
// @:privateAccess marbleToUpdate.isNetUpdate = true;
// var m = lastMove.move.move;
// Debug.drawSphere(@:privateAccess marbleToUpdate.newPos, marbleToUpdate._radius);
// @:privateAccess marbleToUpdate.moveMotionDir = lastMove.move.motionDir;
// @:privateAccess marbleToUpdate.advancePhysics(advanceTimeState, m, this.collisionWorld, this.pathedInteriors);
// @:privateAccess marbleToUpdate.isNetUpdate = false;
// }
// }
// Now mark them all as applied
for (client => lastMove in lastMoves) {
lastMove.applied = true;
}
}

View file

@ -477,4 +477,51 @@ class Collision {
}
return null;
}
public static function capsuleSphereNearestOverlap(a0:Vector, a1:Vector, radA:Float, b:Vector, radB:Float) {
var V = a1.sub(a0);
var A0B = a0.sub(b);
var d1 = A0B.dot(V);
var d2 = A0B.dot(A0B);
var d3 = V.dot(V);
var R2 = (radA + radB) * (radA + radB);
if (d2 < R2) {
// starting in collision state
return {
result: true,
t: 0.0
}
}
if (d3 < 0.01) // no movement, and don't start in collision state, so no collision
return {
result: false,
t: 0.0
}
var b24ac = Math.sqrt(d1 * d1 - d2 * d3 + d3 * R2);
var t1 = (-d1 - b24ac) / d3;
if (t1 > 0 && t1 < 1.0) {
return {
result: true,
t: t1
}
}
var t2 = (-d1 + b24ac) / d3;
if (t2 > 0 && t2 < 1.0) {
return {
result: true,
t: t2
}
}
if (t1 < 0 && t2 > 0) {
return {
result: true,
t: 0.0
}
}
return {
result: false,
t: 0.0
}
}
}

View file

@ -13,7 +13,7 @@ class CollisionWorld {
public var dynamicEntities:Array<CollisionEntity> = [];
public var dynamicOctree:Octree;
var marbleEntities:Array<SphereCollisionEntity> = [];
public var marbleEntities:Array<SphereCollisionEntity> = [];
var dynamicEntitySet:Map<CollisionEntity, Bool> = [];

View file

@ -36,7 +36,7 @@ class MoveManager {
var lastMove:NetMove;
var lastAckMoveId:Int = -1;
static var maxMoves = 16;
static var maxMoves = 32;
public function new(connection:ClientConnection) {
queuedMoves = [];
@ -155,16 +155,20 @@ class MoveManager {
public function acknowledgeMove(m:Int) {
if (m == 65535 || m == -1)
return;
return -1;
if (m <= lastAckMoveId)
return; // Already acked
return -1; // Already acked
if (queuedMoves.length == 0)
return;
return -1;
while (m != queuedMoves[0].id) {
queuedMoves.shift();
}
if (m == queuedMoves[0].id)
var delta = -1;
if (m == queuedMoves[0].id) {
delta = queuedMoves[0].id - lastAckMoveId;
queuedMoves.shift();
}
lastAckMoveId = m;
return delta;
}
}

View file

@ -34,6 +34,7 @@ class MarbleUpdatePacket implements NetPacket {
var clientId:Int;
var move:NetMove;
var serverTicks:Int;
var calculationTicks:Int;
var position:Vector;
var velocity:Vector;
var omega:Vector;
@ -60,6 +61,7 @@ class MarbleUpdatePacket implements NetPacket {
clientId = b.readUInt16();
move = MoveManager.unpackMove(b);
serverTicks = b.readUInt16();
calculationTicks = serverTicks;
position = new Vector(b.readFloat(), b.readFloat(), b.readFloat());
velocity = new Vector(b.readFloat(), b.readFloat(), b.readFloat());
omega = new Vector(b.readFloat(), b.readFloat(), b.readFloat());