fix(ThreadedTransport): sleep detection (#3901)

This commit is contained in:
mischa 2024-09-24 10:13:14 +02:00 committed by GitHub
parent a13a00215f
commit 2b0603f741
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -5,10 +5,12 @@
// note that ThreadLog.cs is required for Debug.Log from threads to work in builds.
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Net;
using System.Threading;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace Mirror
{
@ -43,6 +45,9 @@ enum ThreadEventType
DoClientSend,
DoClientDisconnect,
Sleep,
Wake,
DoShutdown
}
@ -142,6 +147,10 @@ public abstract class ThreadedTransport : Transport
// very large limit to prevent deadlocks.
const int MaxProcessingPerTick = 10_000_000;
[Tooltip("Detect device sleep mode and automatically disconnect + hibernate the thread after 'sleepTimeout' seconds.\nFor example: on mobile / VR, we don't want to drain the battery after putting down the device.")]
public bool sleepDetection = true;
public float sleepTimeoutInSeconds = 30;
// communication between main & worker thread //////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void EnqueueClientMain(
@ -173,10 +182,19 @@ protected virtual void Awake()
{
// start the thread.
// if main application terminates, this thread needs to terminate too.
EnsureThread();
}
// starts the thread if not created or not active yet.
void EnsureThread()
{
if (thread != null && thread.IsAlive) return;
thread = new WorkerThread(ToString());
thread.Tick = ThreadTick;
thread.Cleanup = ThreadedShutdown;
thread.Start();
Debug.Log($"ThreadedTransport: started worker thread!");
}
protected virtual void OnDestroy()
@ -188,6 +206,8 @@ protected virtual void OnDestroy()
}
// worker thread ///////////////////////////////////////////////////////
// sleep timeout to automatically end if the device was put to sleep.
Stopwatch sleepTimer = null; // NOT THREAD SAFE: ONLY USE THIS IN WORKER THREAD!
void ProcessThreadQueue()
{
// TODO deadlock protection. worker thread may be to slow to process all.
@ -252,6 +272,28 @@ void ProcessThreadQueue()
break;
}
// SLEEP ////////////////////////////////////////////////
case ThreadEventType.Sleep:
{
// start the sleep timer if not started yet
if (sleepTimer == null)
{
Debug.Log($"ThreadedTransport: sleep detected, sleeping in {sleepTimeoutInSeconds:F0}s!");
sleepTimer = Stopwatch.StartNew();
}
break;
}
case ThreadEventType.Wake:
{
// stop the sleep timer (if any)
if (sleepTimer != null)
{
Debug.Log($"ThreadedTransport: Woke up, interrupting sleep timer!");
sleepTimer = null;
}
break;
}
// SHUTDOWN ////////////////////////////////////////////////
case ThreadEventType.DoShutdown:
{
@ -267,6 +309,21 @@ void ProcessThreadQueue()
// without needing to throw InterruptExceptions or similar.
bool ThreadTick()
{
// was the device put to sleep?
if (sleepTimer != null &&
sleepTimer.Elapsed.TotalSeconds >= sleepTimeoutInSeconds)
{
Debug.Log("ThreadedTransport: entering sleep mode and stopping/disconnecting.");
ThreadedServerStop();
ThreadedClientDisconnect();
sleepTimer = null;
// if the device was put to sleep, end the thread gracefully.
// all threads must end, otherwise putting down the device would
// slowly drain the battery after a day or more.
return false;
}
// early update the implementation first
ThreadedClientEarlyUpdate();
ThreadedServerEarlyUpdate();
@ -279,7 +336,6 @@ bool ThreadTick()
ThreadedServerLateUpdate();
// save some cpu power.
// TODO update interval and sleep extra time would be ideal
Thread.Sleep(1);
return true;
}
@ -456,6 +512,9 @@ public override void ClientConnect(string address)
return;
}
// start worker thread if not started yet
EnsureThread();
// enqueue to process in worker thread
EnqueueThread(ThreadEventType.DoClientConnect, address, null, null);
@ -473,6 +532,9 @@ public override void ClientConnect(Uri uri)
return;
}
// start worker thread if not started yet
EnsureThread();
// enqueue to process in worker thread
EnqueueThread(ThreadEventType.DoClientConnect, uri, null, null);
@ -586,6 +648,9 @@ public override void ServerStart()
return;
}
// start worker thread if not started yet
EnsureThread();
// enqueue to process in worker thread
EnqueueThread(ThreadEventType.DoServerStart, null, null, null);
@ -631,6 +696,30 @@ public override void ServerStop()
serverActive = false;
}
// sleep ///////////////////////////////////////////////////////////////
// when a device goes to sleep, we must end the worker thread after a while.
// otherwise putting down the device would slowly drain the battery after a day or more.
void OnApplicationPause(bool pauseStatus)
{
Debug.Log($"{GetType()}: OnApplicationPause={pauseStatus}");
// is sleep detection feature enabled?
if (!sleepDetection) return;
// pause thread if application pauses
if (pauseStatus)
{
// enqueue to process in worker thread
EnqueueThread(ThreadEventType.Sleep, null, null, null);
}
// resume thread if application resumes
else
{
// enqueue to process in worker thread
EnqueueThread(ThreadEventType.Wake, null, null, null);
}
}
// shutdown ////////////////////////////////////////////////////////////
public override void Shutdown()
{