using System; using System.Threading; using System.Threading.Tasks; using asap.core; using UniRx; using UnityEngine; public class HeartBeat { [Inject] public IStageService stageService {get; set;} [Inject] public IConfig config {get; set;} private CompositeDisposable disposables; private CancellationTokenSource tokenSource; private bool initialized; private bool isPaused; private bool hasPauseSnapshot; private DateTime pauseTimeUtc; private TimeSpan remainingDelayWhenPaused; private DateTime nextHeartbeatTimeUtc; public void Init() { if (initialized) { return; } initialized = true; isPaused = true; hasPauseSnapshot = false; remainingDelayWhenPaused = TimeSpan.Zero; disposables = new CompositeDisposable(); var stageLoadDisposable = GContext.OnEvent() .Where(e => e.state == EStageLoadState.Loaded && e.stageId == "FishingStage") .Take(1) .Subscribe(OnFishingStageLoad); disposables.Add(stageLoadDisposable); var pauseDisposable = Observable.EveryApplicationPause() .Subscribe(OnApplicationPauseChanged); disposables.Add(pauseDisposable); } public void Dispose() { if (!initialized) { return; } initialized = false; isPaused = true; hasPauseSnapshot = false; remainingDelayWhenPaused = TimeSpan.Zero; disposables.Dispose(); disposables = null; StopHeartBeatLoop(); } private void OnApplicationPauseChanged(bool paused) { if (paused) { PauseHeartBeat(); } else { ResumeHeartBeat(); } } private void OnFishingStageLoad(StageLoadEvent @event) { if (tokenSource != null || stageService.GetStage("FishingStage") == null) { return; } StartHeartBeatLoop(GetInterval()); } private void PauseHeartBeat() { if (isPaused) { return; } isPaused = true; pauseTimeUtc = DateTime.UtcNow; hasPauseSnapshot = true; if (nextHeartbeatTimeUtc > pauseTimeUtc) { remainingDelayWhenPaused = nextHeartbeatTimeUtc - pauseTimeUtc; } else { remainingDelayWhenPaused = TimeSpan.Zero; } StopHeartBeatLoop(); } private void ResumeHeartBeat() { TimeSpan delay; bool sendImmediately = false; if (!isPaused) { return; } var interval = GetInterval(); if (!hasPauseSnapshot) { delay = interval; } else { var bgElapsed = DateTime.UtcNow - pauseTimeUtc; var remainingDelay = remainingDelayWhenPaused; if (remainingDelay <= TimeSpan.Zero) { remainingDelay = interval; } if (bgElapsed >= remainingDelay) { sendImmediately = true; delay = interval; } else { delay = remainingDelay - bgElapsed; } } if (stageService.GetStage("FishingStage") == null) { return; } if (sendImmediately) { SendHeartBeat(); } StartHeartBeatLoop(delay); } private void StartHeartBeatLoop(TimeSpan firstDelay) { if (tokenSource != null) { return; } tokenSource = new CancellationTokenSource(); isPaused = false; nextHeartbeatTimeUtc = DateTime.UtcNow + firstDelay; StartHeartBeat(tokenSource.Token, firstDelay); } private void StopHeartBeatLoop() { if (tokenSource != null) { tokenSource.Cancel(); tokenSource.Dispose(); tokenSource = null; } } private async void StartHeartBeat(CancellationToken token, TimeSpan firstDelay) { try { var delay = firstDelay; while (!token.IsCancellationRequested) { if (delay > TimeSpan.Zero) { await Task.Delay(delay, token); } if (token.IsCancellationRequested || isPaused) { return; } SendHeartBeat(); delay = GetInterval(); } } catch (OperationCanceledException) { } catch (Exception) { } } private void SendHeartBeat() { using (var e = GEvent.TackEvent("HeartBeat", gaSend: false)) { e.AddContent("FPS", game.RootCtx.fps); } nextHeartbeatTimeUtc = DateTime.UtcNow + GetInterval(); } private TimeSpan GetInterval() { var intervalSeconds = Mathf.Max(1, (int)config.GetFloat("HeartBeat", 300)); return TimeSpan.FromSeconds(intervalSeconds); } }