mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
fix(ThreadedTransport): sleep detection (#3901)
This commit is contained in:
parent
a13a00215f
commit
2b0603f741
@ -5,10 +5,12 @@
|
|||||||
// note that ThreadLog.cs is required for Debug.Log from threads to work in builds.
|
// note that ThreadLog.cs is required for Debug.Log from threads to work in builds.
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using Debug = UnityEngine.Debug;
|
||||||
|
|
||||||
namespace Mirror
|
namespace Mirror
|
||||||
{
|
{
|
||||||
@ -43,6 +45,9 @@ enum ThreadEventType
|
|||||||
DoClientSend,
|
DoClientSend,
|
||||||
DoClientDisconnect,
|
DoClientDisconnect,
|
||||||
|
|
||||||
|
Sleep,
|
||||||
|
Wake,
|
||||||
|
|
||||||
DoShutdown
|
DoShutdown
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +147,10 @@ public abstract class ThreadedTransport : Transport
|
|||||||
// very large limit to prevent deadlocks.
|
// very large limit to prevent deadlocks.
|
||||||
const int MaxProcessingPerTick = 10_000_000;
|
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 //////////////////////////
|
// communication between main & worker thread //////////////////////////
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
void EnqueueClientMain(
|
void EnqueueClientMain(
|
||||||
@ -173,10 +182,19 @@ protected virtual void Awake()
|
|||||||
{
|
{
|
||||||
// start the thread.
|
// start the thread.
|
||||||
// if main application terminates, this thread needs to terminate too.
|
// 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 = new WorkerThread(ToString());
|
||||||
thread.Tick = ThreadTick;
|
thread.Tick = ThreadTick;
|
||||||
thread.Cleanup = ThreadedShutdown;
|
thread.Cleanup = ThreadedShutdown;
|
||||||
thread.Start();
|
thread.Start();
|
||||||
|
Debug.Log($"ThreadedTransport: started worker thread!");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnDestroy()
|
protected virtual void OnDestroy()
|
||||||
@ -188,6 +206,8 @@ protected virtual void OnDestroy()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// worker thread ///////////////////////////////////////////////////////
|
// 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()
|
void ProcessThreadQueue()
|
||||||
{
|
{
|
||||||
// TODO deadlock protection. worker thread may be to slow to process all.
|
// TODO deadlock protection. worker thread may be to slow to process all.
|
||||||
@ -252,6 +272,28 @@ void ProcessThreadQueue()
|
|||||||
break;
|
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 ////////////////////////////////////////////////
|
// SHUTDOWN ////////////////////////////////////////////////
|
||||||
case ThreadEventType.DoShutdown:
|
case ThreadEventType.DoShutdown:
|
||||||
{
|
{
|
||||||
@ -267,6 +309,21 @@ void ProcessThreadQueue()
|
|||||||
// without needing to throw InterruptExceptions or similar.
|
// without needing to throw InterruptExceptions or similar.
|
||||||
bool ThreadTick()
|
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
|
// early update the implementation first
|
||||||
ThreadedClientEarlyUpdate();
|
ThreadedClientEarlyUpdate();
|
||||||
ThreadedServerEarlyUpdate();
|
ThreadedServerEarlyUpdate();
|
||||||
@ -279,7 +336,6 @@ bool ThreadTick()
|
|||||||
ThreadedServerLateUpdate();
|
ThreadedServerLateUpdate();
|
||||||
|
|
||||||
// save some cpu power.
|
// save some cpu power.
|
||||||
// TODO update interval and sleep extra time would be ideal
|
|
||||||
Thread.Sleep(1);
|
Thread.Sleep(1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -456,6 +512,9 @@ public override void ClientConnect(string address)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// start worker thread if not started yet
|
||||||
|
EnsureThread();
|
||||||
|
|
||||||
// enqueue to process in worker thread
|
// enqueue to process in worker thread
|
||||||
EnqueueThread(ThreadEventType.DoClientConnect, address, null, null);
|
EnqueueThread(ThreadEventType.DoClientConnect, address, null, null);
|
||||||
|
|
||||||
@ -473,6 +532,9 @@ public override void ClientConnect(Uri uri)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// start worker thread if not started yet
|
||||||
|
EnsureThread();
|
||||||
|
|
||||||
// enqueue to process in worker thread
|
// enqueue to process in worker thread
|
||||||
EnqueueThread(ThreadEventType.DoClientConnect, uri, null, null);
|
EnqueueThread(ThreadEventType.DoClientConnect, uri, null, null);
|
||||||
|
|
||||||
@ -586,6 +648,9 @@ public override void ServerStart()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// start worker thread if not started yet
|
||||||
|
EnsureThread();
|
||||||
|
|
||||||
// enqueue to process in worker thread
|
// enqueue to process in worker thread
|
||||||
EnqueueThread(ThreadEventType.DoServerStart, null, null, null);
|
EnqueueThread(ThreadEventType.DoServerStart, null, null, null);
|
||||||
|
|
||||||
@ -631,6 +696,30 @@ public override void ServerStop()
|
|||||||
serverActive = false;
|
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 ////////////////////////////////////////////////////////////
|
// shutdown ////////////////////////////////////////////////////////////
|
||||||
public override void Shutdown()
|
public override void Shutdown()
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user