From 4300459c1d2a2edd1103dea6b485f0c77ec65d42 Mon Sep 17 00:00:00 2001 From: RandomityGuy <31925790+RandomityGuy@users.noreply.github.com> Date: Mon, 17 Nov 2025 23:39:59 +0000 Subject: [PATCH] make camera 60fps only, and minor MP microoptimizations --- src/CameraController.hx | 25 +++++-- src/MarbleWorld.hx | 125 +++++++++++-------------------- src/net/MarblePredictionStore.hx | 70 ++++++++++------- src/net/MarbleUpdateQueue.hx | 108 +++++++++++++------------- src/net/NetPacket.hx | 59 +++++---------- 5 files changed, 174 insertions(+), 213 deletions(-) diff --git a/src/CameraController.hx b/src/CameraController.hx index d08269f3..572e2e28 100644 --- a/src/CameraController.hx +++ b/src/CameraController.hx @@ -222,13 +222,8 @@ class CameraController extends Object { } } - public function update(currentTime:Float, dt:Float) { - // camera.position.set(marblePosition.x, marblePosition.y, marblePosition.z).sub(directionVector.clone().multiplyScalar(2.5)); - // this.level.scene.camera.target = marblePosition.add(cameraVerticalTranslation); - // camera.position.add(cameraVerticalTranslation); - var camera = level.scene.camera; - this.dt = dt; - + public function updateFixed() { + var dt = (1 / 60.0); var lerpt = 1 - Math.pow(0.5, dt / 0.016); // Math.min(1, 1 - Math.pow(0.6, dt / 0.032)); // hxd.Math.min(1, 1 - Math.pow(0.6, dt * 600)); var gamepadX = applyNonlinearScale(rescaleDeadZone(Gamepad.getAxis(Settings.gamepadSettings.cameraXAxis), Settings.gamepadSettings.axisDeadzone)); @@ -316,6 +311,22 @@ class CameraController extends Object { CameraYaw = Util.lerp(CameraYaw, nextCameraYaw, lerpt); CameraPitch = Util.lerp(CameraPitch, nextCameraPitch, lerpt); + } + + var accumulatedTime:Float = 0.0; + + public function update(currentTime:Float, dt:Float) { + // camera.position.set(marblePosition.x, marblePosition.y, marblePosition.z).sub(directionVector.clone().multiplyScalar(2.5)); + // this.level.scene.camera.target = marblePosition.add(cameraVerticalTranslation); + // camera.position.add(cameraVerticalTranslation); + var camera = level.scene.camera; + this.dt = dt; + + accumulatedTime += dt; + while (accumulatedTime >= 1 / 60.0) { + updateFixed(); + accumulatedTime -= 1 / 60.0; + } CameraPitch = Util.clamp(CameraPitch, -0.35, 1.5); // Util.clamp(CameraPitch, -Math.PI / 12, Math.PI / 2); diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index e24428a5..699fa0d9 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -1246,13 +1246,31 @@ class MarbleWorld extends Scheduler { return packets; } + inline function hasPredictionFlag(mask:Int, clientId:Int):Bool { + return (mask & (1 << clientId)) != 0; + } + public function applyReceivedMoves() { var needsPrediction = 0; + + inline function correctPrediction(target:Marble, packet:MarbleUpdatePacket, tick:Int, bitMask:Int, ?pending:Array) { + target.unpackUpdate(packet); + needsPrediction |= bitMask; + if (pending != null) + pending.insert(0, packet); + if (tick >= 0) + predictions.clearStatesAfterTick(target, tick); + } + if (!lastMoves.ourMoveApplied) { var ourMove = lastMoves.myMarbleUpdate; if (ourMove != null) { var ourMoveStruct = Net.clientConnection.acknowledgeMove(ourMove.move, timeState); lastMoves.ourMoveApplied = true; + + var hasStruct = ourMoveStruct != null; + var ourTick = hasStruct ? ourMoveStruct.timeState.ticks : -1; + for (client => arr in lastMoves.otherMarbleUpdates) { var lastMove = null; while (arr.packets.length > 0) { @@ -1263,67 +1281,35 @@ class MarbleWorld extends Scheduler { break; } } - if (lastMove != null) { - // clientMarbles[Net.clientIdMap[client]].unpackUpdate(lastMove); - // needsPrediction |= 1 << client; - // arr.insert(0, lastMove); - var clientMarble = clientMarbles[Net.clientIdMap[client]]; - if (clientMarble != null) { - if (ourMove.serverTicks == lastMove.serverTicks) { - if (ourMoveStruct != null) { - var otherPred = predictions.retrieveState(clientMarble, ourMoveStruct.timeState.ticks); - if (otherPred != null) { - if (otherPred.getError(lastMove) > 0.01) { - // Debug.drawSphere(@:privateAccess clientMarbles[Net.clientIdMap[client]].newPos, 0.2, 0.5); - // trace('Prediction error: ${otherPred.getError(lastMove)}'); - // trace('Desync for tick ${ourMoveStruct.timeState.ticks}'); - clientMarble.unpackUpdate(lastMove); - needsPrediction |= 1 << client; - arr.packets.insert(0, lastMove); - predictions.clearStatesAfterTick(clientMarbles[Net.clientIdMap[client]], ourMoveStruct.timeState.ticks); - } - } else { - // Debug.drawSphere(@:privateAccess clientMarbles[Net.clientIdMap[client]].newPos, 0.2, 0.5); - // trace('Desync for tick ${ourMoveStruct.timeState.ticks}'); - clientMarble.unpackUpdate(lastMove); - needsPrediction |= 1 << client; - arr.packets.insert(0, lastMove); - predictions.clearStatesAfterTick(clientMarble, ourMoveStruct.timeState.ticks); - } - } else { - // Debug.drawSphere(@:privateAccess clientMarbles[Net.clientIdMap[client]].newPos, 0.2, 0.5); - // trace('Desync in General'); - clientMarble.unpackUpdate(lastMove); - needsPrediction |= 1 << client; - arr.packets.insert(0, lastMove); - // predictions.clearStatesAfterTick(clientMarbles[Net.clientIdMap[client]], ourMoveStruct.timeState.ticks); - } - } - } - } - } - // marble.unpackUpdate(ourMove); - // needsPrediction |= 1 << Net.clientId; - if (ourMoveStruct != null) { - var ourPred = predictions.retrieveState(marble, ourMoveStruct.timeState.ticks); - if (ourPred != null) { - if (ourPred.getError(ourMove) > 0.01) { - // trace('Desync for tick ${ourMoveStruct.timeState.ticks}'); - marble.unpackUpdate(ourMove); - needsPrediction |= 1 << Net.clientId; - predictions.clearStatesAfterTick(marble, ourMoveStruct.timeState.ticks); + + if (lastMove == null || ourMove.serverTicks != lastMove.serverTicks) + continue; + + var connection = Net.clientIdMap[client]; + if (connection == null) + continue; + var clientMarble = clientMarbles[connection]; + if (clientMarble == null) + continue; + + var mask = 1 << client; + if (hasStruct) { + var otherPred = predictions.retrieveState(clientMarble, ourMoveStruct.timeState.ticks); + if (otherPred == null || otherPred.getError(lastMove) > 0.01) { + correctPrediction(clientMarble, lastMove, ourTick, mask, arr.packets); } } else { - // trace('Desync for tick ${ourMoveStruct.timeState.ticks}'); - marble.unpackUpdate(ourMove); - needsPrediction |= 1 << Net.clientId; - predictions.clearStatesAfterTick(marble, ourMoveStruct.timeState.ticks); + correctPrediction(clientMarble, lastMove, -1, mask, arr.packets); + } + } + + if (hasStruct) { + var ourPred = predictions.retrieveState(marble, ourTick); + if (ourPred == null || ourPred.getError(ourMove) > 0.01) { + correctPrediction(marble, ourMove, ourTick, 1 << Net.clientId); } } else { - // trace('Desync in General'); - marble.unpackUpdate(ourMove); - needsPrediction |= 1 << Net.clientId; - // predictions.clearStatesAfterTick(marble, ourMoveStruct.timeState.ticks); + correctPrediction(marble, ourMove, -1, 1 << Net.clientId); } } } @@ -1335,25 +1321,15 @@ class MarbleWorld extends Scheduler { var ourLastMove = lastMoves.myMarbleUpdate; if (ourLastMove == null || marbleNeedsPrediction == 0) return -1; - var ackLag = @:privateAccess Net.clientConnection.getQueuedMovesLength(); var ourLastMoveTime = ourLastMove.serverTicks; - var ourQueuedMoves = @:privateAccess Net.clientConnection.getQueuedMoves().copy(); - var qm = ourQueuedMoves[0]; var advanceTimeState = qm != null ? qm.timeState.clone() : timeState.clone(); advanceTimeState.dt = 0.032; advanceTimeState.ticks = ourLastMoveTime; - // if (marbleNeedsPrediction & (1 << Net.clientId) > 0) { // Only for our clients pls - // if (qm != null) { - // var mvs = qm.powerupStates.copy(); for (pw in marble.level.powerUps) { - // var val = mvs.shift(); - // if (pw.lastPickUpTime != val) - // Console.log('Revert powerup pickup: ${pw.lastPickUpTime} -> ${val}'); - if (pw.pickupClient != -1 && marbleNeedsPrediction & (1 << pw.pickupClient) > 0) pw.lastPickUpTime = powerupPredictions.getState(pw.netIndex); } @@ -1365,10 +1341,6 @@ class MarbleWorld extends Scheduler { huntMode.setGemHiddenStatus(activeGem, gemPredictions.getState(activeGem)); } } - // } - // } - - ackLag = ourQueuedMoves.length; // Tick the remaining moves (ours) @:privateAccess this.marble.isNetUpdate = true; @@ -1382,25 +1354,17 @@ class MarbleWorld extends Scheduler { for (client => arr in lastMoves.otherMarbleUpdates) { if (marbleNeedsPrediction & (1 << client) > 0 && arr.packets.length > 0) { var m = arr.packets[0]; - // if (m.serverTicks == ourLastMoveTime) { + var marbleToUpdate = clientMarbles[Net.clientIdMap[client]]; if (@:privateAccess marbleToUpdate.newPos == null) continue; - // Debug.drawSphere(@:privateAccess marbleToUpdate.newPos, marbleToUpdate._radius); - // var distFromUs = @:privateAccess marbleToUpdate.newPos.distance(this.marble.newPos); - // if (distFromUs < 5) // { m.calculationTicks = ourQueuedMoves.length; @:privateAccess marbleToUpdate.posStore.load(marbleToUpdate.newPos); @:privateAccess marbleToUpdate.netCorrected = true; - // } else { - // m.calculationTicks = Std.int(Math.max(1, ourQueuedMoves.length - (distFromUs - 5) / 3)); - // } - // - Std.int((@:privateAccess Net.clientConnection.moveManager.ackRTT - ourLastMove.moveQueueSize) / 2); marblesToTick.set(client, m); arr.packets.shift(); - // } } } @@ -1418,7 +1382,6 @@ class MarbleWorld extends Scheduler { @:privateAccess this.marble.advancePhysics(advanceTimeState, m, this.collisionWorld, this.pathedInteriors); this.predictions.storeState(this.marble, move.timeState.ticks); } - // var collidings = @:privateAccess this.marble.contactEntities.filter(x -> x is SphereCollisionEntity); for (client => m in marblesToTick) { if (m.calculationTicks > 0) { diff --git a/src/net/MarblePredictionStore.hx b/src/net/MarblePredictionStore.hx index 7f7da41a..b1803ba2 100644 --- a/src/net/MarblePredictionStore.hx +++ b/src/net/MarblePredictionStore.hx @@ -46,41 +46,61 @@ class MarblePredictionStore { } 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]); - } + var arr = ensureHistory(marble); + truncateFromTick(arr, tick); + arr.push(new MarblePrediction(marble, tick)); } 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; + var arr = predictions.get(marble); + if (arr == null) return null; - } - return null; + + dropBeforeTick(arr, tick); + return (arr.length != 0 && arr[0].tick == tick) ? arr[0] : null; } public function clearStatesAfterTick(marble:Marble, tick:Int) { - if (predictions.exists(marble)) { - var arr = predictions[marble]; - while (arr.length != 0 && arr[arr.length - 1].tick >= tick) - arr.pop(); - } + var arr = predictions.get(marble); + if (arr != null) + truncateFromTick(arr, tick); } public function removeMarbleFromPrediction(marble:Marble) { this.predictions.remove(marble); } + + inline function ensureHistory(marble:Marble) { + var arr = predictions.get(marble); + if (arr == null) { + arr = []; + predictions.set(marble, arr); + } + return arr; + } + + inline function dropBeforeTick(arr:Array, tick:Int) { + var idx = lowerBound(arr, tick); + if (idx > 0) + arr.splice(0, idx); + } + + inline function truncateFromTick(arr:Array, tick:Int) { + var idx = lowerBound(arr, tick); + if (idx < arr.length) + arr.splice(idx, arr.length - idx); + } + + static inline function lowerBound(arr:Array, tick:Int) { + var lo = 0; + var hi = arr.length; + while (lo < hi) { + var mid = (lo + hi) >> 1; + if (arr[mid].tick < tick) + lo = mid + 1; + else + hi = mid; + } + return lo; + } } diff --git a/src/net/MarbleUpdateQueue.hx b/src/net/MarbleUpdateQueue.hx index 44f8e35a..209240e3 100644 --- a/src/net/MarbleUpdateQueue.hx +++ b/src/net/MarbleUpdateQueue.hx @@ -25,70 +25,62 @@ class MarbleUpdateQueue { public function new() {} + inline function applyFlagged(flag:Int, updateHas:Bool, updateValue:T, lastValue:T, setter:(T) -> Void, updater:(T) -> Void) { + if (updateHas) + updater(updateValue); + else + setter(lastValue); + } + public function enqueue(update:MarbleUpdatePacket) { var cc = update.clientId; + var flags = update.netFlags; + if (cc != Net.clientId) { - // if (myMarbleUpdate != null && update.serverTicks > myMarbleUpdate.serverTicks) - // ourMoveApplied = true; - if (otherMarbleUpdates.exists(cc)) { - var otherUpdate = otherMarbleUpdates[cc]; - var ourList = otherUpdate.packets; - // Copy the netflagg'd fields - if (update.netFlags & MarbleNetFlags.DoBlast == 0) - update.blastTick = otherUpdate.lastBlastTick; - else - otherUpdate.lastBlastTick = update.blastTick; - if (update.netFlags & MarbleNetFlags.DoHelicopter == 0) - update.heliTick = otherUpdate.lastHeliTick; - else - otherUpdate.lastHeliTick = update.heliTick; - if (update.netFlags & MarbleNetFlags.DoMega == 0) - update.megaTick = otherUpdate.lastMegaTick; - else - otherUpdate.lastMegaTick = update.megaTick; - if (update.netFlags & MarbleNetFlags.PickupPowerup == 0) - update.powerUpId = otherUpdate.lastPowerUpId; - else - otherUpdate.lastPowerUpId = update.powerUpId; - if (update.netFlags & MarbleNetFlags.GravityChange == 0) - update.gravityDirection = otherUpdate.lastGravityUp; - else - otherUpdate.lastGravityUp = update.gravityDirection; - ourList.push(update); - } else { - var otherUpdate = new OtherMarbleUpdate(); - otherUpdate.packets.push(update); - // Copy the netflagg'd fields - if (update.netFlags & MarbleNetFlags.DoBlast != 0) - otherUpdate.lastBlastTick = update.blastTick; - if (update.netFlags & MarbleNetFlags.DoHelicopter != 0) - otherUpdate.lastHeliTick = update.heliTick; - if (update.netFlags & MarbleNetFlags.DoMega != 0) - otherUpdate.lastMegaTick = update.megaTick; - if (update.netFlags & MarbleNetFlags.PickupPowerup != 0) - otherUpdate.lastPowerUpId = update.powerUpId; - if (update.netFlags & MarbleNetFlags.GravityChange != 0) - otherUpdate.lastGravityUp = update.gravityDirection; + var otherUpdate = otherMarbleUpdates.get(cc); + if (otherUpdate == null) { + otherUpdate = new OtherMarbleUpdate(); otherMarbleUpdates[cc] = otherUpdate; } - } else { - if (myMarbleUpdate == null || update.serverTicks > myMarbleUpdate.serverTicks) { - if (myMarbleUpdate != null) { - // Copy the netflagg'd fields - if (update.netFlags & MarbleNetFlags.DoBlast == 0) - update.blastTick = myMarbleUpdate.blastTick; - if (update.netFlags & MarbleNetFlags.DoHelicopter == 0) - update.heliTick = myMarbleUpdate.heliTick; - if (update.netFlags & MarbleNetFlags.DoMega == 0) - update.megaTick = myMarbleUpdate.megaTick; - if (update.netFlags & MarbleNetFlags.PickupPowerup == 0) - update.powerUpId = myMarbleUpdate.powerUpId; - if (update.netFlags & MarbleNetFlags.GravityChange == 0) - update.gravityDirection = myMarbleUpdate.gravityDirection; - } - myMarbleUpdate = update; - ourMoveApplied = false; + if (otherUpdate.packets.length == 0) { + if (flags & MarbleNetFlags.DoBlast != 0) + otherUpdate.lastBlastTick = update.blastTick; + if (flags & MarbleNetFlags.DoHelicopter != 0) + otherUpdate.lastHeliTick = update.heliTick; + if (flags & MarbleNetFlags.DoMega != 0) + otherUpdate.lastMegaTick = update.megaTick; + if (flags & MarbleNetFlags.PickupPowerup != 0) + otherUpdate.lastPowerUpId = update.powerUpId; + if (flags & MarbleNetFlags.GravityChange != 0) + otherUpdate.lastGravityUp = update.gravityDirection; + } else { + applyFlagged(MarbleNetFlags.DoBlast, (flags & MarbleNetFlags.DoBlast) != 0, update.blastTick, otherUpdate.lastBlastTick, + v -> update.blastTick = v, v -> otherUpdate.lastBlastTick = v); + applyFlagged(MarbleNetFlags.DoHelicopter, (flags & MarbleNetFlags.DoHelicopter) != 0, update.heliTick, otherUpdate.lastHeliTick, + v -> update.heliTick = v, v -> otherUpdate.lastHeliTick = v); + applyFlagged(MarbleNetFlags.DoMega, (flags & MarbleNetFlags.DoMega) != 0, update.megaTick, otherUpdate.lastMegaTick, v -> update.megaTick = v, + v -> otherUpdate.lastMegaTick = v); + applyFlagged(MarbleNetFlags.PickupPowerup, (flags & MarbleNetFlags.PickupPowerup) != 0, update.powerUpId, otherUpdate.lastPowerUpId, + v -> update.powerUpId = v, v -> otherUpdate.lastPowerUpId = v); + applyFlagged(MarbleNetFlags.GravityChange, (flags & MarbleNetFlags.GravityChange) != 0, update.gravityDirection, otherUpdate.lastGravityUp, + v -> update.gravityDirection = v, v -> otherUpdate.lastGravityUp = v); } + otherUpdate.packets.push(update); + } else if (myMarbleUpdate == null || update.serverTicks > myMarbleUpdate.serverTicks) { + if (myMarbleUpdate != null) { + if ((flags & MarbleNetFlags.DoBlast) == 0) + update.blastTick = myMarbleUpdate.blastTick; + if ((flags & MarbleNetFlags.DoHelicopter) == 0) + update.heliTick = myMarbleUpdate.heliTick; + if ((flags & MarbleNetFlags.DoMega) == 0) + update.megaTick = myMarbleUpdate.megaTick; + if ((flags & MarbleNetFlags.PickupPowerup) == 0) + update.powerUpId = myMarbleUpdate.powerUpId; + if ((flags & MarbleNetFlags.GravityChange) == 0) + update.gravityDirection = myMarbleUpdate.gravityDirection; + } + myMarbleUpdate = update; + ourMoveApplied = false; } } } diff --git a/src/net/NetPacket.hx b/src/net/NetPacket.hx index 34f9fcc9..82030ff0 100644 --- a/src/net/NetPacket.hx +++ b/src/net/NetPacket.hx @@ -75,6 +75,7 @@ class MarbleUpdatePacket implements NetPacket { b.writeByte(clientId); MoveManager.packMove(move, b); b.writeUInt16(serverTicks); + b.writeInt(netFlags, 6); // All bits flagged in one, UsePowerup flag already serialized in this b.writeByte(moveQueueSize); b.writeFloat(position.x); b.writeFloat(position.y); @@ -89,44 +90,24 @@ class MarbleUpdatePacket implements NetPacket { b.writeFloat(lastContactNormal.y); b.writeFloat(lastContactNormal.z); b.writeInt(blastAmount, 11); + b.writeFlag(oob); + if (netFlags & MarbleNetFlags.DoBlast > 0) { - b.writeFlag(true); b.writeUInt16(blastTick); - } else { - b.writeFlag(false); } if (netFlags & MarbleNetFlags.DoHelicopter > 0) { - b.writeFlag(true); b.writeUInt16(heliTick); - } else { - b.writeFlag(false); } if (netFlags & MarbleNetFlags.DoMega > 0) { - b.writeFlag(true); b.writeUInt16(megaTick); - } else { - b.writeFlag(false); } - b.writeFlag(oob); - if (netFlags & MarbleNetFlags.UsePowerup > 0) { - b.writeFlag(true); - } else { - b.writeFlag(false); - } - if (netFlags & MarbleNetFlags.PickupPowerup > 0) { - b.writeFlag(true); b.writeInt(powerUpId, 9); - } else { - b.writeFlag(false); } if (netFlags & MarbleNetFlags.GravityChange > 0) { - b.writeFlag(true); b.writeFloat(gravityDirection.x); b.writeFloat(gravityDirection.y); b.writeFloat(gravityDirection.z); - } else { - b.writeFlag(false); } } @@ -134,35 +115,29 @@ class MarbleUpdatePacket implements NetPacket { clientId = b.readByte(); move = MoveManager.unpackMove(b); serverTicks = b.readUInt16(); + netFlags = b.readInt(6); moveQueueSize = b.readByte(); 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()); lastContactNormal = new Vector(b.readFloat(), b.readFloat(), b.readFloat()); blastAmount = b.readInt(11); - this.netFlags = 0; - if (b.readFlag()) { - blastTick = b.readUInt16(); - this.netFlags |= MarbleNetFlags.DoBlast; - } - if (b.readFlag()) { - heliTick = b.readUInt16(); - this.netFlags |= MarbleNetFlags.DoHelicopter; - } - if (b.readFlag()) { - megaTick = b.readUInt16(); - this.netFlags |= MarbleNetFlags.DoMega; - } oob = b.readFlag(); - if (b.readFlag()) - this.netFlags |= MarbleNetFlags.UsePowerup; - if (b.readFlag()) { - powerUpId = b.readInt(9); - this.netFlags |= MarbleNetFlags.PickupPowerup; + if (netFlags & MarbleNetFlags.DoBlast > 0) { + blastTick = b.readUInt16(); } - if (b.readFlag()) { + if (netFlags & MarbleNetFlags.DoHelicopter > 0) { + heliTick = b.readUInt16(); + } + if (netFlags & MarbleNetFlags.DoMega > 0) { + megaTick = b.readUInt16(); + } + + if (netFlags & MarbleNetFlags.PickupPowerup > 0) { + powerUpId = b.readInt(9); + } + if (netFlags & MarbleNetFlags.GravityChange > 0) { gravityDirection = new Vector(b.readFloat(), b.readFloat(), b.readFloat()); - this.netFlags |= MarbleNetFlags.GravityChange; } } }