diff --git a/src/Console.hx b/src/Console.hx index aced50f2..88eb5d40 100644 --- a/src/Console.hx +++ b/src/Console.hx @@ -7,6 +7,7 @@ import mis.MisParser; import src.Settings; import src.Debug; import src.MarbleGame; +import src.ProfilerUI; @:publicFields class ConsoleEntry { @@ -122,6 +123,7 @@ class Console { log("rewindTimeScale "); log("drawBounds "); log("wireframe "); + log("fps "); } else if (cmdType == "timeScale") { if (cmdSplit.length == 2) { var scale = Std.parseFloat(cmdSplit[1]); @@ -163,6 +165,14 @@ class Console { } else { error("Expected one argument, got " + (cmdSplit.length - 1)); } + } else if (cmdType == "fps") { + if (cmdSplit.length == 2) { + var scale = MisParser.parseBoolean(cmdSplit[1]); + ProfilerUI.setEnabled(scale); + log("FPS Display set to " + scale); + } else { + error("Expected one argument, got " + (cmdSplit.length - 1)); + } } else if (cmdType == 'rollback') { var t = Std.parseFloat(cmdSplit[1]); MarbleGame.instance.world.rollback(t); diff --git a/src/Marble.hx b/src/Marble.hx index 99a4e075..2e85b7a3 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -1637,12 +1637,13 @@ class Marble extends GameObject { oldPos = this.collider.transform.getPosition(); prevRot = this.getRotationQuat().clone(); - if (this.controllable) { - for (interior in pathedInteriors) { - // interior.pushTickState(); - interior.computeNextPathStep(timeRemaining); - } + // if (this.controllable) { + for (interior in pathedInteriors) { + if (Net.isMP) + interior.pushTickState(); + interior.computeNextPathStep(timeRemaining); } + // } // Blast if (m != null && m.blast) { @@ -1795,11 +1796,11 @@ class Marble extends GameObject { timeRemaining -= timeStep; - if (this.controllable) { - for (interior in pathedInteriors) { - interior.advance(timeStep); - } + // if (this.controllable) { + for (interior in pathedInteriors) { + interior.advance(timeStep); } + // } piTime += timeStep; @@ -1808,11 +1809,11 @@ class Marble extends GameObject { } while (true); if (timeRemaining > 0) { // Advance pls - if (this.controllable) { - for (interior in pathedInteriors) { - interior.advance(timeRemaining); - } + // if (this.controllable) { + for (interior in pathedInteriors) { + interior.advance(timeRemaining); } + // } } this.queuedContacts = []; @@ -1856,6 +1857,10 @@ class Marble extends GameObject { this.level.cancel(this.oobSchedule); this.level.restart(cast this); } + + for (interior in pathedInteriors) { + interior.popTickState(); + } } } @@ -1899,6 +1904,7 @@ class Marble extends GameObject { // return false; // } // } + // trace('Tick RTT: ', this.serverTicks - p.serverTicks); this.serverTicks = p.serverTicks; this.recvServerTick = p.serverTicks; // this.oldPos = this.newPos; @@ -2034,13 +2040,13 @@ class Marble extends GameObject { var smooth = 1.0 / (newDt * (newDt * 0.235 * newDt) + newDt + 1.0 + 0.48 * newDt * newDt); this.netSmoothOffset.scale(smooth); var smoothScale = this.netSmoothOffset.lengthSq(); - if (smoothScale < 0.1 || smoothScale > 10.0) + if (smoothScale < 0.01 || smoothScale > 10.0) this.netSmoothOffset.set(0, 0, 0); if (oldPos != null && newPos != null) { var deltaT = physicsAccumulator / 0.032; - if (Net.isClient && !this.controllable) - deltaT *= 0.75; // Don't overshoot + // if (Net.isClient && !this.controllable) + // deltaT *= 0.75; // Don't overshoot var renderPos = Util.lerpThreeVectors(this.oldPos, this.newPos, deltaT); if (Net.isClient) { renderPos.load(renderPos.add(this.netSmoothOffset)); diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index 77c8e904..99d416e1 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -1547,6 +1547,12 @@ class MarbleWorld extends Scheduler { @:privateAccess this.marble.posStore.load(this.marble.newPos); @:privateAccess this.marble.netCorrected = true; + // if ((marbleNeedsPrediction & (1 << Net.clientId) > 0)) { + for (pi in this.pathedInteriors) { + pi.rollbackToTick(currentTick); + } + // } + for (move in ourQueuedMoves) { var m = move.move; Debug.drawSphere(@:privateAccess this.marble.newPos, this.marble._radius); @@ -1574,6 +1580,13 @@ class MarbleWorld extends Scheduler { advanceTimeState.currentAttemptTime += 0.032; advanceTimeState.ticks++; currentTick++; + + // if ((marbleNeedsPrediction & (1 << Net.clientId) > 0)) { + for (pi in this.pathedInteriors) { + pi.computeNextPathStep(0.032); + pi.advance(0.032); + } + // } } lastMoves.ourMoveApplied = true; @@ -1858,6 +1871,10 @@ class MarbleWorld extends Scheduler { this.marble.clearNetFlags(); } } + for (pi in this.pathedInteriors) { + pi.computeNextPathStep(0.032); + pi.advance(0.032); + } timeState.ticks++; } timeState.subframe = tickAccumulator / 0.032; diff --git a/src/PathedInterior.hx b/src/PathedInterior.hx index c6068163..1df00841 100644 --- a/src/PathedInterior.hx +++ b/src/PathedInterior.hx @@ -40,6 +40,9 @@ class PathedInterior extends InteriorObject { public var currentTime:Float; public var targetTime:Float; + var initialPosition:Float; + var initialTargetPosition:Float; + var basePosition:Vector; var baseOrientation:Quat; var baseScale:Vector; @@ -52,6 +55,12 @@ class PathedInterior extends InteriorObject { var stopped:Bool = false; var stoppedPosition:Vector; + var savedPosition:Vector; + var savedVelocity:Vector; + var savedStopped:Bool; + var savedStoppedPosition:Vector; + var savedTime:Float; + var soundChannel:Channel; public static function createFromSimGroup(simGroup:MissionElementSimGroup, level:MarbleWorld, onFinish:PathedInterior->Void) { @@ -173,6 +182,33 @@ class PathedInterior extends InteriorObject { } } + public function getInternalTime(externalTime:Float) { + if (this.targetTime < 0) { + var direction = (this.targetTime == -1) ? 1 : (this.targetTime == -2) ? -1 : 0; + return Util.adjustedMod(this.currentTime + externalTime * direction, this.duration); + } else { + var dur = Math.abs(this.currentTime - this.targetTime); + + var compvarion = Util.clamp(dur != 0 ? externalTime / dur : 1, 0, 1); + return Util.clamp(Util.lerp(this.currentTime, this.targetTime, compvarion), 0, this.duration); + } + } + + public function rollbackToTick(tick:Int) { + // this.reset(); + // Reset + this.currentTime = initialPosition; + this.targetTime = initialTargetPosition; + if (this.targetTime < 0) { + var direction = (this.targetTime == -1) ? 1 : (this.targetTime == -2) ? -1 : 0; + this.currentTime = Util.adjustedMod(this.currentTime + (tick * 0.032) * direction, duration); + } else { + this.currentTime = Util.clamp(this.currentTime + (tick * 0.032), 0, duration); + } + this.computeNextPathStep(0.032); + this.advance(0.032); + } + public function advance(timeDelta:Float) { if (stopped) return; @@ -213,6 +249,25 @@ class PathedInterior extends InteriorObject { this.stoppedPosition = this.position.clone(); } + public function pushTickState() { + savedPosition = this.position.clone(); + savedVelocity = this.velocity.clone(); + savedStopped = this.stopped; + savedStoppedPosition = this.stoppedPosition != null ? this.stoppedPosition.clone() : null; + savedTime = this.currentTime; + } + + public function popTickState() { + this.position.load(savedPosition); + this.velocity.load(savedVelocity); + this.stopped = savedStopped; + this.stoppedPosition = savedStoppedPosition; + this.collider.transform.setPosition(savedPosition); + collisionWorld.updateTransform(this.collider); + + this.currentTime = savedTime; + } + function computeDuration() { var total = 0.0; for (i in 0...(markerData.length - 1)) { @@ -311,9 +366,12 @@ class PathedInterior extends InteriorObject { override function reset() { this.currentTime = 0; this.targetTime = 0; + this.initialPosition = 0; + this.initialTargetPosition = 0; if (this.element.initialposition != "") { this.currentTime = MisParser.parseNumber(this.element.initialposition) / 1000; + initialPosition = this.currentTime; } if (this.element.initialtargetposition != "") { @@ -323,6 +381,8 @@ class PathedInterior extends InteriorObject { // Alright this is strange. In Torque, there are some FPS-dependent client/server desync issues that cause the interior to start at the end position whenever the initialTargetPosition is somewhere greater than 1 and, like, approximately below 50. if (this.targetTime > 0 && this.targetTime < 0.05) this.currentTime = this.duration; + + initialTargetPosition = this.targetTime; } this.stopped = false; diff --git a/src/ProfilerUI.hx b/src/ProfilerUI.hx index 4d30d88e..61234ddc 100644 --- a/src/ProfilerUI.hx +++ b/src/ProfilerUI.hx @@ -1,44 +1,136 @@ package src; +import net.Net; +import src.MarbleGame; import h3d.Vector; import hxd.res.DefaultFont; import h2d.Text; class ProfilerUI { var fpsCounter:Text; + var networkStats:Text; var debugProfiler:h3d.impl.Benchmark; + var s2d:h2d.Scene; public var fps:Float; public static var instance:ProfilerUI; + static var enabled:Bool = false; + static var mode:Int = 0; + public function new(s2d:h2d.Scene) { if (instance != null) return; instance = this; - // debugProfiler = new h3d.impl.Benchmark(s2d); - // debugProfiler.y = 40; - - // fpsCounter = new Text(DefaultFont.get(), s2d); - // fpsCounter.y = 80; - // fpsCounter.color = new Vector(1, 1, 1, 1); + this.s2d = s2d; } public static function begin() { - // instance.debugProfiler.begin(); + if (!enabled) + return; + // if (type == mode) + instance.debugProfiler.begin(); } public static function measure(name:String) { - // instance.debugProfiler.measure(name); + if (!enabled) + return; + // if (type == mode) + instance.debugProfiler.measure(name); } public static function end() { - // instance.debugProfiler.end(); + if (!enabled) + return; + // if (type == mode) + instance.debugProfiler.end(); } public static function update(fps:Float) { instance.fps = fps; - // instance.fpsCounter.text = "FPS: " + fps; + if (!enabled) + return; + instance.fpsCounter.text = "FPS: " + fps; + updateNetworkStats(); + } + + public static function setEnabled(val:Bool) { + enabled = val; + if (enabled) { + if (instance.debugProfiler != null) { + instance.debugProfiler.remove(); + instance.debugProfiler = null; + } + if (instance.fpsCounter != null) { + instance.fpsCounter.remove(); + instance.fpsCounter = null; + } + if (instance.networkStats != null) { + instance.networkStats.remove(); + instance.networkStats = null; + } + instance.debugProfiler = new h3d.impl.Benchmark(instance.s2d); + instance.debugProfiler.y = 40; + + instance.fpsCounter = new Text(DefaultFont.get(), instance.s2d); + instance.fpsCounter.y = 80; + instance.fpsCounter.color = new Vector(1, 1, 1, 1); + + instance.networkStats = new Text(DefaultFont.get(), instance.s2d); + instance.networkStats.y = 150; + instance.networkStats.color = new Vector(1, 1, 1, 1); + + instance.debugProfiler.end(); // End current frame + } else { + instance.debugProfiler.remove(); + instance.fpsCounter.remove(); + instance.networkStats.remove(); + instance.debugProfiler = null; + instance.fpsCounter = null; + instance.networkStats = null; + } + } + + public static function setDisplayMode(m:Int) { + mode = m; + if (instance.debugProfiler != null) { + instance.debugProfiler.end(); // End + } + } + + static function updateNetworkStats() { + if (MarbleGame.instance.world != null && MarbleGame.instance.world.isMultiplayer) { + static var lastSentMove = 0; + if (Net.isClient && Net.clientConnection.getQueuedMovesLength() > 0) { + lastSentMove = @:privateAccess Net.clientConnection.moveManager.queuedMoves[Net.clientConnection.moveManager.queuedMoves.length - 1].id; + } + + if (Net.isClient + && lastSentMove != 0 + && @:privateAccess MarbleGame.instance.world.lastMoves != null + && @:privateAccess MarbleGame.instance.world.lastMoves.myMarbleUpdate != null) { + instance.networkStats.text = 'Client World Ticks: ${MarbleGame.instance.world.timeState.ticks}\n' + + 'Client Marble Ticks: ${@:privateAccess MarbleGame.instance.world.marble.serverTicks}\n' + + 'Server Ticks: ${@:privateAccess MarbleGame.instance.world.lastMoves.myMarbleUpdate.serverTicks}\n' + + 'Client Move Queue Size: ${Net.isClient ? Net.clientConnection.getQueuedMovesLength() : 0}\n' + + 'Server Move Queue Size: ${Net.isClient ? @:privateAccess MarbleGame.instance.world.lastMoves.myMarbleUpdate.moveQueueSize : 0}\n' + + 'Last Sent Move: ${Net.isClient ? lastSentMove : 0}\n' + + 'Last Ack Move: ${Net.isClient ? @:privateAccess Net.clientConnection.moveManager.lastAckMoveId : 0}\n' + + 'Move Ack RTT: ${Net.isClient ? @:privateAccess Net.clientConnection.moveManager.ackRTT : 0}'; + } + if (Net.isHost) { + var strs = []; + strs.push('World Ticks: ${MarbleGame.instance.world.timeState.ticks}'); + for (dc => cc in Net.clients) { + strs.push('${cc.id} move: sz ${@:privateAccess cc.moveManager.getQueueSize()} avg ${@:privateAccess cc.moveManager.serverAvgMoveListSize}'); + } + + instance.networkStats.text = strs.join('\n'); + } + } else { + instance.networkStats.text = ""; + } } } diff --git a/src/gui/MPPlayMissionGui.hx b/src/gui/MPPlayMissionGui.hx index dbe9090a..d8eb0199 100644 --- a/src/gui/MPPlayMissionGui.hx +++ b/src/gui/MPPlayMissionGui.hx @@ -156,7 +156,7 @@ class MPPlayMissionGui extends GuiImage { searchBtn.position = new Vector(255, 514); searchBtn.extent = new Vector(44, 44); searchBtn.pressedAction = (e) -> { - MarbleGame.canvas.pushDialog(new MPSearchGui(false)); + MarbleGame.canvas.pushDialog(new MPSearchGui(currentCategory == "custom")); } if (Net.isHost) window.addChild(searchBtn); diff --git a/src/gui/MPSearchGui.hx b/src/gui/MPSearchGui.hx index dfa3674f..1bd3826f 100644 --- a/src/gui/MPSearchGui.hx +++ b/src/gui/MPSearchGui.hx @@ -33,7 +33,7 @@ class MPSearchGui extends GuiImage { } } } else { - var customsList = MissionList.customMissions; + var customsList = Marbleland.multiplayerMissions; for (mis in customsList) { missionList.push({ mis: mis,