Files
2026-05-26 16:15:54 +08:00

282 lines
8.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
namespace com.fpnn
{
public static partial class ClientEngine
{
private static volatile bool inited;
private static volatile bool stopped;
private static object interLocker;
private static Thread routineThread;
private static Semaphore quitSemaphore;
private static bool forbiddenRegisterConnection; //-- Unity iOS only.
private static Dictionary<TCPConnection, Int64> connectingConnections;
private static HashSet<TCPConnection> allConnections;
private static common.TaskThreadPool taskPool;
private static bool dropAllTaskWhenQuit;
internal static DateTime originDateTime;
internal static int globalConnectTimeoutSeconds;
internal static int globalQuestTimeoutSeconds;
internal static int maxPayloadSize;
internal static bool closeConnectionsWhenBackground;
internal static common.ErrorRecorder errorRecorder;
static partial void PlatformInit(); //-- In lock (interLocker) {...}
static partial void PlatformUninit();
static ClientEngine()
{
inited = false;
interLocker = new object();
}
/*
* Is NOT necessary, just uniform the interfaces with Unity version.
*/
public static void Init()
{
Init(null);
}
/*
* Customized Init.
*/
public static void Init(Config config)
{
if (inited)
return;
lock (interLocker)
{
if (inited)
return;
if (config == null)
config = new Config();
//---------------------
stopped = false;
forbiddenRegisterConnection = false;
connectingConnections = new Dictionary<TCPConnection, long>();
allConnections = new HashSet<TCPConnection>();
originDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
dropAllTaskWhenQuit = config.dropAllUnexecutedTaskWhenExiting;
globalConnectTimeoutSeconds = config.globalConnectTimeoutSeconds;
globalQuestTimeoutSeconds = config.globalQuestTimeoutSeconds;
maxPayloadSize = config.maxPayloadSize;
closeConnectionsWhenBackground = config.closeConnectionsWhenBackground;
errorRecorder = config.errorRecorder;
taskPool = new common.TaskThreadPool(config.taskThreadPoolConfig.initThreadCount,
config.taskThreadPoolConfig.perfectThreadCount,
config.taskThreadPoolConfig.maxThreadCount,
config.taskThreadPoolConfig.maxQueueLengthLimitation,
config.taskThreadPoolConfig.tempLatencySeconds,
dropAllTaskWhenQuit
);
taskPool.SetErrorRecorder(config.errorRecorder);
routineThread = new Thread(RoutineFunc)
{
Name = "FPNN.ClientEngine.RoutineThread",
IsBackground = true
};
routineThread.Start();
//---------------------
PlatformInit();
inited = true;
}
}
private static void CheckInitStatus()
{
Init(null);
}
private static void RoutineFunc()
{
while (!stopped)
{
Thread.Sleep(1000);
Int64 currentSeconds = GetCurrentSeconds();
HashSet<TCPConnection> checkingConnections;
HashSet<TCPConnection> connectingTimeoutedConnections;
try
{
checkingConnections = new HashSet<TCPConnection>();
connectingTimeoutedConnections = new HashSet<TCPConnection>();
}
catch (Exception e)
{
errorRecorder?.RecordError(e);
continue;
}
lock (interLocker)
{
foreach (TCPConnection conn in allConnections)
checkingConnections.Add(conn);
foreach (KeyValuePair<TCPConnection, Int64> kvp in connectingConnections)
{
if (kvp.Value <= currentSeconds)
connectingTimeoutedConnections.Add(kvp.Key);
}
}
foreach (TCPConnection conn in connectingTimeoutedConnections)
{
conn.Close();
}
currentSeconds = GetCurrentSeconds();
foreach (TCPConnection conn in checkingConnections)
{
conn.CleanTimeoutedCallbacks(currentSeconds);
}
}
StopAllConnections();
quitSemaphore.Release();
}
/*
* Only for Unity on iOS devices when apps is going to background.
*/
internal static void StopAllConnections()
{
if (inited == false)
return;
CheckInitStatus();
HashSet<TCPConnection> currentConnections = new HashSet<TCPConnection>();
lock (interLocker)
{
foreach (TCPConnection conn in allConnections)
currentConnections.Add(conn);
}
foreach (TCPConnection conn in currentConnections)
{
conn.Close();
}
}
/*
* Only for Unity on iOS devices when apps is going to background.
*/
internal static void ChangeForbiddenRegisterConnection(bool forbidden)
{
lock (interLocker)
{
forbiddenRegisterConnection = forbidden;
}
}
internal static bool RegisterConnectingConnection(TCPConnection conn, int connectTimeout)
{
CheckInitStatus();
lock (interLocker)
{
if (forbiddenRegisterConnection) //-- Unity iOS only.
return false;
if (connectTimeout <= 0)
connectTimeout = globalQuestTimeoutSeconds;
connectingConnections.Add(conn, GetCurrentSeconds() + connectTimeout);
allConnections.Add(conn);
}
return true;
}
internal static bool RegisterConnectedConnection(TCPConnection conn)
{
CheckInitStatus();
lock (interLocker)
{
if (forbiddenRegisterConnection) //-- Unity iOS only.
return false;
connectingConnections.Remove(conn);
allConnections.Add(conn);
}
return true;
}
internal static void UnregisterConnection(TCPConnection conn)
{
CheckInitStatus();
lock (interLocker)
{
connectingConnections.Remove(conn);
allConnections.Remove(conn);
}
}
public static bool RunTask(common.TaskThreadPool.ITask task)
{
CheckInitStatus();
return taskPool.Wakeup(task);
}
public static bool RunTask(Action action)
{
CheckInitStatus();
return taskPool.Wakeup(action);
}
public static Int64 GetCurrentSeconds()
{
TimeSpan span = DateTime.UtcNow - originDateTime;
return (Int64)Math.Floor(span.TotalSeconds);
}
public static Int64 GetCurrentMilliseconds()
{
TimeSpan span = DateTime.UtcNow - originDateTime;
return (Int64)Math.Floor(span.TotalMilliseconds);
}
public static Int64 GetCurrentMicroseconds()
{
TimeSpan span = DateTime.UtcNow - originDateTime;
return (Int64)Math.Floor(span.TotalMilliseconds * 1000);
}
public static void Close()
{
lock (interLocker)
{
if (inited == false)
return;
if (stopped)
return;
quitSemaphore = new Semaphore(0, 1);
stopped = true;
}
quitSemaphore.WaitOne();
quitSemaphore.Close();
quitSemaphore = null;
inited = false;
PlatformUninit();
taskPool.Close(dropAllTaskWhenQuit);
}
}
}