diff --git a/src/Debug.hx b/src/Debug.hx index c172e3a6..ab36beac 100644 --- a/src/Debug.hx +++ b/src/Debug.hx @@ -12,7 +12,8 @@ class Debug { static var _triangles:Array = []; static var _spheres:Array<{ position:Vector, - radius:Float + radius:Float, + lifetime:Float }> = []; static var debugTriangles:h3d.scene.Mesh; @@ -20,7 +21,7 @@ class Debug { public static function init() {} - public static function update() { + public static function update(dt:Float) { if (_triangles.length != 0 && drawBounds) { var prim = new h3d.prim.Polygon(_triangles.copy()); if (debugTriangles != null) { @@ -48,12 +49,17 @@ class Debug { MarbleGame.instance.scene.addChild(debugSphere); } debugSphere.begin(_spheres.length); + var toremove = []; for (sph in _spheres) { debugSphere.setPosition(sph.position.x, sph.position.y, sph.position.z); debugSphere.setScale(sph.radius); debugSphere.emitInstance(); + sph.lifetime -= dt; + if (sph.lifetime < 0) + toremove.push(sph); } - _spheres = []; + for (sph in toremove) + _spheres.remove(sph); } else { if (debugSphere != null) { debugSphere.remove(); @@ -72,6 +78,6 @@ class Debug { public static function drawSphere(centre:Vector, radius:Float) { if (drawBounds) - _spheres.push({position: centre.clone(), radius: radius}); + _spheres.push({position: centre.clone(), radius: radius, lifetime: 0.032}); } } diff --git a/src/Marble.hx b/src/Marble.hx index ee9eb2fc..6765fa15 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -243,6 +243,7 @@ class Marble extends GameObject { public var contacts:Array = []; public var bestContact:CollisionInfo; public var contactEntities:Array = []; + public var collidingMarbles:Array = []; var queuedContacts:Array = []; var appliedImpulses:Array<{impulse:Vector, contactImpulse:Bool}> = []; @@ -295,6 +296,7 @@ class Marble extends GameObject { var connection:net.Net.ClientConnection; var moveMotionDir:Vector; var isNetUpdate:Bool = false; + var collisionToken:Int = 0; public function new() { super(); @@ -527,6 +529,11 @@ class Marble extends GameObject { this.contacts = queuedContacts; var c = collisiomWorld.sphereIntersection(this.collider, timeState); this.contactEntities = c.foundEntities; + this.collidingMarbles = []; + for (e in this.contacts) { + if (e.collider is SphereCollisionEntity) + this.collidingMarbles.push(cast(e.collider, SphereCollisionEntity).marble); + } contacts = contacts.concat(c.contacts); } @@ -1661,7 +1668,7 @@ class Marble extends GameObject { newPos = this.collider.transform.getPosition(); - if (this.prevPos != null) { + if (this.prevPos != null && !this.isNetUpdate) { var tempTimeState = timeState.clone(); tempTimeState.currentAttemptTime = passedTime; this.level.callCollisionHandlers(cast this, tempTimeState, oldPos, newPos); @@ -1697,7 +1704,7 @@ class Marble extends GameObject { // } this.oldPos = this.newPos; this.newPos = p.position; - this.collider.transform.setPosition(this.newPos); + this.collider.transform.setPosition(p.position); this.velocity = p.velocity; this.omega = p.omega; return true; @@ -1739,6 +1746,12 @@ class Marble extends GameObject { playedSounds = []; advancePhysics(timeState, move.move, collisionWorld, pathedInteriors); + for (marble in this.collidingMarbles) { + marble.collisionToken = timeState.ticks; + } + if (this.collidingMarbles.length != 0) + this.collisionToken = timeState.ticks; + physicsAccumulator = 0; if (Net.isHost) { diff --git a/src/MarbleGame.hx b/src/MarbleGame.hx index 1632aee3..f3520b6d 100644 --- a/src/MarbleGame.hx +++ b/src/MarbleGame.hx @@ -189,7 +189,7 @@ class MarbleGame { world = null; return; } - Debug.update(); + Debug.update(dt); if (Util.isTouchDevice()) { touchInput.update(); } diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index 9f955e75..5563b26d 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -1,5 +1,6 @@ package src; +import net.MarbleUpdateQueue; import haxe.Exception; import net.NetPacket.MarbleUpdatePacket; import net.NetPacket.MarbleMovePacket; @@ -202,7 +203,7 @@ class MarbleWorld extends Scheduler { var clientMarbles:Map = []; - public var lastMoves:Map = []; + public var lastMoves:MarbleUpdateQueue; // Loading var resourceLoadFuncs:Array<(() -> Void)->Void> = []; @@ -242,6 +243,7 @@ class MarbleWorld extends Scheduler { if (this.isMultiplayer) { isRecording = false; isWatching = false; + lastMoves = new MarbleUpdateQueue(); } // Set the network RNG for hunt @@ -1023,180 +1025,113 @@ class MarbleWorld extends Scheduler { } public function applyReceivedMoves() { - if (!lastMoves[Net.clientId].applied) { - var allApplied = false; - for (client => lastMove in lastMoves) { - 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); + if (!lastMoves.ourMoveApplied) { + var ourMove = lastMoves.myMarbleUpdate; + if (ourMove != null) { + marble.unpackUpdate(ourMove); + for (client => arr in lastMoves.otherMarbleUpdates) { + var lastMove = null; + while (arr.length > 0) { + var p = arr[0]; + if (p.serverTicks <= ourMove.serverTicks && p.serverTicks >= ourMove.collisionToken) { + clientMarbles[Net.clientIdMap[client]].unpackUpdate(p); + lastMove = arr.shift(); + } else { + break; + } + } + if (lastMove != null) + arr.insert(0, 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]; + var ourLastMove = lastMoves.myMarbleUpdate; if (ourLastMove == null) return; var ackLag = -1; - if (!ourLastMove.applied) + if (!lastMoves.ourMoveApplied) 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; - 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) { - 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 { - lastMove.applied = true; - } - } else { - 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; - } - } - */ // Tick the remaining moves (ours) - if (!ourLastMove.applied) { + if (!lastMoves.ourMoveApplied) { @:privateAccess this.marble.isNetUpdate = true; var totalTicksToDo = ourQueuedMoves.length; var endTick = ourLastMoveTime + totalTicksToDo; + var currentTick = ourLastMoveTime; + + var marblesToTick = new Map(); + + for (client => arr in lastMoves.otherMarbleUpdates) { + if (arr.length > 0) { + var m = arr[0]; + if (m.serverTicks <= ourLastMoveTime && m.serverTicks >= ourLastMove.collisionToken) { + m.calculationTicks = Std.int(/*ourLastMoveTime - m.serverTicks + */ ourQueuedMoves.length); + marblesToTick.set(client, m); + arr.shift(); + } + } + } + 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); + if (this.marble.collidingMarbles.length > 0) + this.lastMoves.addCollisionFrame(currentTick); - // for (client => lastMove in lastMoves) { - // if (lastMove.clientId == Net.clientId || lastMove.calculationTicks >= endTick) - // continue; + for (client => m in marblesToTick) { + if (m.calculationTicks > 0) { + var marbleToUpdate = clientMarbles[Net.clientIdMap[client]]; - // trace('tick diff: ${lastMove.serverTicks - ourLastMoveTime}'); + Debug.drawSphere(@:privateAccess marbleToUpdate.newPos, marbleToUpdate._radius); - // 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; - // } + 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; + if (marbleToUpdate.collidingMarbles.length > 0) + this.lastMoves.addCollisionFrame(currentTick); + m.calculationTicks--; + } + } + currentTick++; } - 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) { + for (client => m in marblesToTick) { + if (m.calculationTicks >= 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--; + + 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; + if (marbleToUpdate.collidingMarbles.length > 0) + this.lastMoves.addCollisionFrame(currentTick); + m.calculationTicks--; + } } } - // if (ourQueuedMoves.length >= 2) { - // trace('Move queue tick diff: ${ourQueuedMoves[ourQueuedMoves.length - 1].timeState.ticks - ourQueuedMoves[0].timeState.ticks}'); - // } - ourLastMove.applied = true; + lastMoves.ourMoveApplied = 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; - } } public function rollback(t:Float) { diff --git a/src/net/MarbleUpdateQueue.hx b/src/net/MarbleUpdateQueue.hx new file mode 100644 index 00000000..cfb7e73b --- /dev/null +++ b/src/net/MarbleUpdateQueue.hx @@ -0,0 +1,36 @@ +package net; + +import net.NetPacket.MarbleUpdatePacket; +import net.Net; + +@:publicFields +class MarbleUpdateQueue { + var otherMarbleUpdates:Map> = []; + var myMarbleUpdate:MarbleUpdatePacket; + var ourMoveApplied:Bool = false; + var collisionFrame:Int; + + public function new() {} + + public function enqueue(update:MarbleUpdatePacket) { + var cc = update.clientId; + if (update.serverTicks < collisionFrame) + return; + if (cc != Net.clientId) { + if (otherMarbleUpdates.exists(cc)) { + otherMarbleUpdates[cc].push(update); + } else { + otherMarbleUpdates[cc] = [update]; + } + } else { + if (myMarbleUpdate == null || update.serverTicks > myMarbleUpdate.serverTicks) { + myMarbleUpdate = update; + ourMoveApplied = false; + } + } + } + + public function addCollisionFrame(tick:Int) { + collisionFrame = tick + 2; + } +} diff --git a/src/net/MoveManager.hx b/src/net/MoveManager.hx index d9702405..ad38e42f 100644 --- a/src/net/MoveManager.hx +++ b/src/net/MoveManager.hx @@ -36,7 +36,7 @@ class MoveManager { var lastMove:NetMove; var lastAckMoveId:Int = -1; - static var maxMoves = 32; + static var maxMoves = 45; public function new(connection:ClientConnection) { queuedMoves = []; diff --git a/src/net/Net.hx b/src/net/Net.hx index ad1bb4cd..9cd74244 100644 --- a/src/net/Net.hx +++ b/src/net/Net.hx @@ -248,12 +248,7 @@ class Net { var cc = marbleUpdatePacket.clientId; if (MarbleGame.instance.world != null) { var m = MarbleGame.instance.world.lastMoves; - if (m.exists(cc)) { - if (m[cc].serverTicks < marbleUpdatePacket.serverTicks) - m.set(cc, marbleUpdatePacket); - } else { - m.set(cc, marbleUpdatePacket); - } + m.enqueue(marbleUpdatePacket); } case MarbleMove: diff --git a/src/net/NetPacket.hx b/src/net/NetPacket.hx index c6ab53ba..66b2ad46 100644 --- a/src/net/NetPacket.hx +++ b/src/net/NetPacket.hx @@ -34,11 +34,11 @@ class MarbleUpdatePacket implements NetPacket { var clientId:Int; var move:NetMove; var serverTicks:Int; - var calculationTicks:Int; + var calculationTicks:Int = -1; var position:Vector; var velocity:Vector; var omega:Vector; - var applied:Bool = false; + var collisionToken:Int; public function new() {} @@ -46,6 +46,7 @@ class MarbleUpdatePacket implements NetPacket { b.writeUInt16(clientId); MoveManager.packMove(move, b); b.writeUInt16(serverTicks); + b.writeUInt16(collisionToken); b.writeFloat(position.x); b.writeFloat(position.y); b.writeFloat(position.z); @@ -61,7 +62,7 @@ class MarbleUpdatePacket implements NetPacket { clientId = b.readUInt16(); move = MoveManager.unpackMove(b); serverTicks = b.readUInt16(); - calculationTicks = serverTicks; + collisionToken = b.readUInt16(); 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());