make camera 60fps only, and minor MP microoptimizations

This commit is contained in:
RandomityGuy 2025-11-17 23:39:59 +00:00
parent c765734ba3
commit 4300459c1d
5 changed files with 174 additions and 213 deletions

View file

@ -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);

View file

@ -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<MarbleUpdatePacket>) {
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) {

View file

@ -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<MarblePrediction>, tick:Int) {
var idx = lowerBound(arr, tick);
if (idx > 0)
arr.splice(0, idx);
}
inline function truncateFromTick(arr:Array<MarblePrediction>, tick:Int) {
var idx = lowerBound(arr, tick);
if (idx < arr.length)
arr.splice(idx, arr.length - idx);
}
static inline function lowerBound(arr:Array<MarblePrediction>, 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;
}
}

View file

@ -25,70 +25,62 @@ class MarbleUpdateQueue {
public function new() {}
inline function applyFlagged<T>(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;
}
}
}

View file

@ -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;
}
}
}