private Task ExecuteUpdateAsync() { var timer = new Timer { Interval = 1000, AutoReset = true }; timer.Elapsed += (sender, args) => { if (_ticks > 0) { Logger.Debug($"TPS: {_ticks}/{TicksPerSecondLimit} TPT: {_passedTickTime / _ticks} ms PHY: {_physicsTime / _ticks} ms"); } _passedTickTime = 0; _ticks = 0; }; timer.Start(); Listen(OnTick, PhysicsStep); return(Task.Run(async() => { _running = true; var watch = new Stopwatch(); while (_running) { var players = Players; if (players.Length == 0) { await Task.Delay(1000); continue; } if (_ticks >= TicksPerSecondLimit) { continue; } watch.Restart(); // Update all tick tasks in parallel var tickTasks = new List <Task>(); foreach (var updatedObject in UpdatedObjects.ToArray()) { if (updatedObject.Stuck) { continue; } if (updatedObject.Associate is GameObject gameObject) { if (players.All(p => !p.Perspective.LoadedObjects.Contains(gameObject))) { continue; } } updatedObject.DeltaTime += DeltaTime; if (updatedObject.Frequency != ++updatedObject.Ticks) { continue; } updatedObject.Ticks = 0; tickTasks.Add(Task.Run(async() => { var start = watch.ElapsedMilliseconds; try { var tickTask = Task.Run(async() => { await updatedObject.Delegate(updatedObject.DeltaTime); updatedObject.Stuck = false; }); var delay = Task.Delay(150); await Task.WhenAny(tickTask, delay); var elapsed = watch.ElapsedMilliseconds - start; // Any task that takes more than 3 ticks to complete is considered stuck if (delay.IsCompleted && !tickTask.IsCompleted) { Logger.Warning($"{updatedObject.Associate} is now defined as stuck!"); updatedObject.Stuck = true; } // Any task that took more than two ticks to execute but less than 3 is considered slow else if (elapsed > 100) { Logger.Warning($"Slow update: {updatedObject.Associate} in {elapsed}ms"); } } catch (Exception e) { Logger.Error(e); } updatedObject.DeltaTime = 0; })); } await Task.WhenAll(tickTasks); await OnTick.InvokeAsync(); _ticks++; var passedMs = watch.ElapsedMilliseconds; _passedTickTime += passedMs; // Sync with the system tick clock await Task.Delay((int)Math.Max(1000 / TicksPerSecondLimit - passedMs, 0)); passedMs = watch.ElapsedMilliseconds; DeltaTime = passedMs / 1000f; } })); }